
想了解更多内容,鸿蒙换实请访问:
和华为官方合作共建的内核鸿蒙技术社区
https://harmonyos.51cto.com
关于中断部分系列篇将用三篇详细说明整个过程.
● 中断概念篇 中断概念很多,比如中断控制器,中断源,中断向量,中断共享,中断处理程序等等.本篇做一次整理.先了解透概念才好理解中断过程.用海公公打比方说明白中断各个概念.可前往鸿蒙内核源码分析(总目录)查看.
● 中断管理篇 用自上而下的方式,从C语言中断注册管理开始往下跟踪代码细节,一直到汇编调用的4个HalIrqHandler OsTaskProcSignal OsSchedPreempt OsSaveSignalContextIrqC函数为止.可前往鸿蒙内核源码分析(总目录)查看.
● 中断切换篇(本篇) 用自下而上的方式,从汇编开始处往上跟踪代码细节.说清楚保存和恢复TaskIrqContext,以及调用HalIrqHandler的入口.
在鸿蒙的内核线程就是任务,系列篇中说的任务和线程当一个东西去理解.
一般二种场景下需要切换任务上下文:
● 在中断环境下,从当前线程切换到目标线程,源码这种方式也称为硬切换.不由软件控制的被动式切换.哪些情况下会出现硬切换呢?
◊ 由硬件产生的中断,比如 鼠标,键盘外部设备每次点击和敲打,屏幕的触摸,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
#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 */ #endif UINT32 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 */ #endif UINT32 resved; /* Its 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寄存器的云南idc服务商值,这说明在整个中断处理过程中,都不会用到R4-R11寄存器.不会用到就不会改变,当然就没必要保存了.这也说明内核开发者的严谨程度,不造成时间和空间上的一丁点浪费.效率的提升是从细节处入手的,每个小地方优化那么一丢丢,整体性能就上来了.
● TaskIrqContext中有两个变量有点奇怪 unsigned int USP; unsigned int ULR; 指的是用户模式下的SP和LR值, 这个要怎么理解? 因为对一个正运行的任务而言,中断的到来是颗不定时炸弹,无法预知,也无法提前准备,中断一来它立即被打断,压根没有时间去保存现场到自己的栈中,那保存工作只能是放在IRQ栈或者SVC栈中.而IRQ栈非常的小,只有64个字节,16个栈空间,指望不上了,就保存在SVC栈中,SVC模式栈可是有 8K空间的.
● 从接下来的 OsIrqHandler代码中可以看出,鸿蒙内核整个中断的工作其实都是在SVC模式下完成的,而irq的栈只是个过渡栈.具体看汇编代码逐行注解分析.
● 跳转到 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,一般是 1 MOV 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 结束恢复中断现场 ......
● 访问注解仓库地址
● Fork 本仓库 >> 新建 Feat_xxx 分支 >> 提交代码注解 >> 新建 Pull Request
● 新建 Issue
想了解更多内容,请访问:
和华为官方合作共建的鸿蒙技术社区
https://harmonyos.51cto.com

(责任编辑:IT科技)