開局一張圖。一步一步分析就好。 (一)什麼是任務? 在多任務系統中,我們按照功能不同,把整個系統分割成一個個獨立的,且無法返回的函數,這個函數我們稱為任務;任務包含幾個屬性:任務堆棧,任務函數、任務控制塊、任務優先順序;下麵主要介紹一下任務控制塊,其他都比較容易理解。 (二)什麼是任務控制塊? 任務控 ...
開局一張圖。一步一步分析就好。
(一)什麼是任務?
在多任務系統中,我們按照功能不同,把整個系統分割成一個個獨立的,且無法返回的函數,這個函數我們稱為任務;任務包含幾個屬性:任務堆棧,任務函數、任務控制塊、任務優先順序;下麵主要介紹一下任務控制塊,其他都比較容易理解。
(二)什麼是任務控制塊?
任務控制塊內包含了該任務的全部信息,任務的執行需要通過任務調度器來控制,那麼任務調度器怎麼“控制”任務實體的呢?就要抓住任務的小辮子---“任務控制塊”,系統對任務的全部操作都可以通過任務控制塊來實現!它是一種特別的數據結構。
在任務創建函數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(); } }
一定要耐心分析,別無他法,加油!不難。