FreeRTOS進階之任務(wù)創(chuàng)建完全解析
前言
在FreeRTOS基礎(chǔ)系列 FreeRTOS任務(wù)創(chuàng)建和刪除中介紹了任務(wù)創(chuàng)建API函數(shù)xTaskCreate(),我們這里先回顧一下這個函數(shù)的聲明:
BaseType_t xTaskCreate(
TaskFunction_tp vTaskCode,
const char * constpcName,
unsigned short usStackDepth,
void *pvParameters,
UBaseType_t uxPriority,
TaskHandle_t *pvCreatedTask
);這個API函數(shù)的作用是創(chuàng)建新的任務(wù)并將它加入到任務(wù)就緒列表,函數(shù)參數(shù)含義為:
pvTaskCode:函數(shù)指針,指向任務(wù)函數(shù)的入口。任務(wù)永遠不會返回(位于死循環(huán)內(nèi))。該參數(shù)類型TaskFunction_t定義在文件projdefs.h中,定義為:typedef void(*TaskFunction_t)( void * ),即參數(shù)為空指針類型并返回空類型。
pcName:任務(wù)描述。主要用于調(diào)試。字符串的最大長度(包括字符串結(jié)束字符)由宏configMAX_TASK_NAME_LEN指定,該宏位于FreeRTOSConfig.h文件中。
usStackDepth:指定任務(wù)堆棧大小,能夠支持的堆棧變量數(shù)量(堆棧深度),而不是字節(jié)數(shù)。
比如,在16位寬度的堆棧下,usStackDepth定義為100,則實際使用200字節(jié)堆棧存儲空間。堆棧的寬度乘以深度必須不超過size_t類型所能表示的最大值。
比如,size_t為16位,則可以表示堆棧的最大值是65535字節(jié)。這是因為堆棧在申請時是以字節(jié)為單位的,申請的字節(jié)數(shù)就是堆棧寬度乘以深度,如果這個乘積超出size_t所表示的范圍,就會溢出,分配的堆棧空間也不是我們想要的。
pvParameters:指針,當(dāng)任務(wù)創(chuàng)建時,作為一個參數(shù)傳遞給任務(wù)。
uxPriority:任務(wù)的優(yōu)先級。具有MPU支持的系統(tǒng),可以通過置位優(yōu)先級參數(shù)的portPRIVILEGE_BIT位,隨意的在特權(quán)(系統(tǒng))模式下創(chuàng)建任務(wù)。
比如,創(chuàng)建一個優(yōu)先級為2的特權(quán)任務(wù),參數(shù)uxPriority可以設(shè)置為 ( 2 | portPRIVILEGE_BIT )。
pvCreatedTask:用于回傳一個句柄(ID),創(chuàng)建任務(wù)后可以使用這個句柄引用任務(wù)。
雖然xTaskCreate()看上去很像函數(shù),但其實是一個宏,真正被調(diào)用的函數(shù)是xTaskGenericCreate(),xTaskCreate()宏定義如下所示:
#define xTaskCreate( pvTaskCode, pcName, usStackDepth,pvParameters, uxPriority, pxCreatedTask ) \
xTaskGenericCreate( ( pvTaskCode ),( pcName ), ( usStackDepth ), ( pvParameters ), ( uxPriority ), ( pxCreatedTask), ( NULL ), ( NULL ), ( NULL ) )可以看到,xTaskCreate比xTaskGenericCreate少了三個參數(shù),在宏定義中,這三個參數(shù)被設(shè)置為NULL。這三個參數(shù)用于使用靜態(tài)變量的方法分配堆棧、任務(wù)TCB空間以及設(shè)置MPU相關(guān)的參數(shù)。
一般情況下,這三個參數(shù)是不使用的,所以任務(wù)創(chuàng)建宏xTaskCreate定義的時候,將這三個參數(shù)對用戶隱藏了。接下來的章節(jié)中,為了方便,我們還是稱xTaskCreate()為函數(shù),雖然它是一個宏定義。
上面我們提到了任務(wù)TCB(任務(wù)控制塊),這是一個需要重點介紹的關(guān)鍵點。它用于存儲任務(wù)的狀態(tài)信息,包括任務(wù)運行時的環(huán)境。每個任務(wù)都有自己的任務(wù)TCB。
任務(wù)TCB是一個相對比較大的數(shù)據(jù)結(jié)構(gòu),這也是情理之中的,因為與任務(wù)相關(guān)的代碼占到整個FreeRTOS代碼量的一半左右,這些代碼大都與任務(wù)TCB相關(guān),我們先來介紹一下任務(wù)TCB數(shù)據(jù)結(jié)構(gòu)的定義:
typedef struct tskTaskControlBlock
{
volatile StackType_t *pxTopOfStack; /*當(dāng)前堆棧的棧頂,必須位于結(jié)構(gòu)體的第一項*/
#if ( portUSING_MPU_WRAPPERS == 1 )
xMPU_SETTINGS xMPUSettings; /*MPU設(shè)置,必須位于結(jié)構(gòu)體的第二項*/
#endif
ListItem_t xStateListItem; /*任務(wù)的狀態(tài)列表項,以引用的方式表示任務(wù)的狀態(tài)*/
ListItem_t xEventListItem; /*事件列表項,用于將任務(wù)以引用的方式掛接到事件列表*/
UBaseType_t uxPriority; /*保存任務(wù)優(yōu)先級,0表示最低優(yōu)先級*/
StackType_t *pxStack; /*指向堆棧的起始位置*/
char pcTaskName[ configMAX_TASK_NAME_LEN ];/*任務(wù)名字*/
#if ( portSTACK_GROWTH > 0 )
StackType_t *pxEndOfStack; /*指向堆棧的尾部*/
#endif
#if ( portCRITICAL_NESTING_IN_TCB == 1 )
UBaseType_t uxCriticalNesting; /*保存臨界區(qū)嵌套深度*/
#endif
#if ( configUSE_TRACE_FACILITY == 1 )
UBaseType_t uxTCBNumber; /*保存一個數(shù)值,每個任務(wù)都有唯一的值*/
UBaseType_t uxTaskNumber; /*存儲一個特定數(shù)值*/
#endif
#if ( configUSE_MUTEXES == 1 )
UBaseType_t uxBasePriority; /*保存任務(wù)的基礎(chǔ)優(yōu)先級*/
UBaseType_t uxMutexesHeld;
#endif
#if ( configUSE_APPLICATION_TASK_TAG == 1 )
TaskHookFunction_t pxTaskTag;
#endif
#if( configNUM_THREAD_LOCAL_STORAGE_POINTERS > 0 )
void *pvThreadLocalStoragePointers[configNUM_THREAD_LOCAL_STORAGE_POINTERS ];
#endif
#if( configGENERATE_RUN_TIME_STATS == 1 )
uint32_t ulRunTimeCounter; /*記錄任務(wù)在運行狀態(tài)下執(zhí)行的總時間*/
#endif
#if ( configUSE_NEWLIB_REENTRANT == 1 )
/* 為任務(wù)分配一個Newlibreent結(jié)構(gòu)體變量。Newlib是一個C庫函數(shù),并非FreeRTOS維護,F(xiàn)reeRTOS也不對使用結(jié)果負責(zé)。如果用戶使用Newlib,必須熟知Newlib的細節(jié)*/
struct _reent xNewLib_reent;
#endif
#if( configUSE_TASK_NOTIFICATIONS == 1 )
volatile uint32_t ulNotifiedValue; /*與任務(wù)通知相關(guān)*/
volatile uint8_t ucNotifyState;
#endif
#if( configSUPPORT_STATIC_ALLOCATION == 1 )
uint8_t ucStaticAllocationFlags; /* 如果堆棧由靜態(tài)數(shù)組分配,則設(shè)置為pdTRUE,如果堆棧是動態(tài)分配的,則設(shè)置為pdFALSE*/
#endif
#if( INCLUDE_xTaskAbortDelay == 1 )
uint8_t ucDelayAborted;
#endif
} tskTCB;
typedef tskTCB TCB_t;下面我們詳細的介紹這個數(shù)據(jù)結(jié)構(gòu)的主要成員:
指針pxTopOfStack必須位于結(jié)構(gòu)體的第一項,指向當(dāng)前堆棧的棧頂,對于向下增長的堆棧,pxTopOfStack總是指向最后一個入棧的項目。
如果使用MPU,xMPUSettings必須位于結(jié)構(gòu)體的第二項,用于MPU設(shè)置。
接下來是狀態(tài)列表項xStateListItem和事件列表項xEventListItem,我們在上一章介紹列表和列表項的文章中提到過:列表被FreeRTOS調(diào)度器使用,用于跟蹤任務(wù),處于就緒、掛起、延時的任務(wù),都會被掛接到各自的列表中。
調(diào)度器就是通過把任務(wù)TCB中的狀態(tài)列表項xStateListItem和事件列表項xEventListItem掛接到不同的列表中來實現(xiàn)上述過程的。
在task.c中,定義了一些靜態(tài)列表變量,其中有就緒、阻塞、掛起列表,例如當(dāng)某個任務(wù)處于就緒態(tài)時,調(diào)度器就將這個任務(wù)TCB的xStateListItem列表項掛接到就緒列表。
事件列表項也與之類似,當(dāng)隊列滿的情況下,任務(wù)因入隊操作而阻塞時,就會將事件列表項掛接到隊列的等待入隊列表上。
uxPriority用于保存任務(wù)的優(yōu)先級,0為最低優(yōu)先級。任務(wù)創(chuàng)建時,指定的任務(wù)優(yōu)先級就被保存到該變量中。
指針pxStack指向堆棧的起始位置,任務(wù)創(chuàng)建時會分配指定數(shù)目的任務(wù)堆棧,申請堆棧內(nèi)存函數(shù)返回的指針就被賦給該變量。
很多剛接觸FreeRTOS的人會分不清指針pxTopOfStack和pxStack的區(qū)別,這里簡單說一下:
pxTopOfStack指向當(dāng)前堆棧棧頂,隨著進棧出棧,pxTopOfStack指向的位置是會變化的;pxStack指向當(dāng)前堆棧的起始位置,一經(jīng)分配后,堆棧起始位置就固定了,不會被改變了。
那么為什么需要pxStack變量呢,這是因為隨著任務(wù)的運行,堆棧可能會溢出,在堆棧向下增長的系統(tǒng)中,這個變量可用于檢查堆棧是否溢出;
如果在堆棧向上增長的系統(tǒng)中,要想確定堆棧是否溢出,還需要另外一個變量pxEndOfStack來輔助診斷是否堆棧溢出,后面會講到這個變量。
字符數(shù)組pcTaskName用于保存任務(wù)的描述或名字,在任務(wù)創(chuàng)建時,由參數(shù)指定。
名字的長度由宏configMAX_TASK_NAME_LEN(位于FreeRTOSConfig.h中)指定,包含字符串結(jié)束標(biāo)志。
如果堆棧向上生長(portSTACK_GROWTH > 0),指針pxEndOfStack指向堆棧尾部,用于檢驗堆棧是否溢出。
變量uxCriticalNesting用于保存臨界區(qū)嵌套深度,初始值為0。
接下來兩個變量用于可視化追蹤,僅當(dāng)宏configUSE_TRACE_FACILITY(位于FreeRTOSConfig.h中)為1時有效。變量uxTCBNumber存儲一個數(shù)值,在創(chuàng)建任務(wù)時由內(nèi)核自動分配數(shù)值(通常每創(chuàng)建一個任務(wù),值增加1),每個任務(wù)的uxTCBNumber值都不同,主要用于調(diào)試。
變量uxTaskNumber用于存儲一個特定值,與變量uxTCBNumber不同,uxTaskNumber的數(shù)值不是由內(nèi)核分配的,而是通過API函數(shù)vTaskSetTaskNumber()來設(shè)置的,數(shù)值由函數(shù)參數(shù)指定。
如果使用互斥量(configUSE_MUTEXES == 1),任務(wù)優(yōu)先級被臨時提高時,變量uxBasePriority用來保存任務(wù)原來的優(yōu)先級。
變量ucStaticAllocationFlags也需要說明一下,我們前面說過任務(wù)創(chuàng)建API函數(shù)xTaskCreate()只能使用動態(tài)內(nèi)存分配的方式創(chuàng)建任務(wù)堆棧和任務(wù)TCB,如果要使用靜態(tài)變量實現(xiàn)任務(wù)堆棧和任務(wù)TCB就需要使用函數(shù)xTaskGenericCreate()來實現(xiàn)。
如果任務(wù)堆棧或任務(wù)TCB由靜態(tài)數(shù)組和靜態(tài)變量實現(xiàn),則將該變量設(shè)置為pdTRUE(任務(wù)堆棧空間由靜態(tài)數(shù)組變量實現(xiàn)時為0x01,任務(wù)TCB由靜態(tài)變量實現(xiàn)時為0x02,任務(wù)堆棧和任務(wù)TCB都由靜態(tài)變量實現(xiàn)時為0x03),如果堆棧是動態(tài)分配的,則將該變量設(shè)置為pdFALSE。
到這里任務(wù)TCB的數(shù)據(jù)結(jié)構(gòu)就講完了,下面我們用一個例子來講述任務(wù)創(chuàng)建的過程,為方便起見,假設(shè)被創(chuàng)建的任務(wù)叫“任務(wù)A”,任務(wù)函數(shù)為vTask_A():
TaskHandle_t xHandle;
xTaskCreate(vTask_A,”Task A”,120,NULL,1,&xHandle);這里創(chuàng)建了一個任務(wù),任務(wù)優(yōu)先級為1,由于硬件平臺是32為架構(gòu),所以指定了120*4=480字節(jié)的任務(wù)堆棧,向任務(wù)函數(shù)vTask_A()傳遞的參數(shù)為空(NULL),任務(wù)句柄由變量xHandle保存。
當(dāng)這個語句執(zhí)行后,任務(wù)A被創(chuàng)建并加入就緒任務(wù)列表,我們這章的主要目的,就是看看這個語句在執(zhí)行過程中,發(fā)生了什么事情。
1.創(chuàng)建任務(wù)堆棧和任務(wù)TCB
調(diào)用函數(shù)prvAllocateTCBAndStack()創(chuàng)建任務(wù)堆棧和任務(wù)TCB。有兩種方式創(chuàng)建任務(wù)堆棧和任務(wù)TCB,一種是使用動態(tài)內(nèi)存分配方法,這樣當(dāng)任務(wù)刪除時,任務(wù)堆棧和任務(wù)控制塊空間會被釋放,可用于其它任務(wù);
另一種是使用靜態(tài)變量來實現(xiàn),在創(chuàng)建任務(wù)前定義好全局或者靜態(tài)堆棧數(shù)組和任務(wù)控制塊變量,在調(diào)用創(chuàng)建任務(wù)API函數(shù)時,將這兩個變量以參數(shù)的形式傳遞給任務(wù)創(chuàng)建函數(shù)xTaskGenericCreate()。
如果使用默認的xTaskCreate()創(chuàng)建任務(wù)函數(shù),則使用動態(tài)內(nèi)存分配,因為與靜態(tài)內(nèi)存分配有關(guān)的參數(shù)不可見(在本文一開始我們說過xTaskCreate()其實是一個帶參數(shù)的宏定義,真正被執(zhí)行的函數(shù)是xTaskGenericCreate(),參考宏xTaskCreate()的定義可以知道,xTaskCreate()對外隱藏了使用靜態(tài)內(nèi)存分配的參數(shù),在調(diào)用xTaskGenericCreate()時,這些參數(shù)被設(shè)置為NULL)。
任務(wù)堆棧成功分配后,經(jīng)過對齊的堆棧起始地址被保存到任務(wù)TCB的pxStack字段。
如果使能堆棧溢出檢查或者使用可視化追蹤功能,則使用固定值tskSTACK_FILL_BYTE(0xa5)填充堆棧。
函數(shù)prvAllocateTCBAndStack()的源碼去除斷言和不常用的條件編譯后如下所示:
static TCB_t *prvAllocateTCBAndStack( const uint16_t usStackDepth, StackType_t * const puxStackBuffer, TCB_t * const pxTaskBuffer )
{
TCB_t *pxNewTCB;
StackType_t *pxStack;
/* 分配堆??臻g*/
pxStack = ( StackType_t * ) pvPortMallocAligned( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ), puxStackBuffer );
if( pxStack != NULL )
{
/* 分配TCB空間 */
pxNewTCB = ( TCB_t * ) pvPortMallocAligned( sizeof( TCB_t ), pxTaskBuffer );
if( pxNewTCB != NULL )
{
/* 將堆棧起始位置存入TCB*/
pxNewTCB->pxStack = pxStack;
}
else
{
/* 如果TCB分配失敗,釋放之前申請的堆??臻g */
if( puxStackBuffer == NULL )
{
vPortFree( pxStack );
}
}
}
else
{
pxNewTCB = NULL;
}
if( pxNewTCB != NULL )
{
/* 如果需要,使用固定值填充堆棧 */
#if( ( configCHECK_FOR_STACK_OVERFLOW> 1 ) || ( configUSE_TRACE_FACILITY == 1 ) || ( INCLUDE_uxTaskGetStackHighWaterMark== 1 ) )
{
/* 僅用于調(diào)試 */
( void ) memset( pxNewTCB->pxStack, ( int ) tskSTACK_FILL_BYTE, ( size_t ) usStackDepth * sizeof( StackType_t ) );
}
#endif
}
return pxNewTCB;
}2.初始化任務(wù)TCB必要的字段
調(diào)用函數(shù)prvInitialiseTCBVariables()初始化任務(wù)TCB必要的字段。在調(diào)用創(chuàng)建任務(wù)API函數(shù)xTaskCreate()時,參數(shù)pcName(任務(wù)描述)、uxPriority(任務(wù)優(yōu)先級)都會被寫入任務(wù)TCB相應(yīng)的字段,TCB字段中的xStateListItem和xEventListItem列表項也會被初始化,初始化后的列表項如圖2-1所示。
在圖2-1中,列表項xEventListItem的成員列表項值xItemValue被初始為4,這是因為我在應(yīng)用中設(shè)置的最大優(yōu)先級數(shù)目(configMAX_PRIORITIES)為5,而xEventListItem. xItemValue等于configMAX_PRIORITIES減去任務(wù)A的優(yōu)先級(為1),即5-1=4。這一點很重要,在這里xItemValue不是直接保存任務(wù)優(yōu)先級,而是保存優(yōu)先級的補數(shù),這意味著xItemValue的值越大,對應(yīng)的任務(wù)優(yōu)先級越小。
FreeRTOS內(nèi)核使用vListInsert函數(shù)(詳細見FreeRTOS進階列表和列表項示例分析)將事件列表項插入到一個列表,這個函數(shù)根據(jù)xItemValue的值的大小順序來進行插入操作。
使用宏listGET_OWNER_OF_HEAD_ENTRY獲得列表中的第一個列表項的xItemValue值總是最小,也就是優(yōu)先級最高的任務(wù)!

圖2-1:初始化狀態(tài)和事件列表項
此外,TCB其它的一些字段也被初始化,比如臨界區(qū)嵌套次數(shù)、運行時間計數(shù)器、任務(wù)通知值、任務(wù)通知狀態(tài)等,函數(shù)prvInitialiseTCBVariables()的源碼如下所示:
static void prvInitialiseTCBVariables( TCB_t * const pxTCB, const char * const pcName, UBaseType_t uxPriority, \
const MemoryRegion_t * const xRegions, const uint16_t usStackDepth )
{
UBaseType_t x;
/* 將任務(wù)描述存入TCB */
for( x = ( UBaseType_t ) 0; x < ( UBaseType_t ) configMAX_TASK_NAME_LEN; x++ )
{
pxTCB->pcTaskName[ x ] = pcName[ x ];
if( pcName[ x ] == 0x00 )
{
break;
}
}
/* 確保字符串有結(jié)束 */
pxTCB->pcTaskName[ configMAX_TASK_NAME_LEN - 1 ] = '\0';
/* 調(diào)整優(yōu)先級,宏configMAX_PRIORITIES的值在FreeRTOSConfig.h中設(shè)置 */
if( uxPriority >= ( UBaseType_t ) configMAX_PRIORITIES )
{
uxPriority = ( UBaseType_t ) configMAX_PRIORITIES - ( UBaseType_t ) 1U;
}
pxTCB->uxPriority = uxPriority;
#if ( configUSE_MUTEXES == 1 ) /*使用互斥量*/
{
pxTCB->uxBasePriority = uxPriority;
pxTCB->uxMutexesHeld = 0;
}
#endif /* configUSE_MUTEXES */
/*初始化列表項*/
vListInitialiseItem( &( pxTCB->xStateListItem ) );
vListInitialiseItem( &( pxTCB->xEventListItem ) );
/* 設(shè)置列表項xStateListItem的成員pvOwner指向當(dāng)前任務(wù)控制塊 */
listSET_LIST_ITEM_OWNER( &( pxTCB->xStateListItem ), pxTCB );
/* 設(shè)置列表項xEventListItem的成員xItemValue*/
listSET_LIST_ITEM_VALUE( &( pxTCB->xEventListItem ), ( TickType_t ) configMAX_PRIORITIES - ( TickType_t ) uxPriority );
/* 設(shè)置列表項xEventListItem的成員pvOwner指向當(dāng)前任務(wù)控制塊 */
listSET_LIST_ITEM_OWNER( &( pxTCB->xEventListItem ), pxTCB );
#if ( portCRITICAL_NESTING_IN_TCB ==1 ) /*使能臨界區(qū)嵌套功能*/
{
pxTCB->uxCriticalNesting = ( UBaseType_t ) 0U;
}
#endif /* portCRITICAL_NESTING_IN_TCB */
#if ( configUSE_APPLICATION_TASK_TAG == 1 ) /*使能任務(wù)標(biāo)簽功能*/
{
pxTCB->pxTaskTag = NULL;
}
#endif /* configUSE_APPLICATION_TASK_TAG */
#if ( configGENERATE_RUN_TIME_STATS == 1 ) /*使能事件統(tǒng)計功能*/
{
pxTCB->ulRunTimeCounter = 0UL;
}
#endif /* configGENERATE_RUN_TIME_STATS */
#if ( portUSING_MPU_WRAPPERS == 1 ) /*使用MPU功能*/
{
vPortStoreTaskMPUSettings( &( pxTCB->xMPUSettings ), xRegions, pxTCB->pxStack, usStackDepth );
}
#else /* portUSING_MPU_WRAPPERS */
{
( void ) xRegions;
( void ) usStackDepth;
}
#endif /* portUSING_MPU_WRAPPERS */
#if( configNUM_THREAD_LOCAL_STORAGE_POINTERS != 0 )/*使能線程本地存儲指針*/
{
for( x = 0; x < ( UBaseType_t )configNUM_THREAD_LOCAL_STORAGE_POINTERS; x++ )
{
pxTCB->pvThreadLocalStoragePointers[ x ] = NULL;
}
}
#endif
#if ( configUSE_TASK_NOTIFICATIONS == 1 ) /*使能任務(wù)通知功能*/
{
pxTCB->ulNotifiedValue = 0;
pxTCB->ucNotifyState = taskNOT_WAITING_NOTIFICATION;
}
#endif
#if ( configUSE_NEWLIB_REENTRANT == 1 ) /*使用Newlib*/
{
_REENT_INIT_PTR( ( &( pxTCB->xNewLib_reent ) ) );
}
#endif
#if( INCLUDE_xTaskAbortDelay == 1 )
{
pxTCB->ucDelayAborted = pdFALSE;
}
#endif
}3.初始化任務(wù)堆棧
調(diào)用函數(shù)pxPortInitialiseStack()初始化任務(wù)堆棧,并將最新的棧頂指針賦值給任務(wù)TCB的pxTopOfStack字段。
調(diào)用函數(shù)pxPortInitialiseStack()后,相當(dāng)于執(zhí)行了一次系統(tǒng)節(jié)拍時鐘中斷:將一些重要寄存器入棧。雖然任務(wù)還沒開始執(zhí)行,也并沒有中斷發(fā)生,但看上去就像寄存器已經(jīng)被入棧了,并且部分堆棧值被修改成了我們需要的已知值。
對于不同的硬件架構(gòu),入棧的寄存器也不相同,所以我們看到這個函數(shù)是由移植層提供的。對于Cortex-M3架構(gòu),需要依次入棧xPSR、PC、LR、R12、R3~R0、R11~R4,假設(shè)堆棧是向下生長的,初始化后的堆棧如圖3-1所示。
在圖3-1中我們看到寄存器xPSR被初始為0x01000000,其中bit24被置1,表示使用Thumb指令;
寄存器PC被初始化為任務(wù)函數(shù)指針vTask_A,這樣當(dāng)某次任務(wù)切換后,任務(wù)A獲得CPU控制權(quán),任務(wù)函數(shù)vTask_A被出棧到PC寄存器,之后會執(zhí)行任務(wù)A的代碼;
LR寄存器初始化為函數(shù)指針prvTaskExitError,這是由移植層提供的一個出錯處理函數(shù)。
當(dāng)中斷發(fā)生時,LR被設(shè)置成中斷要返回的地址,但是每個任務(wù)都是一個死循環(huán),正常情況下不應(yīng)該退出任務(wù)函數(shù),所以一旦從任務(wù)函數(shù)退出,說明那里出錯了,這個時候會調(diào)用寄存器LR指向的函數(shù)來處理這個錯誤,即prvTaskExitError;
根據(jù)ATPCS(ARM-Thumb過程調(diào)用標(biāo)準(zhǔn)),我們知道子函數(shù)調(diào)用通過寄存器R0~R3傳遞參數(shù),在文章的最開始講xTaskCreate()函數(shù)時,提到這個函數(shù)有一個空指針類型的參數(shù)pvParameters,當(dāng)任務(wù)創(chuàng)建時,它作為一個參數(shù)傳遞給任務(wù),所以這個參數(shù)被保存到R0中,用來向任務(wù)傳遞參數(shù)。
任務(wù)TCB結(jié)構(gòu)體成員pxTopOfStack表示當(dāng)前堆棧的棧頂,它指向最后一個入棧的項目,所以在圖中它指向R4,TCB結(jié)構(gòu)體另外一個成員pxStack表示堆棧的起始位置,所以在圖中它指向堆棧的最開始處。

圖3-1:初始化任務(wù)堆棧
4.進入臨界區(qū)
調(diào)用taskENTER_CRITICAL()進入臨界區(qū),這是一個宏定義,最終進入臨界區(qū)的代碼由移植層提供。
5.當(dāng)前任務(wù)數(shù)量增加1
在tasks.c中 ,定義了一些靜態(tài)私有變量,用來跟蹤任務(wù)的數(shù)量或者狀態(tài)等等,其中變量uxCurrentNumberOfTasks表示當(dāng)前任務(wù)的總數(shù)量,每創(chuàng)建一個任務(wù),這個變量都會增加1。
6.為第一次運行做必要的初始化
如果這是第一個任務(wù)(uxCurrentNumberOfTasks等于1),則調(diào)用函數(shù)prvInitialiseTaskLists()初始化任務(wù)列表。FreeRTOS使用列表來跟蹤任務(wù),在tasks.c中,定義了靜態(tài)類型的列表變量:
PRIVILEGED_DATAstatic List_t pxReadyTasksLists[ configMAX_PRIORITIES ];/*按照優(yōu)先級排序的就緒態(tài)任務(wù)*/
PRIVILEGED_DATAstatic List_t xDelayedTaskList1; /*延時的任務(wù) */
PRIVILEGED_DATAstatic List_t xDelayedTaskList2; /*延時的任務(wù) */
PRIVILEGED_DATAstatic List_t xPendingReadyList; /*任務(wù)已就緒,但調(diào)度器被掛起 */
#if (INCLUDE_vTaskDelete == 1 )
PRIVILEGED_DATA static List_t xTasksWaitingTermination; /*任務(wù)已經(jīng)被刪除,但內(nèi)存尚未釋放*/
#endif
#if (INCLUDE_vTaskSuspend == 1 )
PRIVILEGED_DATA static List_t xSuspendedTaskList; /*當(dāng)前掛起的任務(wù)*/
#endif現(xiàn)在這些列表都要進行初始化,會調(diào)用API函數(shù)vListInitialise()初始化列表,這個函數(shù)在FreeRTOS列表和列表項中講過,每個列表的初始化方式都是相同的,以就緒態(tài)列表pxReadyTasksLists[0]為例,初始化后如圖6-1所示:

圖6-1:初始化后的列表
函數(shù)prvInitialiseTaskLists()的源代碼如下所示:
static void prvInitialiseTaskLists( void )
{
UBaseType_tuxPriority;
for( uxPriority = ( UBaseType_t ) 0U; uxPriority < ( UBaseType_t ) configMAX_PRIORITIES; uxPriority++ )
{
vListInitialise( &( pxReadyTasksLists[ uxPriority ] ) );
}
vListInitialise( &xDelayedTaskList1 );
vListInitialise( &xDelayedTaskList2 );
vListInitialise( &xPendingReadyList );
#if ( INCLUDE_vTaskDelete == 1 )
{
vListInitialise( &xTasksWaitingTermination );
}
#endif /* INCLUDE_vTaskDelete */
#if ( INCLUDE_vTaskSuspend == 1 )
{
vListInitialise( &xSuspendedTaskList );
}
#endif /* INCLUDE_vTaskSuspend */
/* Start with pxDelayedTaskList using list1 and the pxOverflowDelayedTaskListusing list2. */
pxDelayedTaskList = &xDelayedTaskList1;
pxOverflowDelayedTaskList = &xDelayedTaskList2;
}7.更新當(dāng)前正在運行的任務(wù)TCB指針
tasks.c中定義了一個任務(wù)TCB指針型變量:
PRIVILEGED_DATA TCB_t * volatile pxCurrentTCB= NULL;
這是一個全局變量,在tasks.c中只定義了這一個全局變量。這個變量用來指向當(dāng)前正在運行的任務(wù)TCB,我們需要多了解一下這個變量。
FreeRTOS的核心是確保處于優(yōu)先級最高的就緒任務(wù)獲得CPU運行權(quán)。在下一章講述任務(wù)切換時會知道,任務(wù)切換就是找到優(yōu)先級最高的就緒任務(wù),而找出的這個最高優(yōu)先級任務(wù)的TCB,就被賦給變量pxCurrentTCB。
如果調(diào)度器還沒有準(zhǔn)備好(程序剛開始運行時,可能會先創(chuàng)建幾個任務(wù),之后才會啟動調(diào)度器),并且新創(chuàng)建的任務(wù)優(yōu)先級大于變量pxCurrentTCB指向的任務(wù)優(yōu)先級,則設(shè)置pxCurrentTCB指向當(dāng)前新創(chuàng)建的任務(wù)TCB(確保pxCurrentTCB指向優(yōu)先級最高的就緒任務(wù))。
if( xSchedulerRunning == pdFALSE )
{
if( pxCurrentTCB->uxPriority <= uxPriority )
{
pxCurrentTCB = pxNewTCB;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}8.將新創(chuàng)建的任務(wù)加入就緒列表數(shù)組
調(diào)用prvAddTaskToReadyList(pxNewTCB)將創(chuàng)建的任務(wù)TCB加入到就緒列表數(shù)組中,任務(wù)的優(yōu)先級確定了加入到就緒列表數(shù)組的哪個下標(biāo)。比如我們新創(chuàng)建的任務(wù)優(yōu)先級為1,則這個任務(wù)被加入到列表pxReadyTasksLists[1]中。
prvAddTaskToReadyList()其實是一個宏,由一系列語句組成,去除其中的跟蹤宏外,這個宏定義如下所示:
#defineprvAddTaskToReadyList( pxTCB ) \
taskRECORD_READY_PRIORITY( ( pxTCB)->uxPriority ); \
vListInsertEnd( &( pxReadyTasksLists[ (pxTCB )->uxPriority ] ), &( ( pxTCB )->xStateListItem ) );宏taskRECORD_READY_PRIORITY()用來更新變量uxTopReadyPriority,這個變量在tasks.c中定義為靜態(tài)變量,記錄處于就緒態(tài)的最高任務(wù)優(yōu)先級。這個變量參與了FreeRTOS的最核心代碼:確保處于優(yōu)先級最高的就緒任務(wù)獲得CPU運行權(quán)。它在這里參與如何最快的找到優(yōu)先級最高的就緒任務(wù)。為了最快,不同的架構(gòu)會各顯神通,一些架構(gòu)還有特殊指令可用,所以這個宏由移植層提供。我們會在下一章介紹任務(wù)切換時,以Cortex-M3架構(gòu)為例,詳細介紹如何最快的找到優(yōu)先級最高的就緒任務(wù)。
函數(shù)vListInsertEnd()將列表項插入到列表末端,在FreeRTOS進階列表和列表項示例分析中已經(jīng)提到過,這里會結(jié)合著例子再看一下這個函數(shù)。
從前面我們直到,在調(diào)用函數(shù)vListInsertEnd()之前,就緒列表pxReadyTasksLists[1]和任務(wù)TCB的狀態(tài)列表項xStateListItem都已經(jīng)初始化好了,見圖6-1和圖2-1,為了方便查看,我們將這兩幅圖合成一副,見圖8-1。

圖8-1:初始化后的列表和列表項
調(diào)用vListInsertEnd(a,b)會將列表項b,插入到列表a的后面,函數(shù)執(zhí)行完畢后,列表和列表項的關(guān)系如圖8-2所示。

圖8-2:插入一個列表項后的列表
在此基礎(chǔ)上,假設(shè)又創(chuàng)建了任務(wù)B,任務(wù)A和任務(wù)B優(yōu)先級相同,都為1。和任務(wù)A一樣,任務(wù)B也有它自己的任務(wù)TCB,其中的狀態(tài)列表項字段xStateListItem也要插入到列表pxReadyTasksLists[1]中,新的列表和列表項如圖8-3所示。

圖8-3:相同優(yōu)先級就緒列表掛接兩個列表項
9.退出臨界區(qū)
調(diào)用taskEXIT_CRITICAL()退出臨界區(qū),這是一個宏定義,最終退出臨界區(qū)的代碼由移植層提供。
10.執(zhí)行上下文切換
如果上面的步驟都正確執(zhí)行,并且調(diào)度器也開始工作,則判斷當(dāng)前任務(wù)的優(yōu)先級是否大于新創(chuàng)建的任務(wù)優(yōu)先級。如果新創(chuàng)建的任務(wù)優(yōu)先級更高,則調(diào)用taskYIELD_IF_USING_PREEMPTION()強制進行一次上下文切換,切換后,新創(chuàng)建的任務(wù)將獲得CPU控制權(quán),精簡后的代碼如下所示。
if( xReturn == pdPASS )
{
if( xSchedulerRunning != pdFALSE )
{
/* 如果新創(chuàng)建的任務(wù)優(yōu)先級大于當(dāng)前任務(wù)優(yōu)先級,則新創(chuàng)建的任務(wù)應(yīng)該被立即執(zhí)行。*/
if(pxCurrentTCB->uxPriority < uxPriority )
{
taskYIELD_IF_USING_PREEMPTION();
}
}
}以上就是FreeRTOS進階之任務(wù)創(chuàng)建完全解析的詳細內(nèi)容,更多關(guān)于FreeRTOS進階任務(wù)創(chuàng)建分析的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
FreeRTOS實時操作系統(tǒng)多任務(wù)管理基礎(chǔ)知識
這篇文章主要為大家介紹了FreeRTOS實時操作系統(tǒng)多任務(wù)管理的基礎(chǔ)知識,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步早日升職加薪2022-04-04
FreeRTOS動態(tài)內(nèi)存分配管理heap_4示例
這篇文章主要為大家介紹了FreeRTOS動態(tài)內(nèi)存分配管理heap_4示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步早日升職加薪2022-04-04
FreeRTOS實時操作系統(tǒng)在Cortex-M3上的移植過程
這篇文章主要為大家介紹了FreeRTOS實時操作系統(tǒng)在Cortex-M3上的移植過程的示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步早日升職加薪2022-04-04
freertos實時操作系統(tǒng)臨界段保護開關(guān)中斷及進入退出
這篇文章主要介紹了freertos實時操作系統(tǒng)臨界段保護開關(guān)中斷及進入退出,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步早日升職加薪2022-04-04
FreeRTOS實時操作系統(tǒng)的內(nèi)存管理分析
這篇文章主要為大家介紹了FreeRTOS實時操作系統(tǒng)的內(nèi)存管理的示例分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步早日升職加薪2022-04-04

