FreeRTOS列舉一個簡單的任務控制塊結構體:
typedef struct tskTaskControlBlock
{
volatile StackType_t *pxTopOfStack; /* 棧頂 */
ListItem_t xStateListItem; /* 任務節點 */
StackType_t *pxStack; /* 任務棧起始地址 */
char pcTaskName[ configMAX_TASK_NAME_LEN ]; /* 任務名稱,長度默認16 */
} tskTCB;
typedef tskTCB TCB_t;
在多任務系統中,任務的執行是由系統調度的,系統爲每一個任務都額外定義了一個任務控制塊,存放任務所有信息,例如任務的棧指針、任務名稱、任務形式等。
靜態任務創建函數:
typedef void * TaskHandle_t;
typedef void ( *TaskFunction_t )( void * )
#if( configSUPPORT_STATIC_ALLOCATION == 1 )
TaskHandle_t xTaskCreateStatic( TaskFunction_t pxTaskCode, /* 任務入口 */
const char * const pcName, /* 任務名稱,字符串形式 */
const uint32_t ulStackDepth, /* 任務棧大小,單位爲字 */
void * const pvParameters, /* 任務形參 */
StackType_t * const puxStackBuffer, /* 任務棧起始地址 */
TCB_t * const pxTaskBuffer ) /* 任務控制塊指針 */
{
TCB_t *pxNewTCB;
TaskHandle_t xReturn;
if( ( pxTaskBuffer != NULL ) && ( puxStackBuffer != NULL ) )
{
pxNewTCB = ( TCB_t * ) pxTaskBuffer;
pxNewTCB->pxStack = ( StackType_t * ) puxStackBuffer;
/* 創建新的任務 */
prvInitialiseNewTask( pxTaskCode, /* 任務入口 */
pcName, /* 任務名稱,字符串形式 */
ulStackDepth, /* 任務棧大小,單位爲字 */
pvParameters, /* 任務形參 */
&xReturn, /* 任務句柄 */
pxNewTCB); /* 任務控制塊指針 */
}
else
{
xReturn = NULL;
}
/* 返回任務句柄,如果任務創建成功,此時xReturn應該指向任務控制塊 */
return xReturn;
}
#endif /* configSUPPORT_STATIC_ALLOCATION */
在FreeRTOS中,任務的創建可採用兩種方法,一種是使用動態創建,另一種是使用靜態創建。動態創建:任務控制塊和棧的內存是創建任務時動態分配的,任務刪除時,內存可以釋放;靜態創建:任務控制塊和棧的內存需要事先定義好,是靜態的內存,任務刪除時,內存不能釋放。
可以看到xTaskCreateStatic函數做了兩個動作:
1.設置任務控制塊棧內存起始地址,並返回任務控制塊指針
2.調用prvInitialiseNewTask函數來創建新任務,接下來分析prvInitialiseNewTask函數做了什麼
static void prvInitialiseNewTask( TaskFunction_t pxTaskCode, /* 任務入口 */
const char * const pcName, /* 任務名稱,字符串形式 */
const uint32_t ulStackDepth, /* 任務棧大小,單位爲字 */
void * const pvParameters, /* 任務形參 */
TaskHandle_t * const pxCreatedTask, /* 任務句柄 */
TCB_t *pxNewTCB ) /* 任務控制塊指針 */
{
StackType_t *pxTopOfStack;
UBaseType_t x;
/* 獲取棧頂地址 */
pxTopOfStack = pxNewTCB->pxStack + ( ulStackDepth - ( uint32_t ) 1 );
/* 向下做8字節對齊 */
pxTopOfStack = ( StackType_t * ) ( ( ( uint32_t ) pxTopOfStack ) & ( ~( ( uint32_t ) 0x0007 ) ) );
/* 將任務的名字存儲在TCB中 */
for( x = ( UBaseType_t ) 0; x < ( UBaseType_t ) configMAX_TASK_NAME_LEN; x++ )
{
pxNewTCB->pcTaskName[ x ] = pcName[ x ];
if( pcName[ x ] == 0x00 )
{
break;
}
}
/* 任務名字的長度不能超過configMAX_TASK_NAME_LEN */
pxNewTCB->pcTaskName[ configMAX_TASK_NAME_LEN - 1 ] = '\0';
/* 初始化TCB中的xStateListItem節點 */
vListInitialiseItem( &( pxNewTCB->xStateListItem ) );
/* 設置xStateListItem節點的擁有者 */
listSET_LIST_ITEM_OWNER( &( pxNewTCB->xStateListItem ), pxNewTCB );
/* 初始化任務棧 */
pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxTaskCode, pvParameters );
/* 讓任務句柄指向任務控制塊 */
if( ( void * ) pxCreatedTask != NULL )
{
*pxCreatedTask = ( TaskHandle_t ) pxNewTCB;
}
}
函數解析:
1)棧頂地址向下做8字節對齊,在Cortex-M3(Cortex-M4或Cortex-M7)內核的單片機中,因爲總線寬度時32位的,通常只要棧保持4字節對齊即可,那麼此處爲何需要8字節?難道有哪些操作是64位?確實有,那就是浮點運算,所以要8字節對齊(但是目前還沒有涉及浮點運算,此處的設置只是爲了後續能夠兼容浮點運算)。如果棧頂指針是8字節對齊的,在進行向下8字節對齊時,指針不會移動;如果不是8字節對齊的,在做向下8字節對齊時,就會空出幾個字節,如pxTopOfStack是33時,明顯不能被8整除,進行向下8字節對齊就是32,那麼就會空出一個字節不能使用。進行8字節對齊,只需要將地址低三位清零(1000值等於8,所以x000形式代表地址8字節對齊)。
2)初始化TCB中的xStateListItem節點,即初始化該節點所在的鏈表爲空,表士節點還沒有插入任何鏈表
3)調用pxPortInitialiseStack()函數初始化任務棧,並更新棧頂指針,任務第一次運行的環境參數就存在任務棧中。
繼續深入分析pxPortInitialiseStack()函數:
#define portINITIAL_XPSR ( 0x01000000 )
#define portSTART_ADDRESS_MASK ( ( StackType_t ) 0xfffffffeUL )
static void prvTaskExitError( void )
{
/* 函數停止在這裏 */
for(;;);
}
StackType_t *pxPortInitialiseStack( StackType_t *pxTopOfStack, TaskFunction_t pxCode, void *pvParameters )
{
/* 異常發生時,自動加載到CPU寄存器的內容 */
pxTopOfStack--;
*pxTopOfStack = portINITIAL_XPSR; /* xPSR的bit24必須置1 */
pxTopOfStack--;
*pxTopOfStack = ( ( StackType_t ) pxCode ) & portSTART_ADDRESS_MASK; /* PC,即任務入口函數 */
pxTopOfStack--;
*pxTopOfStack = ( StackType_t ) prvTaskExitError; /* LR,函數返回地址 */
pxTopOfStack -= 5; /* R12, R3, R2 and R1 默認初始化爲0 */
*pxTopOfStack = ( StackType_t ) pvParameters; /* R0,任務形參 */
/* 異常發生時,手動加載到CPU寄存器的內容 */
pxTopOfStack -= 8; /* R11, R10, R9, R8, R7, R6, R5 and R4默認初始化爲0 */
/* 返回棧頂指針,此時pxTopOfStack指向空閒棧 */
return pxTopOfStack;
}
1)異常發生時,CPU自動從棧中加載到CPU寄存器的內存,包括8個寄存器,分別是r0、r1、r2、r3、r12、r14、r15和xPSR的位24,且順序不能改變
2)任務返回地址概念,通常任務時不會返回的,如果返回了就跳轉到prvTaskExitError,該函數是一個無限循環
3)r12、r3、r2、r1默認初始化爲0;異常發生時,需要手動加載到CPU寄存器的內容,總共有8個,分別爲r4、r5、r6、r7、r8、r9、r10、r11,默認初始化爲0
4)返回棧頂指針,任務第一次運行時,就是從這個棧頂指針開始手動加載8個字的內容到CPU寄存器:r4、r5、r6、r7、r8、r9、r10、r11,當推出異常時,棧中剩下的8個字的內容會自動加載到CPU寄存器:r0、r1、r2、r3、r12、r14、r15和xPSR的位24,此時PC指針就指向了任務入口地址,從而成功跳轉到第一個任務。