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

本文分享自华为云社区《鸿蒙轻内核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;
#endifUINT32 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);}
}

小结

本文带领大家一起学习了鸿蒙轻内核的任务栈、任务上下文的基础概念,剖析了任务栈初始化的代码。后续也会陆续推出更多的分享文章,敬请期待,也欢迎大家分享学习、使用鸿蒙轻内核的心得,有任何问题、建议,都可以留言给我们: https://gitee.com/openharmony/kernel_liteos_m/issues 。为了更容易找到鸿蒙轻内核代码仓,建议访问 https://gitee.com/openharmony/kernel_liteos_m ,关注Watch、点赞Star、并Fork到自己账户下,谢谢。

点击关注,第一时间了解华为云新鲜技术~

带你剖析鸿蒙轻内核任务栈的源代码相关推荐

  1. 深层剖析鸿蒙轻内核M核的动态内存如何支持多段非连续性内存

    摘要:鸿蒙轻内核M核新增支持了多段非连续性内存区域,把多个非连续性内存逻辑上合一,用户不感知底层的不同内存块. 本文分享自华为云社区<鸿蒙轻内核M核源码分析系列九 动态内存Dynamic Mem ...

  2. 事件Event:带你体验鸿蒙轻内核中一对多、多对多任务同步

    摘要:本文通过分析鸿蒙轻内核事件模块的源码,深入掌握事件的使用. 本文分享自华为云社区<鸿蒙轻内核M核源码分析系列十二 事件Event>,原文作者:zhushy . 事件(Event)是一 ...

  3. 从五大结构体,带你掌握鸿蒙轻内核动态内存Dynamic Memory

    摘要:本文带领大家一起剖析了鸿蒙轻内核的动态内存模块的源代码,包含动态内存的结构体.动态内存池初始化.动态内存申请.释放等. 本文分享自华为云社区<鸿蒙轻内核M核源码分析系列九 动态内存Dyna ...

  4. 带你熟悉鸿蒙轻内核Kconfig使用指南

    摘要:本文介绍了Kconfig的基础知识,和鸿蒙轻内核的图形化配置及进阶的使用方法. 本文分享自华为云社区<鸿蒙轻内核Kconfig使用笔记>,作者: zhushy. 1. Kconfig ...

  5. 鸿蒙轻内核源码分析:掌握信号量使用差异

    摘要:本文带领大家一起剖析鸿蒙轻内核的信号量模块的源代码,包含信号量的结构体.信号量池初始化.信号量创建删除.申请释放等. 本文分享自华为云社区<鸿蒙轻内核M核源码分析系列十一 信号量Semap ...

  6. 互斥锁Mutex:鸿蒙轻内核中处理临界资源独占的“法官”

    摘要:本文带领大家一起剖析鸿蒙轻内核的互斥锁模块的源代码,包含互斥锁的结构体.互斥锁池初始化.互斥锁创建删除.申请释放等. 本文分享自华为云社区<鸿蒙轻内核M核源码分析系列十 互斥锁Mutex& ...

  7. MPU:鸿蒙轻内核的任务栈的溢出检察官

    摘要:MPU(Memory Protection Unit,内存保护单元)把内存映射为一系列内存区域,定义这些内存区域的维洲,大小,访问权限和内存熟悉信息. 本文分享自华为云社区<鸿蒙轻内核M核 ...

  8. 鸿蒙轻内核的得力助手:带你掌握4种内存调试方法

    摘要:内存调测方法旨在辅助定位动态内存相关问题,提供了内存池信息统计.内存泄漏检测和踩内存检测三种调测手段. 本文分享自华为云社区<鸿蒙轻内核-内存调测-内存信息统计>,作者:zhushy ...

  9. 鸿蒙轻内核M核的故障管家:Fault异常处理

    摘要:本文先简单介绍下Fault异常类型,向量表及其代码,异常处理C语言程序,然后详细分析下异常处理汇编函数实现代码. 本文分享自华为云社区<鸿蒙轻内核M核源码分析系列十八 Fault异常处理& ...

最新文章

  1. linux zipinfo 命令详解
  2. SAP CRM Fiori应用My Task的前台优化
  3. 单位矩阵的逆矩阵是它本身吗_矩阵运算、单位矩阵与逆矩阵(二)
  4. [剑指offer]面试题第[58]题[Leetcode][JAVA][第151题][翻转单词][字符串常用函数总结]
  5. fastadmin绑定edit.html,FAST-ADMIN 根据生成命令行修改页面
  6. 微博返回顶部按钮实现方法大剧透
  7. shell脚本使用getopts自定义传入参数选项
  8. RBF神经网络MATLAB代码实现
  9. MSDTC不可用解决办法
  10. 电脑显示网络计算机和设备不可见,win10系统网络发现已关闭看不到网络计算机和设备的解决方法...
  11. Android 免root 备份数据,真正免root的完美备份详细使用教程
  12. 腾达路由器怎么设置网站服务服务器,腾达(Tenda)-无线路由器如何设置上网?
  13. 境外诈骗,最新来自senegal的邮件诈骗
  14. “落子无悔,抉择本身就是向前”——2022年度总结
  15. 动态修改spine动画渲染层次
  16. python可视化之matplotlib绘图--蜘蛛侠钢铁侠(组合分区绘图)
  17. 名茶事典——【贡(寿)眉】
  18. Windows10 U盘无法格式化怎么办?U盘拒绝访问?
  19. Android中如何使用代码打开各种类型的文件
  20. 数据传输完整性_基于IBIS模型的FPGA信号完整性仿真验证方法

热门文章

  1. Bootstrap3 带提示的进度条
  2. bat随机打开目录下的一个音乐文件_Tomcat bin目录详解
  3. 第二十二课php注入,php第二十三节课
  4. php 变量 长度,php 怎么计算字符串长度
  5. Tomcat系列(6)——Tomcat处理一个HTTP请求的过程
  6. 【Linux】awk指令
  7. 2018.12-2019.1 TO-DO LIST
  8. requirejs 多页面,多js 打包代码,requirejs多对多打包
  9. Java 判断是否包含指定的子串 contains()
  10. 总结—angularjs项目