您的位置:首页 > 文旅 > 旅游 > 鸿蒙内核源码分析(中断切换篇) | 系统因中断活力四射

鸿蒙内核源码分析(中断切换篇) | 系统因中断活力四射

2024/10/10 7:23:17 来源:https://blog.csdn.net/u012165769/article/details/141367842  浏览:    关键词:鸿蒙内核源码分析(中断切换篇) | 系统因中断活力四射

关于中断部分系列篇将用三篇详细说明整个过程.

  • 中断概念篇 中断概念很多,比如中断控制器,中断源,中断向量,中断共享,中断处理程序等等.本篇做一次整理.先了解透概念才好理解中断过程.用海公公打比方说明白中断各个概念.

  • 中断管理篇 从中断初始化HalIrqInit开始,到注册中断的LOS_HwiCreate函数,到消费中断函数的 HalIrqHandler,剖析鸿蒙内核实现中断的过程,很像设计模式中的观察者模式.

  • 中断切换篇(本篇) 用自下而上的方式,从中断源头纯汇编代码往上跟踪代码细节.说清楚保存和恢复中断现场TaskIrqContext过程.

中断环境下的任务切换

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

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

  • 在中断环境下,从当前线程切换到目标线程,这种方式也称为硬切换.它们通常由硬件产生或是软件发生异常时的被动式切换.哪些情况下会出现硬切换呢?

    • 中断源可分外部和内部中断源两大类,比如 鼠标,键盘外部设备每次点击和敲打,屏幕的触摸,USB的插拔等等这些都是外部中断源.存储器越限、缺页,核间中断,断点中断等等属于内部中断源.由此产生的硬切换都需要换栈运行,硬切换硬在需切换工作模式(中断模式).所以会比线程环境下的切换更复杂点,但道理还是一样要保存和恢复切换现场寄存器的值, 而保存寄存器顺序格式结构体叫:任务中断上下文(TaskIrqContext).
  • 在线程环境下,从当前线程切换到目标线程,这种方式也称为软切换,能由软件控制的自主式切换.哪些情况下会出现软切换呢?

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

本篇说清楚在中断环境下切换(硬切换)的实现过程.

ARM的七种工作模式中,有两个是和中断相关.

  • 普通中断模式(irq):一般中断模式也叫普通中断模式,用于处理一般的中断请求,通常在硬件产生中断信号之后自动进入该模式,该模式可以自由访问系统硬件资源。
  • 快速中断模式(fiq):快速中断模式是相对一般中断模式而言的,用来处理高优先级中断的模式,处理对时间要求比较紧急的中断请求,主要用于高速数据传输及通道处理中。

此处分析普通中断模式下的任务切换过程.

普通中断模式相关寄存器

这张图一定要刻在脑海里,系列篇会多次拿出来,目的是为了能牢记它.

  • 普通中断模式(图中IRQ列)是一种异常模式,有自己独立运行的栈空间.一个(IRQ)中断发生后,硬件会将CPSR寄存器工作模式置为IRQ模式.并跳转到入口地址OsIrqHandler执行.
#define OS_EXC_IRQ_STACK_SIZE    64 //中断模式栈大小 64个字节
__irq_stack:.space OS_EXC_IRQ_STACK_SIZE * CORE_NUM
__irq_stack_top:
  • OsIrqHandler汇编代码实现过程,就干了三件事:
    • 1.保存任务中断上下文TaskIrqContext
    • 2.执行中断处理程序HalIrqHandler,这是个C函数,由汇编调用
    • 3.恢复任务中断上下文TaskIrqContext,返回被中断的任务继续执行

TaskIrqContext 和 TaskContext

先看本篇结构体 TaskIrqContext

#define TASK_IRQ_CONTEXT \unsigned int R0;     \unsigned int R1;     \unsigned int R2;     \unsigned int R3;     \unsigned int R12;    \unsigned int USP;    \unsigned int ULR;    \unsigned int CPSR;   \unsigned int PC;typedef struct {//任务中断上下文
#if !defined(LOSCFG_ARCH_FPU_DISABLE)UINT64 D[FP_REGS_NUM]; /* D0-D31 */UINT32 regFPSCR;       /* FPSCR */UINT32 regFPEXC;       /* FPEXC */
#endifUINT32 resved;TASK_IRQ_CONTEXT
} TaskIrqContext;

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;          //保存CPSR寄存器UINT32 R[GEN_REGS_NUM]; /* R0-R12 */UINT32 SP;              /* R13 */UINT32 LR;              /* R14 */UINT32 PC;              /* R15 */
} TaskContext;
  • 两个结构体很简单,目的更简单,就是用来保存寄存器现场的值的. TaskContext把17个寄存器全部保存了,TaskIrqContext保存的少些,在栈中并没有保存R4-R11寄存器的值,这说明在整个中断处理过程中,都不会用到R4-R11寄存器.不会用到就不会改变,当然就没必要保存了.这也说明内核开发者的严谨程度,不造成时间和空间上的一丁点浪费.效率的提升是从细节处入手的,每个小地方优化那么一丢丢,整体性能就上来了.
  • TaskIrqContext中有两个变量有点奇怪 unsigned int USP; unsigned int ULR; 指的是用户模式下的SP和LR值, 这个要怎么理解? 因为对一个正运行的任务而言,中断的到来是颗不定时炸弹,无法预知,也无法提前准备,中断一来它立即被打断,压根没有时间去保存现场到自己的栈中,那保存工作只能是放在IRQ栈或者SVC栈中.而IRQ栈非常的小,只有64个字节,16个栈空间,指望不上了,就保存在SVC栈中,SVC模式栈可是有 8K空间的.
  • 从接下来的 OsIrqHandler代码中可以看出,鸿蒙内核整个中断的工作其实都是在SVC模式下完成的,而irq的栈只是个过渡栈.具体看汇编代码逐行注解分析.

普通中断处理程序

OsIrqHandler:	@硬中断处理,此时已切换到硬中断栈SUB     LR, LR, #4	@记录译码指令地址,以防切换过程丢失指令/* push r0-r3 to irq stack */ @irq栈只是个过渡栈STMFD   SP, {R0-R3}		@r0-r3寄存器入 irq 栈SUB     R0, SP, #(4 * 4)@r0 = sp - 16,目的是记录{R0-R3}4个寄存器保存的开始位置,届时从R3开始出栈MRS     R1, SPSR		@获取程序状态控制寄存器MOV     R2, LR			@r2=lr/* disable irq, switch to svc mode */@超级用户模式(SVC 模式),主要用于 SWI(软件中断)和 OS(操作系统)。CPSID   i, #0x13				@切换到SVC模式,此处一切换,后续指令将在SVC栈运行@CPSID i为关中断指令,对应的是CPSIE@TaskIrqContext 开始保存中断现场 ......							/* push spsr and pc in svc stack */STMFD   SP!, {R1, R2} @实际是将 SPSR,和PC入栈对应TaskIrqContext.PC,TaskIrqContext.CPSR,STMFD   SP, {LR}	  @LR再入栈,SP不自增,如果是用户模式,LR值将被 282行:STMFD   SP, {R13, R14}^覆盖  @如果非用户模式,将被 286行:SUB     SP, SP, #(2 * 4) 跳过.AND     R3, R1, #CPSR_MASK_MODE	@获取CPU的运行模式CMP     R3, #CPSR_USER_MODE		@中断是否发生在用户模式BNE     OsIrqFromKernel			@非用户模式不用将USP,ULR保存在TaskIrqContext/* push user sp, lr in svc stack */STMFD   SP, {R13, R14}^ 		@将用户模式的sp和LR入svc栈OsIrqFromKernel:	@从内核发起中断/* from svc not need save sp and lr */@svc模式下发生的中断不需要保存sp和lr寄存器值SUB     SP, SP, #(2 * 4)	@目的是为了留白给 TaskIrqContext.USP,TaskIrqContext.ULR@TaskIrqContext.ULR已经在 276行保存了,276行用的是SP而不是SP!,所以此处要跳2个空间/* pop r0-r3 from irq stack*/LDMFD   R0, {R0-R3}		    @从R0位置依次出栈 /* push caller saved regs as trashed regs in svc stack */STMFD   SP!, {R0-R3, R12}	@寄存器入栈,对应 TaskIrqContext.R0~R3,R12/* 8 bytes stack align */SUB     SP, SP, #4			@栈对齐 对应TaskIrqContext.resved/** save fpu regs in case in case those been* altered in interrupt handlers.*/PUSH_FPU_REGS   R0 @保存fpu regs,以防中断处理程序中的fpu regs被修改。@TaskIrqContext 结束保存中断现场......	@开始执行真正的中断处理函数了.
#ifdef LOSCFG_IRQ_USE_STANDALONE_STACK @是否使用了独立的IRQ栈PUSH    {R4}	@R4先入栈保存,接下来要切换栈,需保存现场MOV     R4, SP	@R4=SPEXC_SP_SET __svc_stack_top, OS_EXC_SVC_STACK_SIZE, R1, R2 @切换到svc栈
#endif/*BLX 带链接和状态切换的跳转*/BLX     HalIrqHandler /* 调用硬中断处理程序,无参 ,说明HalIrqHandler在svc栈中执行 */#ifdef LOSCFG_IRQ_USE_STANDALONE_STACK @是否使用了独立的IRQ栈MOV     SP, R4	@恢复现场,sp = R4 POP     {R4}	@弹出R4
#endif/* process pending signals */ 	@处理挂起信号BL      OsTaskProcSignal 		@跳转至C代码 /* check if needs to schedule */@检查是否需要调度CMP     R0, #0	@是否需要调度,R0为参数保存值BLNE    OsSchedPreempt @不相等,即R0非0,一般是 1MOV     R0,SP	@参数MOV     R1,R7	@参数BL      OsSaveSignalContextIrq @跳转至C代码 /* restore fpu regs */POP_FPU_REGS    R0 @恢复fpu寄存器值ADD     SP, SP, #4 @sp = sp + 4 OsIrqContextRestore:	@恢复硬中断环境LDR     R0, [SP, #(4 * 7)]	@R0 = sp + 7,目的是跳到恢复中断现场TaskIrqContext.CPSR位置,刚好是TaskIrqContext倒数第7的位置.MSR     SPSR_cxsf, R0		@恢复spsr 即:spsr = TaskIrqContext.CPSRAND     R0, R0, #CPSR_MASK_MODE @掩码找出当前工作模式CMP     R0, #CPSR_USER_MODE	@是否为用户模式?@TaskIrqContext 开始恢复中断现场 ......	LDMFD   SP!, {R0-R3, R12}	@从SP位置依次出栈 对应 TaskIrqContext.R0~R3,R12@此时已经恢复了5个寄存器,接来下是TaskIrqContext.USP,TaskIrqContext.ULRBNE     OsIrqContextRestoreToKernel @看非用户模式,怎么恢复中断现场./* load user sp and lr, and jump cpsr */LDMFD   SP, {R13, R14}^ @出栈,恢复用户模式sp和lr值 即:TaskIrqContext.USP,TaskIrqContext.ULRADD     SP, SP, #(3 * 4) @跳3个位置,跳过 CPSR ,因为上一句不是 SP!,所以跳3个位置,刚好到了保存TaskIrqContext.PC的位置/* return to user mode */LDMFD   SP!, {PC}^ @回到用户模式,整个中断过程完成@TaskIrqContext 结束恢复中断现场(用户模式下) ......	OsIrqContextRestoreToKernel:@从内核恢复中断/* svc mode not load sp */ADD     SP, SP, #4 @其实是跳过TaskIrqContext.USP,因为在内核模式下并没有保存这个值,翻看 287行LDMFD   SP!, {LR} @弹出LR/* jump cpsr and return to svc mode */ADD     SP, SP, #4 @跳过cpsrLDMFD   SP!, {PC}^ @回到svc模式,整个中断过程完成@TaskIrqContext 结束恢复中断现场(内核模式下) ......

逐句解读

  • 跳转到 OsIrqFromKernel硬件会自动切换到__irq_stack执行
  • 1句:SUB LR, LR, #4 在arm执行过程中一般分为取指,译码,执行阶段,而PC是指向取指,正在执行的指令为 PC-8 ,译码指令为PC-4.当中断发生时硬件自动执行 mov lr pc, 中间的PC-4译码指令因为没有寄存器去记录它,就会被丢失掉.所以SUB LR, LR, #4 的结果是lr = PC -4 ,定位到了被中断时译码指令,将在栈中保存这个位置,确保回来后能继续执行.
  • 2句:STMFD SP, {R0-R3} 当前4个寄存器入__irq_stack保存
  • 3句:SUB R0, SP, #(4 * 4) 因为SP没有自增,R0跳到保存R0内容地址
  • 4,5句:读取SPSR,LR寄存器内容,目的是为了后面在SVC栈中保存TaskIrqContext
  • 6句:CPSID i, #0x13禁止中断和切换SVC模式,执行完这条指令后工作模式将切到 SVC模式
  • @TaskIrqContext 开始保存中断现场 …
  • 中间代码需配合TaskIrqContext来看,不然100%懵逼.结合看就秒懂,代码都已经注释,不再做解释,注解中提到的 翻看276行 是指源码的第276行,请对照注解源码看理解会更透彻.
  • @TaskIrqContext 结束保存中断现场 …
  • TaskIrqContext保存完现场后就真正的开始处理中断了.
	/*BLX 带链接和状态切换的跳转*/BLX     HalIrqHandler /* 调用硬中断处理程序,无参 ,说明HalIrqHandler在svc栈中执行 */
#ifdef LOSCFG_IRQ_USE_STANDALONE_STACK @是否使用了独立的IRQ栈MOV     SP, R4	@恢复现场,sp = R4 POP     {R4}	@弹出R4
#endif/* process pending signals */ 	@处理挂起信号BL      OsTaskProcSignal 		@跳转至C代码 /* check if needs to schedule */@检查是否需要调度CMP     R0, #0	@是否需要调度,R0为参数保存值BLNE    OsSchedPreempt @不相等,即R0非0,一般是 1MOV     R0,SP	@参数MOV     R1,R7	@参数BL      OsSaveSignalContextIrq @跳转至C代码 /* restore fpu regs */POP_FPU_REGS    R0 @恢复fpu寄存器值ADD     SP, SP, #4 @sp = sp + 4 
  • 这段代码都是跳转到C语言去执行,分别是 HalIrqHandler OsTaskProcSignal OsSchedPreempt OsSaveSignalContextIrq C语言部分内容很多,将在中断管理篇中说明.

  • @TaskIrqContext 开始恢复中断现场 …

  • 同样的中间代码需配合TaskIrqContext来看,不然100%懵逼.结合看就秒懂,代码都已经注释,不再做解释,注解中提到的 翻看287行 是指源码的第287行,请对照注解源码看理解会更透彻.进入源码注解地址查看

  • @TaskIrqContext 结束恢复中断现场 …

经常有很多小伙伴抱怨说:不知道学习鸿蒙开发哪些技术?不知道需要重点掌握哪些鸿蒙应用开发知识点?

为了能够帮助到大家能够有规划的学习,这里特别整理了一套纯血版鸿蒙(HarmonyOS Next)全栈开发技术的学习路线,包含了鸿蒙开发必掌握的核心知识要点,内容有(ArkTS、ArkUI开发组件、Stage模型、多端部署、分布式应用开发、WebGL、元服务、OpenHarmony多媒体技术、Napi组件、OpenHarmony内核、OpenHarmony驱动开发、系统定制移植等等)鸿蒙(HarmonyOS NEXT)技术知识点。

在这里插入图片描述

《鸿蒙 (Harmony OS)开发学习手册》(共计892页):https://gitcode.com/HarmonyOS_MN

如何快速入门?

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

开发基础知识:

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.……

在这里插入图片描述

鸿蒙开发面试真题(含参考答案):https://gitcode.com/HarmonyOS_MN

在这里插入图片描述

OpenHarmony 开发环境搭建

图片

《OpenHarmony源码解析》:https://gitcode.com/HarmonyOS_MN

  • 搭建开发环境
  • Windows 开发环境的搭建
  • Ubuntu 开发环境搭建
  • Linux 与 Windows 之间的文件共享
  • ……
  • 系统架构分析
  • 构建子系统
  • 启动流程
  • 子系统
  • 分布式任务调度子系统
  • 分布式通信子系统
  • 驱动子系统
  • ……

图片

OpenHarmony 设备开发学习手册:https://gitcode.com/HarmonyOS_MN

图片

写在最后

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

  • 点赞,转发,有你们的 『点赞和评论』,才是我创造的动力。
  • 关注小编,同时可以期待后续文章ing🚀,不定期分享原创知识。
  • 想要获取更多完整鸿蒙最新学习资源,请移步前往在这里插入图片描述

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com