FreeRTOS --(10)任務管理之任務延時

目錄

1、接口介紹

1.1、vTaskDelay

1.1.1、Usage

1.1.2、Implement

1.2、vTaskDelayUntil

1.2.1、Usage

1.2.2、Implement


 

在《FreeRTOS --(7)任務管理之入門篇》中講過,如果有幾個任務同時跑,但是又都不阻塞的話,那麼最高優先級的任務將會佔領整個 CPU,因爲每次都會調度到它,一直處於 Ready 狀態,所以呢,調度器每次都要選擇優先級最高的任務來讓它執行;所以,不管怎麼樣,任務做完自己該做的事情,就應該進入阻塞狀態,等待下次該自己做任務的時候,在佔領 CPU,這樣既可以讓 Idle 線程,在系統空閒的時候跑,也可以讓讓任務在合理的時間佔領 CPU;

之前也說過,讓任務進入阻塞狀態的方式有兩種:

1、讓任務延時:因爲任務是一個 While 1 的無限循環,所以執行完自己的事情後,可以調用接口進行延時,進入阻塞,讓出 CPU;

2、讓任務等待某個事件:當任務需要的資源滿足不了的時候,可以讓任務阻塞的等待所需的資源,這樣也是合理的;

這章就是要講讓任務進入延時進入阻塞的方法以及相關的原理;

 

1、接口介紹

任務執行完自己的事情後,可以調用如下接口,讓任務進入阻塞態,delay 一段時間:

1、vTaskDelay()

2、vTaskDelayUntil() 

下面分別來介紹這兩個函數的用法和官方的解釋;

 

1.1、vTaskDelay

1.1.1、Usage

這個函數是相對延時函數,它的函數原型爲:

/**
 * task. h
 * <pre>void vTaskDelay( const TickType_t xTicksToDelay );</pre>
 *
 * Delay a task for a given number of ticks.  The actual time that the
 * task remains blocked depends on the tick rate.  The constant
 * portTICK_PERIOD_MS can be used to calculate real time from the tick
 * rate - with the resolution of one tick period.
 *
 * INCLUDE_vTaskDelay must be defined as 1 for this function to be available.
 * See the configuration section for more information.
 *
 *
 * vTaskDelay() specifies a time at which the task wishes to unblock relative to
 * the time at which vTaskDelay() is called.  For example, specifying a block
 * period of 100 ticks will cause the task to unblock 100 ticks after
 * vTaskDelay() is called.  vTaskDelay() does not therefore provide a good method
 * of controlling the frequency of a periodic task as the path taken through the
 * code, as well as other task and interrupt activity, will effect the frequency
 * at which vTaskDelay() gets called and therefore the time at which the task
 * next executes.  See vTaskDelayUntil() for an alternative API function designed
 * to facilitate fixed frequency execution.  It does this by specifying an
 * absolute time (rather than a relative time) at which the calling task should
 * unblock.
 *
 * @param xTicksToDelay The amount of time, in tick periods, that
 * the calling task should block.
 *
 * Example usage:

 void vTaskFunction( void * pvParameters )
 {
 // Block for 500ms.
 const TickType_t xDelay = 500 / portTICK_PERIOD_MS;

     for( ;; )
     {
         // Simply toggle the LED every 500ms, blocking between each toggle.
         vToggleLED();
         vTaskDelay( xDelay );
     }
 }

 * \defgroup vTaskDelay vTaskDelay
 * \ingroup TaskCtrl
 */
void vTaskDelay( const TickType_t xTicksToDelay ) PRIVILEGED_FUNCTION;

官方的註釋寫得非常非常仔細,甚至於用法都寫進了註釋;

任務調用這個函數,傳入一個 xTicksToDelay ,代表相對現在,對任務進行延時;這個的這個 xTicksToDelay 的單位是 Tick,比如,你 1ms 一個 Tick 的話,那麼設置 50,就是延時 50ms;如果 10ms 一個 Tick 的話,設置 50,就是延時 500ms;

實際情況是,將當前任務阻塞,到時間的時候,再把它加入到就緒隊列,參與調度;

簡單的 Demo 如下所示:

 void vTaskFunction( void * pvParameters )
 {
 // Block for 250ms.
 const TickType_t xDelay250ms = pdMS_TO_TICKS( 250 );

     for( ;; )
     {
         // Simply toggle the LED every 250ms, blocking between each toggle.
         vToggleLED();

         vTaskDelay( xDelay250ms );
     }
 }

時序上比如:

 

1.1.2、Implement

知道了用法後,我們來看下 vTaskDelay 的實現,在 task.c 文件中:

#if ( INCLUDE_vTaskDelay == 1 )

void vTaskDelay( const TickType_t xTicksToDelay )
{
BaseType_t xAlreadyYielded = pdFALSE;


    /* 如果延時時間爲0,則不會將當前任務加入延時列表 */
    if( xTicksToDelay > ( TickType_t ) 0U )
    {
        vTaskSuspendAll();
        {
            /* 將當前任務從就緒列表中移除,並根據當前系統節拍計數器值計算喚醒時間,然後將任務加入延時列表 */
            prvAddCurrentTaskToDelayedList( xTicksToDelay, pdFALSE );
        }
        xAlreadyYielded = xTaskResumeAll();
    }


    /* 強制執行一次上下文切換*/
    if( xAlreadyYielded == pdFALSE )
    {
        portYIELD_WITHIN_API();
    }
}

#endif /* INCLUDE_vTaskDelay */

vTaskDelay 的實現依賴於宏 INCLUDE_vTaskDelay ,必須定義這個宏,才能夠使用這個函數;

先判空,如果入參,也就是需要延時的時間大於 0 纔有效;

先調用 vTaskSuspendAll(); 來掛起調度器,暫時暫停調度;

然後調用 prvAddCurrentTaskToDelayedList 將當前的這個任務添加到 Delayed 鏈表;

這個函數稍後深入分析,這裏先擴展分析一下幾個鏈表:

/* Lists for ready and blocked tasks. --------------------
xDelayedTaskList1 and xDelayedTaskList2 could be move to function scople but
doing so breaks some kernel aware debuggers and debuggers that rely on removing
the static qualifier. */
PRIVILEGED_DATA static List_t pxReadyTasksLists[ configMAX_PRIORITIES ];/*< Prioritised ready tasks. */
PRIVILEGED_DATA static List_t xDelayedTaskList1;                        /*< Delayed tasks. */
PRIVILEGED_DATA static List_t xDelayedTaskList2;                        /*< Delayed tasks (two lists are used - one for delays that have overflowed the current tick count. */
PRIVILEGED_DATA static List_t * volatile pxDelayedTaskList;             /*< Points to the delayed task list currently being used. */
PRIVILEGED_DATA static List_t * volatile pxOverflowDelayedTaskList;     /*< Points to the delayed task list currently being used to hold tasks that have overflowed the current tick count. */
PRIVILEGED_DATA static List_t xPendingReadyList;                        /*< Tasks that have been readied while the scheduler was suspended.  They will be moved to the ready list when the scheduler is resumed. */

對於一個任務,有很多種狀態,可能是 Running、Ready、Blocked、Suspend,那對於不一樣的的狀態,FreeRTOS 中將其掛接到不同的鏈表,進行管理;

單核情況下,同一時間,只有一個任務處於 Running 狀態,所以用 pxCurrentTCB 便可以代表了 Running 狀態;

Blocked 阻塞態的任務,阻塞在時間上的任務,被掛接到名爲 xxxDelayedTaskListx 上:

PRIVILEGED_DATA static List_t xDelayedTaskList1;                        /*< Delayed tasks. */
PRIVILEGED_DATA static List_t xDelayedTaskList2;                        /*< Delayed tasks (two lists are used - one for delays that have overflowed the current tick count. */
PRIVILEGED_DATA static List_t * volatile pxDelayedTaskList;             /*< Points to the delayed task list currently being used. */
PRIVILEGED_DATA static List_t * volatile pxOverflowDelayedTaskList;     /*< Points to the delayed task list currently being used to hold tasks that have overflowed the current tick count. */

用了兩個鏈表,主要是爲了處理時間迴繞的場景(時間迴繞,的含義爲,用 U32 來記錄運行時間,每次 SysTick 的時候增加1個計數,由於 SysTick 的週期我們是知道的,比如 1ms,所以我們就知道當前的時間,但是如果系統長時間運行,記錄時間的 U32 勢必會溢出,就導致了時間迴繞);具體的處理方式我們細細來品;

最後調用 xTaskResumeAll 查看是否有需要調度的任務,有的話,強制觸發一次調度;

下面來看看 prvAddCurrentTaskToDelayedList 的實現:

static void prvAddCurrentTaskToDelayedList( TickType_t xTicksToWait, const BaseType_t xCanBlockIndefinitely )
{
TickType_t xTimeToWake;
const TickType_t xConstTickCount = xTickCount;

    #if( INCLUDE_xTaskAbortDelay == 1 )
    {
        /* About to enter a delayed list, so ensure the ucDelayAborted flag is
        reset to pdFALSE so it can be detected as having been set to pdTRUE
        when the task leaves the Blocked state. */
        pxCurrentTCB->ucDelayAborted = pdFALSE;
    }
    #endif

    /* Remove the task from the ready list before adding it to the blocked list
    as the same list item is used for both lists. */
    if( uxListRemove( &( pxCurrentTCB->xStateListItem ) ) == ( UBaseType_t ) 0 )
    {
        /* The current task must be in a ready list, so there is no need to
        check, and the port reset macro can be called directly. */
        portRESET_READY_PRIORITY( pxCurrentTCB->uxPriority, uxTopReadyPriority ); /*lint !e931 pxCurrentTCB cannot change as it is the calling task.  pxCurrentTCB->uxPriority and uxTopReadyPriority cannot change as called with scheduler suspended or in a critical section. */
    }
    else
    {
        mtCOVERAGE_TEST_MARKER();
    }

    #if ( INCLUDE_vTaskSuspend == 1 )
    {
        if( ( xTicksToWait == portMAX_DELAY ) && ( xCanBlockIndefinitely != pdFALSE ) )
        {
            /* Add the task to the suspended task list instead of a delayed task
            list to ensure it is not woken by a timing event.  It will block
            indefinitely. */
            vListInsertEnd( &xSuspendedTaskList, &( pxCurrentTCB->xStateListItem ) );
        }
        else
        {
            /* Calculate the time at which the task should be woken if the event
            does not occur.  This may overflow but this doesn't matter, the
            kernel will manage it correctly. */
            xTimeToWake = xConstTickCount + xTicksToWait;

            /* The list item will be inserted in wake time order. */
            listSET_LIST_ITEM_VALUE( &( pxCurrentTCB->xStateListItem ), xTimeToWake );

            if( xTimeToWake < xConstTickCount )
            {
                /* Wake time has overflowed.  Place this item in the overflow
                list. */
                vListInsert( pxOverflowDelayedTaskList, &( pxCurrentTCB->xStateListItem ) );
            }
            else
            {
                /* The wake time has not overflowed, so the current block list
                is used. */
                vListInsert( pxDelayedTaskList, &( pxCurrentTCB->xStateListItem ) );

                /* If the task entering the blocked state was placed at the
                head of the list of blocked tasks then xNextTaskUnblockTime
                needs to be updated too. */
                if( xTimeToWake < xNextTaskUnblockTime )
                {
                    xNextTaskUnblockTime = xTimeToWake;
                }
                else
                {
                    mtCOVERAGE_TEST_MARKER();
                }
            }
        }
    }
    #else /* INCLUDE_vTaskSuspend */
    {
        /* Calculate the time at which the task should be woken if the event
        does not occur.  This may overflow but this doesn't matter, the kernel
        will manage it correctly. */
        xTimeToWake = xConstTickCount + xTicksToWait;

        /* The list item will be inserted in wake time order. */
        listSET_LIST_ITEM_VALUE( &( pxCurrentTCB->xStateListItem ), xTimeToWake );

        if( xTimeToWake < xConstTickCount )
        {
            /* Wake time has overflowed.  Place this item in the overflow list. */
            vListInsert( pxOverflowDelayedTaskList, &( pxCurrentTCB->xStateListItem ) );
        }
        else
        {
            /* The wake time has not overflowed, so the current block list is used. */
            vListInsert( pxDelayedTaskList, &( pxCurrentTCB->xStateListItem ) );

            /* If the task entering the blocked state was placed at the head of the
            list of blocked tasks then xNextTaskUnblockTime needs to be updated
            too. */
            if( xTimeToWake < xNextTaskUnblockTime )
            {
                xNextTaskUnblockTime = xTimeToWake;
            }
            else
            {
                mtCOVERAGE_TEST_MARKER();
            }
        }

        /* Avoid compiler warning when INCLUDE_vTaskSuspend is not 1. */
        ( void ) xCanBlockIndefinitely;
    }
    #endif /* INCLUDE_vTaskSuspend */
}

代碼的邏輯是:

1、首先,當前的這個任務,肯定是處於 Ready 鏈表,所以將它從 Ready 鏈表移除;

2、獲取當前的絕對時間 xTickCount,也就是 SysTick 會累積增加的那個,然後將延時的時間加上這個基準時間,配置成爲喚醒該任務的時間,並賦值給這個 Item 的 Value 字段,並將其掛接到 Delay 鏈表;

雖然代碼邏輯如上所示,不過在鏈表掛接的時候,需要處理一些臨界狀態,比如,將當前任務從 Ready 鏈表中拿去的時候,需要判斷當前 Ready 鏈表中,拿去這個任務後,是否已爲空,如果是這樣的話,就要清除記錄優先級對應的 uxTopReadyPriority (Bitmap);

配置喚醒時間的時候,就要通過比對時間基準來判斷 U32 的迴繞,如果時間迴繞,那麼將其掛接到 pxOverflowDelayedTaskList 這個鏈表,否則掛接到 pxDelayedTaskList 鏈表;

喚醒的時間如果比最近的喚醒時間還早,那麼需要更新喚醒時間到全局變量 xNextTaskUnblockTime 中,在 SysTick 來的時候進行判斷比對喚醒時間;

總的來說,vTaskDelay 接口實現基於當前時間的一個增量延時,並 Block 了當前任務;

 

1.2、vTaskDelayUntil

我們再來看看這個 vTaskDelayUntil ;

1.2.1、Usage

這個函數是絕對延時函數,可以用來做週期性任務(必須最高優先級纔可以),它的函數原型爲:

/**
 * task. h
 * <pre>void vTaskDelayUntil( TickType_t *pxPreviousWakeTime, const TickType_t xTimeIncrement );</pre>
 *
 * INCLUDE_vTaskDelayUntil must be defined as 1 for this function to be available.
 * See the configuration section for more information.
 *
 * Delay a task until a specified time.  This function can be used by periodic
 * tasks to ensure a constant execution frequency.
 *
 * This function differs from vTaskDelay () in one important aspect:  vTaskDelay () will
 * cause a task to block for the specified number of ticks from the time vTaskDelay () is
 * called.  It is therefore difficult to use vTaskDelay () by itself to generate a fixed
 * execution frequency as the time between a task starting to execute and that task
 * calling vTaskDelay () may not be fixed [the task may take a different path though the
 * code between calls, or may get interrupted or preempted a different number of times
 * each time it executes].
 *
 * Whereas vTaskDelay () specifies a wake time relative to the time at which the function
 * is called, vTaskDelayUntil () specifies the absolute (exact) time at which it wishes to
 * unblock.
 *
 * The constant portTICK_PERIOD_MS can be used to calculate real time from the tick
 * rate - with the resolution of one tick period.
 *
 * @param pxPreviousWakeTime Pointer to a variable that holds the time at which the
 * task was last unblocked.  The variable must be initialised with the current time
 * prior to its first use (see the example below).  Following this the variable is
 * automatically updated within vTaskDelayUntil ().
 *
 * @param xTimeIncrement The cycle time period.  The task will be unblocked at
 * time *pxPreviousWakeTime + xTimeIncrement.  Calling vTaskDelayUntil with the
 * same xTimeIncrement parameter value will cause the task to execute with
 * a fixed interface period.
 *
 * Example usage:
   <pre>
 // Perform an action every 10 ticks.
 void vTaskFunction( void * pvParameters )
 {
 TickType_t xLastWakeTime;
 const TickType_t xFrequency = 10;

	 // Initialise the xLastWakeTime variable with the current time.
	 xLastWakeTime = xTaskGetTickCount ();
	 for( ;; )
	 {
		 // Wait for the next cycle.
		 vTaskDelayUntil( &xLastWakeTime, xFrequency );

		 // Perform action here.
	 }
 }
   </pre>
 * \defgroup vTaskDelayUntil vTaskDelayUntil
 * \ingroup TaskCtrl
 */
void vTaskDelayUntil( TickType_t * const pxPreviousWakeTime, const TickType_t xTimeIncrement ) PRIVILEGED_FUNCTION;

兩個入參:

pxPreviousWakeTime:初始化爲當前的時間,後面便不用管了;

xTimeIncrement:任務的週期;

簡單的 Demo 如下所示:

void vTaskB( void * pvParameters )  
{  
    static portTickType xLastWakeTime;  
    const portTickType xFrequency = pdMS_TO_TICKS(500);  
   
    // 使用當前時間初始化變量xLastWakeTime ,注意這和vTaskDelay()函數不同 
    xLastWakeTime = xTaskGetTickCount();  
   
    for( ;; )  
    {  
        /* 調用系統延時函數,週期性阻塞500ms */        
        vTaskDelayUntil( &xLastWakeTime,xFrequency );  
   
         //  ...
         //  這裏爲任務主體代碼,週期性執行.注意這和vTaskDelay()函數也不同
         //  ...
  
    }  
}  

 

1.2.2、Implement

它的實現如下所示:

#if ( INCLUDE_vTaskDelayUntil == 1 )

    void vTaskDelayUntil( TickType_t * const pxPreviousWakeTime, const TickType_t xTimeIncrement )
    {
    TickType_t xTimeToWake;
    BaseType_t xAlreadyYielded, xShouldDelay = pdFALSE;

        configASSERT( pxPreviousWakeTime );
        configASSERT( ( xTimeIncrement > 0U ) );
        configASSERT( uxSchedulerSuspended == 0 );

        vTaskSuspendAll();
        {
            /* Minor optimisation.  The tick count cannot change in this
            block. */
            const TickType_t xConstTickCount = xTickCount;

            /* Generate the tick time at which the task wants to wake. */
            xTimeToWake = *pxPreviousWakeTime + xTimeIncrement;

            if( xConstTickCount < *pxPreviousWakeTime )
            {
                /* The tick count has overflowed since this function was
                lasted called.  In this case the only time we should ever
                actually delay is if the wake time has also overflowed,
                and the wake time is greater than the tick time.  When this
                is the case it is as if neither time had overflowed. */
                if( ( xTimeToWake < *pxPreviousWakeTime ) && ( xTimeToWake > xConstTickCount ) )
                {
                    xShouldDelay = pdTRUE;
                }
                else
                {
                    mtCOVERAGE_TEST_MARKER();
                }
            }
            else
            {
                /* The tick time has not overflowed.  In this case we will
                delay if either the wake time has overflowed, and/or the
                tick time is less than the wake time. */
                if( ( xTimeToWake < *pxPreviousWakeTime ) || ( xTimeToWake > xConstTickCount ) )
                {
                    xShouldDelay = pdTRUE;
                }
                else
                {
                    mtCOVERAGE_TEST_MARKER();
                }
            }

            /* Update the wake time ready for the next call. */
            *pxPreviousWakeTime = xTimeToWake;

            if( xShouldDelay != pdFALSE )
            {
                traceTASK_DELAY_UNTIL( xTimeToWake );

                /* prvAddCurrentTaskToDelayedList() needs the block time, not
                the time to wake, so subtract the current tick count. */
                prvAddCurrentTaskToDelayedList( xTimeToWake - xConstTickCount, pdFALSE );
            }
            else
            {
                mtCOVERAGE_TEST_MARKER();
            }
        }
        xAlreadyYielded = xTaskResumeAll();

        /* Force a reschedule if xTaskResumeAll has not already done so, we may
        have put ourselves to sleep. */
        if( xAlreadyYielded == pdFALSE )
        {
            portYIELD_WITHIN_API();
        }
        else
        {
            mtCOVERAGE_TEST_MARKER();
        }
    }

#endif /* INCLUDE_vTaskDelayUntil */

也是先掛起任務,最後恢復;

與 xTaskDelay 不同,它定義了幾個變量:

*pxPreviousWakeTime 代表上一次解除阻塞執行任務的時間;

xConstTickCount 是當前的時間;

xTimeToWake 是下一次喚醒的時間;

首先還是判斷 Tick 溢出的場景;

可以看到,在最後調用:

prvAddCurrentTaskToDelayedList

的時候,它每次都是動態的去調整 Delay 的時間:xTimeToWake - xConstTickCount,儘量做到任務執行開始的時間保持一致;

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章