@(嵌入式)
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)
系統(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ù)控制塊中有兩個鏈表項 xStateListItem
和 xEventListItem
, 在前面文章提到鏈表項中有一個指針指向所屬的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)存的方式。
以下主要介紹 xTaskCreateStatic
和 xTaskCreate
這兩個函數(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)度(如果使能),