鴻蒙輕內核M核源碼分析系列六 任務及任務調度(1)任務棧

目錄:git

一、 TaskContext上下文結構體定義shell

二、任務棧相關函數函數

三、任務進入退出函數源碼分析

四、小結post

繼續分析鴻蒙輕內核源碼,咱們本文開始要分析下任務及任務調度模塊。首先,咱們介紹下任務棧的基礎概念。任務棧是高地址向低地址生長的遞減棧,棧指針指向即將入棧的元素位置。初始化後未使用過的棧空間初始化的內容爲宏OS_TASK_STACK_INIT表明的數值0xCACACACA,棧頂初始化爲宏OS_TASK_MAGIC_WORD表明的數值0xCCCCCCCC。一個任務棧的示意圖以下,其中,棧底指針是棧的最大的內存地址,棧頂指針,是棧的最小的內存地址,棧指針從棧底向棧頂方向生長。學習

LOS_STACK.png

任務上下文(Task Context)是任務及任務調度模塊的另一個重要的概念,它指的是任務運行的環境,例如包括程序計數器、堆棧指針、通用寄存器等內容。在多任務調度中,任務上下文切換(Task Context Switching)屬於核心內容,是多個任務運行在同一CPU核上的基礎。在任務調度時,保存退出運行狀態的任務使用的寄存器信息到任務棧,還會從進入運行狀態的任務的棧中讀取上下文信息,恢復寄存器信息。url

下面,咱們剖析下任務棧、任務棧初始化的源代碼,若涉及開發板部分,以開發板工程targets\cortex-m7_nucleo_f767zi_gcc\爲例進行源碼分析。首先,看下任務上下文結構體。spa

一、 TaskContext上下文結構體定義

在文件kernel\arch\arm\cortex-m7\gcc\los_arch_context.h中,定義的上下文的結構體以下,主要是浮點寄存器,通用寄存器。.net

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.1 任務棧初始化函數

在文件kernel\arch\arm\cortex-m7\gcc\los_context.c中定義了任務棧初始化函數VOID *HalTskStackInit(t()。該函數被文件kernel\src\los_task.c中的函數UINT32 OsNewTaskInit()調用完成任務初始化,並進一步在建立任務函數UINT32 LOS_TaskCreateOnly()中調用,完成新建立任務的任務棧初始化。指針

該函數使用3個參數,一個是任務編號UINT32 taskID,一個是初始化的棧的大小UINT32 stackSize,第3個參數是棧頂指針VOID *topStack。⑴處代碼把棧內容初始化爲OS_TASK_STACK_INIT,⑵處把棧頂初始化爲OS_TASK_MAGIC_WORD

⑶處代碼獲取任務上下文的指針地址TaskContext *context。對於新建立任務,從棧的底部開始,大小爲sizeof(TaskContext)的棧空間存放上下文的數據。⑷處若是支持浮點數計算,須要初始化浮點數相關的寄存器。⑸初始化通用寄存器,其中.uwLR初始化爲(UINT32)(UINTPTR)HalSysExit.uwPC初始化爲(UINT32)(UINTPTR)OsTaskEntry,這是CPU首次執行該任務時運行的第一條指令的位置。這2個函數下文會分析。

⑹處返回值是指針(VOID *)taskContext,這個就是任務初始化後的棧指針,注意不是從棧底開始了,棧底保存的是上下文,棧指針要減去上下文佔用的棧大小。在棧中,從TaskContext *context指針增長的方向,依次保存上下文結構體的第一個成員,第二個成員…另外,初始化棧的時候,除了特殊的幾個寄存器,不一樣寄存器的初始值雖然沒有什麼意義,也有些初始化的規律。好比R2寄存器初始化爲0x02020202LR12寄存器初始化爲0x12121212L初始化的內容和寄存器編號有關聯,其他相似。

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 獲取任務棧水線函數

隨着任務棧入棧、出棧,當前棧使用的大小不必定是最大值,UINT32 OsGetTaskWaterLine(UINT32 taskID)能夠獲取的棧使用的最大值即水線WaterLine。該函數定義在文件kernel\src\los_task.c,它須要1個參數,即UINT32 taskID任務編號,返回值UINT32 peakUsed表示獲取的水線值,即任務棧使用的最大值。

咱們詳細看下代碼,⑴處代碼表示若是棧頂等於設置的魔術字,說明棧沒有被溢出破壞,從棧頂開始棧內容被寫滿宏OS_TASK_STACK_INIT的部分是沒有使用過的棧空間。使用臨時棧指針stackPtr指針變量依次向棧底方向增長,判斷棧是否被使用過,while循環結束,棧指針stackPtr指向最大的未使用過的棧地址。⑵處代碼獲取最大的使用過的棧空間大小,即須要的水線。⑶處若是棧頂溢出,則返回無效值OS_NULL_INT

該函數被kernel\base\los_task.c中的函數LOS_TaskInfoGet(UINT32 taskId, TSK_INFO_S *taskInfo)調用,獲取任務的信息。在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.一、任務退出函數

在初始化上下文的時候,連接寄存器設置的是函數(UINT32)(UINTPTR)HalSysExit,該函數定義在文件kernel\src\los_task.c。函數代碼裏調用LOS_IntLock()關中斷,而後進入死循環。在任務正常調度期間,該函數理論上不會被執行。在系統異常時,主動調用LOS_Panic()c觸發異常時,也會調用該函數。

LITE_OS_SEC_TEXT_MINOR VOID HalSysExit(VOID) { LOS_IntLock(); while (1) { } } 

3.二、任務進入函數

在初始化上下文的時候,PC寄存器設置的是函數VOID OsTaskEntry(UINT32 taskId),該函數定義在文件kernel\base\los_task.c,咱們來分析下源代碼,⑴處代碼獲取taskCB,而後執行⑵調用任務的入口函數。等任務執行完畢後,執行⑶刪除任務。一般任務入口執行函數都是while循環,任務不執行時,會調度到其餘任務或者空閒任務,不會執行到刪除任務階段。

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); } } 

小結

本文帶領你們一塊兒學習了鴻蒙輕內核的任務棧、任務上下文的基礎概念,剖析了任務棧初始化的代碼。後續也會陸續推出更多的分享文章,敬請期待,也歡迎你們分享學習、使用鴻蒙輕內核的心得,有任何問題、建議,均可以留言給咱們: https://gitee.com/openharmony/kernel_liteos_m/issues 。爲了更容易找到鴻蒙輕內核代碼倉,建議訪問 https://gitee.com/openharmony/kernel_liteos_m ,關注Watch、點贊Star、並Fork到本身帳戶下,謝謝。

 

做者:zhushangyuan_

想了解更多內容,請訪問51CTO和華爲合做共建的鴻蒙社區:https://harmonyos.51cto.com

相關文章
相關標籤/搜索