鸿蒙轻内核M核源码分析系列六 任务及任务调度(1)任务栈

往期知识点记录:

继续分析鸿蒙轻内核源码,我们本文开始要分析下任务及任务调度模块。首先,我们介绍下任务栈的基础概念。任务栈是高地址向低地址生长的递减栈,栈指针指向即将入栈的元素位置。初始化后未使用过的栈空间初始化的内容为宏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寄存器初始化为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、 任务进入退出函数

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

小结

本文带领大家一起学习了鸿蒙轻内核的任务栈、任务上下文的基础概念,剖析了任务栈初始化的代码。

写在最后

如果你觉得这篇内容对你还蛮有帮助,我想邀请你帮我三个小忙

  • 关注小编,同时可以期待后续文章ing🚀,不定期分享原创知识。
  • 想要获取更多完整鸿蒙最新学习资源,请看下图提示:
相关推荐
康康这名还挺多34 分钟前
鸿蒙HarmonyOS list优化一: list 结合 lazyforeach用法
数据结构·list·harmonyos·lazyforeach
欢乐熊嵌入式编程2 小时前
智能手表固件升级 OTA 策略文档初稿
嵌入式硬件·学习·智能手表
欢乐熊嵌入式编程2 小时前
智能手表 MCU 任务调度图
单片机·嵌入式硬件·智能手表
【云轩】2 小时前
电机密集型工厂环境下的无线通信技术选型与优化策略
经验分享·嵌入式硬件
sword devil9003 小时前
将arduino开发的Marlin部署到stm32(3D打印机驱动)
stm32·单片机·嵌入式硬件
GodKK老神灭3 小时前
STM32 变量存储
stm32·单片机·嵌入式硬件
木宁kk3 小时前
51单片机引脚功能概述
单片机·嵌入式硬件
JANYI20183 小时前
嵌入式MCU和Linux开发哪个好?
linux·单片机·嵌入式硬件
晚秋大魔王4 小时前
OpenHarmony 开源鸿蒙南向开发——linux下使用make交叉编译第三方库——nettle库
linux·开源·harmonyos
sword devil9005 小时前
Arduino快速入门
stm32·单片机·嵌入式硬件