当前位置: 首页 > 科技观察

Harmonyos内核源码分析(中断切换)-编译笔记中断切换实现流程

时间:2023-03-12 09:20:59 科技观察

更多内容请访问:Harmonyos技术社区https://harmonyos.51cto.com,与华为官方共同打造关于中断系列部分将使用三篇文章讲解整个流程详细。●中断概念中断的概念有很多,比如中断控制器、中断源、中断向量、中断共享、中断处理程序等,本文将进行梳理。先理解概念理解中断过程很容易。以海公公为例,解释一下中断的各种概念。可以去鸿蒙内核源码解析(总目录)查看。中断管理采用自上而下的方式,从C语言中断注册管理开始,向下跟踪代码细节,直到汇编调用的四个HalIrqHandlerOsTaskProcSignalOsSchedPreemptOsSaveSignalContextIrqC函数。可以去鸿蒙内核源码解析(总目录)查看。跟踪代码详细信息。解释清楚TaskIrqContext的保存和恢复,调用HalIrqHandler的入口。中断环境下的任务切换是鸿蒙内核线程中的一个任务。系列中提到的任务和线程应该理解为一个东西。通常有两种场景需要切换任务上下文:●在中断环境下,从当前线程切换到目标线程,这种方式也称为硬切换。不受软件控制的无源开关。硬切换在什么情况下会发生??由硬件产生的中断,比如鼠标,键盘,外接设备,每一次点击点击,触摸屏幕,USB插拔等都是硬中断。同样需要切换栈来运行和复用寄存器,但是和软切换不同的是,硬切换会切换工作模式(中断模式)。所以会比较复杂,但是保存和恢复switch域寄存器的值的原理还是一样的,保存的寄存器的顺序格式结构称为:任务中断上下文(TaskIrqContext)。在线程环境下,从当前线程切换到目标线程,这种方式也称为软切换,可以由软件自主控制。什么情况下会出现软切换??正在运行的线程在申请某种资源(如各种锁、读写消息队列)失败时,需要主动释放CPU的控制权,将自己挂入等待队列,调度算法重新调度新的任务跑步。?OsTickHandler节拍处理函数,每10ms执行一次,检测任务的时间片是否用完,会发起任务的重新调度,切换到新的任务运行。?是否是内核中的任务模式或用户态的任务在切换方面统一对待,一视同仁,因为切换需要改变栈才能运行,而寄存器有限,需要频繁复用。这就需要先把当前的寄存器值保存到task自己的栈中,这样别人用完了,就轮到你恢复寄存器的当前值,保证老的task可以继续运行。保存寄存器的顺序格式结构称为:任务上下文(TaskContext)。这篇文章清楚地解释了中断环境下的切换(硬切换)实现过程。线程切换(软切换)的实现过程在鸿蒙内核源码分析(总目录)任务切换章节已经详细介绍。ARM的七种工作模式中,有两种与中断有关。普通中断模式(irq):普通中断模式也叫普通中断模式,用于处理普通中断请求,通常在硬件产生中断信号后自动进入该模式,该模式可以自由访问系统硬件资源快速中断模式(fiq):fast中断模式是相对于一般中断模式而言的。它用于处理高优先级的中断和处理需要更多时间的中断请求。主要用于高速数据传输和信道处理。这里我们分析一下普通中断模式下的任务切换过程。这张图是普通中断模式的相关寄存器,必须牢记在心,为了牢记,系列会多次拿出来。普通中断模式(图中Column中的IRQ)是一种异常模式,有自己独立的堆栈空间。(IRQ)中断发生后,硬件会将CPSR寄存器的工作模式设置为IRQ模式,并跳转到入口地址OsIrqHandler执行。#defineOS_EXC_IRQ_STACK_SIZE64//中断模式栈大小为64字节__irq_stack:.spaceOS_EXC_IRQ_STACK_SIZE*CORE_NUM__irq_stack_top:OsIrqHandler汇编代码实现过程,它做了三件事:?1.保存任务中断上下文TaskIrqContext?2.执行中断处理程序HalIrqHandler,即一个C函数,由汇编调用?3.恢复任务中断上下文TaskIrqContext,返回被中断的任务并继续执行TaskIrqContext和TaskContext先看本文的结构\unsignedintULR;\unsignedintCPSR;\unsignedintPC;typedefstruct{//task中断上下文#if!defined(LOSCFG_ARCH_FPU_DISABLE)UINT64D[FP_REGS_NUM];/*D0-D31*/UINT32regFPSCR;/*FPSCR*/UINT32regFPEXC;/*FPEXC*/#endifUINT32resved;TASK_IRQ_CONTEXT}TaskIrqContext;typedefstruct{//任务上下文,其中任务切换章节已经详述,放这里对比#if!defined(LOSCFG_ARCH_FPU_DISABLE)UINT64D[FP_REGS_NUM];/*D0-D31*/UINT32regFPSCR;/*FPSCR*/UINT32regFPEXC;/*FPEXC*/#endifUINT32resved;/*是stack8aligned*/UINT32regPSR;//保存CPSR寄存器UINT32R[GEN_REGS_NUM];/*R0-R12*/UINT32SP;/*R13*/UINT32LR;/*R14*/UINT32PC;/*R15*/}TaskContext;●两种结构都很简单,目的也比较简单。它们用于保存寄存器字段的值。TaskContext保存了全部17个寄存器,而TaskIrqContext保存的较少,在栈中没有保存R4-R11寄存器的值,也就是说在整个中断处理过程中不会用到R4-R11寄存器。不用就不会改,当然也不需要保存。这也说明了内核开发者的严谨性,并没有造成一点时间和空间的浪费。效率的提升从细节做起,每一个小地方都进行优化,让整体性能提升。TaskIrqContext中有两个变量。奇怪的unsignedintUSP;无符号整数ULR;指的是用户态的SP和LR值,这个怎么理解?因为对于一个正在运行的任务来说,中断的到来是一颗不可预知的定时炸弹,无法预知或提前准备,中断来了就会立即中断,来不及将场景保存到自己的栈中,所以保存的工作只能放在IRQ栈或者SVC栈中。IRQ栈很小,只有64个字Section,16个栈空间,实在不行就存到SVC栈里。SVC模式堆栈有8K空间。●从下面的OsIrqHandler代码可以看出,鸿蒙内核的整个中断工作实际上是在SVC模式下完成的,irq栈只是一个过渡栈。详见汇编代码逐行注释分析。普通中断处理程序OsIrqHandler:@硬中断处理,此时已经切换到硬中断栈SUBLR,LR,#4@记录译码指令的地址,防止切换过程中指令丢失/*pushr0-r3toirqstack*/@irqstack只是一个过渡栈STMFDSP,{R0-R3}@r0-r3寄存器进入irq栈SUBR0,SP,#(4*4)@r0=sp-16,目的是记录起始位置{R0-R3}4个寄存器,然后从R3中弹出MRSR1,SPSR@获取程序状态控制寄存器MOVR2,LR@r2=lr/*disableirq,switchtosvcmode*/@superuser模式(SVC模式),主要用于SWI(软件中断)和OS(操作系统)CPSIDi,#0x13@切换到SVC模式,一旦切换到这里,后面的指令都会在SVC栈上运行@CPSIDi是一个off-interrupt指令,对应CPSIE@TaskIrqContext开始保存中断场景.../*pushspsrandpcinsvcstack*/STMFDSP!,{R1,R2}@实际上是将SPSR和PC压入TaskIrqContext.PC,TaskIrqContext.CPSR,STMFDSP,{LR}@LR对应的栈中,再次入栈。SP不会自动增加。如果是用户态,LR的值会被282行覆盖:STMFDSP,{R13,R14}^@如果不是用户态,会被286行跳过:SUBSP,SP,#(2*4).ANDR3,R1,#CPSR_MASK_MODE@获取CPU的工作模式CMPR3,#CPSR_USER_MODE@中断是否发生在用户态BNEOsIrqFromKernel@非用户态不需要保存USP,ULR在TaskIrqContext/*pushusersp,lrinsvcstack*/STMFDSP,{R13,R14}^@PutusermodespandLRintosvcstackOsIrqFromKernel:@Initiateaninterruptfromthekernel/*fromsvc??notneedsavespandlr*/@svc模式下发生的中断不需要保存sp和lr寄存器值SUBSP,SP,#(2*4)@目的是为TaskIrqContext.USP,TaskIrqContext留空。ULR@TaskIrqContext.ULR在276行已经保存了,而276行用的是SP而不是SP!,所以这里需要跳2个空格/*popr0-r3fromirqstack*/LDMFDR0,{R0-R3}@从R0位置按顺序popout/*pushcallerssavedregsastrashedregsinsvcstack*/STMFDSP!,{R0-R3,R12}@注册入栈,对应TaskIrqContext.R0~R3,R12/*8bytesstackalign*/SUBSP,SP,#4@TaskIrqContext对应的栈对齐。resved/**savefpuregsincaseincasethosebeen*改变中断处理程序。*/PUSH_FPU_REGSR0@savefpuregs,以防中断处理程序中的fpuregs被修改@TaskIrqContext保存完中断场景...@开始执行真正的中断处理函数。#ifdefLOSCFG_IRQ_USE_STANDALONE_STACK@是否使用独立的IRQ栈PUSH{R4}@R4先入栈保存,再切换栈,需要保存On-siteMOVR4,SP@R4=SPEXC_SP_SET__svc_stack_top,OS_EXC_SVC_STACK_SIZE,R1,R2@切换到svc栈#endif/*BLX跳转带链接和状态切换*/BLXHalIrqHandler/*调用硬件中断处理程序,无参数,说明HalIrqHandler在svc栈中执行*/#ifdefLOSCFG_IRQ_USE_STANDALONE_STACK@是否使用一个独立的IRQ栈MOVSP,R4@resumethescene,sp=R4POP{R4}@popR4#endif/*processingsignals*/@processingpendingsignalsBLOsTaskProcSignal@jumptoCcode/*checkifneedstoschedule*/@检查CMPR0是否需要被调度,#0@是否需要被调度,R0为参数保存值BLNEOsSchedPreempt@不等于,即R0不为0,一般为1MOVR0,SP@参数MOVR1,R7@参数BLOsSaveSignalContextIrq@跳转到C代码/*restorefpuregs*/POP_FPU_REGSR0@Restorefpu寄存器值ADDSP,SP,#4@sp=sp+4OsIrqContextRestore:@RestorehardinterruptenvironmentLDRR0,[SP,#(4*7)]@R0=sp+7,目的是跳转到TaskIrqContext.CPSR恢复中断站点的位置,恰好是TaskIrqContext底部的第7个位置。MSRSPSR_cxsf,R0@recoveryspsr是:spsr=TaskIrqContext.CPSRANDR0,R0,#CPSR_MASK_MODE@mask找出当前工作模式CMPR0,#CPSR_USER_MODE@是用户模式吗?@TaskIrqContext开始恢复被中断的场景...LDMFDSP!,{R0-R3,R12}@从SP位置弹出为了对应TaskIrqContext.R0~R3,R12@此时已经恢复了5个寄存器,接下来是TaskIrqContext.USP,TaskIrqContext.ULRBNEOsIrqContextRestoreToKernel@看非用户态,如何恢复被中断的场景/*loaduserspandlr,andjumpcpsr*/LDMFDSP,{R13,R14}^@popout,恢复用户态sp和lr值,即:TaskIrqContext.USP,TaskIrqContext.ULRADDSP,SP,#(3*4)@跳转3个位置,跳过CPSR,因为最后一句不是SP!,所以跳转3个位置,刚好到达TaskIrqContext.PC保存的位置/*返回用户模式*/LDMFDSP!,{PC}^@回到用户态,整个中断过程完成@TaskIrqContext结束,恢复被中断的场景(用户态)...osIrqContextRestoreToKernel:@restoresinterruptfromkernel/*svcmodenotloadsp*/ADDSP,SP,#4@ActuallyskipTaskIrqContext.USP,因为这个值在内核态没有保存,看287行LDMFDSP!,{LR}@popupLR/*jumpcpsrandreturntosvcmode*/ADDSP,SP,#4@skipcpsrLDMFDSP!,{PC}^@回到svc模式,整个中断过程完成@TaskIrqContext结束,恢复被中断的场景(内核态)...逐句解释●跳转到OsIrqFromKernel硬件会自动切换到__irq_stack执行●1句:SUBLR,LR,#4在arm执行过程中,一般分为取指、译码、执行阶段,而PC指向取指,正在执行的指令为PC-8,译码指令为PC-4。当中断发生时,硬件自动执行movlrpc,中间的PC-4译码指令会因为没有寄存器记录而丢失。所以SUBLR,LR,#4的结果是lr=PC-4,它位于中断时,指令会被解码,这个位置会保存在栈中,保证返回后可以继续执行.●2句:STMFDSP,{R0-R3}当前4个寄存器存放在__irq_stack中●3句:SUBR0,SP,#(4*4)因为SP不是自增的,所以R0跳转到保存地址R0内容●4,5句:读SPSR、LR寄存器内容,目的是稍后将TaskIrqContext保存在SVC栈中●6句:CPSIDi,#0x13DisableinterruptandswitchSVCmode,执行该指令后,工作模式将切换到SVC模式●@TaskIrqContext开始保存中断场景...●中间代码需要结合TaskIrqContext来看,否则百分百混乱。结合起来,秒懂。代码已注释,不再解释。注释中提到的第276行是指源码第276行。请参考评论阅读源码会更透彻。进入源码注释地址查看●@TaskIrqContext中断场景保存结束……●TaskIrqContext保存场景后,才真正开始处理中断。/*带链接和状态切换的BLX跳转*/BLXHalIrqHandler/*调用硬件中断处理程序,不带参数,表示HalIrqHandler在svc栈中执行*/#ifdefLOSCFG_IRQ_USE_STANDALONE_STACK@是否使用独立的IRQ栈MOVSP,R4@restore场景,sp=R4POP{R4}@popR4#endif/*processingsignals*/@processingpendingsignalBLOsTaskProcSignal@jumptoCcode/*checkifneedstoschedule*/@检查CMPR0是否需要调度,#0@是否需要调度,R0为参数BLNEOsSchedPreempt@保存的值不相等,即R0不为0,通常为1MOVR0,SP@参数MOVR1,R7@参数BLOsSaveSignalContextIrq@跳转到C代码/*restorefpuregs*/POP_FPU_REGSR0@restorefpu寄存器值ADDSP,SP,#4@sp=sp+4这段代码是跳转到C语言去执行,分别是HalIrqHandlerOsTaskProcSignalOsSchedPreemptOsSaveSignalContextIrqC语言的内容很多,在中断管理章节会讲解。@TaskIrqContext开始恢复被打断的场景。。。同样的中间代码需要和TaskIrqContext一起看,否则100%无知。一起看的话,秒懂。代码已注释,不再解释。注释中提到的第287行是指源码第287行。请参阅带注释的源代码以更透彻地理解它。输入源码注释地址查看●@TaskIrqContext结束恢复中断的站点...参与贡献●访问注解仓库地址●fork本仓库>>新建一个Feat_xxx分支>>提交代码注解>>新建一个PullRequest●新建一个Issueformore信息请访问:https://harmonyos.51cto.com,与华为共建的鸿蒙技术社区