FreeRTOS操作系統之任務控制塊與任務靜態創建函數

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指針就指向了任務入口地址,從而成功跳轉到第一個任務。

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章