鸿蒙内核源码分析(任务切换篇) | 看汇编如何切换任务

在鸿蒙的内核线程就是任务,系列篇中说的任务和线程当一个东西去理解.

一般二种场景下需要切换任务上下文:

  • 在线程环境下,从当前线程切换到目标线程,这种方式也称为软切换,能由软件控制的自主式切换.哪些情况下会出现软切换呢?

    • 运行的线程申请某种资源(比如各种锁,读/写消息队列)失败时,需要主动释放CPU的控制权,将自己挂入等待队列,调度算法重新调度新任务运行.
    • 每隔10ms就执行一次的OsTickHandler节拍处理函数,检测到任务的时间片用完了,就发起任务的重新调度,切换到新任务运行.
    • 不管是内核态的任务还是用户态的任务,于切换而言是统一处理,一视同仁的,因为切换是需要换栈运行,寄存器有限,需要频繁的复用,这就需要将当前寄存器值先保存到任务自己的栈中,以便别人用完了轮到自己再用时恢复寄存器当时的值,确保老任务还能继续跑下去. 而保存寄存器顺序的结构体叫:任务上下文(TaskContext).
  • 在中断环境下,从当前线程切换到目标线程,这种方式也称为硬切换.不由软件控制的被动式切换.哪些情况下会出现硬切换呢?

    • 由硬件产生的中断,比如 鼠标,键盘外部设备每次点击和敲打,屏幕的触摸,USB的插拔等等这些都是硬中断.同样的需要切换栈运行,需要复用寄存器,但与软切换不一样的是,硬切换会切换工作模式(中断模式).所以会更复杂点,但道理还是一样要保存和恢复切换现场寄存器的值, 而保存寄存器顺序的结构体叫:任务中断上下文(TaskIrqContext).

本篇具体说清楚以下几个问题:

  • 任务上下文(TaskContext)怎么保存的?
  • 代码的实现细节是怎样的?
  • 如何保证切换不会发生错误,指令不会丢失?

前置条件

一个任务要跑起来,需要两个必不可少的硬性条件:

  • 1.从代码段哪个位置取指令? 也就是入口地址,main函数是应用程序的入口地址, 注意main函数也是一个线程,只是不需要你来new而已,加载程序阶段会默认创建好. run()是new一个线程执行的入口地址.高级语言是这么叫,但到了汇编层的叫法就是PC寄存器.给PC寄存器赋什么值,指令就从哪里开始执行.

  • 2.运行的场地(栈空间)在哪里? ARM有7种工作模式,到了进程层面只需要考虑内核模式和用户模式两种,对应到任务会有内核态栈空间和用户态栈空间.内核模式的任务只有内核态的栈空间,用户模式任务二者都有.栈空间是在初始化一个任务时就分配指定好的.以下是两种栈空间的初始化过程.为了精练省去了部分代码,留下了核心部分.

//任务控制块中对两个栈空间的描述
typedef struct {VOID            *stackPointer;      /**< Task stack pointer */  //内核态栈指针,SP位置,切换任务时先保存上下文并指向TaskContext位置.UINT32          stackSize;          /**< Task stack size */     //内核态栈大小UINTPTR         topOfStack;         /**< Task stack top */      //内核态栈顶 bottom = top + size// ....UINTPTR         userArea;       //使用区域,由运行时划定,根据运行态不同而不同UINTPTR         userMapBase;    //用户态下的栈底位置UINT32          userMapSize;    /**< user thread stack size ,real size : userMapSize + USER_STACK_MIN_SIZE */
} LosTaskCB;    

//内核态运行栈初始化
LITE_OS_SEC_TEXT_INIT VOID *OsTaskStackInit(UINT32 taskID, UINT32 stackSize, VOID *topStack, BOOL initFlag)
{UINT32 index = 1;TaskContext *taskContext = NULL;taskContext = (TaskContext *)(((UINTPTR)topStack + stackSize) - sizeof(TaskContext));//上下文存放在栈的底部/* initialize the task context */ //初始化任务上下文taskContext->PC = (UINTPTR)OsTaskEntry;//程序计数器,CPU首次执行task时跑的第一条指令位置taskContext->LR = (UINTPTR)OsTaskExit;  /* LR should be kept, to distinguish it's THUMB or ARM instruction */taskContext->resved = 0x0;taskContext->R[0] = taskID;             /* R0 */taskContext->R[index++] = 0x01010101;   /* R1, 0x01010101 : reg initialed magic word */ //0x55for (; index < GEN_REGS_NUM; index++) {//R2 - R12的初始化很有意思taskContext->R[index] = taskContext->R[index - 1] + taskContext->R[1]; /* R2 - R12 */}taskContext->regPSR = PSR_MODE_SVC_ARM;   /* CPSR (Enable IRQ and FIQ interrupts, ARM-mode) */return (VOID *)taskContext;
}

//用户态运行栈初始化
LITE_OS_SEC_TEXT_INIT VOID OsUserTaskStackInit(TaskContext *context, TSK_ENTRY_FUNC taskEntry, UINTPTR stack)
{context->regPSR = PSR_MODE_USR_ARM;//工作模式:用户模式 + 工作状态:armcontext->R[0] = stack;//栈指针给r0寄存器context->SP = TRUNCATE(stack, LOSCFG_STACK_POINT_ALIGN_SIZE);//给SP寄存器值使用context->LR = 0;//保存子程序返回地址 例如 a call b ,在b中保存 a地址context->PC = (UINTPTR)taskEntry;//入口函数
}

您一定注意到了TaskContext,说的全是它,这就是任务上下文结构体,理解它是理解任务切换的钥匙.它不仅在C语言层面出现,而且还在汇编层出现,TaskContext是连接或者说打通 C->汇编->C 实现任务切换的最关键概念.本篇全是围绕着它来展开.先看看它张啥样,LOOK!

TaskContext 任务上下文

typedef struct {
#if !defined(LOSCFG_ARCH_FPU_DISABLE)UINT64 D[FP_REGS_NUM]; /* D0-D31 */UINT32 regFPSCR;       /* FPSCR */UINT32 regFPEXC;       /* FPEXC */
#endifUINT32 resved;          /* It's stack 8 aligned */UINT32 regPSR;          UINT32 R[GEN_REGS_NUM]; /* R0-R12 */UINT32 SP;              /* R13 */UINT32 LR;              /* R14 */UINT32 PC;              /* R15 */
} TaskContext;
  • 结构很简单,目的更简单,就是用来保存寄存器现场的值的. 到了汇编层就是寄存器在玩,当CPU工作在用户和系统模式下时寄存器是复用的,玩的是17个寄存器和内存地址,访问内存地址也是通过寄存器来玩.

  • 哪17个? R0~R15和CPSR. 当调度(主动式)或者中断(被动式)发生时.将这17个寄存器压入任务的内核栈的过程叫保护案发现场.从任务栈中弹出依次填入寄存器的过程叫恢复案发现场.

  • 从栈空间的具体哪个位置开始恢复呢? 答案是:stackPointer,任务控制块(LosTaskCB)的首个变量.对应到汇编层的就是SP寄存器.

  • TaskContext(任务上下文)就是一定的顺序来保存和恢复这17个寄存器的.任务上下文在任务还没有开始执行的时候就已经保存在内核栈中了,只不过是一些默认的值,OsTaskStackInit干的就是这个默认的事. 而OsUserTaskStackInit是对用户栈的初始化,改变的是(CPSR)工作模式和SP寄存器.

  • 新任务的运行栈指针(stackPointer)给SP寄存器意味着切换了运行栈,这是本篇最重要的一句话.

以下通过汇编代码逐行分析如何保存和恢复TaskContext(任务上下文)

OsSchedResched 调度算法

//调度算法的实现
VOID OsSchedResched(VOID)
{// ...此处省去 .../* do the task context switch */OsTaskSchedule(newTask, runTask);//切换任务上下文,注意OsTaskSchedule是一个汇编函数 见于 los_dispatch.s
}
  • 本篇要完成整个分析过程.OsTaskSchedule实现了任务的上下文切换,汇编代码见于los_dispatch.S中
  • OsTaskSchedule的参数指向的是新老两个任务,这两个参数分别保存在R0,R1寄存器中.

OsTaskSchedule 汇编实现

读这段汇编代码一定要对照上面的TaskContext,不然很难看懂,容易懵圈,但对照着看就秒懂.

/** R0: new task * R1: run task*/
OsTaskSchedule:	/*任务调度,OsTaskSchedule的目的是将寄存器值按TaskContext的格式保存起来*/MRS      R2, CPSR 	/*MRS 指令用于将特殊寄存器(如 CPSR 和 SPSR)中的数据传递给通用寄存器,要读取特殊寄存器的数据只能使用 MRS 指令*/STMFD    SP!, {LR}	/*返回地址入栈,LR = PC-4 ,对应TaskContext->PC(R15寄存器)*/STMFD    SP!, {LR}	/*再次入栈对应,对应TaskContext->LR(R14寄存器)*//* jump sp */SUB      SP, SP, #4 /* 跳的目的是为了,对应TaskContext->SP(R13寄存器)*//* push r0-r12*/STMFD    SP!, {R0-R12} 	 @对应TaskContext->R[GEN_REGS_NUM](R0~R12寄存器)。STMFD    SP!, {R2}		/*R2 入栈 对应TaskContext->regPSR*//* 8 bytes stack align */SUB      SP, SP, #4		@栈对齐,对应TaskContext->resved/* save fpu registers */PUSH_FPU_REGS   R2	/*保存fpu寄存器*//* store sp on running task */STR     SP, [R1] @在运行的任务栈中保存SP,即runTask->stackPointer = spOsTaskContextLoad: @加载上下文/* clear the flag of ldrex */ @LDREX 可从内存加载数据,如果物理地址有共享TLB属性,则LDREX会将该物理地址标记为由当前处理器独占访问,并且会清除该处理器对其他任何物理地址的任何独占访问标记。CLREX @清除ldrex指令的标记/* switch to new task's sp */LDR     SP, [R0] @ 即:sp =  task->stackPointer/* restore fpu registers */POP_FPU_REGS    R2 @恢复fpu寄存器,这里用了汇编宏R2是宏的参数/* 8 bytes stack align */ADD     SP, SP, #4 @栈对齐LDMFD   SP!, {R0}  @此时SP!位置保存的是CPSR的内容,弹出到R0MOV     R4, R0	@R4=R0,将CPSR保存在r4, 将在OsKernelTaskLoad中保存到SPSR AND     R0, R0, #CPSR_MASK_MODE @R0 =R0&CPSR_MASK_MODE ,目的是清除高16位CMP     R0, #CPSR_USER_MODE @R0 和 用户模式比较BNE     OsKernelTaskLoad @非用户模式则跳转到OsKernelTaskLoad执行,跳出/*此处省去 LOSCFG_KERNEL_SMP 部分*/MVN     R3, #CPSR_INT_DISABLE @按位取反 R3 = 0x3FAND     R4, R4, R3		@使能中断MSR     SPSR_cxsf, R4	@修改spsr值/* restore r0-r12, lr */LDMFD   SP!, {R0-R12}	@恢复寄存器值LDMFD   SP, {R13, R14}^	@恢复SP和LR的值,注意此时SP值已经变了,CPU换地方上班了.ADD     SP, SP, #(2 * 4)@sp = sp + 8LDMFD   SP!, {PC}^		@恢复PC寄存器值,如此一来 SP和PC都有了新值,完成了上下文切换.完美!
OsKernelTaskLoad: 			@内核任务的加载MSR     SPSR_cxsf, R4 	@将R4整个写入到程序状态保存寄存器/* restore r0-r12, lr */LDMFD   SP!, {R0-R12} 	@出栈,依次保存到 R0-R12,其实就是恢复现场ADD     SP, SP, #4 		@sp=SP+4LDMFD   SP!, {LR, PC}^ 	@返回地址赋给pc指针,直接跳出.

解读

  • 汇编分成了三段 OsTaskSchedule, OsTaskContextLoad, OsKernelTaskLoad.

  • 第一段OsTaskSchedule其实就是在保存现场.代码都有注释,对照着TaskContext来的,它就干了一件事把17个寄存器的值按TaskContext的格式入栈,因为鸿蒙用栈方式采用的是满栈递减的方式,所以存放顺序是从最后一个往前依次入栈.

  • 连着来两句STMFD SP!, {LR}之前让笔者懵圈了很久, 看了TaskContext才恍然大悟,因为三级流水线的原因,LR和PC寄存器之间是差了一条指令的,LR指向了处于译码阶段指令,而PC指向了取指阶段的指令,所以此处做了两次LR入栈,其实是保存了未执行的译码指令地址,确保执行不会丢失一条指令.

  • R1是正在运行的任务栈, OsTaskSchedule总的理解是在任务R1的运行栈中插入一个TaskContext结构块.而STR SP, [R1],是改变了LosTaskCB->stackPointer的值,这个值只能在汇编层进行精准的改变,而在整个鸿蒙内核C代码层面都没有看到对它有任何修改的地方.这个改变意义极为重要.因为新的任务被调度后的第一件事情就是恢复现场!!!

  • OsTaskSchedule执行完成后,因为PC寄存器并没有发生跳转,所以紧接着往下执行OsTaskContextLoad

  • OsTaskContextLoad的任务就是恢复现场,谁的现场?当然是R0: new task的,所以第一条指令就是CLREX,清除干净后立马执行LDR SP, [R0],所指向的就是LosTaskCB->stackPointer,这个位置存的是新任务的TaskContext结构块,是上一次R0任务被打断时保存下来当时这17个寄存器的值啊,依次出栈就是恢复这17个寄存器的值.

  • OsTaskContextLoad在开始之前会判断下工作模式,即判断下是内核栈还是用户栈,两种处理方式稍有不同.但都是在恢复现场.

  • BNE OsKernelTaskLoad是查询CPSR后判断此时为内核栈的现场恢复过程,代码很简单就是恢复17个寄存器. 如此一来,任务执行的两个条件,第一个SP的在LDR SP, [R0]时就有了.第二个条件:PC寄存器的值也在最后一条汇编LDMFD SP!, {LR, PC}^ 也已经有了.改变了PC和LR有了新值,下一条指令位置一样是上次任务被中断时还没被执行的处于译码阶段的指令地址.

  • 如果是用户态区别是需要恢复中断.因为用户模式的优先级是最低的,必须允许响应中断,也是依次恢复各寄存器的值,最后一句LDMFD SP!, {PC}^结束本次旅行,下一条指令位置一样是上次任务被中断时还没被执行的处于译码阶段的指令地址.

  • 如此,说清楚了任务上下文切换的整个过程,初看可能不太容易理解,建议多看几篇,用笔画下栈的运行过程,脑海中会很清晰的浮现出整个切换过程的运行图.

鸿蒙全栈开发全新学习指南

也为了积极培养鸿蒙生态人才,让大家都能学习到鸿蒙开发最新的技术,针对一些在职人员、0基础小白、应届生/计算机专业、鸿蒙爱好者等人群,整理了一套纯血版鸿蒙(HarmonyOS Next)全栈开发技术的学习路线【包含了大厂APP实战项目开发】

本路线共分为四个阶段:

第一阶段:鸿蒙初中级开发必备技能

第二阶段:鸿蒙南北双向高工技能基础:gitee.com/MNxiaona/733GH

第三阶段:应用开发中高级就业技术

第四阶段:全网首发-工业级南向设备开发就业技术:https://gitee.com/MNxiaona/733GH

《鸿蒙 (Harmony OS)开发学习手册》(共计892页)

如何快速入门?

1.基本概念
2.构建第一个ArkTS应用
3.……

开发基础知识:gitee.com/MNxiaona/733GH

1.应用基础知识
2.配置文件
3.应用数据管理
4.应用安全管理
5.应用隐私保护
6.三方应用调用管控机制
7.资源分类与访问
8.学习ArkTS语言
9.……

基于ArkTS 开发

1.Ability开发
2.UI开发
3.公共事件与通知
4.窗口管理
5.媒体
6.安全
7.网络与链接
8.电话服务
9.数据管理
10.后台任务(Background Task)管理
11.设备管理
12.设备使用信息统计
13.DFX
14.国际化开发
15.折叠屏系列
16.……

鸿蒙开发面试真题(含参考答案):gitee.com/MNxiaona/733GH

鸿蒙入门教学视频:

美团APP实战开发教学:gitee.com/MNxiaona/733GH

写在最后

  • 如果你觉得这篇内容对你还蛮有帮助,我想邀请你帮我三个小忙:
  • 点赞,转发,有你们的 『点赞和评论』,才是我创造的动力。
  • 关注小编,同时可以期待后续文章ing🚀,不定期分享原创知识。
  • 想要获取更多完整鸿蒙最新学习资源,请移步前往小编:gitee.com/MNxiaona/733GH

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

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

相关文章

优雅的实现接口统一调用!

有些时候我们在进行接口调用的时候&#xff0c;比如说一个 push 推送接口&#xff0c;有可能会涉及到不同渠道的推送。 比如做结算后端服务的&#xff0c;会与金蝶财务系统进行交互&#xff0c;那么我结算后端会涉及到多个结算单类型&#xff0c;如果每一个种类型的结算单都去…

Java基础教程 - 4 流程控制

更好的阅读体验&#xff1a;点这里 &#xff08; www.doubibiji.com &#xff09; 更好的阅读体验&#xff1a;点这里 &#xff08; www.doubibiji.com &#xff09; 更好的阅读体验&#xff1a;点这里 &#xff08; www.doubibiji.com &#xff09; 4 流程控制 4.1 分支结构…

新型中医揿针如何降血糖呢?

点击文末领取揿针的视频教程跟直播讲解 “新型针贴”专用揿针是为“埋针疗法”特制治的一种特殊针具&#xff0c;它是古代针刺留针方法的发展。具体来说&#xff0c;它是将特制针具刺入皮内&#xff0c;固定后留置一定时间&#xff0c;利用其持续微弱的刺激作用来治疗疾病的一…

JSP企业快信系统的设计与实现参考论文(论文 + 源码)

【免费】JSP企业快信系统.zip资源-CSDN文库https://download.csdn.net/download/JW_559/89277688 JSP企业快信系统的设计与实现 摘 要 计算机网络的出现到现在已经经历了翻天覆地的重大改变。因特网也从最早的供科学家交流心得的简单的文本浏览器发展成为了商务和信息的中心…

一文了解栈

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、栈是什么&#xff1f;二、栈的实现思路1.顺序表实现2.单链表实现3.双向链表实现 三、接口函数的实现1.栈的定义2.栈的初始化3.栈的销毁4.入栈5.出栈6.返回栈…

Mybatis-Plus快速上手

依赖 <dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.4.3</version> </dependency> <dependency><groupId>mysql</groupId><artifactId&g…

Git系列:git push (-u) 与 git branch (-u)

&#x1f49d;&#x1f49d;&#x1f49d;欢迎莅临我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:「stormsha的主页」…

景源畅信:个人抖音小店怎么开通?

在数字时代的浪潮中&#xff0c;个体创业已不再是遥不可及的梦想。特别是随着短视频平台的崛起&#xff0c;抖音不仅成为人们娱乐消遣的新宠&#xff0c;更是众多创业者眼中的“新大陆”。你是否也曾憧憬过在抖音上开一家属于自己的小店?那么&#xff0c;如何开通个人抖音小店…

ISIS的基本配置

1.IS-IS协议的基本配置&#xff08;1&#xff09; 2.IS-IS协议的基本配置&#xff08;2&#xff09; 3.IS-IS协议的基本配置&#xff08;3&#xff09; 4.案例&#xff1a;IS-IS配置 R1的配置如下&#xff1a; [AR1czy]isis 1 [AR1czy-isis-1]is-level level-1 [AR1czy-isis-…

矩阵的压缩存储介绍

引入 概述 特殊矩阵的压缩 对称矩阵 三角矩阵 对角矩阵 稀疏矩阵 三元组存储 十字链表法 示例

通过 Java 操作 redis -- String 基本命令

关于 redis String 类型的相关命令推荐看 Redis - String 字符串 要想通过 Java 操作 redis&#xff0c;首先要连接上 redis 服务器&#xff0c;推荐看通过 Java 操作 redis -- 连接 redis 本博客只介绍了一小部分常用的命令&#xff0c;其他的命令根据上面推荐的博客也能很简单…

计算图:深度学习中的链式求导与反向传播引擎

在深度学习的世界中&#xff0c;计算图扮演着至关重要的角色。它不仅是数学计算的图形化表示&#xff0c;更是链式求导与反向传播算法的核心。本文将深入探讨计算图的基本概念、与链式求导的紧密关系及其在反向传播中的应用&#xff0c;旨在为读者提供一个全面而深入的理解。 计…

深度学习之基于Matlab卷积神经网络验证码识别系统

欢迎大家点赞、收藏、关注、评论啦 &#xff0c;由于篇幅有限&#xff0c;只展示了部分核心代码。 文章目录 一项目简介 二、功能三、系统四. 总结 一项目简介 一、项目背景 随着互联网的发展&#xff0c;验证码作为一种常用的安全验证手段&#xff0c;被广泛应用于各种网站和…

基于FPGA的AD7705芯片驱动设计VHDL代码Quartus仿真

名称&#xff1a; 软件&#xff1a;Quartus基于FPGA的AD7705芯片驱动设计VHDL代码Quartus仿真&#xff08;文末获取&#xff09; 语言&#xff1a;VHDL 代码功能&#xff1a; AD77025芯片控制及串口输出 1、使用FPGA控制AD77025芯片&#xff0c;使其输出AD值 2、将数据计…

多长时间能学成黑客,达到找漏洞赚钱的地步?

想要成为黑客&#xff0c;要学的东西有很多很多简单罗列一些基本的吧 1、SQL注入 了解SQL注入发生原理&#xff0c;熟悉掌握sqlmap工具&#xff0c;学会手工注入2、暴力破解 懂得利用burpsuite等软件进行暴力破解3、XSS 学会XSS三种攻击方式&#xff1a;反射型、存储型、dom型…

独立开发,做的页面不好看?我总结了一些工具与方法

前言 我有时候会自己开发一些项目,但是不比在公司里面,自己开发项目的时候没有设计稿,所以做出来的页面比较难看。 开发了几个项目之后,我也总结了以下的一些画页面的资源或者方法,希望对大家有帮助~ 颜色&字体 这一部分主要参考的是antd的方案,主要包括颜色与字…

Python数据爬取超简单入门

## 什么是网络爬虫&#xff1f; 网络爬虫是一种自动浏览器程序&#xff0c;能够自动地从互联网获取数据。爬虫的主要任务是访问网页&#xff0c;分析网页内容&#xff0c;然后提取所需的信息。爬虫广泛应用于数据收集、数据分析、网页内容监控等领域。 ## 爬虫的基本步骤 1.…

25考研英语长难句Day02

25考研英语长难句Day02 【a.词组】【b.断句】 如果你是你讲话对象中的一员&#xff0c;你就能了解你们大家共同的经历和问题&#xff0c;你也可以顺便评论一下食堂里难吃的食物或董事长臭名昭著的领带品味。 【a.词组】 单词解释addressv. 演说&#xff0c; 演讲&#xff1b;…

微生物组的生物合成基因簇(BGCs)分析

Introduction 天然产物&#xff08;natural product&#xff0c;NP&#xff09;是指生物体内的组成成分或其代谢产物&#xff0c;具有广泛的应用价值。 其中&#xff0c;来源于微生物的次级代谢产物&#xff0c;在生物医学、工业和农业中扮演着重要角色[1]。 生物合成基因簇&…

发电机组远程管理,提升管控力,降低运维成本

发电机组是指发电机发动机以及控制系统的总称&#xff0c;用来把发动机提供的动能转化为电能。它通常由动力系统、控制系统、消音系统、减震系统、排气系统组成。发电机组远程管理系统利用物联网技术与PLC远程控制模块集成解决方案&#xff0c;在提高发电机组的运行效率、降低运…