摘要:本文率领大家一起学习了鸿蒙轻内核的工作栈、工作上下文的根底概念,分析了工作栈初始化的代码。

本文分享自华为云社区《鸿蒙轻内核M核源码剖析系列七 工作及任务调度(1)工作栈》,原文作者:zhushy 。

咱们本文开始要剖析下工作及任务调度模块。首先,咱们介绍下工作栈的根底概念。工作栈是高地址向低地址成长的递加栈,栈指针指向行将入栈的元素地位。初始化后未应用过的栈空间初始化的内容为宏OS_TASK_STACK_INIT代表的数值0xCACACACA,栈顶初始化为宏OS_TASK_MAGIC_WORD代表的数值0xCCCCCCCC。一个工作栈的示意图如下,其中,栈底指针是栈的最大的内存地址,栈顶指针,是栈的最小的内存地址,栈指针从栈底向栈顶方向成长。

工作上下文(Task Context)是工作及任务调度模块的另外一个重要的概念,它指的是工作运行的环境,例如包含程序计数器、堆栈指针、通用寄存器等内容。在多任务调度中,工作上下文切换(Task Context Switching)属于核心内容,是多个工作运行在同一CPU核上的根底。在任务调度时,保留退出运行状态的工作应用的寄存器信息到工作栈,还会从进入运行状态的工作的栈中读取上下文信息,复原寄存器信息。

上面,咱们分析下工作栈、工作栈初始化的源代码,若波及开发板局部,以开发板工程targets\cortex-m7_nucleo_f767zi_gcc\为例进行源码剖析。首先,看下工作上下文构造体。

1、 TaskContext上下文构造体定义

在文件kernel\arch\arm\cortex-m7\gcc\los_arch_context.h中,定义的上下文的构造体如下,次要是浮点寄存器,通用寄存器。

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、 工作栈相干函数

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寄存器初始化为0x02020202L,R12寄存器初始化为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、 工作进入退出函数

3.1、工作退出函数

在初始化上下文的时候,链接寄存器设置的是函数(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.2、工作进入函数

在初始化上下文的时候,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... 。为了更容易找到鸿蒙轻内核代码仓,倡议拜访 https://gitee.com/openharmony... ,关注Watch、点赞Star、并Fork到本人账户下,谢谢。

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