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

news2024/9/24 7:25:30

往期知识点记录:

  • 鸿蒙(HarmonyOS)应用层开发(北向)知识点汇总
  • 持续更新中……

继续分析鸿蒙轻内核源码,我们本文开始要分析下任务及任务调度模块。首先,我们介绍下任务栈的基础概念。任务栈是高地址向低地址生长的递减栈,栈指针指向即将入栈的元素位置。初始化后未使用过的栈空间初始化的内容为宏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🚀,不定期分享原创知识。
  • 想要获取更多完整鸿蒙最新学习资源,请看下图提示:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2110628.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SpringBoot整合Minio及阿里云OSS(配置文件无缝切换)

SpringBoot整合Minio及阿里云OSS 文章目录 SpringBoot整合Minio及阿里云OSS1.Minio安装测试1.Docker安装启动容器 2.创建bucket3.上传文件修改权限 2.SpringBoot整合Minio及阿里云OSS1.公共部分抽取2.Minio配置整合1.添加pom依赖2.添加配置文件3.操作接口实现 3.阿里云OSS配置整…

Class4——Esp32|Thonny两种方式同过电脑控制LED灯,路由器与电脑自带热点连接ESP32

上一节我们通过路由器和设备创建了连接&#xff0c;不懂可按上节配置 Class3——Esp32|Thonny——网络连接主机-wifi连接&#xff08;源代码带教程&#xff09;-CSDN博客文章浏览阅读57次。Esp32|Thonny网络连接主机-wifi连接&#xff08;源代码带教程&#xff09;https://blo…

免费开源的低代码表单FormCreate安装教程,支持可视化设计,适配移动端

低代码表单FormCreate 是一个可以通过 JSON 生成具有动态渲染、数据收集、验证和提交功能的表单生成组件。它支持 6 个 UI 框架&#xff0c;适配移动端&#xff0c;并且支持生成任何 Vue 组件。内置 20 种常用表单组件和自定义组件&#xff0c;再复杂的表单都可以轻松搞定 源码…

网页时装购物:Spring Boot框架的创新应用

第2章相关技术 2.1 B/S架构 B/S结构的特点也非常多&#xff0c;例如在很多浏览器中都可以做出信号请求。并且可以适当的减轻用户的工作量&#xff0c;通过对客户端安装或者是配置少量的运行软件就能够逐步减少用户的工作量&#xff0c;这些功能的操作主要是由服务器来进行控制的…

时尚购物革命:Spring Boot技术在网页时装系统中的应用

第1章 绪论 1.1背景及意义 随着社会的快速发展&#xff0c;计算机的影响是全面且深入的。人们生活水平的不断提高&#xff0c;日常生活中人们对时装购物系统方面的要求也在不断提高&#xff0c;喜欢购物的人数更是不断增加&#xff0c;使得时装购物系统的开发成为必需而且紧迫的…

Rspack 1.0 发布了!

文章来源&#xff5c;Rspack Team 项目地址&#xff5c;https://github.com/web-infra-dev/rspack Rspack 是基于 Rust 编写的下一代 JavaScript 打包工具&#xff0c; 兼容 webpack 的 API 和生态&#xff0c;并提供 10 倍于 webpack 的构建性能。 在 18 个月前&#xff0c;我…

深度学习 --- VGG16能让某个指定的feature map激活值最大化图片的可视化(JupyterNotebook实战)

VGG16能让某个指定的feature map激活值最大化图片的可视化 在前面的文章中&#xff0c;我用jupyter notebook分别实现了&#xff0c;预训练好的VGG16模型各层filter权重的可视化和给VGG16输入了一张图像&#xff0c;可视化VGG16各层的feature map。深度学习 --- VGG16卷积核的可…

说一下场外的伦敦银交易的技巧

在很多讨论伦敦银交易技巧的文章中&#xff0c;一上来就介绍各种交易指标、K线信号等等&#xff0c;这种开门见山的方式很直接也很方便&#xff0c;但也容易忽略了一些场外的技巧&#xff0c;下面我们就来讨论一下场外的关于伦敦银交易的技巧。 何为场外的技巧呢&#xff1f;场…

Java进阶13讲__第十讲__精简

字节流 字节输入流&#xff1a;FileInputStream&#xff08;原始流/低级流&#xff09; 字节缓冲流&#xff1a;BufferedInputStream&#xff08;包装流/处理流&#xff09; 参数是"低级流" 字节输入流/缓冲流常用格式 byte[] arr new byte[1024];//字节流 int l…

灯塔:MYSQL笔记(2)函数

函数 是指一段可以直接被另一段程序调用的程序或代码。 字符串函数 SELECT 函数(参数); 数值函数 SELECT 函数(参数); -- 生成一个六位验证码 select lpad(round(rand()*1000000,0) ,6,0)as 验证码; 日期函数 流程函数 总结&#xff1a; 约束&#xff1a; 1. 概述&#xff…

vim 安装与配置教程(详细教程)

vim就是一个功能非常强大的文本编辑器&#xff0c;可以自己DIY的那种 &#xff0c;不但可以写代码 &#xff0c;还可编译 &#xff0c;可以让你手不离键盘的完成鼠标的所有操作。 如果想要了解vim的的发展历史和详细解说&#xff0c;可以自行上网搜索&#xff0c;我主要是记录一…

第T10周:数据增强

>- **&#x1f368; 本文为[&#x1f517;365天深度学习训练营](https://mp.weixin.qq.com/s/0dvHCaOoFnW8SCp3JpzKxg) 中的学习记录博客** >- **&#x1f356; 原作者&#xff1a;[K同学啊](https://mtyjkh.blog.csdn.net/)** 在本教程中&#xff0c;你将学会如何进行数…

【NLP自然语言处理】文本处理的基本方法

目录 &#x1f354;什么是分词 &#x1f354;中文分词工具jieba 2.1 jieba的基本特点 2.2 jieba的功能 2.3 jieba的安装及使用 &#x1f354;什么是命名实体识别 &#x1f354;什么是词性标注 &#x1f354;小结 学习目标 &#x1f340; 了解什么是分词, 词性标注, 命名…

Java笔试面试题AI答之JDBC(3)

文章目录 13. 编写JDBC连Oracle的程序?14. 简述JDBC的主要组件有哪些 &#xff1f;15. JDBC中如何防止SQL注入攻击&#xff1f;1. 使用预处理语句&#xff08;PreparedStatement&#xff09;2. 避免在SQL查询中直接拼接用户输入的数据总结 16. JDBC的脏读是什么&#xff1f;哪…

Windows下Python和PyCharm的应用(一)__第一个测试程序

1、下载Python安装包 直接从官网下载&#xff0c;百度里搜出来的Python下载&#xff0c;很多是别的公司的商业广告&#xff0c;千万要注意&#xff0c;不要乱点进去&#xff0c;免得浪费时间。 从官网下载&#xff0c;链接&#xff1a;Download Python | Python.org 2、安装Pyt…

网络编程day03(网络体系结构、调试命令、TCP/IP对比)

目录 1》网络的体系结构 1> OSI模型 2> TCP/IP模型 3> 常见网络协议 4> DNS域名解析协议 2》 网络调试命令 1> ping&#xff1a;测试网络连通性&#xff08;ICMP&#xff09; 2> netstat 3》Dos &#xff08;拒绝式服务&#xff09;攻击&#xff1f;…

怎么在mathtype中打空格 MathType空格键不能用

MathType是一款数学公式编辑器&#xff0c;可以帮助用户创建复杂的数学公式和方程式。它提供了一个用户友好的界面&#xff0c;使得编辑和排版数学公式变得更加容易和高效。用户可以直接在其界面中输入公式&#xff0c;也可以将已有的公式从其他文档中复制粘贴过来进行编辑。在…

【2024数模国赛赛题思路公开】国赛B题第二套思路丨附可运行代码丨无偿自提

2024年数模国赛B题解题思路 B 题 生产过程中的决策问题 一、问题1解析 问题1的任务是为企业设计一个合理的抽样检测方案&#xff0c;基于少量样本推断整批零配件的次品率&#xff0c;帮助企业决定是否接收供应商提供的这批零配件。具体来说&#xff0c;企业需要依据两个不同…

秋燥拜拜,中秋润起来,酒茶香中秋有“礼”

话说这初秋啊&#xff0c;真是个让人又爱又恨的季节&#xff01; 爱它的秋高气爽&#xff0c;恨它的天干物燥。就像是我们刚刚结束了一个炎热的夏天&#xff0c;身体还没来得及适应&#xff0c;就被秋天的干燥给来了个“突然袭击”。鼻子干、嘴唇干、喉咙干&#xff0c;感觉整个…

hcip什么时候考试?一文带您了解hcip考试报名与预约流程

其他考试一般都会有固定的时间&#xff0c;但hcip不一样&#xff0c;它的考试时间并不固定&#xff0c;这就让考生很是疑惑&#xff1a;hcip什么时候考试呢?除了知道考试时间之外&#xff0c;还要了解hcip的报名条件、报名流程等相关内容。关于这些问题的答案&#xff0c;小编…