LiteOS內核源碼分析:任務棧信息

摘要:LiteOS任務棧是高地址向低地址生長的遞減棧,棧指針指向即將入棧的元素位置。

本文分享自華爲雲社區《LiteOS內核源碼分析系列六 -任務及調度(2)-任務LOS_Task》,原文做者:zhushy 。shell

咱們介紹下LiteOS任務棧的基礎概念。LiteOS任務棧是高地址向低地址生長的遞減棧,棧指針指向即將入棧的元素位置。初始化後未使用過的棧空間初始化的內容爲宏OS_STACK_INIT表明的數值0xCACACACA,棧頂初始化爲宏OS_STACK_MAGIC_WORD表明的數值0xCCCCCCCC。一個任務棧的示意圖以下,其中,棧底指針是棧的最大的內存地址,棧頂指針,是棧的最小的內存地址,棧指針從棧底向棧頂方向生長。函數

一、 LOS_StackInfo任務棧結構體定義

typedef struct {
    VOID *stackTop;     // 棧頂指針
    UINT32 stackSize;   // 棧大小
    CHAR *stackName;    // 棧名稱
} StackInfo;

另外定義了一個宏函數OS_STACK_MAGIC_CHECK(topstack)用於檢測棧是否有效,當棧頂等於OS_STACK_MAGIC_WORD棧是正常的,沒有溢出,不然棧頂被改寫,發生棧溢出。源碼分析

/* 1:有效正常的棧 0:無效,發生溢出的棧 */
#define OS_STACK_MAGIC_CHECK(topstack) (*(UINTPTR *)(topstack) == OS_STACK_MAGIC_WORD)

二、 LOS_StackInfo任務棧支持的操做

2.1 任務棧初始化

棧初始化函數VOID OsStackInit()使用2個參數,一個是棧頂指針VOID *stacktop,一個是初始化的棧的大小。把棧內容初始化爲OS_STACK_INIT,把棧頂初始化爲OS_STACK_MAGIC_WORD。url

該函數被arch\arm\cortex_m\src\task.c的*OsTaskStackInit(UINT32 taskId, UINT32 stackSize, VOID *topStack)方法調用,進一步被建立任務時的OsTaskCreateOnly()方法調用,完成新建立任務的任務棧初始化。spa

VOID OsStackInit(VOID *stacktop, UINT32 stacksize)
{
     (VOID)memset_s(stacktop, stacksize, (INT32)OS_STACK_INIT, stacksize);
    *((UINTPTR *)stacktop) = OS_STACK_MAGIC_WORD;
}

2.2 獲取任務棧水線

隨着任務棧入棧、出棧,當前棧使用的大小不必定是最大值,OsStackWaterLineGet()能夠獲取的棧使用的最大值即水線WaterLine。.net

該函數須要3個參數,UINTPTR *stackBottom是棧底指針,const UINTPTR *stackTop棧頂指針,UINT32 *peakUsed用於返回獲取的水線值,即任務棧使用的最大值。指針

⑴處代碼表示若是*stackTop == OS_STACK_MAGIC_WORD,說明棧沒有被溢出破壞,從棧頂開始棧內容被寫滿宏OS_STACK_INIT的部分是沒有使用過的棧空間。使用tmp指針變量依次向棧底方向增長,判斷棧是否被使用過,while循環結束,棧指針tmp指向最大的未使用過的棧地址。⑵處代碼使用棧底指針stackBottom減去tmp,獲取最大的使用過的棧空間大小,即須要的水線。⑶處若是棧頂溢出,則返回無效值OS_INVALID_WATERLINE。code

該函數被kernel\base\los_task.c中的函數LOS_TaskInfoGet(UINT32 taskId, TSK_INFO_S *taskInfo)調用,獲取任務的信息。在shell模塊也會使用來或者棧信息。blog

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

三、 LOS_Task任務棧初始化

咱們以AArch32 Cortex-M核爲例,剖析下任務棧初始化的過程,相關代碼分佈在arch\arm\cortex_m\include\arch\task.h、arch\arm\cortex_m\src\task.c。首先看下任務上下文。內存

3.1 TaskContext上下文結構體定義

任務上下文(Task Context)指的是任務運行的環境,例如包括程序計數器、堆棧指針、通用寄存器等內容。在多任務調度中,任務上下文切換(Task Context Switching)屬於核心內容,是多個任務運行在同一CPU核上的基礎。LiteOS內核中,上下文的結構體定義以下:

typedef struct tagContext {
#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 R4;
    UINT32 R5;
    UINT32 R6;
    UINT32 R7;
    UINT32 R8;
    UINT32 R9;
    UINT32 R10;
    UINT32 R11;
    UINT32 PriMask;
    UINT32 R0;
    UINT32 R1;
    UINT32 R2;
    UINT32 R3;
    UINT32 R12;
    UINT32 LR;
    UINT32 PC;
    UINT32 xPSR;
#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;

3.2 LOS_Task任務棧初始化

上文中提到在建立任務的時候,會使用VOID *OsTaskStackInit()函數初始化任務棧。咱們分析下函數代碼,它須要3個參數,UINT32 taskId待建立任務的編號,UINT32 stackSize任務棧的大小,VOID *topStack任務棧的棧頂指針。

⑴處代碼調用OsStackInit()函數初始化棧,初始化棧內容和棧頂爲魔術字。⑵處代碼獲取任務上下文的指針地址TaskContext *taskContext,棧的底部大小爲sizeof(TaskContext)的棧空間存放上下文的數據。⑶處若是支持浮點數計算,須要初始化浮點數相關的寄存器。⑷初始化通用寄存器,其中LR初始化爲(UINT32)OsTaskExit,PC初始化爲(UINT32)OsTaskEntry,CPU首次執行該任務時運行的第一條指令的位置,這2個函數下文會分析。⑸處返回值是指針(VOID *)taskContext,這個就是任務初始化後的棧指針,注意不是從棧底開始了,棧底保存的是上下文,棧指針要減去上下文佔用的棧大小。

在棧中,從TaskContext *taskContext指針增長的方向,依次保存上下文結構體的第一個成員,第二個成員…另外,初始化棧的時候,除了特殊的幾個寄存器,不一樣寄存器的初始值沒有什麼意義,也有些初始化的規律。好比R2寄存器初始化爲0x02020202L,R12寄存器初始化爲0x12121212L初始化的內容和寄存器編號有關聯,其他相似。

LITE_OS_SEC_TEXT_INIT VOID *OsTaskStackInit(UINT32 taskId, UINT32 stackSize, VOID *topStack)
{
    TaskContext *taskContext = NULL;

⑴  OsStackInit(topStack, stackSize);
⑵  taskContext = (TaskContext *)(((UINTPTR)topStack + stackSize) - sizeof(TaskContext));

#if ((defined (__FPU_PRESENT) && (__FPU_PRESENT == 1U)) && \
⑶  (defined (__FPU_USED) && (__FPU_USED == 1U)))
    taskContext->S16 = 0xAA000010;
    taskContext->S17 = 0xAA000011;
    taskContext->S18 = 0xAA000012;
    taskContext->S19 = 0xAA000013;
    taskContext->S20 = 0xAA000014;
    taskContext->S21 = 0xAA000015;
    taskContext->S22 = 0xAA000016;
    taskContext->S23 = 0xAA000017;
    taskContext->S24 = 0xAA000018;
    taskContext->S25 = 0xAA000019;
    taskContext->S26 = 0xAA00001A;
    taskContext->S27 = 0xAA00001B;
    taskContext->S28 = 0xAA00001C;
    taskContext->S29 = 0xAA00001D;
    taskContext->S30 = 0xAA00001E;
    taskContext->S31 = 0xAA00001F;
    taskContext->S0  = 0xAA000000;
    taskContext->S1  = 0xAA000001;
    taskContext->S2  = 0xAA000002;
    taskContext->S3  = 0xAA000003;
    taskContext->S4  = 0xAA000004;
    taskContext->S5  = 0xAA000005;
    taskContext->S6  = 0xAA000006;
    taskContext->S7  = 0xAA000007;
    taskContext->S8  = 0xAA000008;
    taskContext->S9  = 0xAA000009;
    taskContext->S10 = 0xAA00000A;
    taskContext->S11 = 0xAA00000B;
    taskContext->S12 = 0xAA00000C;
    taskContext->S13 = 0xAA00000D;
    taskContext->S14 = 0xAA00000E;
    taskContext->S15 = 0xAA00000F;
    taskContext->FPSCR = 0x00000000;
    taskContext->NO_NAME = 0xAA000011;
#endif

⑷  taskContext->R4  = 0x04040404L;
    taskContext->R5  = 0x05050505L;
    taskContext->R6  = 0x06060606L;
    taskContext->R7  = 0x07070707L;
    taskContext->R8  = 0x08080808L;
    taskContext->R9  = 0x09090909L;
    taskContext->R10 = 0x10101010L;
    taskContext->R11 = 0x11111111L;
    taskContext->PriMask = 0;
    taskContext->R0  = taskId;
    taskContext->R1  = 0x01010101L;
    taskContext->R2  = 0x02020202L;
    taskContext->R3  = 0x03030303L;
    taskContext->R12 = 0x12121212L;
    taskContext->LR  = (UINT32)OsTaskExit;
    taskContext->PC  = (UINT32)OsTaskEntry;
    taskContext->xPSR = 0x01000000L;

⑸  return (VOID *)taskContext;
}

3.3 OsTaskExit()函數

在初始化上下文的時候,連接寄存器設置的是函數VOID OsTaskExit(VOID),該函數定義在文件arch\arm\cortex_m\src\task.c。函數代碼裏調用__disable_irq()關中斷,而後進入死循環。該函數理論上不會被執行,忽略便可。

LITE_OS_SEC_TEXT_MINOR VOID OsTaskExit(VOID)
{
    __disable_irq();
    while (1) { }
}

3.4 LOS_Task任務進入函數

在初始化上下文的時候,PC寄存器設置的是函數VOID OsTaskEntry(UINT32 taskId),該函數定義在文件kernel\base\los_task.c,咱們來分析下源代碼,⑴處釋聽任務的自旋鎖,開中斷。而後執行⑵處代碼獲取taskCB,並調用任務的入口函數。等任務執行完畢後,檢查taskCB->taskFlags是否設置爲自刪除標記OS_TASK_FLAG_DETACHED,若是是則刪除任務。

LITE_OS_SEC_TEXT_INIT VOID OsTaskEntry(UINT32 taskId)
{
    LosTaskCB *taskCB = NULL;
    VOID *ret = NULL;

    LOS_ASSERT(OS_TSK_GET_INDEX(taskId) < g_taskMaxNum);

⑴  LOS_SpinUnlock(&g_taskSpin);
    (VOID)LOS_IntUnLock();

⑵  taskCB = OS_TCB_FROM_TID(taskId);

#ifdef LOSCFG_OBSOLETE_API
    ret = taskCB->taskEntry(taskCB->args[0], taskCB->args[1], taskCB->args[2],
        taskCB->args[3]); /* 0~3: just for args array index */
#else
    ret = taskCB->taskEntry(taskCB->args);
#endif

⑶  if (OsTaskDeleteCheckDetached(taskCB)) {
        OsTaskDeleteDetached(taskCB);
    } else {
        OsTaskDeleteJoined(taskCB, ret);
    }
}

 

點擊關注,第一時間瞭解華爲雲新鮮技術~

相關文章
相關標籤/搜索