1. 程式人生 > 實用技巧 >FreeRTOS --(13)任務管理之空閒任務

FreeRTOS --(13)任務管理之空閒任務

轉載自https://blog.csdn.net/zhoutaopower/article/details/107180016

建立完畢任務,啟動排程器,任務控制,系統 SysTick 來臨後判斷是否需上下文切換;

如果沒有其他任務執行的情況下,FreeRTOS 的 Idle 任務將被排程投入執行;

在啟動排程器的時候,Idle 任務就被建立了,優先順序為最低 0;

void vTaskStartScheduler( void )
{
.....................
xReturn = xTaskCreate(  prvIdleTask,
                        configIDLE_TASK_NAME,
                        configMINIMAL_STACK_SIZE,
                        ( 
void * ) NULL, portPRIVILEGE_BIT, &xIdleTaskHandle ); ..................... }

當某時刻所有優先順序高於 Idle 任務的任務處於被阻塞或者部分被掛起的狀態,此刻排程器會排程 Idle 任務執行,它的執行函式為:

/*
 * -----------------------------------------------------------
 * The Idle task.
 * ----------------------------------------------------------
 *
 * The portTASK_FUNCTION() macro is used to allow port/compiler specific
 * language extensions.  The equivalent prototype for this function is:
 *
 * void prvIdleTask( void *pvParameters );
 *
 
*/ static portTASK_FUNCTION( prvIdleTask, pvParameters ) { /* Stop warnings. */ ( void ) pvParameters; /** THIS IS THE RTOS IDLE TASK - WHICH IS CREATED AUTOMATICALLY WHEN THE SCHEDULER IS STARTED. **/ /* In case a task that has a secure context deletes itself, in which case the idle task is responsible for deleting the task's secure context, if any.
*/ portALLOCATE_SECURE_CONTEXT( configMINIMAL_SECURE_STACK_SIZE ); for( ;; ) { /* See if any tasks have deleted themselves - if so then the idle task is responsible for freeing the deleted task's TCB and stack. */ prvCheckTasksWaitingTermination(); #if ( configUSE_PREEMPTION == 0 ) { /* If we are not using preemption we keep forcing a task switch to see if any other task has become available. If we are using preemption we don't need to do this as any task becoming available will automatically get the processor anyway. */ taskYIELD(); } #endif /* configUSE_PREEMPTION */ #if ( ( configUSE_PREEMPTION == 1 ) && ( configIDLE_SHOULD_YIELD == 1 ) ) { /* When using preemption tasks of equal priority will be timesliced. If a task that is sharing the idle priority is ready to run then the idle task should yield before the end of the timeslice. A critical region is not required here as we are just reading from the list, and an occasional incorrect value will not matter. If the ready list at the idle priority contains more than one task then a task other than the idle task is ready to execute. */ if( listCURRENT_LIST_LENGTH( &( pxReadyTasksLists[ tskIDLE_PRIORITY ] ) ) > ( UBaseType_t ) 1 ) { taskYIELD(); } else { mtCOVERAGE_TEST_MARKER(); } } #endif /* ( ( configUSE_PREEMPTION == 1 ) && ( configIDLE_SHOULD_YIELD == 1 ) ) */ #if ( configUSE_IDLE_HOOK == 1 ) { extern void vApplicationIdleHook( void ); /* Call the user defined function from within the idle task. This allows the application designer to add background functionality without the overhead of a separate task. NOTE: vApplicationIdleHook() MUST NOT, UNDER ANY CIRCUMSTANCES, CALL A FUNCTION THAT MIGHT BLOCK. */ vApplicationIdleHook(); } #endif /* configUSE_IDLE_HOOK */ /* This conditional compilation should use inequality to 0, not equality to 1. This is to ensure portSUPPRESS_TICKS_AND_SLEEP() is called when user defined low power mode implementations require configUSE_TICKLESS_IDLE to be set to a value other than 1. */ #if ( configUSE_TICKLESS_IDLE != 0 ) { TickType_t xExpectedIdleTime; /* It is not desirable to suspend then resume the scheduler on each iteration of the idle task. Therefore, a preliminary test of the expected idle time is performed without the scheduler suspended. The result here is not necessarily valid. */ xExpectedIdleTime = prvGetExpectedIdleTime(); if( xExpectedIdleTime >= configEXPECTED_IDLE_TIME_BEFORE_SLEEP ) { vTaskSuspendAll(); { /* Now the scheduler is suspended, the expected idle time can be sampled again, and this time its value can be used. */ configASSERT( xNextTaskUnblockTime >= xTickCount ); xExpectedIdleTime = prvGetExpectedIdleTime(); /* Define the following macro to set xExpectedIdleTime to 0 if the application does not want portSUPPRESS_TICKS_AND_SLEEP() to be called. */ configPRE_SUPPRESS_TICKS_AND_SLEEP_PROCESSING( xExpectedIdleTime ); if( xExpectedIdleTime >= configEXPECTED_IDLE_TIME_BEFORE_SLEEP ) { traceLOW_POWER_IDLE_BEGIN(); portSUPPRESS_TICKS_AND_SLEEP( xExpectedIdleTime ); traceLOW_POWER_IDLE_END(); } else { mtCOVERAGE_TEST_MARKER(); } } ( void ) xTaskResumeAll(); } else { mtCOVERAGE_TEST_MARKER(); } } #endif /* configUSE_TICKLESS_IDLE */ } }

Idle 任務也是一個無限迴圈:

1、呼叫prvCheckTasksWaitingTermination() 判斷是否有需要 Task 自己刪除自己,如果有,那麼在 Idle 任務中來回收這種型別的場景:

static void prvCheckTasksWaitingTermination( void )
{
 
    /** THIS FUNCTION IS CALLED FROM THE RTOS IDLE TASK **/
 
    #if ( INCLUDE_vTaskDelete == 1 )
    {
        TCB_t *pxTCB;
 
        /* uxDeletedTasksWaitingCleanUp is used to prevent taskENTER_CRITICAL()
        being called too often in the idle task. */
        while( uxDeletedTasksWaitingCleanUp > ( UBaseType_t ) 0U )
        {
            taskENTER_CRITICAL();
            {
                pxTCB = listGET_OWNER_OF_HEAD_ENTRY( ( &xTasksWaitingTermination ) ); /*lint !e9079 void * is used as this macro is used with timers and co-routines too.  Alignment is known to be fine as the type of the pointer stored and retrieved is the same. */
                ( void ) uxListRemove( &( pxTCB->xStateListItem ) );
                --uxCurrentNumberOfTasks;
                --uxDeletedTasksWaitingCleanUp;
            }
            taskEXIT_CRITICAL();
 
            prvDeleteTCB( pxTCB );
        }
    }
    #endif /* INCLUDE_vTaskDelete */
}

如果支援任務刪除,而且有需要被刪除的任務的話,進入臨界區,取出要被刪除的任務,更新當前任務個數和待刪除任務個數,退出臨界區,並呼叫 prvDeleteTCB() 介面來刪除任務的資源,其實就是呼叫了vPortFree( pxTCB->pxStack ); 和vPortFree( pxTCB ); 來釋放任務的 TCB 結構和堆疊;

2、如果定義了configUSE_PREEMPTION 為 1(支援搶佔),同時configIDLE_SHOULD_YIELD 也為 1 (如果有與 Idle 任務相同優先順序的任務,並且處於 Ready 狀態,那麼 Idle 任務將為其讓路)的情況,Idle 任務直接呼叫taskYIELD(); 引發一次排程,放棄 CPU;

3、如果使能了configUSE_IDLE_HOOK,也就是使用者的 Idle 鉤子函式,則呼叫vApplicationIdleHook;

4、如果使能了configUSE_TICKLESS_IDLE,就意味著要進入低功耗場景,當然,既然都呼叫 Idle 任務了,進入低功耗理所應當;這裡的 Tickless 的含義是:進入低功耗後,Systick 不在來中斷,因為 Tick 心跳很頻繁的話,處理器很快就被喚醒了,失去了低功耗的意義;

5、呼叫prvGetExpectedIdleTime 獲取距離下一個最近的阻塞任務的執行時間,與 當前的時間做減法,得到最大的可以進入低功耗的時間,當然這裡只能判斷阻塞在時間上的任務,對於事件,我們並不知道什麼時候會來,也許是中斷啟用事件,不過這樣要求中斷能夠喚醒處理器,否則中斷無法得到及時處理,那麼 RTOS 的實時任務的執行也得不到實時的保證;

6、如果獲取得到的最大進入低功耗的時間xExpectedIdleTime 大於了我們配置的期望睡眠的最小時間,也就是滿足進入低功耗的條件,那麼掛起排程器(因為馬上要進入 Tickless),呼叫portSUPPRESS_TICKS_AND_SLEEP( xExpectedIdleTime ); 進入低功耗;

7、portSUPPRESS_TICKS_AND_SLEEP( xExpectedIdleTime ) 的實現是和處理器相關,因為是帶 port 字首,每種處理器進入低功耗的方式不盡相同,即便是同一種處理器,進入低功耗也有幾種模式,所以這裡交給處理器相關 port.c 去實現;

8、喚醒後,一般的,原地繼續執行,呼叫xTaskResumeAll 恢復排程器;

針對 Cortex-M3 進入睡眠部分:

#define portSUPPRESS_TICKS_AND_SLEEP( xExpectedIdleTime ) \
vPortSuppressTicksAndSleep( xExpectedIdleTime )

呼叫到了vPortSuppressTicksAndSleep(xExpectedIdleTime)入參是期待睡眠的時間;也就是 Tick 個數:

#if( configUSE_TICKLESS_IDLE == 1 )
 
    __weak void vPortSuppressTicksAndSleep( TickType_t xExpectedIdleTime )
    {
    uint32_t ulReloadValue, ulCompleteTickPeriods, ulCompletedSysTickDecrements;
    TickType_t xModifiableIdleTime;
 
        /* Make sure the SysTick reload value does not overflow the counter. */
        /* 睡眠的最大值不能夠超過處理器支援的最大值 */
        if( xExpectedIdleTime > xMaximumPossibleSuppressedTicks )
        {
            xExpectedIdleTime = xMaximumPossibleSuppressedTicks;
        }
 
        /* Stop the SysTick momentarily.  The time the SysTick is stopped for
        is accounted for as best it can be, but using the tickless mode will
        inevitably result in some tiny drift of the time maintained by the
        kernel with respect to calendar time. */
        /* 禁止 SysTick */
        portNVIC_SYSTICK_CTRL_REG &= ~portNVIC_SYSTICK_ENABLE_BIT;
 
        /* Calculate the reload value required to wait xExpectedIdleTime
        tick periods.  -1 is used because this code will execute part way
        through one of the tick periods. */
        /* 計算將要配置到 SYSTICK  用於喚醒的值 */
        ulReloadValue = portNVIC_SYSTICK_CURRENT_VALUE_REG + ( ulTimerCountsForOneTick * ( xExpectedIdleTime - 1UL ) );
        if( ulReloadValue > ulStoppedTimerCompensation )
        {
            ulReloadValue -= ulStoppedTimerCompensation;
        }
 
        /* Enter a critical section but don't use the taskENTER_CRITICAL()
        method as that will mask interrupts that should exit sleep mode. */
        /* 關閉中斷 */
        __disable_irq();
        __dsb( portSY_FULL_READ_WRITE );
        __isb( portSY_FULL_READ_WRITE );
 
        /* If a context switch is pending or a task is waiting for the scheduler
        to be unsuspended then abandon the low power entry. */
        /* 再次 Check 有沒有非 Idle 狀態待執行的任務 */
        if( eTaskConfirmSleepModeStatus() == eAbortSleep )
        {
            /* Restart from whatever is left in the count register to complete
            this tick period. */
            /* 重新配置 SysTICK 終止睡眠流程 */
            portNVIC_SYSTICK_LOAD_REG = portNVIC_SYSTICK_CURRENT_VALUE_REG;
 
            /* Restart SysTick. */
            portNVIC_SYSTICK_CTRL_REG |= portNVIC_SYSTICK_ENABLE_BIT;
 
            /* Reset the reload register to the value required for normal tick
            periods. */
            portNVIC_SYSTICK_LOAD_REG = ulTimerCountsForOneTick - 1UL;
 
            /* Re-enable interrupts - see comments above __disable_irq() call
            above. */
            /* 開啟中斷 */
            __enable_irq();
        }
        else
        {
            /* Set the new reload value. */
            /* 將 SysTick 的時間配置為之前計算好的時間 */
            portNVIC_SYSTICK_LOAD_REG = ulReloadValue;
 
            /* Clear the SysTick count flag and set the count value back to
            zero. */
            /* 清除當前 SysTick 的值 */
            portNVIC_SYSTICK_CURRENT_VALUE_REG = 0UL;
 
            /* Restart SysTick. */
            /* 開啟 SysTick */
            portNVIC_SYSTICK_CTRL_REG |= portNVIC_SYSTICK_ENABLE_BIT;
 
            /* Sleep until something happens.  configPRE_SLEEP_PROCESSING() can
            set its parameter to 0 to indicate that its implementation contains
            its own wait for interrupt or wait for event instruction, and so wfi
            should not be executed again.  However, the original expected idle
            time variable must remain unmodified, so a copy is taken. */
            xModifiableIdleTime = xExpectedIdleTime;
            configPRE_SLEEP_PROCESSING( xModifiableIdleTime );
            /* 執行 WFI 睡眠 */
            if( xModifiableIdleTime > 0 )
            {
                __dsb( portSY_FULL_READ_WRITE );
                __wfi();
                __isb( portSY_FULL_READ_WRITE );
            }
            /* 此處為喚醒 */
            configPOST_SLEEP_PROCESSING( xExpectedIdleTime );
 
            /* Re-enable interrupts to allow the interrupt that brought the MCU
            out of sleep mode to execute immediately.  see comments above
            __disable_interrupt() call above. */
            /* 因為可能是其他中斷喚醒的 WFI,立馬開啟中斷,進入 ISR */
            __enable_irq();
            __dsb( portSY_FULL_READ_WRITE );
            __isb( portSY_FULL_READ_WRITE );
 
            /* Disable interrupts again because the clock is about to be stopped
            and interrupts that execute while the clock is stopped will increase
            any slippage between the time maintained by the RTOS and calendar
            time. */
            /* 關閉中斷,做處理 */
            __disable_irq();
            __dsb( portSY_FULL_READ_WRITE );
            __isb( portSY_FULL_READ_WRITE );
            
            /* Disable the SysTick clock without reading the 
            portNVIC_SYSTICK_CTRL_REG register to ensure the
            portNVIC_SYSTICK_COUNT_FLAG_BIT is not cleared if it is set.  Again, 
            the time the SysTick is stopped for is accounted for as best it can 
            be, but using the tickless mode will inevitably result in some tiny 
            drift of the time maintained by the kernel with respect to calendar 
            time*/
            /* 重新配置 SysTick */
            portNVIC_SYSTICK_CTRL_REG = ( portNVIC_SYSTICK_CLK_BIT | portNVIC_SYSTICK_INT_BIT );
 
            /* Determine if the SysTick clock has already counted to zero and
            been set back to the current reload value (the reload back being
            correct for the entire expected idle time) or if the SysTick is yet
            to count to zero (in which case an interrupt other than the SysTick
            must have brought the system out of sleep mode). */
            if( ( portNVIC_SYSTICK_CTRL_REG & portNVIC_SYSTICK_COUNT_FLAG_BIT ) != 0 )
            {
                uint32_t ulCalculatedLoadValue;
 
                /* The tick interrupt is already pending, and the SysTick count
                reloaded with ulReloadValue.  Reset the
                portNVIC_SYSTICK_LOAD_REG with whatever remains of this tick
                period. */
                ulCalculatedLoadValue = ( ulTimerCountsForOneTick - 1UL ) - ( ulReloadValue - portNVIC_SYSTICK_CURRENT_VALUE_REG );
 
                /* Don't allow a tiny value, or values that have somehow
                underflowed because the post sleep hook did something
                that took too long. */
                if( ( ulCalculatedLoadValue < ulStoppedTimerCompensation ) || ( ulCalculatedLoadValue > ulTimerCountsForOneTick ) )
                {
                    ulCalculatedLoadValue = ( ulTimerCountsForOneTick - 1UL );
                }
 
                portNVIC_SYSTICK_LOAD_REG = ulCalculatedLoadValue;
 
                /* As the pending tick will be processed as soon as this
                function exits, the tick value maintained by the tick is stepped
                forward by one less than the time spent waiting. */
                ulCompleteTickPeriods = xExpectedIdleTime - 1UL;
            }
            else
            {
                /* Something other than the tick interrupt ended the sleep.
                Work out how long the sleep lasted rounded to complete tick
                periods (not the ulReload value which accounted for part
                ticks). */
                ulCompletedSysTickDecrements = ( xExpectedIdleTime * ulTimerCountsForOneTick ) - portNVIC_SYSTICK_CURRENT_VALUE_REG;
 
                /* How many complete tick periods passed while the processor
                was waiting? */
                ulCompleteTickPeriods = ulCompletedSysTickDecrements / ulTimerCountsForOneTick;
 
                /* The reload value is set to whatever fraction of a single tick
                period remains. */
                portNVIC_SYSTICK_LOAD_REG = ( ( ulCompleteTickPeriods + 1UL ) * ulTimerCountsForOneTick ) - ulCompletedSysTickDecrements;
            }
 
            /* Restart SysTick so it runs from portNVIC_SYSTICK_LOAD_REG
            again, then set portNVIC_SYSTICK_LOAD_REG back to its standard
            value. */
            portNVIC_SYSTICK_CURRENT_VALUE_REG = 0UL;
            portNVIC_SYSTICK_CTRL_REG |= portNVIC_SYSTICK_ENABLE_BIT;
            vTaskStepTick( ulCompleteTickPeriods );
            portNVIC_SYSTICK_LOAD_REG = ulTimerCountsForOneTick - 1UL;
 
            /* Exit with interrpts enabled. */
            __enable_irq();
        }
    }
 
#endif /* #if configUSE_TICKLESS_IDLE */

內容不少,慢慢看即可:

0、入參是期望睡眠的 Tick 數目,這裡不用這麼抽象,比如配置的 Tick 週期為 1ms,那麼這裡就是 ms 數;

這裡有 3 個全域性變數需要說明一下:

/*
 * The number of SysTick increments that make up one tick period.
 */
#if( configUSE_TICKLESS_IDLE == 1 )
    static uint32_t ulTimerCountsForOneTick = 0;
#endif /* configUSE_TICKLESS_IDLE */
 
/*
 * The maximum number of tick periods that can be suppressed is limited by the
 * 24 bit resolution of the SysTick timer.
 */
#if( configUSE_TICKLESS_IDLE == 1 )
    static uint32_t xMaximumPossibleSuppressedTicks = 0;
#endif /* configUSE_TICKLESS_IDLE */
 
/*
 * Compensate for the CPU cycles that pass while the SysTick is stopped (low
 * power functionality only.
 */
#if( configUSE_TICKLESS_IDLE == 1 )
    static uint32_t ulStoppedTimerCompensation = 0;
#endif /* configUSE_TICKLESS_IDLE */

在開啟排程器,配置 SysTick 的時候,這 3 個全域性變數被賦值初始化:

#if( configUSE_TICKLESS_IDLE == 1 )
{
    ulTimerCountsForOneTick = ( configSYSTICK_CLOCK_HZ / configTICK_RATE_HZ );
    xMaximumPossibleSuppressedTicks = portMAX_24_BIT_NUMBER / ulTimerCountsForOneTick;
    ulStoppedTimerCompensation = portMISSED_COUNTS_FACTOR / ( configCPU_CLOCK_HZ / configSYSTICK_CLOCK_HZ );
}
#endif /* configUSE_TICKLESS_IDLE */

ulTimerCountsForOneTick :代表了一個 SysTick 配置到暫存器的 Tick 的 Count;換句話來說,就是產生 1ms 的SysTick 中斷,需要配置給暫存器的值;

xMaximumPossibleSuppressedTicks:代表了在溢位之前,硬體最大支援多少個Tick;(因為 Cortex-M3 處理器配置給硬體的 Tick Count 最大是 24 bit 的,所以這裡用 24bit 的全 1 除以ulTimerCountsForOneTick);

ulStoppedTimerCompensation:代表了一個時鐘補償的因子,這裡是固定的 45;

1、首先判斷期望睡眠的值是否大於了處理器的xMaximumPossibleSuppressedTicks,如果是,那麼將睡眠的值限定在xMaximumPossibleSuppressedTicks;

2、禁止 SysTick 模組;

3、計算新的 SysTick 的 Load 值,這裡的原理是,因為需要讓處理器進入 WFI (WaitingForInterrupt),進入 WFI 後,處理器可以被中斷喚醒,並繼續執行;其實這裡的 Tickless 並不是真的一直關閉了 SysTick ,而是將睡眠的時間配置到了 SysTick 中,所以這裡才會限制睡眠時間;

ulReloadValue = portNVIC_SYSTICK_CURRENT_VALUE_REG + \
( ulTimerCountsForOneTick * ( xExpectedIdleTime - 1UL ) );
 
if( ulReloadValue > ulStoppedTimerCompensation )
{
    ulReloadValue -= ulStoppedTimerCompensation;
}

使用當前的 SysTick 的值,加上睡眠的時間乘以每個 Tick 的 Count,計算出即將配置到 SysTick 硬體暫存器的值;然後對補償因子做減法;

4、__disable_irq,關閉中斷,刷指令和資料流水線;

5、判斷是否還有需要被執行的任務,如果有,那麼重新配置 SysTick 還是為 1ms,並使能 SysTick,開啟中斷,退出睡眠邏輯;

6、如果沒有要被執行的任務,將計算出來最大的睡眠時間ulReloadValue配置進 SysTick 計數器暫存器,開啟 SysTick,此刻的 SysTick 便是睡眠的時間;

7、進入 WFI 睡眠;

8、如果有中斷,則對 WFI 原地喚醒,繼續執行,這裡可能是 SysTick 的 IRQ喚醒,也可能是其他中斷喚醒;

9、__enable_irq,開啟中斷,因為可能是被其他 IRQ 喚醒,這裡需要立馬執行 ISR;

10、__disable_irq,關閉中斷,刷指令和資料流水線,因為下面的配置不允許被打斷;

11、重新配置 SysTick 成為 OS 的心跳(也就是 1ms),並使能 SysTick;