FreeRTOS 任務(wù)調(diào)度 任務(wù)創(chuàng)建

@(嵌入式)

Freertos

FreeRtos

簡述

FreeRTOS 的任務(wù)調(diào)度在 Source/include/task.c 中實現(xiàn),包含了任務(wù)的創(chuàng)建、切換、掛起、延時和刪除等所有功能。涉及到的鏈表組織見文章 <FreeRTOS 任務(wù)調(diào)度 List 組織> 。任務(wù)切換實現(xiàn)代碼量比較大,因此關(guān)于任務(wù)調(diào)度這一塊會分幾個文章來描述,這一篇主要分析任務(wù)的創(chuàng)建的調(diào)用與實現(xiàn)。

分析的源碼版本是 v9.0.0
(為了方便查看,github 上保留了一份源碼Source目錄下的拷貝)

任務(wù)狀態(tài)

taskstate

系統(tǒng)運行過程,任務(wù)可能處于以下各種狀態(tài),各個狀態(tài)之間切換的關(guān)系如上圖所示。

  • Running
    運行狀態(tài), 當前正在執(zhí)行,占有處理器的任務(wù)
  • Ready
    就緒狀態(tài),準備被運行的任務(wù),沒有被掛起和阻塞,但不是當前正在執(zhí)行的任務(wù),等待更高優(yōu)先級任務(wù)或者同等級任務(wù)時間片結(jié)束釋放處理器
  • Blocked
    阻塞狀態(tài),任務(wù)在等待一個事件而進入阻塞狀態(tài),比如延時、獲取信號量等
  • Suspended
    掛起狀態(tài),任務(wù)由于調(diào)用 vTaskSuspend() 而被掛起不能被執(zhí)行, 直到調(diào)用 xTaskResume() 重新恢復(fù)

使用示例

FreeRTOS 中創(chuàng)建任務(wù)并開始調(diào)度的基本框架如下 :

void vATaskFunction( void *pvParameters )
{
    for( ;; )
    {
    // -- 任務(wù)代碼 --
    }
    // 任務(wù)不能有任何 返回
    // 對自行結(jié)束的任務(wù),退出前需要自行清理
    vTaskDelete( NULL );
}

void main(void)
{
    static unsigned char ucParameterToPass;  
    xTaskHandle xHandle;  
    xTaskCreate( vATaskFunction, /*任務(wù)實現(xiàn)函數(shù)*/
                "TASK_NAME", /*任務(wù)名,方便調(diào)試*/
                STACK_SIZE,  /*任務(wù)堆棧大小 *StackType_t*/
                &ucParameterToPass, /*任務(wù)運行時的參數(shù)*/ 
                tskIDLE_PRIORITY, /*任務(wù)優(yōu)先級*/
                &xHandle );  /*回傳任務(wù)句柄,供其他地方引用任務(wù)*/
    // 其他任務(wù)和拉拉雜雜的初始化
    // 啟動任務(wù)調(diào)度器 loop ....
}

任務(wù)創(chuàng)建函數(shù)中, 設(shè)置的棧大小單位由使用平臺的 StackType_t 決定,不同平臺棧指針對齊有自己的要求。
回傳的句柄(指向TCB的指針)一般用于在其他任務(wù)中發(fā)送消息通知給任務(wù),或者刪除任務(wù)時引用。
任務(wù)成功創(chuàng)建后返回 pdPASS, 否則失敗回傳錯誤碼。

另外,刪除任務(wù),可以通過其他任務(wù)中調(diào)用 voidvTaskDelete進行刪除,此時該任務(wù)會從各種鏈表中移除,并且內(nèi)存會被馬上回收; 但是如果是任務(wù)自己調(diào)用刪除,則其內(nèi)存回收需要由空閑任務(wù)來完成(畢竟當前正在使用這些資源)。
使用 voidvTaskDelete 的前提是在 FreeRTOSConfig.h 設(shè)置 INCLUDE_vTaskDelete 為1(Tips !! API 在使用前最后需要看看是否需要設(shè)置對應(yīng)的宏定義)。


敘述完上層的調(diào)用,后續(xù)介紹背后具體是如何實現(xiàn)的。

數(shù)據(jù)結(jié)構(gòu)

TCB

任務(wù)調(diào)度離不開任務(wù)控制塊(TCB), 用于存儲任務(wù)的狀態(tài)信息、運行時環(huán)境等。源代碼見 tskTaskControlBlock, 以下具體介紹下這個數(shù)據(jù)結(jié)構(gòu)。

typedef struct tskTaskControlBlock
{
    // 任務(wù)棧頂指針
    volatile StackType_t *pxTopOfStack;
    // 啟用MPU 的情況下設(shè)置 
    #if ( portUSING_MPU_WRAPPERS == 1 )
        // 設(shè)置任務(wù)訪問內(nèi)存的權(quán)限
        xMPU_SETTINGS xMPUSettings;
    #endif
    
    // 狀態(tài)鏈表項(Ready, Blocked, Suspended)
    // 任務(wù)處于不同狀態(tài) 該項會被插入到對應(yīng)的鏈表, 供鏈表引用任務(wù)
    ListItem_t xStateListItem;
    // 事件鏈表項
    // 比如任務(wù)延時掛起等,被插入到延時鏈表中,到時間或事件發(fā)生,鏈表引用喚醒任務(wù)
    ListItem_t xEventListItem;
    // 任務(wù)優(yōu)先級 0 最低
    UBaseType_t uxPriority;
    // 任務(wù)棧內(nèi)存起始地址
    StackType_t *pxStack;           
    // 任務(wù)名, 字符串, 一般供調(diào)試時使用
    char pcTaskName[ configMAX_TASK_NAME_LEN ];
    
    // 對于向上生長的棧, 用于指明棧的上邊界,用于判斷是否溢出
    #if ( portSTACK_GROWTH > 0 )
        StackType_t *pxEndOfStack;
    #endif
    
    // 邊界嵌套計數(shù)
    #if ( portCRITICAL_NESTING_IN_TCB == 1 )
        UBaseType_t uxCriticalNesting;
    #endif

    #if ( configUSE_TRACE_FACILITY == 1 )
        // 調(diào)試, 標識這個任務(wù)是第幾個被創(chuàng)建
        // 每創(chuàng)建一個任務(wù), 系統(tǒng)有個全局變量就會加一, 并賦值給這個新任務(wù)
        UBaseType_t uxTCBNumber; 
        // 調(diào)試 供用戶設(shè)置特定數(shù)值 
        UBaseType_t uxTaskNumber;
     #endif

    #if ( configUSE_MUTEXES == 1 )
        // 涉及互斥鎖下的優(yōu)先級繼承(避免優(yōu)先級反轉(zhuǎn)), queue 那邊介紹
        // 當優(yōu)先級被臨時提高(繼承了拿鎖被堵的高優(yōu)先級任務(wù))時,這個變量保存任務(wù)實際的優(yōu)先級
        UBaseType_t uxBasePriority;     
        UBaseType_t uxMutexesHeld;
    #endif

    #if ( configUSE_APPLICATION_TASK_TAG == 1 )
        TaskHookFunction_t pxTaskTag;
    #endif

    #if( configNUM_THREAD_LOCAL_STORAGE_POINTERS > 0 )
        // 存儲一些本地數(shù)據(jù)的指針
        void *pvThreadLocalStoragePointers[ configNUM_THREAD_LOCAL_STORAGE_POINTERS ];
    #endif

    #if( configGENERATE_RUN_TIME_STATS == 1 )
        // 記錄任務(wù)運行狀態(tài)下的總時間
        uint32_t ulRunTimeCounter;
    #endif

    #if ( configUSE_NEWLIB_REENTRANT == 1 )
        //為任務(wù)分配一個Newlibreent結(jié)構(gòu)體變量
        // Newlib是一個C庫函數(shù),非FreeRTOS維護
        // 個人沒用過,不清楚 
        struct  _reent xNewLib_reent;
    #endif

    #if( configUSE_TASK_NOTIFICATIONS == 1 )
        // 任務(wù)通知
        volatile uint32_t ulNotifiedValue;
        volatile uint8_t ucNotifyState;
    #endif

    #if( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 )
        // 表明任務(wù)棧和TCB占用的是 heap 還是 stack
        // 供任務(wù)刪除回收時判斷
        uint8_t ucStaticallyAllocated; 
    #endif

    #if( INCLUDE_xTaskAbortDelay == 1 )
        uint8_t ucDelayAborted;
    #endif
} tskTCB;
typedef tskTCB TCB_t;

任務(wù)控制塊中有兩個鏈表項 xStateListItemxEventListItem, 在前面文章提到鏈表項中有一個指針指向所屬的TCB。當任務(wù)狀態(tài)變化或者等待事件的時候,將任務(wù)所屬的這個鏈表項插入到對應(yīng)的鏈表中,系統(tǒng)調(diào)度器就是通過這個方式追蹤每個任務(wù), 當符合條件的情況下,系統(tǒng)會通過該鏈表項引用任務(wù),實現(xiàn)任務(wù)切換等操作。

鏈表

如上所述, 系統(tǒng)中包含的鏈表定義如下。

// 就緒任務(wù)鏈表 每個優(yōu)先級對應(yīng)一個鏈表
PRIVILEGED_DATA static List_t pxReadyTasksLists[ configMAX_PRIORITIES ];
// 延時任務(wù)鏈表
PRIVILEGED_DATA static List_t xDelayedTaskList1;                        
PRIVILEGED_DATA static List_t xDelayedTaskList2;                        
PRIVILEGED_DATA static List_t * volatile pxDelayedTaskList;
PRIVILEGED_DATA static List_t * volatile pxOverflowDelayedTaskList;
// 就緒任務(wù)鏈表,當任務(wù)調(diào)度器被掛起時,狀態(tài)變換為就緒的任務(wù)先保存在此, 
// 恢復(fù)后移到 pxReadyTasksLists 中
PRIVILEGED_DATA static List_t xPendingReadyList;                
// 任務(wù)刪除后,等待空閑任務(wù)釋放內(nèi)存
#if( INCLUDE_vTaskDelete == 1 )
    PRIVILEGED_DATA static List_t xTasksWaitingTermination;
    PRIVILEGED_DATA static volatile UBaseType_t uxDeletedTasksWaitingCleanUp = 
        ( UBaseType_t ) 0U;
#endif
// 被掛起的任務(wù)鏈表
#if ( INCLUDE_vTaskSuspend == 1 )
    PRIVILEGED_DATA static List_t xSuspendedTaskList;                   
#endif

任務(wù)創(chuàng)建

FreeRTOS V9.0.0 版本提供三個函數(shù)用于創(chuàng)建任務(wù)

  • xTaskCreateStatic
    通過傳遞的靜態(tài)內(nèi)存創(chuàng)建任務(wù)
  • xTaskCreate
    通過動態(tài)申請的內(nèi)存創(chuàng)建任務(wù)
  • xTaskCreateRestricted
    創(chuàng)建任務(wù)參數(shù)通過TaskParameters_t傳遞給函數(shù),用戶自己申請棧的內(nèi)存,創(chuàng)建函數(shù)只負責申請 TCB 所需內(nèi)存空間

項目中接觸版本 V8.0.0, 發(fā)現(xiàn)有一些改動, 舊版中實際創(chuàng)建任務(wù)的函數(shù)實際是 xTaskGenericCreate, 參數(shù)比較多, 可以實現(xiàn)從 heap 動態(tài)申請內(nèi)存或通過靜態(tài)內(nèi)存創(chuàng)建任務(wù), 而一般用到的xTaskCreate 實際是一個宏,調(diào)用了 xTaskGenericCreate, 默認采用動態(tài)申請內(nèi)存的方式。

以下主要介紹 xTaskCreateStaticxTaskCreate 這兩個函數(shù)的實現(xiàn)。

靜態(tài)創(chuàng)建任務(wù)

源代碼 xTaskCreateStatic
靜態(tài)的方式創(chuàng)建任務(wù),需要用戶先申請任務(wù)控制模塊和任務(wù)棧需要的內(nèi)存(一般使用靜態(tài)內(nèi)存),然后把內(nèi)存地址傳遞給函數(shù),函數(shù)負責其他初始化。
函數(shù)按順序完成:

  • 根據(jù)用戶傳遞內(nèi)存,初始化任務(wù) TCB
  • 初始化任務(wù)堆棧
  • 將新建任務(wù)加入到就緒鏈表中
  • 如果調(diào)度器運行,新任務(wù)優(yōu)先級更高,觸發(fā)系統(tǒng)切換
TaskHandle_t xTaskCreateStatic( 
    TaskFunction_t pxTaskCode,
    const char * const pcName,
    const uint32_t ulStackDepth,
    void * const pvParameters,
    UBaseType_t uxPriority,
    StackType_t * const puxStackBuffer,
    StaticTask_t * const pxTaskBuffer )
{
    TCB_t *pxNewTCB;
    TaskHandle_t xReturn;
    configASSERT( puxStackBuffer != NULL );
    configASSERT( pxTaskBuffer != NULL );
    
    if ((pxTaskBuffer != NULL) && (puxStackBuffer != NULL)) 
    {
        // 設(shè)置用戶傳遞進來的任務(wù)控制塊和棧的內(nèi)存地址到對應(yīng)指針變量
        pxNewTCB = (TCB_t *)pxTaskBuffer; 
        pxNewTCB->pxStack = (StackType_t *)puxStackBuffer;

        #if( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 )
        {
            // 標識這個任務(wù)控制塊和棧內(nèi)存時靜態(tài)的
            // 刪除任務(wù)的時候, 系統(tǒng)不會做內(nèi)存回收處理
            pxNewTCB->ucStaticallyAllocated = 
                tskSTATICALLY_ALLOCATED_STACK_AND_TCB;
        }
        #endif
        // 初始化任務(wù)控制塊 下文介紹
        prvInitialiseNewTask( pxTaskCode, pcName,
            ulStackDepth, pvParameters, uxPriority, 
            &xReturn, pxNewTCB, NULL );
        
        // 把新任務(wù)插入就緒鏈表 下文介紹
        prvAddNewTaskToReadyList( pxNewTCB );
    }
    else 
    {
        xReturn = NULL;
    }
    return xReturn;
}

動態(tài)創(chuàng)建任務(wù)

源代碼 xTaskCreate
動態(tài)創(chuàng)建任務(wù), 調(diào)用函數(shù)內(nèi)部向系統(tǒng)申請創(chuàng)建新任務(wù)所需的內(nèi)存,包括任務(wù)控制塊和棧。 所以調(diào)用這個函數(shù),在內(nèi)存堆空間不足或者碎片話的情況下,可能創(chuàng)建新任務(wù)失敗,需要判斷函數(shù)執(zhí)行后是否成功返回。 其源碼解析如下所示。

BaseType_t xTaskCreate( 
    TaskFunction_t pxTaskCode,
    const char * const pcName,
    const uint16_t usStackDepth,
    void * const pvParameters,
    UBaseType_t uxPriority,
    TaskHandle_t * const pxCreatedTask )    
{
    TCB_t *pxNewTCB;
    BaseType_t xReturn;

    // 如果是向下增長的棧, 先申請棧內(nèi)存再申請任務(wù)控制塊內(nèi)存
    // 可以避免棧溢出覆蓋了自己任務(wù)控制塊
    // 對應(yīng)向上增長的則相反
    
    // 在舊版本 V8.0.0 中沒有這么處理,統(tǒng)一先 TCB 后 Stack
    // 項目上碰到平臺棧向下增長, 棧溢出錯時候覆蓋了自己的 TCB 
    // 導(dǎo)致調(diào)試的時候無法獲取出錯任務(wù)信息(比如任務(wù)名)
    #if( portSTACK_GROWTH > 0 )
    {
        // 申請任務(wù)控制塊內(nèi)存
        pxNewTCB = (TCB_t *)pvPortMalloc(sizeof(TCB_t));
        if( pxNewTCB != NULL )
        {
            // 申請棧內(nèi)存, 返回地址設(shè)置任務(wù)中的棧指針
            pxNewTCB->pxStack = (StackType_t *)pvPortMalloc(
                (((size_t)usStackDepth) * sizeof(StackType_t)));
                 
            if( pxNewTCB->pxStack == NULL )
            {
                // 棧內(nèi)存申請失敗, 釋放前面申請的任務(wù)控制塊內(nèi)存
                vPortFree( pxNewTCB );
                pxNewTCB = NULL;
            }
        }
    }
    #else /*棧向下增長*/
    {
        StackType_t *pxStack;
        pxStack = (StackType_t *)pvPortMalloc(
            (((size_t)usStackDepth) * sizeof(StackType_t)));
        
        if( pxStack != NULL )
        {
            pxNewTCB = (TCB_t *)pvPortMalloc(sizeof(TCB_t));
            if( pxNewTCB != NULL )
            {
                pxNewTCB->pxStack = pxStack;
            }
            else
            {
                vPortFree( pxStack );
            }
        }
        else
        {
            pxNewTCB = NULL;
        }
    }
    #endif
    
    
    if( pxNewTCB != NULL )
    {
        // 成功申請所需內(nèi)存 執(zhí)行任務(wù)初始化操作
        
        #if( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 )
        {
            // 標志任務(wù)控制塊和棧是動態(tài)申請
            // 刪除任務(wù)系統(tǒng)會自動回收內(nèi)存
            pxNewTCB->ucStaticallyAllocated = 
                tskDYNAMICALLY_ALLOCATED_STACK_AND_TCB;
        }
        #endif /* configSUPPORT_STATIC_ALLOCATION */
        
        // 初始任務(wù)控制塊
        prvInitialiseNewTask(pxTaskCode, pcName,
            (uint32_t)usStackDepth, pvParameters, 
            uxPriority, pxCreatedTask, pxNewTCB, NULL );
        
        // 將新任務(wù)插入到就緒鏈表  
        prvAddNewTaskToReadyList( pxNewTCB );
        xReturn = pdPASS;
    }
    else
    {
        // 創(chuàng)建任務(wù)失敗,返回錯誤碼
        xReturn = errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY;
    }
    return xReturn;
}

初始化任務(wù)控制塊

在創(chuàng)建任務(wù)的函數(shù)中, 如果成功獲得新任務(wù)所需要的內(nèi)存空間, 則會調(diào)用以下函數(shù)對任務(wù)控制塊 TCB 的成員變量進行初始化。

static void prvInitialiseNewTask(
    TaskFunction_t pxTaskCode,
    const char * const pcName,
    const uint32_t ulStackDepth,
    void * const pvParameters,
    UBaseType_t uxPriority,
    TaskHandle_t * const pxCreatedTask,
    TCB_t *pxNewTCB,
    const MemoryRegion_t * const xRegions )
{
    StackType_t *pxTopOfStack;
    UBaseType_t x;
    
    // 如果開啟了 MPU, 判斷任務(wù)是否運行在特權(quán)模式
    #if( portUSING_MPU_WRAPPERS == 1 )
        BaseType_t xRunPrivileged;
        if( ( uxPriority & portPRIVILEGE_BIT ) != 0U )
        {
            // 優(yōu)先級特權(quán)模式掩碼置位
            // 任務(wù)運行在特權(quán)模式
            xRunPrivileged = pdTRUE;
        }
        else
        {
            xRunPrivileged = pdFALSE;
        }
        uxPriority &= ~portPRIVILEGE_BIT;
    #endif /* portUSING_MPU_WRAPPERS == 1 */
    
    #if( ( configCHECK_FOR_STACK_OVERFLOW > 1 ) 
        || ( configUSE_TRACE_FACILITY == 1 ) 
        || ( INCLUDE_uxTaskGetStackHighWaterMark == 1 ) )
    {
        // 調(diào)試 棧初始化填充指定數(shù)據(jù)(默認 0x5a)
        (void)memset(pxNewTCB->pxStack, 
                (int)tskSTACK_FILL_BYTE, 
                (size_t)ulStackDepth * sizeof(StackType_t));
    }
    #endif
    
    #if( portSTACK_GROWTH < 0 )
    {
        // 向下增長棧, 初始化棧頂在內(nèi)存高位
        pxTopOfStack = pxNewTCB->pxStack + (ulStackDepth - (uint32_t )1);
        // 字節(jié)對齊處理
        pxTopOfStack = (StackType_t *)(((portPOINTER_SIZE_TYPE)pxTopOfStack) &
            (~(( portPOINTER_SIZE_TYPE)portBYTE_ALIGNMENT_MASK)));
        configASSERT((((portPOINTER_SIZE_TYPE)pxTopOfStack & 
            (portPOINTER_SIZE_TYPE)portBYTE_ALIGNMENT_MASK) == 0UL));
    }
    #else
    {
        // 向上增長棧, 初始化棧頂在內(nèi)存低位
        pxTopOfStack = pxNewTCB->pxStack;
        // 字節(jié)對齊斷言
        configASSERT((((portPOINTER_SIZE_TYPE)pxTopOfStack & 
            (portPOINTER_SIZE_TYPE)portBYTE_ALIGNMENT_MASK) == 0UL));
        // 設(shè)置上邊界
        pxNewTCB->pxEndOfStack = 
            pxNewTCB->pxStack + (ulStackDepth - (uint32_t)1);
    }
    #endif /* portSTACK_GROWTH */

    // 存儲任務(wù)名數(shù)組 方便調(diào)試
    for( x = (UBaseType_t)0; x < (UBaseType_t)configMAX_TASK_NAME_LEN; x++ )
    {
        pxNewTCB->pcTaskName[x] = pcName[x];
        // 字符串結(jié)束    
        if( pcName[ x ] == 0x00 )
        {
            break;
        }
        else
        {
            mtCOVERAGE_TEST_MARKER();
        }
    }
    // 確保任務(wù)名有正確字符串結(jié)尾
    pxNewTCB->pcTaskName[ configMAX_TASK_NAME_LEN - 1 ] = '\0';
    
    // 限制任務(wù)優(yōu)先級在設(shè)置范圍內(nèi)
    if( uxPriority >= ( UBaseType_t ) configMAX_PRIORITIES )
    {
        uxPriority = (UBaseType_t)configMAX_PRIORITIES - (UBaseType_t)1U;
    }
    else
    {
        mtCOVERAGE_TEST_MARKER();
    }

    pxNewTCB->uxPriority = uxPriority;
    #if ( configUSE_MUTEXES == 1 )
    {
        pxNewTCB->uxBasePriority = uxPriority;
        pxNewTCB->uxMutexesHeld = 0;
    }
    #endif /* configUSE_MUTEXES */
    
    // 初始化包含的兩個鏈表項
    vListInitialiseItem( &( pxNewTCB->xStateListItem ) );
    vListInitialiseItem( &( pxNewTCB->xEventListItem ) );
    
    // 設(shè)置狀態(tài)鏈表項的 pvOwner 指向所屬 TCB
    // 如此,系統(tǒng)可以通過該項引用到任務(wù)
    // 比如任務(wù)狀態(tài)切換到就緒時,則這個鏈表項會被插入到 就緒鏈表
    // 系統(tǒng)從就緒鏈表取出這一項進而獲得 TCB(ListItem->pvOwner),切換到運行狀態(tài) 
    listSET_LIST_ITEM_OWNER( &( pxNewTCB->xStateListItem ), pxNewTCB );
    
    // 寫入優(yōu)先級 用于在對應(yīng)事件鏈表中排序
    // 鏈表中是按從小到達排序,因此為了實現(xiàn)優(yōu)先級高的在前
    // 兩者相反,所以寫入優(yōu)先級的 “補數(shù)”
    // 保證優(yōu)先級高的任務(wù),插入時在鏈表靠前
    listSET_LIST_ITEM_VALUE(&(pxNewTCB->xEventListItem), 
        (TickType_t)configMAX_PRIORITIES - (TickType_t)uxPriority);
    // 設(shè)置所屬 TCB, 同上  
    listSET_LIST_ITEM_OWNER( &( pxNewTCB->xEventListItem ), pxNewTCB );
    
    // 初始化嵌套 0
    #if ( portCRITICAL_NESTING_IN_TCB == 1 )
    {
        pxNewTCB->uxCriticalNesting = ( UBaseType_t ) 0U;
    }
    #endif /* portCRITICAL_NESTING_IN_TCB */

    #if ( configUSE_APPLICATION_TASK_TAG == 1 )
    {
        pxNewTCB->pxTaskTag = NULL;
    }
    #endif /* configUSE_APPLICATION_TASK_TAG */

    #if ( configGENERATE_RUN_TIME_STATS == 1 )
    {
        pxNewTCB->ulRunTimeCounter = 0UL;
    }
    #endif /* configGENERATE_RUN_TIME_STATS */

    #if ( portUSING_MPU_WRAPPERS == 1 )
    {
        // 設(shè)置 MPU,任務(wù)內(nèi)存訪問權(quán)限設(shè)置
        vPortStoreTaskMPUSettings(&(pxNewTCB->xMPUSettings), 
            xRegions, pxNewTCB->pxStack, ulStackDepth );
    }
    #else
    {
        // 避免編譯報 warning 沒有使用變量
        ( void ) xRegions;
    }
    #endif
    
    // 初始化任務(wù)局部數(shù)據(jù)指針
    #if( configNUM_THREAD_LOCAL_STORAGE_POINTERS != 0 )
    {
        for( x = 0; x < (UBaseType_t) configNUM_THREAD_LOCAL_STORAGE_POINTERS; x++ )
        {
            pxNewTCB->pvThreadLocalStoragePointers[ x ] = NULL;
        }
    }
    #endif
    
    // 初始化任務(wù)消息通知變量
    #if ( configUSE_TASK_NOTIFICATIONS == 1 )
    {
        pxNewTCB->ulNotifiedValue = 0;
        pxNewTCB->ucNotifyState = taskNOT_WAITING_NOTIFICATION;
    }
    #endif

    #if ( configUSE_NEWLIB_REENTRANT == 1 )
    {
        _REENT_INIT_PTR( ( &( pxNewTCB->xNewLib_reent ) ) );
    }
    #endif

    #if( INCLUDE_xTaskAbortDelay == 1 )
    {
        pxNewTCB->ucDelayAborted = pdFALSE;
    }
    #endif

    // 初始化棧 使其像任務(wù)已經(jīng)運行了,但是被調(diào)度器中斷切換,入棧做了現(xiàn)場保護
    // 當任務(wù)被調(diào)度器取出后, 可以直接執(zhí)行出棧恢復(fù)現(xiàn)場,運行任務(wù)
    // 而不需要調(diào)度器額外特殊處理第一次運行的任務(wù)
    // 棧初始化涉及系統(tǒng)底層, 由對應(yīng)平臺移植層提供
    // 見下舉例棧初始化
    #if( portUSING_MPU_WRAPPERS == 1 )
    {
        pxNewTCB->pxTopOfStack = pxPortInitialiseStack(pxTopOfStack, 
            pxTaskCode, pvParameters, xRunPrivileged);
    }
    #else /* portUSING_MPU_WRAPPERS */
    {
        pxNewTCB->pxTopOfStack = pxPortInitialiseStack(pxTopOfStack, 
            pxTaskCode, pvParameters);
    }
    #endif /* portUSING_MPU_WRAPPERS */

    if(( void *)pxCreatedTask != NULL )
    {
        // 返回任務(wù)引用, 可用于修改優(yōu)先級,通知或者刪除任務(wù)等.
        *pxCreatedTask = ( TaskHandle_t ) pxNewTCB;
    }
    else
    {
        mtCOVERAGE_TEST_MARKER();
    }
}

棧初始化舉例

新任務(wù)初始化任務(wù)后,使得當前新建任務(wù)像已經(jīng)運行,但是被調(diào)度器中斷,棧中保存該任務(wù)被中斷時的現(xiàn)場,但輪到該任務(wù)執(zhí)行的時候,系統(tǒng)可以直接執(zhí)行現(xiàn)場恢復(fù),運行任務(wù)。
不同平臺實現(xiàn)任務(wù)切換時的現(xiàn)場保護可能不一樣,所以該函數(shù)由平臺移植層提供
列舉 Cotex-M3 沒有MPU下的棧初始化函數(shù), 向下增長棧。

StackType_t *pxPortInitialiseStack( StackType_t *pxTopOfStack, TaskFunction_t pxCode, void *pvParameters )
{
    // 模擬任務(wù)被切換前的現(xiàn)場保護
    // 調(diào)度切換回來可以統(tǒng)一執(zhí)行恢復(fù)操作
    pxTopOfStack--; 
    *pxTopOfStack = portINITIAL_XPSR;   /* xPSR */
    pxTopOfStack--;
    // 指向任務(wù)函數(shù)
    *pxTopOfStack = ( ( StackType_t ) pxCode ) & portSTART_ADDRESS_MASK;
    pxTopOfStack--;
    *pxTopOfStack = ( StackType_t ) prvTaskExitError;   /* LR */
    pxTopOfStack -= 5;  /* R12, R3, R2 and R1. */
    // 傳遞參數(shù)
    *pxTopOfStack = ( StackType_t ) pvParameters;   /* R0 */
    pxTopOfStack -= 8;  /* R11, R10, R9, R8, R7, R6, R5 and R4. */

    return pxTopOfStack;
}

如上初始化后棧如下所示

-- 低位地址

pxStack-> ..
.. ..
pxTopOfStack-> R4
..
R11
R0
R1
R2
R3
R12
LR : prvTaskExitError
PC : pxCode
XPSR :portINITIAL_XPSR

-- 高位地址

初始化后,當任務(wù)第一次真正被運行,當前環(huán)境設(shè)置,使其從對應(yīng)的函數(shù)入口開始執(zhí)行。
其中LR 寄存器設(shè)置的地址是系統(tǒng)的出錯處理函數(shù),如果任務(wù)錯誤返回,就會調(diào)用該函數(shù)。
根據(jù) 約定, R0~R3保存調(diào)用時傳遞的參數(shù)。

插入就緒鏈表

任務(wù)創(chuàng)建初始化后,需要將任務(wù)插入到就緒鏈表中,通過調(diào)度器切換到運行狀態(tài)。
該函數(shù)主要實現(xiàn)將新任務(wù)加入就緒鏈表,第一次調(diào)用該函數(shù)會進行系統(tǒng)必要的初始化,同時,判斷是否需要馬上執(zhí)行任務(wù)切換,保證更高優(yōu)先級的就緒任務(wù)可以及時獲得CPU 的使用權(quán)限。

注意,這里提到的把任務(wù)插入到鏈表,是指將任務(wù)所含的鏈表項插入到合適的鏈表中,而但需要重新取回任務(wù),則通過該鏈表項中指向所屬任務(wù)的指針實現(xiàn)。

static void prvAddNewTaskToReadyList( TCB_t *pxNewTCB )
{
    // 進入邊界, 關(guān)閉中斷(平臺相關(guān),移植層實現(xiàn))
    taskENTER_CRITICAL();
    {
        // 當前任務(wù)數(shù)加一
        uxCurrentNumberOfTasks++;
        if( pxCurrentTCB == NULL )
        {
            // 如果當前沒有運行任務(wù),設(shè)置新任務(wù)為當前運行任務(wù)
            pxCurrentTCB = pxNewTCB;

            if( uxCurrentNumberOfTasks == ( UBaseType_t ) 1 )
            {
                // 第一個任務(wù),系統(tǒng)執(zhí)行必要的初始化
                // 初始化各個鏈表
                prvInitialiseTaskLists();
            }
            else
            {
                mtCOVERAGE_TEST_MARKER();
            }
        }
        else
        {
            if( xSchedulerRunning == pdFALSE )
            {
                // 調(diào)度器沒有運行
                // 新任務(wù)優(yōu)先級優(yōu)先級更高
                // 直接設(shè)置新任務(wù)為當前任務(wù)
                if( pxCurrentTCB->uxPriority <= pxNewTCB->uxPriority )
                {
                    pxCurrentTCB = pxNewTCB;
                }
                else
                {
                    mtCOVERAGE_TEST_MARKER();
                }
            }
            else
            {
                mtCOVERAGE_TEST_MARKER();
            }
        }
        // 記錄創(chuàng)建任務(wù)數(shù)
        uxTaskNumber++;

        #if ( configUSE_TRACE_FACILITY == 1 )
        {
            // 調(diào)試追蹤用
            pxNewTCB->uxTCBNumber = uxTaskNumber;
        }
        #endif /* configUSE_TRACE_FACILITY */
        traceTASK_CREATE( pxNewTCB );
        
        // 將任務(wù)加入到就緒鏈表
        // 不同優(yōu)先級對應(yīng)不同就緒鏈表
        // 宏實現(xiàn),同時更新就緒的最高優(yōu)先級
        prvAddTaskToReadyList( pxNewTCB );
        
        portSETUP_TCB( pxNewTCB );
    }
    // 退出邊界,恢復(fù)中斷
    taskEXIT_CRITICAL();

    if( xSchedulerRunning != pdFALSE )
    {
        // 調(diào)度器已經(jīng)啟動
        // 新任務(wù)優(yōu)先級比正在運行的任務(wù)高
        // 觸發(fā)系統(tǒng)執(zhí)行任務(wù)切換
        if( pxCurrentTCB->uxPriority < pxNewTCB->uxPriority )
        {
            taskYIELD_IF_USING_PREEMPTION();
        }
        else
        {
            mtCOVERAGE_TEST_MARKER();
        }
    }
    else
    {
        mtCOVERAGE_TEST_MARKER();
    }
}

之前的文章分析過 FreeRtos 的鏈表,同樣,當?shù)谝淮握{(diào)用將新任務(wù)插入就緒鏈表這個函數(shù),會對系統(tǒng)涉及的幾個鏈表進行初始化。

調(diào)度器會在每次任務(wù)切換中,依據(jù)優(yōu)先級順序從鏈表中選出合適的任務(wù),相同優(yōu)先級任務(wù)在同一個就緒鏈表中,系統(tǒng)按照時間片輪序調(diào)度(如果使能),

參考

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,646評論 6 533
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,595評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,560評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,035評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,814評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,224評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,301評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,444評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,988評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,804評論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,998評論 1 370
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,544評論 5 360
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,237評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,665評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,927評論 1 287
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,706評論 3 393
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,993評論 2 374

推薦閱讀更多精彩內(nèi)容