鴻蒙輕內(nèi)核M核源碼分析系列六 任務(wù)及任務(wù)調(diào)度(1)任務(wù)棧
51CTO和華為官方合作共建的鴻蒙技術(shù)社區(qū)
繼續(xù)分析鴻蒙輕內(nèi)核源碼,我們本文開始要分析下任務(wù)及任務(wù)調(diào)度模塊。首先,我們介紹下任務(wù)棧的基礎(chǔ)概念。任務(wù)棧是高地址向低地址生長的遞減棧,棧指針指向即將入棧的元素位置。初始化后未使用過的??臻g初始化的內(nèi)容為宏OS_TASK_STACK_INIT代表的數(shù)值0xCACACACA,棧頂初始化為宏OS_TASK_MAGIC_WORD代表的數(shù)值0xCCCCCCCC。一個任務(wù)棧的示意圖如下,其中,棧底指針是棧的最大的內(nèi)存地址,棧頂指針,是棧的最小的內(nèi)存地址,棧指針從棧底向棧頂方向生長。
任務(wù)上下文(Task Context)是任務(wù)及任務(wù)調(diào)度模塊的另外一個重要的概念,它指的是任務(wù)運行的環(huán)境,例如包括程序計數(shù)器、堆棧指針、通用寄存器等內(nèi)容。在多任務(wù)調(diào)度中,任務(wù)上下文切換(Task Context Switching)屬于核心內(nèi)容,是多個任務(wù)運行在同一CPU核上的基礎(chǔ)。在任務(wù)調(diào)度時,保存退出運行狀態(tài)的任務(wù)使用的寄存器信息到任務(wù)棧,還會從進入運行狀態(tài)的任務(wù)的棧中讀取上下文信息,恢復寄存器信息。
下面,我們剖析下任務(wù)棧、任務(wù)棧初始化的源代碼,若涉及開發(fā)板部分,以開發(fā)板工程targets\cortex-m7_nucleo_f767zi_gcc\為例進行源碼分析。首先,看下任務(wù)上下文結(jié)構(gòu)體。
1、TaskContext上下文結(jié)構(gòu)體定義
在文件kernel\arch\arm\cortex-m7\gcc\los_arch_context.h中,定義的上下文的結(jié)構(gòu)體如下,主要是浮點寄存器,通用寄存器。
- typedef struct TagTskContext {
 - #if ((defined(__FPU_PRESENT) && (__FPU_PRESENT == 1U)) && \
 - (defined(__FPU_USED) && (__FPU_USED == 1U)))
 - UINT32 S16;
 - UINT32 S17;
 - UINT32 S18;
 - UINT32 S19;
 - UINT32 S20;
 - UINT32 S21;
 - UINT32 S22;
 - UINT32 S23;
 - UINT32 S24;
 - UINT32 S25;
 - UINT32 S26;
 - UINT32 S27;
 - UINT32 S28;
 - UINT32 S29;
 - UINT32 S30;
 - UINT32 S31;
 - #endif
 - UINT32 uwR4;
 - UINT32 uwR5;
 - UINT32 uwR6;
 - UINT32 uwR7;
 - UINT32 uwR8;
 - UINT32 uwR9;
 - UINT32 uwR10;
 - UINT32 uwR11;
 - UINT32 uwPriMask;
 - UINT32 uwR0;
 - UINT32 uwR1;
 - UINT32 uwR2;
 - UINT32 uwR3;
 - UINT32 uwR12;
 - UINT32 uwLR;
 - UINT32 uwPC;
 - UINT32 uwxPSR;
 - #if ((defined(__FPU_PRESENT) && (__FPU_PRESENT == 1U)) && \
 - (defined(__FPU_USED) && (__FPU_USED == 1U)))
 - UINT32 S0;
 - UINT32 S1;
 - UINT32 S2;
 - UINT32 S3;
 - UINT32 S4;
 - UINT32 S5;
 - UINT32 S6;
 - UINT32 S7;
 - UINT32 S8;
 - UINT32 S9;
 - UINT32 S10;
 - UINT32 S11;
 - UINT32 S12;
 - UINT32 S13;
 - UINT32 S14;
 - UINT32 S15;
 - UINT32 FPSCR;
 - UINT32 NO_NAME;
 - #endif
 - } TaskContext;
 
2、任務(wù)棧相關(guān)函數(shù)
2.1 任務(wù)棧初始化函數(shù)
在文件kernel\arch\arm\cortex-m7\gcc\los_context.c中定義了任務(wù)棧初始化函數(shù)VOID *HalTskStackInit(t()。該函數(shù)被文件kernel\src\los_task.c中的函數(shù)UINT32 OsNewTaskInit()調(diào)用完成任務(wù)初始化,并進一步在創(chuàng)建任務(wù)函數(shù)UINT32 LOS_TaskCreateOnly()中調(diào)用,完成新創(chuàng)建任務(wù)的任務(wù)棧初始化。
該函數(shù)使用3個參數(shù),一個是任務(wù)編號UINT32 taskID,一個是初始化的棧的大小UINT32 stackSize,第3個參數(shù)是棧頂指針VOID *topStack。⑴處代碼把棧內(nèi)容初始化為OS_TASK_STACK_INIT,⑵處把棧頂初始化為OS_TASK_MAGIC_WORD。
⑶處代碼獲取任務(wù)上下文的指針地址TaskContext *context。對于新創(chuàng)建任務(wù),從棧的底部開始,大小為sizeof(TaskContext)的??臻g存放上下文的數(shù)據(jù)。⑷處如果支持浮點數(shù)計算,需要初始化浮點數(shù)相關(guān)的寄存器。⑸初始化通用寄存器,其中.uwLR初始化為(UINT32)(UINTPTR)HalSysExit。.uwPC初始化為(UINT32)(UINTPTR)OsTaskEntry,這是CPU首次執(zhí)行該任務(wù)時運行的第一條指令的位置。這2個函數(shù)下文會分析。
⑹處返回值是指針(VOID *)taskContext,這個就是任務(wù)初始化后的棧指針,注意不是從棧底開始了,棧底保存的是上下文,棧指針要減去上下文占用的棧大小。在棧中,從TaskContext *context指針增加的方向,依次保存上下文結(jié)構(gòu)體的第一個成員,第二個成員…另外,初始化棧的時候,除了特殊的幾個寄存器,不同寄存器的初始值雖然沒有什么意義,也有些初始化的規(guī)律。比如R2寄存器初始化為0x02020202L,R12寄存器初始化為0x12121212L初始化的內(nèi)容和寄存器編號有關(guān)聯(lián),其余類似。
- LITE_OS_SEC_TEXT_INIT VOID *HalTskStackInit(UINT32 taskID, UINT32 stackSize, VOID *topStack)
 - {
 - TaskContext *context = NULL;
 - errno_t result;
 - /* initialize the task stack, write magic num to stack top */
 - ⑴ result = memset_s(topStack, stackSize, (INT32)(OS_TASK_STACK_INIT & 0xFF), stackSize);
 - if (result != EOK) {
 - printf("memset_s is failed:%s[%d]\r\n", __FUNCTION__, __LINE__);
 - }
 - ⑵ *((UINT32 *)(topStack)) = OS_TASK_MAGIC_WORD;
 - ⑶ context = (TaskContext *)(((UINTPTR)topStack + stackSize) - sizeof(TaskContext));
 - #if ((defined(__FPU_PRESENT) && (__FPU_PRESENT == 1U)) && \
 - (defined(__FPU_USED) && (__FPU_USED == 1U)))
 - ⑷ context->S16 = 0xAA000010;
 - context->S17 = 0xAA000011;
 - context->S18 = 0xAA000012;
 - context->S19 = 0xAA000013;
 - context->S20 = 0xAA000014;
 - context->S21 = 0xAA000015;
 - context->S22 = 0xAA000016;
 - context->S23 = 0xAA000017;
 - context->S24 = 0xAA000018;
 - context->S25 = 0xAA000019;
 - context->S26 = 0xAA00001A;
 - context->S27 = 0xAA00001B;
 - context->S28 = 0xAA00001C;
 - context->S29 = 0xAA00001D;
 - context->S30 = 0xAA00001E;
 - context->S31 = 0xAA00001F;
 - context->S0 = 0xAA000000;
 - context->S1 = 0xAA000001;
 - context->S2 = 0xAA000002;
 - context->S3 = 0xAA000003;
 - context->S4 = 0xAA000004;
 - context->S5 = 0xAA000005;
 - context->S6 = 0xAA000006;
 - context->S7 = 0xAA000007;
 - context->S8 = 0xAA000008;
 - context->S9 = 0xAA000009;
 - context->S10 = 0xAA00000A;
 - context->S11 = 0xAA00000B;
 - context->S12 = 0xAA00000C;
 - context->S13 = 0xAA00000D;
 - context->S14 = 0xAA00000E;
 - context->S15 = 0xAA00000F;
 - context->FPSCR = 0x00000000;
 - context->NO_NAME = 0xAA000011;
 - #endif
 - ⑸ context->uwR4 = 0x04040404L;
 - context->uwR5 = 0x05050505L;
 - context->uwR6 = 0x06060606L;
 - context->uwR7 = 0x07070707L;
 - context->uwR8 = 0x08080808L;
 - context->uwR9 = 0x09090909L;
 - context->uwR10 = 0x10101010L;
 - context->uwR11 = 0x11111111L;
 - context->uwPriMask = 0;
 - context->uwR0 = taskID;
 - context->uwR1 = 0x01010101L;
 - context->uwR2 = 0x02020202L;
 - context->uwR3 = 0x03030303L;
 - context->uwR12 = 0x12121212L;
 - context->uwLR = (UINT32)(UINTPTR)HalSysExit;
 - context->uwPC = (UINT32)(UINTPTR)OsTaskEntry;
 - context->uwxPSR = 0x01000000L;
 - ⑹ return (VOID *)context;
 - }
 
2.2 獲取任務(wù)棧水線函數(shù)
隨著任務(wù)棧入棧、出棧,當前棧使用的大小不一定是最大值,UINT32 OsGetTaskWaterLine(UINT32 taskID)可以獲取的棧使用的最大值即水線WaterLine。該函數(shù)定義在文件kernel\src\los_task.c,它需要1個參數(shù),即UINT32 taskID任務(wù)編號,返回值UINT32 peakUsed表示獲取的水線值,即任務(wù)棧使用的最大值。
我們詳細看下代碼,⑴處代碼表示如果棧頂?shù)扔谠O(shè)置的魔術(shù)字,說明棧沒有被溢出破壞,從棧頂開始棧內(nèi)容被寫滿宏OS_TASK_STACK_INIT的部分是沒有使用過的??臻g。使用臨時棧指針stackPtr指針變量依次向棧底方向增加,判斷棧是否被使用過,while循環(huán)結(jié)束,棧指針stackPtr指向最大的未使用過的棧地址。⑵處代碼獲取最大的使用過的棧空間大小,即需要的水線。⑶處如果棧頂溢出,則返回無效值OS_NULL_INT。
該函數(shù)被kernel\base\los_task.c中的函數(shù)LOS_TaskInfoGet(UINT32 taskId, TSK_INFO_S *taskInfo)調(diào)用,獲取任務(wù)的信息。在shell模塊也會使用來或者棧信息。
- UINT32 OsStackWaterLineGet(const UINTPTR *stackBottom, const UINTPTR *stackTop, UINT32 *peakUsed)
 - {
 - UINT32 size;
 - const UINTPTR *tmp = NULL;
 - ⑴ if (*stackTop == OS_STACK_MAGIC_WORD) {
 - tmp = stackTop + 1;
 - while ((tmp < stackBottom) && (*tmp == OS_STACK_INIT)) {
 - tmp++;
 - }
 - ⑵ size = (UINT32)((UINTPTR)stackBottom - (UINTPTR)tmp);
 - *peakUsed = (size == 0) ? size : (size + sizeof(CHAR *));
 - return LOS_OK;
 - } else {
 - *peakUsed = OS_INVALID_WATERLINE;
 - return LOS_NOK;
 - }
 - }
 
- UINT32 OsGetTaskWaterLine(UINT32 taskID)
 - {
 - UINT32 *stackPtr = NULL;
 - UINT32 peakUsed;
 - ⑴ if (*(UINT32 *)(UINTPTR)OS_TCB_FROM_TID(taskID)->topOfStack == OS_TASK_MAGIC_WORD) {
 - stackPtr = (UINT32 *)(UINTPTR)(OS_TCB_FROM_TID(taskID)->topOfStack + OS_TASK_STACK_TOP_OFFSET);
 - while ((stackPtr < (UINT32 *)(OS_TCB_FROM_TID(taskID)->stackPointer)) && (*stackPtr == OS_TASK_STACK_INIT)) {
 - stackPtr += 1;
 - }
 - ⑵ peakUsed = OS_TCB_FROM_TID(taskID)->stackSize -
 - ((UINT32)(UINTPTR)stackPtr - OS_TCB_FROM_TID(taskID)->topOfStack);
 - } else {
 - ⑶ PRINT_ERR("CURRENT task %s stack overflow!\n", OS_TCB_FROM_TID(taskID)->taskName);
 - peakUsed = OS_NULL_INT;
 - }
 - return peakUsed;
 - }
 
3、任務(wù)進入退出函數(shù)
3.1、任務(wù)退出函數(shù)
在初始化上下文的時候,鏈接寄存器設(shè)置的是函數(shù)(UINT32)(UINTPTR)HalSysExit,該函數(shù)定義在文件kernel\src\los_task.c。函數(shù)代碼里調(diào)用LOS_IntLock()關(guān)中斷,然后進入死循環(huán)。在任務(wù)正常調(diào)度期間,該函數(shù)理論上不會被執(zhí)行。在系統(tǒng)異常時,主動調(diào)用LOS_Panic()c觸發(fā)異常時,也會調(diào)用該函數(shù)。
- LITE_OS_SEC_TEXT_MINOR VOID HalSysExit(VOID)
 - {
 - LOS_IntLock();
 - while (1) {
 - }
 - }
 
3.2、任務(wù)進入函數(shù)
在初始化上下文的時候,PC寄存器設(shè)置的是函數(shù)VOID OsTaskEntry(UINT32 taskId),該函數(shù)定義在文件kernel\base\los_task.c,我們來分析下源代碼,⑴處代碼獲取taskCB,然后執(zhí)行⑵調(diào)用任務(wù)的入口函數(shù)。等任務(wù)執(zhí)行完畢后,執(zhí)行⑶刪除任務(wù)。通常任務(wù)入口執(zhí)行函數(shù)都是while循環(huán),任務(wù)不執(zhí)行時,會調(diào)度到其他任務(wù)或者空閑任務(wù),不會執(zhí)行到刪除任務(wù)階段。
- LITE_OS_SEC_TEXT_INIT VOID OsTaskEntry(UINT32 taskID)
 - {
 - UINT32 retVal;
 - ⑴ LosTaskCB *taskCB = OS_TCB_FROM_TID(taskID);
 - ⑵ (VOID)taskCB->taskEntry(taskCB->arg);
 - ⑶ retVal = LOS_TaskDelete(taskCB->taskID);
 - if (retVal != LOS_OK) {
 - PRINT_ERR("Delete Task[TID: %d] Failed!\n", taskCB->taskID);
 - }
 - }
 
小結(jié)
本文帶領(lǐng)大家一起學習了鴻蒙輕內(nèi)核的任務(wù)棧、任務(wù)上下文的基礎(chǔ)概念,剖析了任務(wù)棧初始化的代碼。后續(xù)也會陸續(xù)推出更多的分享文章,敬請期待。
51CTO和華為官方合作共建的鴻蒙技術(shù)社區(qū)

















 
 
 





 
 
 
 