关于iot:LiteOS内核源码分析任务栈信息

38次阅读

共计 6383 个字符,预计需要花费 16 分钟才能阅读完成。

摘要:LiteOS 工作栈是高地址向低地址成长的递加栈,栈指针指向行将入栈的元素地位。

本文分享自华为云社区《LiteOS 内核源码剖析系列六 - 工作及调度(2)- 工作 LOS_Task》,原文作者:zhushy。

咱们介绍下 LiteOS 工作栈的根底概念。LiteOS 工作栈是高地址向低地址成长的递加栈,栈指针指向行将入栈的元素地位。初始化后未应用过的栈空间初始化的内容为宏 OS_STACK_INIT 代表的数值 0xCACACACA,栈顶初始化为宏 OS_STACK_MAGIC_WORD 代表的数值 0xCCCCCCCC。一个工作栈的示意图如下,其中,栈底指针是栈的最大的内存地址,栈顶指针,是栈的最小的内存地址,栈指针从栈底向栈顶方向成长。

1、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)

2、LOS_StackInfo 工作栈反对的操作

2.1 工作栈初始化

栈初始化函数 VOID OsStackInit()应用 2 个参数,一个是栈顶指针 VOID *stacktop,一个是初始化的栈的大小。把栈内容初始化为 OS_STACK_INIT,把栈顶初始化为 OS_STACK_MAGIC_WORD。

该函数被 archarmcortex_msrctask.c 的 OsTaskStackInit(UINT32 taskId, UINT32 stackSize, VOID topStack) 办法调用,进一步被创立工作时的 OsTaskCreateOnly()办法调用,实现新创建工作的工作栈初始化。

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。

该函数须要 3 个参数,UINTPTR stackBottom 是栈底指针,const UINTPTR stackTop 栈顶指针,UINT32 *peakUsed 用于返回获取的水线值,即工作栈应用的最大值。

⑴处代码示意如果 *stackTop == OS_STACK_MAGIC_WORD,阐明栈没有被溢出毁坏,从栈顶开始栈内容被写满宏 OS_STACK_INIT 的局部是没有应用过的栈空间。应用 tmp 指针变量顺次向栈底方向减少,判断栈是否被应用过,while 循环完结,栈指针 tmp 指向最大的未应用过的栈地址。⑵处代码应用栈底指针 stackBottom 减去 tmp,获取最大的应用过的栈空间大小,即须要的水线。⑶处如果栈顶溢出,则返回有效值 OS_INVALID_WATERLINE。

该函数被 kernelbaselos_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;
    }
}

3、LOS_Task 工作栈初始化

咱们以 AArch32 Cortex- M 核为例,分析下工作栈初始化的过程,相干代码散布在 archarmcortex_mincludearchtask.h、archarmcortex_msrctask.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),该函数定义在文件 archarmcortex_msrctask.c。函数代码里调用__disable_irq()关中断,而后进入死循环。该函数实践上不会被执行,疏忽即可。

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

3.4 LOS_Task 工作进入函数

在初始化上下文的时候,PC 寄存器设置的是函数 VOID OsTaskEntry(UINT32 taskId),该函数定义在文件 kernelbaselos_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);
    }
}

点击关注,第一工夫理解华为云陈腐技术~

正文完
 0