開局一張圖。一步一步分析就好。
(一)什麼是任務?
在多工系統中,我們按照功能不同,把整個系統分割成一個個獨立的,且無法返回的函式,這個函式我們稱為任務;任務包含幾個屬性:任務堆疊,任務函式、任務控制塊、任務優先順序;下面主要介紹一下任務控制塊,其他都比較容易理解。
(二)什麼是任務控制塊?
任務控制塊內包含了該任務的全部資訊,任務的執行需要通過任務排程器來控制,那麼任務排程器怎麼“控制”任務實體的呢?就要抓住任務的小辮子---“任務控制塊”,系統對任務的全部操作都可以通過任務控制塊來實現!它是一種特別的資料結構。
在任務建立函式xTaskCreat()建立任務的時候就會自動給每個任務分配一個任務控制塊。
typedef struct tskTaskControlBlock { volatile StackType_t *pxTopOfStack; /*任務堆疊棧頂指標*/ #if ( portUSING_MPU_WRAPPERS == 1 ) xMPU_SETTINGS xMPUSettings; /*MPU相關設定*/ #endif ListItem_t xStateListItem; /*狀態列表項,這是一個內建在TCB控制塊中的一個連結串列節點,通過這個節點,將任務掛到其他連結串列中
比如就緒列表,阻塞列表,掛起列表等*/
ListItem_t xEventListItem; /*事件列表項,用於引用事件列表中的任務*/ UBaseType_t uxPriority; /*任務優先順序*/ StackType_t *pxStack; /*任務堆疊起始地址,是一個棧底*/ char pcTaskName[ configMAX_TASK_NAME_LEN ]; /*任務名字*/ #if ( portSTACK_GROWTH > 0 ) StackType_t *pxEndOfStack; /*任務堆疊棧底*/ #endif #if ( portCRITICAL_NESTING_IN_TCB == 1 ) UBaseType_t uxCriticalNesting; /*臨界區巢狀深度*/ #endif #if ( configUSE_TRACE_FACILITY == 1 ) UBaseType_t uxTCBNumber; /*debug的時候用到*/ UBaseType_t uxTaskNumber; /*trace的時候用到*/ #endif #if ( configUSE_MUTEXES == 1 ) UBaseType_t uxBasePriority; /*任務基礎優先順序,優先順序反轉時用到*/ 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; /*用來記錄任務執行總時間*/ #endif #if ( configUSE_NEWLIB_REENTRANT == 1 ) struct _reent xNewLib_reent; /*定義一個newlib結構體變數*/ #endif #if( configUSE_TASK_NOTIFICATIONS == 1 ) /*任務通知相關變數*/ volatile uint32_t ulNotifiedValue; /*任務通知值*/ volatile uint8_t ucNotifyState; /*任務通知狀態*/ #endif /* 用來標記任務是動態建立還是靜態建立*/ #if( tskSTATIC_AND_DYNAMIC_ALLOCATION_POSSIBLE != 0 ) uint8_t ucStaticallyAllocated; /*靜態建立此變數為pdTURE;動態建立此變數為pdFALSE*/ #endif #if( INCLUDE_xTaskAbortDelay == 1 ) uint8_t ucDelayAborted; #endif } tskTCB;
注:#if 開頭的都是條件編譯,我們們可以先不用理解。基本結構如下:
指標pxStack指向堆疊的起始位置,任務建立時會分配指定數目的任務堆疊,申請堆疊記憶體函式返回的指標就被賦給該變數。
很多剛接觸FreeRTOS的人會分不清指標pxTopOfStack和pxStack的區別,這裡簡單說一下:pxTopOfStack指向當前堆疊棧頂,隨著進棧出棧,pxTopOfStack指向的位置是會變化的;pxStack指向當前堆疊的起始位置,一經分配後,堆疊起始位置就固定了,不會被改變了。那麼為什麼需要pxStack變數呢,這是因為隨著任務的執行,堆疊可能會溢位,在堆疊向下增長的系統中,這個變數可用於檢查堆疊是否溢位;如果在堆疊向上增長的系統中,要想確定堆疊是否溢位,還需要另外一個變數pxEndOfStack來輔助診斷是否堆疊溢位。
(三)任務是怎麼建立出來的?
任務有兩種建立方式,動態建立和靜態建立,兩者的區別就是: 靜態建立時候任務控制塊和任務堆疊的記憶體是由使用者自己定義的,任務刪除的時候,記憶體不能自動釋放。動態建立,任務堆疊和任務控制塊的記憶體是由系統自動建立的,自動釋放的。
動態建立任務的函式為 xTaskCreate();
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; #define portSTACK_GROWTH //-1表示滿減棧 #if( portSTACK_GROWTH > 0 ){ } #else{ /* portSTACK_GROWTH<0 代表堆疊向下增長 */ StackType_t *pxStack; /* 任務棧記憶體分配,stm32是向下增長的堆疊,獲取到的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 /* portSTACK_GROWTH */ if( pxNewTCB != NULL ) { /* 新建任務初始化 */ prvInitialiseNewTask( pxTaskCode, pcName, ( uint32_t ) usStackDepth, pvParameters, uxPriority, pxCreatedTask, pxNewTCB, NULL ); /* 把任務新增到就緒列表中 */ prvAddNewTaskToReadyList( pxNewTCB ); xReturn = pdPASS; } else{ xReturn = errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY; } return xReturn; }
之後,又呼叫了函式 prvInitialiseNewTask()來新建任務初始化。我們看看下面是如何定義的。
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; /* 計算棧頂的地址 */ #if( portSTACK_GROWTH < 0 ){ /* 把棧空間的高地址分配給棧頂 */ pxTopOfStack = pxNewTCB->pxStack + ( ulStackDepth - ( uint32_t ) 1 ); /* 棧對齊----棧要8位元組對齊 */ 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 /* portSTACK_GROWTH */ { } #endif /* portSTACK_GROWTH */ /* 儲存任務名稱 */ for( x = ( UBaseType_t ) 0; x < ( UBaseType_t ) configMAX_TASK_NAME_LEN; x++ ){ pxNewTCB->pcTaskName[ x ] = pcName[ x ]; if( pcName[ x ] == 0x00 ){ break; } else{ mtCOVERAGE_TEST_MARKER(); } } /* \0補齊字串 */ pxNewTCB->pcTaskName[ configMAX_TASK_NAME_LEN - 1 ] = '\0'; /* 判斷任務分配的優先順序,是否大於最大值 如果超過最大值,賦值最大值 */ if( uxPriority >= ( UBaseType_t ) configMAX_PRIORITIES ){ uxPriority = ( UBaseType_t ) configMAX_PRIORITIES - ( UBaseType_t ) 1U; } else{ mtCOVERAGE_TEST_MARKER(); } /* 賦值任務優先順序到任務控制塊 */ pxNewTCB->uxPriority = uxPriority; /* 任務狀態表 事件表初始化 */ vListInitialiseItem( &( pxNewTCB->xStateListItem ) ); vListInitialiseItem( &( pxNewTCB->xEventListItem ) ); /* 設定任務控制塊中的狀態列表項的成員變數ower ,是屬於PxNewTCB(擁有該結點的核心物件) */ listSET_LIST_ITEM_OWNER( &( pxNewTCB->xStateListItem ), pxNewTCB ); /*更改事件列表項中的成員變數xItemValue的值,目的是列表在排列的時候,是按照優先順序由大到小排列 */ listSET_LIST_ITEM_VALUE( &( pxNewTCB->xEventListItem ), ( TickType_t ) configMAX_PRIORITIES - ( TickType_t ) uxPriority );
/*設定任務控制塊中事件列表項的成員變數ower,同上*/ listSET_LIST_ITEM_OWNER( &( pxNewTCB->xEventListItem ), pxNewTCB ); #if( portUSING_MPU_WRAPPERS == 1 ){ } #else{ /* portUSING_MPU_WRAPPERS */ /* 初始化任務堆疊,之後返回任務棧頂 */ pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxTopOfStack, pxTaskCode, pvParameters ); } #endif /* portUSING_MPU_WRAPPERS */ if( ( void * ) pxCreatedTask != NULL ){ /* 任務控制程式碼指向任務控制塊 */ *pxCreatedTask = ( TaskHandle_t ) pxNewTCB; } else{ mtCOVERAGE_TEST_MARKER(); } }
prvInitialiseNewTask()函式的形參,出來xTaskCreat()的形參之外,又多出來pxNewTCB和xRegions兩個形參;
後面又呼叫了 pxPortInitialiseStack(StackType_t *pxTopOfStack, TaskFunction_t pxCode, void *pvParameters)
來初始化任務堆疊。
StackType_t *pxPortInitialiseStack(StackType_t *pxTopOfStack, TaskFunction_t pxCode, void *pvParameters){ pxTopOfStack--; /* 入棧程式狀態暫存器 */ *pxTopOfStack = portINITIAL_XPSR; /* xPSR */ pxTopOfStack--; /* 入棧PC指標 */ *pxTopOfStack = ( ( StackType_t ) pxCode ) & portSTART_ADDRESS_MASK; /* PC */ pxTopOfStack--; /* 入棧LR連結暫存器 */ *pxTopOfStack = ( StackType_t ) prvTaskExitError; /* LR */ pxTopOfStack -= 5; /* 跳過R12, R3, R2 and R1這四個暫存器,不初始化 */ *pxTopOfStack = ( StackType_t ) pvParameters; /* R0作為傳參入棧 */ pxTopOfStack--; /* 儲存EXC_RETURN的值,用於退出SVC或PendSV中斷時候,處理器處於什麼狀態*/ *pxTopOfStack = portINITIAL_EXEC_RETURN; pxTopOfStack -= 8; /* 跳過R11, R10, R9, R8, R7, R6, R5 and R4這8個暫存器,不初始化 */ return pxTopOfStack; /*最終返回棧頂*/
初始化堆疊完成之後堆疊如下圖:
層層深入完畢,現在我們返回到xTaskCreat()函式後面,看看 prvAddNewTaskToReadyList( pxNewTCB ); 函式是怎麼把任務新增到就緒列表中!
static void prvAddNewTaskToReadyList( TCB_t *pxNewTCB ) { taskENTER_CRITICAL(); { uxCurrentNumberOfTasks++; if( pxCurrentTCB == NULL ) //正在執行的任務塊為NULL,沒有任務執行; { pxCurrentTCB = pxNewTCB; //將新任務控制塊賦值給pxCurrentTCB if( uxCurrentNumberOfTasks == ( UBaseType_t ) 1 ) //為1說明正在建立的任務是第一個任務。 { prvInitialiseTaskLists(); //初始化列表,就緒列表、阻塞列表等等 } else { mtCOVERAGE_TEST_MARKER(); } } else { if( xSchedulerRunning == pdFALSE ) //判斷任務排程器是否執行,pdfalse代表沒有執行 { if( pxCurrentTCB->uxPriority <= pxNewTCB->uxPriority ) { pxCurrentTCB = pxNewTCB;// 將新建立的任務控制塊賦值給當前任務控制塊 } else { mtCOVERAGE_TEST_MARKER(); } } else { mtCOVERAGE_TEST_MARKER(); } } uxTaskNumber++; // 用於任務控制塊編號 #if ( configUSE_TRACE_FACILITY == 1 ) { pxNewTCB->uxTCBNumber = uxTaskNumber; } #endif /* configUSE_TRACE_FACILITY */ traceTASK_CREATE( pxNewTCB ); prvAddTaskToReadyList( pxNewTCB ); //將任務新增到就緒列表 portSETUP_TCB( pxNewTCB ); } taskEXIT_CRITICAL(); if( xSchedulerRunning != pdFALSE ) //如果任務調排程器在執行,新任務優先順序比正在執行的優先順序高 { if( pxCurrentTCB->uxPriority < pxNewTCB->uxPriority ) { taskYIELD_IF_USING_PREEMPTION(); //呼叫此函式完成一次任務切換 } else { mtCOVERAGE_TEST_MARKER(); } } else { mtCOVERAGE_TEST_MARKER(); } }
一定要耐心分析,別無他法,加油!不難。