鸿蒙内核源码分析(中断切换篇) | 系统因中断活力四射 | 百篇博客分析OpenHarmony源码 | v42.02
百篇博客系列篇.本篇为:
硬件架构相关篇为:
- v22.xx 鸿蒙内核源码分析(汇编基础篇) | CPU在哪里打卡上班 | 51.c.h .o
- v23.xx 鸿蒙内核源码分析(汇编传参篇) | 如何传递复杂的参数 | 51.c.h .o
- v36.xx 鸿蒙内核源码分析(工作模式篇) | CPU是韦小宝,七个老婆 | 51.c.h .o
- v38.xx 鸿蒙内核源码分析(寄存器篇) | 小强乃宇宙最忙存储器 | 51.c.h .o
- v39.xx 鸿蒙内核源码分析(异常接管篇) | 社会很单纯,复杂的是人 | 51.c.h .o
- v40.xx 鸿蒙内核源码分析(汇编汇总篇) | 汇编可爱如邻家女孩 | 51.c.h .o
- v42.xx 鸿蒙内核源码分析(中断切换篇) | 系统因中断活力四射 | 51.c.h .o
- v43.xx 鸿蒙内核源码分析(中断概念篇) | 海公公的日常工作 | 51.c.h .o
- v44.xx 鸿蒙内核源码分析(中断管理篇) | 江湖从此不再怕中断 | 51.c.h .o
关于中断部分系列篇将用三篇详细说明整个过程.
中断概念篇 中断概念很多,比如中断控制器,中断源,中断向量,中断共享,中断处理程序等等.本篇做一次整理.先了解透概念才好理解中断过程.用海公公打比方说明白中断各个概念.可前往鸿蒙内核源码分析(总目录)查看.
中断管理篇 从中断初始化
HalIrqInit
开始,到注册中断的LOS_HwiCreate
函数,到消费中断函数的HalIrqHandler
,剖析鸿蒙内核实现中断的过程,很像设计模式中的观察者模式. 可前往鸿蒙内核源码分析(总目录)查看.中断切换篇(本篇) 用自下而上的方式,从中断源头纯汇编代码往上跟踪代码细节.说清楚保存和恢复中断现场
TaskIrqContext
过程.
中断环境下的任务切换
在鸿蒙的内核线程就是任务,系列篇中说的任务和线程当一个东西去理解.
一般二种场景下需要切换任务上下文:
在中断环境下,从当前线程切换到目标线程,这种方式也称为硬切换.它们通常由硬件产生或是软件发生异常时的被动式切换.哪些情况下会出现硬切换呢?
- 中断源可分外部和内部中断源两大类,比如 鼠标,键盘外部设备每次点击和敲打,屏幕的触摸,USB的插拔等等这些都是外部中断源.存储器越限、缺页,核间中断,断点中断等等属于内部中断源.由此产生的硬切换都需要换栈运行,硬切换硬在需切换工作模式(中断模式).所以会比线程环境下的切换更复杂点,但道理还是一样要保存和恢复切换现场寄存器的值, 而保存寄存器顺序格式结构体叫:任务中断上下文(
TaskIrqContext
).
- 中断源可分外部和内部中断源两大类,比如 鼠标,键盘外部设备每次点击和敲打,屏幕的触摸,USB的插拔等等这些都是外部中断源.存储器越限、缺页,核间中断,断点中断等等属于内部中断源.由此产生的硬切换都需要换栈运行,硬切换硬在需切换工作模式(中断模式).所以会比线程环境下的切换更复杂点,但道理还是一样要保存和恢复切换现场寄存器的值, 而保存寄存器顺序格式结构体叫:任务中断上下文(
在线程环境下,从当前线程切换到目标线程,这种方式也称为软切换,能由软件控制的自主式切换.哪些情况下会出现软切换呢?
- 运行的线程申请某种资源(比如各种锁,读/写消息队列)失败时,需要主动释放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
,返回被中断的任务继续执行
- 1.保存任务中断上下文
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 */
#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; /* 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=SP
EXC_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,一般是 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
OsIrqContextRestore: @恢复硬中断环境
LDR R0, [SP, #(4 * 7)] @R0 = sp + 7,目的是跳到恢复中断现场TaskIrqContext.CPSR位置,刚好是TaskIrqContext倒数第7的位置.
MSR SPSR_cxsf, R0 @恢复spsr 即:spsr = TaskIrqContext.CPSR
AND 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.ULR
BNE OsIrqContextRestoreToKernel @看非用户模式,怎么恢复中断现场.
/* load user sp and lr, and jump cpsr */
LDMFD SP, {R13, R14}^ @出栈,恢复用户模式sp和lr值 即:TaskIrqContext.USP,TaskIrqContext.ULR
ADD 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 @跳过cpsr
LDMFD 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,一般是 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 结束恢复中断现场 ......
鸿蒙内核源码分析.总目录
v08.xx 鸿蒙内核源码分析(总目录) | 百万汉字注解 百篇博客分析 | 51.c.h .o
百万汉字注解.百篇博客分析
百万汉字注解 >> 精读鸿蒙源码,中文注解分析, 深挖地基工程,大脑永久记忆,四大码仓每日同步更新< gitee| github| csdn| coding >
百篇博客分析 >> 故事说内核,问答式导读,生活式比喻,表格化说明,图形化展示,主流站点定期更新中< 51cto| csdn| harmony| osc >
关注不迷路.代码即人生
QQ群:790015635 | 入群密码: 666
原创不易,欢迎转载,但请注明出处.
鸿蒙内核源码分析(中断切换篇) | 系统因中断活力四射 | 百篇博客分析OpenHarmony源码 | v42.02的更多相关文章
- 鸿蒙内核源码分析(中断管理篇) | 江湖从此不再怕中断 | 百篇博客分析OpenHarmony源码 | v44.02
百篇博客系列篇.本篇为: v44.xx 鸿蒙内核源码分析(中断管理篇) | 江湖从此不再怕中断 | 51.c.h .o 硬件架构相关篇为: v22.xx 鸿蒙内核源码分析(汇编基础篇) | CPU在哪 ...
- 鸿蒙内核源码分析(中断概念篇) | 海公公的日常工作 | 百篇博客分析OpenHarmony源码 | v43.02
百篇博客系列篇.本篇为: v43.xx 鸿蒙内核源码分析(中断概念篇) | 海公公的日常工作 | 51.c.h .o 硬件架构相关篇为: v22.xx 鸿蒙内核源码分析(汇编基础篇) | CPU在哪里 ...
- 鸿蒙内核源码分析(任务切换篇) | 看汇编如何切换任务 | 百篇博客分析OpenHarmony源码 | v41.03
百篇博客系列篇.本篇为: v41.xx 鸿蒙内核源码分析(任务切换篇) | 看汇编如何切换任务 | 51.c.h .o 任务管理相关篇为: v03.xx 鸿蒙内核源码分析(时钟任务篇) | 触发调度谁 ...
- 鸿蒙内核源码分析(信号消费篇) | 谁让CPU连续四次换栈运行 | 百篇博客分析OpenHarmony源码 | v49.04
百篇博客系列篇.本篇为: v49.xx 鸿蒙内核源码分析(信号消费篇) | 谁让CPU连续四次换栈运行 | 51.c.h .o 进程管理相关篇为: v02.xx 鸿蒙内核源码分析(进程管理篇) | 谁 ...
- 鸿蒙内核源码分析(信号生产篇) | 信号安装和发送过程是怎样的? | 百篇博客分析OpenHarmony源码 | v48.03
百篇博客系列篇.本篇为: v48.xx 鸿蒙内核源码分析(信号生产篇) | 年过半百,依然活力十足 | 51.c.h .o 进程管理相关篇为: v02.xx 鸿蒙内核源码分析(进程管理篇) | 谁在管 ...
- 鸿蒙内核源码分析(进程回收篇) | 老父亲如何向老祖宗临终托孤 ? | 百篇博客分析OpenHarmony源码 | v47.01
百篇博客系列篇.本篇为: v47.xx 鸿蒙内核源码分析(进程回收篇) | 临终前如何向老祖宗托孤 | 51.c.h .o 进程管理相关篇为: v02.xx 鸿蒙内核源码分析(进程管理篇) | 谁在管 ...
- 鸿蒙内核源码分析(fork篇) | 一次调用,两次返回 | 百篇博客分析OpenHarmony源码 | v45.03
百篇博客系列篇.本篇为: v45.xx 鸿蒙内核源码分析(Fork篇) | 一次调用,两次返回 | 51.c.h .o 进程管理相关篇为: v02.xx 鸿蒙内核源码分析(进程管理篇) | 谁在管理内 ...
- 鸿蒙内核源码分析(汇编汇总篇) | 所有的汇编代码都在这里 | 百篇博客分析OpenHarmony源码 | v40.03
百篇博客系列篇.本篇为: v40.xx 鸿蒙内核源码分析(汇编汇总篇) | 汇编可爱如邻家女孩 | 51.c.h .o 硬件架构相关篇为: v22.xx 鸿蒙内核源码分析(汇编基础篇) | CPU在哪 ...
- 鸿蒙内核源码分析(异常接管篇) | 社会很单纯 , 复杂的是人 | 百篇博客分析OpenHarmony源码 | v39.03
百篇博客系列篇.本篇为: v39.xx 鸿蒙内核源码分析(异常接管篇) | 社会很单纯,复杂的是人 | 51.c.h .o 硬件架构相关篇为: v22.xx 鸿蒙内核源码分析(汇编基础篇) | CPU ...
随机推荐
- 题解 Merchant
传送门 可以发现如果我们最终选择的物品集合已经确定,就很好求了 \(\sum k*t+\sum b \geqslant s\) ,二分即可 但现在我们无法确定该选哪些物品 因此我们只需要check一下 ...
- idea中的springboot的maven项目报错Failed to clean project: Failed to delete D:\new_shunyi\shunyi\target\shunyi\WEB-INF\classes\static\
正准备打包上传到测试环境,本想先clean下,没想到报了个这个错,意思大概是无法删除target下的某个文件,没有权限(一脸懵逼): 后来百度发现可能是因为我之前启动了tomcat,未关闭,然后关闭了 ...
- C++CLR类库封装Native类库并用C#调用 - 草稿
1.创建Native类库 新建项目->其他语言->Visual C++->Win32控制台应用程序->DLL 添加头文件 添加源文件 选择生成路 ...
- msql中@RequestParam、@Param、@PathVariable的用法
@RequestParam的用法 1.可以对传入参数指定参数名,将请求参数绑定至方法参数 // 下面的对传入参数指定为aa,如果前端不传aa参数名,会报错 @RequestParam(value=&q ...
- C++ 计算MD5
头文件: #pragma once #ifndef MD5_H #define MD5_H #include <string> #include <fstream> /* Ty ...
- C#中的信号量---Semaphore
emaphore是System.Threading下的类,限制可同时访问某一资源或资源池的线程数. 常用构造方法 https://msdn.microsoft.com/zh-cn/library/e1 ...
- OpenCV入门系列教学(三)绘制几何形状及添加文本
一.绘制简单的几何形状和添加文本 opencv中绘制图形很简单,我们只需要使用下面这些常用函数即可. #画线 cv2.line() #画圆 cv2.circle() #画矩形 cv. rectangl ...
- 项目中经常用到的sass语法汇总
1.定义变量 使用:$(符号定义变量) 注意:使用时要带有'$'符号,定义变量的方式与PHP相同 $变量:数值; $color_r : red; div{ color:$color_r; } 2.if ...
- Go语言 判断key是否在map里 if _, ok := map[key]; ok
if val, ok := map[key]; ok { //do something here } 如果key在map里 val 被赋值map[key] ok 是true 否则val得到相应类型的零 ...
- 自己封装一个Object.freeze()方法
1.遍历所有属性和方法 2.修改遍历到的属性的描述 3.Object.seal() Object.defineProperty(Object,'freezePolyfill',{ value:func ...