首页 文章 精选 留言 我的

精选列表

搜索[HarmonyOS SDK],共10000篇文章
优秀的个人博客,低调大师

鸿蒙内核源码分析(异常接管篇) | 社会很单纯 , 复杂的是人 | 中文注解HarmonyOS源码 | v39.02

百万汉字注解 >> 精读内核源码,中文注解分析, 深挖地基工程,大脑永久记忆,四大码仓每日同步更新< gitee | github | csdn | coding > 百篇博客分析 >> 故事说内核,问答式导读,生活式比喻,表格化说明,图形化展示,多站点每日同步更新< oschina | csdn | weharmony > 系列篇ARM部分说明基于ARM720T.pdf文档. 为何要有异常接管? 拿小孩成长打比方,大人总希望孩子能健康成长,但在成长过程中总会遇到各种各样的问题,树欲静而风不止,成长路上有危险,有时是自己的问题有时是外在环境问题.就像抖音最近的流行口水歌一样,社会很单纯,复杂的是人啊,每次听到都想站起来扭几下.哎! 老衲到底做错什么了? 比如:老被小朋友欺负怎么弄? 发现乱花钱怎么搞? 青春期发育怎么应对? 失恋要跳楼又怎么办? 意思超过他的认知范围,就是靠它自己解决不了了,就需要有更高权限,更高智慧的人介入进来,帮着解决,干擦屁股的事. 那么应用程序就是那个小孩,内核就是监护人,有更高的权限,更高的智慧.而且监护人还不止一个,而是六个,每个监护人对应解决一种情况,情况发生了就由它来接管这件事的处理,小朋友你就别管了,先把你关家里,处理好了外面安全了再把应用程序放出来玩去. 这六个人处理问题都自带工具,有标准的解决方案,有自己独立的办公场所,办公场所就是栈空间(独立的),标准解决方案就是私有代码段,放在固定的位置.而自带的工具就是 SPSR_***,SP_***,LR_***寄存器组.详见 系列篇之工作模式篇 ,这里再简单回顾下有哪些工作模式,包括小孩自己(用户模式)一共是七种模式. 七种工作模式 图来源于 ARM720T.pdf第43页,在ARM体系中,CPU工作在以下七种模式中: 用户模式(usr):属于正常的用户模式,不能直接切换到其他模式,ARM处理器正常的程序执行状态。 快速中断模式(fiq):支持高速数据传输及通道处理,FIQ异常响应时进入此模式 外部中断模式(irq):用于通用中断处理,IRQ异常响应时进入此模式 管理模式(svc):操作系统保护模式,系统复位和软件中断响应时进入此模式(由系统调用执行软中断SWI命令触发) 数据访问终止模式(abt):当数据或指令预取终止时进入该模式,可用于处理存储器故障、实现虚拟存储器和存储器保护。 系统模式(sys):运行具有特权的操作系统任务,与用户模式类似,但具有可以直接切换到其他模式等特权 未定义指令中止模式(und):处理未定义的指令陷阱,当未定义的指令执行时进入该模式,可用于支持硬件协处理器的软件仿真。 除用户模式外,其余6种工作模式都属于特权模式 特权模式中除了系统模式以外的其余5种模式称为异常模式 大多数程序运行于用户模式 进入特权模式是为了处理中断、异常、或者访问被保护的系统资源 硬件权限级别:系统模式 > 异常模式 > 用户模式 快中断(fiq)与慢中断(irq)区别:快中断处理时禁止中断 每种模式都有自己独立的入口和独立的运行栈空间. 系列篇之CPU篇 已介绍过只要提供了入口函数和运行空间,CPU就可以干活了.入口函数解决了指令来源问题,运行空间解决了指令的运行场地问题. 而且在多核情况下,每个CPU核的每种特权模式都有自己独立的栈空间.注意是特权模式下的栈空间,用户模式的栈空间是由用户(应用)程序提供的. 官方概念 异常接管是操作系统对运行期间发生的异常情况(芯片硬件异常)进行处理的一系列动作,例如打印异常发生时当前函数的调用栈信息、CPU现场信息、任务的堆栈情况等。 异常接管作为一种调测手段,可以在系统发生异常时给用户提供有用的异常信息,譬如异常类型、发生异常时的系统状态等,方便用户定位分析问题。 鸿蒙的异常接管,在系统发生异常时的处理动作为:显示异常发生时正在运行的任务信息(包括任务名、任务号、堆栈大小等),以及CPU现场等信息。 进入和退出异常方式 异常接管切换需要处理好两件事: 一个是代码要切到哪个位置,也就是要重置PC寄存器,每种异常模式下的切换方式如图: 另一个是要恢复每种模式的状态,即 CPSR(1个) 和 SPSR(共5个) 的关系,对M[4:0]的修改,如图: 以下是M[4:0]在每种模式下具体操作方式: 栈帧 每个函数都有自己的栈空间,称为栈帧。调用函数时,会创建子函数的栈帧,同时将函数入参、局部变量、寄存器入栈。栈帧从高地址向低地址生长,也就是说栈底是高地址,栈顶是底地址. 详见 系列篇之用栈方式篇 以ARM32 CPU架构为例,每个栈帧中都会保存PC、LR、SP和FP寄存器的历史值。 堆栈分析原理如下图所示,实际堆栈信息根据不同CPU架构有所差异,此处仅做示意。 图中不同颜色的寄存器表示不同的函数。可以看到函数调用过程中,寄存器的保存。通过FP寄存器,栈回溯到异常函数的父函数,继续按照规律对栈进行解析,推出函数调用关系,方便用户定位问题。 解读 LR寄存器(Link Register),链接寄存器,指向函数的返回地址。 R11:可以用作通用寄存器,在开启特定编译选项时可以用作帧指针寄存器FP,用来实现栈回溯功能。 GNU编译器(gcc)默认将R11作为存储变量的通用寄存器,因而默认情况下无法使用FP的栈回溯功能。为支持调用栈解析功能,需要在编译参数中添加-fno-omit-frame-pointer选项,提示编译器将R11作为FP使用。 FP寄存器(Frame Point),帧指针寄存器,指向当前函数的父函数的栈帧起始地址。利用该寄存器可以得到父函数的栈帧,从栈帧中获取父函数的FP,就可以得到祖父函数的栈帧,以此类推,可以追溯程序调用栈,得到函数间的调用关系。 当系统发生异常时,系统打印异常函数的栈帧中保存的寄存器内容,以及父函数、祖父函数的栈帧中的LR、FP寄存器内容,用户就可以据此追溯函数间的调用关系,定位异常原因。 六种异常模式实现代码 /* Define exception type ID */ //ARM处理器一共有7种工作模式,除了用户和系统模式其余都叫异常工作模式 #define OS_EXCEPT_RESET 0x00 //重置功能,例如:开机就进入CPSR_SVC_MODE模式 #define OS_EXCEPT_UNDEF_INSTR 0x01 //未定义的异常,就是others #define OS_EXCEPT_SWI 0x02 //软中断 #define OS_EXCEPT_PREFETCH_ABORT 0x03 //预取异常(取指异常), 指令三步骤: 取指,译码,执行, #define OS_EXCEPT_DATA_ABORT 0x04 //数据异常 #define OS_EXCEPT_FIQ 0x05 //快中断异常 #define OS_EXCEPT_ADDR_ABORT 0x06 //地址异常 #define OS_EXCEPT_IRQ 0x07 //普通中断异常 地址异常处理(Address abort) @ Description: Address abort exception handler _osExceptAddrAbortHdl: @地址异常处理 SUB LR, LR, #8 @ LR offset to return from this exception: -8. STMFD SP, {R0-R7} @ Push working registers, but don`t change SP. MOV R0, #OS_EXCEPT_ADDR_ABORT @ Set exception ID to OS_EXCEPT_ADDR_ABORT. B _osExceptDispatch @跳到异常分发统一处理 快中断处理(fiq) @ Description: Fast interrupt request exception handler _osExceptFiqHdl: @快中断异常处理 SUB LR, LR, #4 @ LR offset to return from this exception: -4. STMFD SP, {R0-R7} @ Push working registers. MOV R0, #OS_EXCEPT_FIQ @ Set exception ID to OS_EXCEPT_FIQ. B _osExceptDispatch @ Branch to global exception handler. 解读 快中断处理时需禁用普通中断 取指异常(Prefectch abort) @ Description: Prefectch abort exception handler _osExceptPrefetchAbortHdl: #ifdef LOSCFG_GDB #if __LINUX_ARM_ARCH__ >= 7 GDB_HANDLE OsPrefetchAbortExcHandleEntry #endif #else SUB LR, LR, #4 @ LR offset to return from this exception: -4. STMFD SP, {R0-R7} @ Push working registers, but don`t change SP. MOV R5, LR MRS R1, SPSR MOV R0, #OS_EXCEPT_PREFETCH_ABORT @ Set exception ID to OS_EXCEPT_PREFETCH_ABORT. AND R4, R1, #CPSR_MASK_MODE @ Interrupted mode CMP R4, #CPSR_USER_MODE @ User mode BEQ _osExcPageFault @ Branch if user mode _osKernelExceptPrefetchAbortHdl: MOV LR, R5 B _osExceptDispatch @ Branch to global exception handler. #endif 数据访问异常(Data abort) @ Description: Data abort exception handler _osExceptDataAbortHdl: @数据异常处理,缺页就属于数据异常 #ifdef LOSCFG_GDB #if __LINUX_ARM_ARCH__ >= 7 GDB_HANDLE OsDataAbortExcHandleEntry #endif #else SUB LR, LR, #8 @ LR offset to return from this exception: -8. STMFD SP, {R0-R7} @ Push working registers, but don`t change SP. MOV R5, LR MRS R1, SPSR MOV R0, #OS_EXCEPT_DATA_ABORT @ Set exception ID to OS_EXCEPT_DATA_ABORT. B _osExcPageFault @跳到缺页异常处理 #endif 软中断处理(swi) @ Description: Software interrupt exception handler _osExceptSwiHdl: @软中断异常处理 SUB SP, SP, #(4 * 16) @先申请16个栈空间用于处理本次软中断 STMIA SP, {R0-R12} @保存R0-R12寄存器值 MRS R3, SPSR @读取本模式下的SPSR值 MOV R4, LR @保存回跳寄存器LR AND R1, R3, #CPSR_MASK_MODE @ Interrupted mode 获取中断模式 CMP R1, #CPSR_USER_MODE @ User mode 是否为用户模式 BNE OsKernelSVCHandler @ Branch if not user mode 非用户模式下跳转 @ 当为用户模式时,获取SP和LR寄出去值 @ we enter from user mode, we need get the values of USER mode r13(sp) and r14(lr). @ stmia with ^ will return the user mode registers (provided that r15 is not in the register list). MOV R0, SP @获取SP值,R0将作为OsArmA32SyscallHandle的参数 STMFD SP!, {R3} @ Save the CPSR 入栈保存CPSR值 ADD R3, SP, #(4 * 17) @ Offset to pc/cpsr storage 跳到PC/CPSR存储位置 STMFD R3!, {R4} @ Save the CPSR and r15(pc) 保存LR寄存器 STMFD R3, {R13, R14}^ @ Save user mode r13(sp) and r14(lr) 保存用户模式下的SP和LR寄存器 SUB SP, SP, #4 PUSH_FPU_REGS R1 @保存中断模式(用户模式模式) MOV FP, #0 @ Init frame pointer CPSIE I @开中断,表明在系统调用期间可响应中断 BLX OsArmA32SyscallHandle /*交给C语言处理系统调用*/ CPSID I @执行后续指令前必须先关中断 POP_FPU_REGS R1 @弹出FP值给R1 ADD SP, SP,#4 @ 定位到保存旧SPSR值的位置 LDMFD SP!, {R3} @ Fetch the return SPSR 弹出旧SPSR值 MSR SPSR_cxsf, R3 @ Set the return mode SPSR 恢复该模式下的SPSR值 @ we are leaving to user mode, we need to restore the values of USER mode r13(sp) and r14(lr). @ ldmia with ^ will return the user mode registers (provided that r15 is not in the register list) LDMFD SP!, {R0-R12} @恢复R0-R12寄存器 LDMFD SP, {R13, R14}^ @ Restore user mode R13/R14 恢复用户模式的R13/R14寄存器 ADD SP, SP, #(2 * 4) @定位到保存旧PC值的位置 LDMFD SP!, {PC}^ @ Return to user 切回用户模式运行 普通中断处理(irq) OsIrqHandler: @硬中断处理,此时已切换到硬中断栈 SUB LR, LR, #4 /* push r0-r3 to irq stack */ STMFD SP, {R0-R3} @r0-r3寄存器入 irq 栈 SUB R0, SP, #(4 * 4)@r0 = sp - 16 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 /* push spsr and pc in svc stack */ STMFD SP!, {R1, R2} @实际是将 SPSR,和LR入栈,入栈顺序为 R1,R2,SP自增 STMFD SP, {LR} @LR再入栈,SP不自增 AND R3, R1, #CPSR_MASK_MODE @获取CPU的运行模式 CMP R3, #CPSR_USER_MODE @中断是否发生在用户模式 BNE OsIrqFromKernel @中断不发生在用户模式下则跳转到OsIrqFromKernel /* push user sp, lr in svc stack */ STMFD SP, {R13, R14}^ @sp和LR入svc栈 解读 普通中断处理时可以响应快中断 未定义异常处理(undef) @ Description: Undefined instruction exception handler _osExceptUndefInstrHdl:@出现未定义的指令处理 #ifdef LOSCFG_GDB GDB_HANDLE OsUndefIncExcHandleEntry #else @ LR offset to return from this exception: 0. STMFD SP, {R0-R7} @ Push working registers, but don`t change SP. MOV R0, #OS_EXCEPT_UNDEF_INSTR @ Set exception ID to OS_EXCEPT_UNDEF_INSTR. B _osExceptDispatch @ Branch to global exception handler. #endif 异常分发统一处理 _osExceptDispatch: @异常模式统一分发处理 MRS R2, SPSR @ Save CPSR before exception. MOV R1, LR @ Save PC before exception. SUB R3, SP, #(8 * 4) @ Save the start address of working registers. MSR CPSR_c, #(CPSR_INT_DISABLE | CPSR_SVC_MODE) @ Switch to SVC mode, and disable all interrupts MOV R5, SP EXC_SP_SET __exc_stack_top, OS_EXC_STACK_SIZE, R6, R7 STMFD SP!, {R1} @ Push Exception PC STMFD SP!, {LR} @ Push SVC LR STMFD SP!, {R5} @ Push SVC SP STMFD SP!, {R8-R12} @ Push original R12-R8, LDMFD R3!, {R4-R11} @ Move original R7-R0 from exception stack to original stack. STMFD SP!, {R4-R11} STMFD SP!, {R2} @ Push task`s CPSR (i.e. exception SPSR). CMP R0, #OS_EXCEPT_DATA_ABORT @是数据异常吗? BNE 1f @不是跳到 锚点1处 MRC P15, 0, R8, C6, C0, 0 @R8=C6(内存失效的地址) 0(访问数据失效) MRC P15, 0, R9, C5, C0, 0 @R9=C5(内存失效的状态) 0(无效整个指令cache) B 3f @跳到锚点3处执行 1: CMP R0, #OS_EXCEPT_PREFETCH_ABORT @是预取异常吗? BNE 2f @不是跳到 锚点2处 MRC P15, 0, R8, C6, C0, 2 @R8=C6(内存失效的地址) 2(访问指令失效) MRC P15, 0, R9, C5, C0, 1 @R9=C5(内存失效的状态) 1(虚拟地址) B 3f @跳到锚点3处执行 2: MOV R8, #0 MOV R9, #0 3: AND R2, R2, #CPSR_MASK_MODE CMP R2, #CPSR_USER_MODE @ User mode BNE 4f @不是用户模式 STMFD SP, {R13, R14}^ @ save user mode sp and lr 4: SUB SP, SP, #(4 * 2) @sp=sp-(4*2) 非常重要的ARM37个寄存器 详见 系列篇之寄存器篇 结尾 以上为异常接管对应的代码处理,具体每种异常发生的场景和代码细节处理,因内容太多,太复杂,系列篇后续将分篇一一分析.敬请关注! 参与贡献 访问注解仓库地址 Fork 本仓库 >> 新建 Feat_xxx 分支 >> 提交代码注解 >> 新建 Pull Request 新建 Issue 喜欢请大方 点赞+关注+收藏 吧 各大站点搜 "鸿蒙内核源码分析" ,快速找到组织. 欢迎转载,请注明出处,公众号转载申请方式: 关注后直接回复您的公众号名称即可. 百万汉字注解 >> 精读内核源码,中文注解分析, 深挖地基工程,大脑永久记忆,四大码仓每日同步更新< gitee | github | csdn | coding > 百篇博客分析 >> 故事说内核,问答式导读,生活式比喻,表格化说明,图形化展示,多站点每日同步更新< oschina | csdn | weharmony >

优秀的个人博客,低调大师

鸿蒙内核源码分析(异常接管篇) | 社会很单纯 , 复杂的是人 | 中文注解HarmonyOS源码 | v39.01

百万汉字注解 >> 精读内核源码,中文注解分析, 深挖地基工程,大脑永久记忆,四大码仓每日同步更新< gitee | github | csdn | coding > 百篇博客分析 >> 故事说内核,问答式导读,生活式比喻,表格化说明,图形化展示,多站点每日同步更新< oschina | csdn | weharmony > 系列篇ARM部分说明基于ARM720T.pdf文档. 为何要有异常接管? 拿小孩成长打比方,大人总希望孩子能健康成长,但在成长过程中总会遇到各种各样的问题,树欲静而风不止,成长路上有危险,有时是自己的问题有时是外在环境问题.就像抖音最近的流行口水歌一样,社会很单纯,复杂的是人啊,每次听到都想站起来扭几下.哎! 老衲到底做错什么了? 比如:老被小朋友欺负怎么弄? 发现乱花钱怎么搞? 青春期发育怎么应对? 失恋要跳楼又怎么办? 意思超过他的认知范围,就是靠它自己解决不了了,就需要有更高权限,更高智慧的人介入进来,帮着解决,干擦屁股的事. 那么应用程序就是那个小孩,内核就是监护人,有更高的权限,更高的智慧.而且监护人还不止一个,而是六个,每个监护人对应解决一种情况,情况发生了就由它来接管这件事的处理,小朋友你就别管了,先把你关家里,处理好了外面安全了再把应用程序放出来玩去. 这六个人处理问题都自带工具,有标准的解决方案,有自己独立的办公场所,办公场所就是栈空间(独立的),标准解决方案就是私有代码段,放在固定的位置.而自带的工具就是 SPSR_***,SP_***,LR_***寄存器组.详见 系列篇之工作模式篇 ,这里再简单回顾下有哪些工作模式,包括小孩自己(用户模式)一共是七种模式. 七种工作模式 图来源于 ARM720T.pdf第43页,在ARM体系中,CPU工作在以下七种模式中: 用户模式(usr):属于正常的用户模式,ARM处理器正常的程序执行状态。 快速中断模式(fiq):用于处理快速中断,对高速数据传输或通道处理 外部中断模式(irq):对一般情况下的中断进行处理。 管理模式(svc):属于操作系统使用的保护模式,处理软件中断swi reset。 数据访问终止模式(abt):当数据或指令预取终止时进入该模式,可用于处理存储器故障、实现虚拟存储器和存储器保护。 系统模式(sys):运行具有特权的操作系统任务。 未定义指令中止模式(und):处理未定义的指令陷阱,当未定义的指令执行时进入该模式,可用于支持硬件协处理器的软件仿真。 除了用户模式外,其它六种均为特权模式或者叫异常模式。每种模式都有自己独立的入口和独立的运行栈空间. 而且在多核情况下,每个CPU核的每种异常模式都有自己独立的栈空间.注意是异常模式下的栈空间,用户模式的栈空间是由用户(应用)程序提供的. 官方概念 异常接管是操作系统对运行期间发生的异常情况(芯片硬件异常)进行处理的一系列动作,例如打印异常发生时当前函数的调用栈信息、CPU现场信息、任务的堆栈情况等。 异常接管作为一种调测手段,可以在系统发生异常时给用户提供有用的异常信息,譬如异常类型、发生异常时的系统状态等,方便用户定位分析问题。 鸿蒙的异常接管,在系统发生异常时的处理动作为:显示异常发生时正在运行的任务信息(包括任务名、任务号、堆栈大小等),以及CPU现场等信息。 进入和退出异常方式 异常接管切换需要处理好两件事: 一个是代码要切到哪个位置,也就是要重置PC寄存器,每种异常模式下的切换方式如图: 另一个是要恢复每种模式的状态,即 CPSR(1个) 和 SPSR(共5个) 的关系,对M[4:0]的修改,如图: 以下是M[4:0]在每种模式下具体操作方式: 栈帧 每个函数都有自己的栈空间,称为栈帧。调用函数时,会创建子函数的栈帧,同时将函数入参、局部变量、寄存器入栈。栈帧从高地址向低地址生长,也就是说栈底是高地址,栈顶是底地址. 详见 系列篇之用栈方式篇 以ARM32 CPU架构为例,每个栈帧中都会保存PC、LR、SP和FP寄存器的历史值。 堆栈分析原理如下图所示,实际堆栈信息根据不同CPU架构有所差异,此处仅做示意。 图中不同颜色的寄存器表示不同的函数。可以看到函数调用过程中,寄存器的保存。通过FP寄存器,栈回溯到异常函数的父函数,继续按照规律对栈进行解析,推出函数调用关系,方便用户定位问题。 解读 LR寄存器(Link Register),链接寄存器,指向函数的返回地址。 R11:可以用作通用寄存器,在开启特定编译选项时可以用作帧指针寄存器FP,用来实现栈回溯功能。 GNU编译器(gcc)默认将R11作为存储变量的通用寄存器,因而默认情况下无法使用FP的栈回溯功能。为支持调用栈解析功能,需要在编译参数中添加-fno-omit-frame-pointer选项,提示编译器将R11作为FP使用。 FP寄存器(Frame Point),帧指针寄存器,指向当前函数的父函数的栈帧起始地址。利用该寄存器可以得到父函数的栈帧,从栈帧中获取父函数的FP,就可以得到祖父函数的栈帧,以此类推,可以追溯程序调用栈,得到函数间的调用关系。 当系统发生异常时,系统打印异常函数的栈帧中保存的寄存器内容,以及父函数、祖父函数的栈帧中的LR、FP寄存器内容,用户就可以据此追溯函数间的调用关系,定位异常原因。 六种异常模式实现代码 地址异常处理(Address abort) @ Description: Address abort exception handler _osExceptAddrAbortHdl: @地址异常处理 SUB LR, LR, #8 @ LR offset to return from this exception: -8. STMFD SP, {R0-R7} @ Push working registers, but don`t change SP. MOV R0, #OS_EXCEPT_ADDR_ABORT @ Set exception ID to OS_EXCEPT_ADDR_ABORT. B _osExceptDispatch @跳到异常分发统一处理 快中断处理(fiq) @ Description: Fast interrupt request exception handler _osExceptFiqHdl: @快中断异常处理 SUB LR, LR, #4 @ LR offset to return from this exception: -4. STMFD SP, {R0-R7} @ Push working registers. MOV R0, #OS_EXCEPT_FIQ @ Set exception ID to OS_EXCEPT_FIQ. B _osExceptDispatch @ Branch to global exception handler. 取指异常(Prefectch abort) @ Description: Prefectch abort exception handler _osExceptPrefetchAbortHdl: #ifdef LOSCFG_GDB #if __LINUX_ARM_ARCH__ >= 7 GDB_HANDLE OsPrefetchAbortExcHandleEntry #endif #else SUB LR, LR, #4 @ LR offset to return from this exception: -4. STMFD SP, {R0-R7} @ Push working registers, but don`t change SP. MOV R5, LR MRS R1, SPSR MOV R0, #OS_EXCEPT_PREFETCH_ABORT @ Set exception ID to OS_EXCEPT_PREFETCH_ABORT. AND R4, R1, #CPSR_MASK_MODE @ Interrupted mode CMP R4, #CPSR_USER_MODE @ User mode BEQ _osExcPageFault @ Branch if user mode _osKernelExceptPrefetchAbortHdl: MOV LR, R5 B _osExceptDispatch @ Branch to global exception handler. #endif 数据访问异常(Data abort) @ Description: Data abort exception handler _osExceptDataAbortHdl: @数据异常处理,缺页就属于数据异常 #ifdef LOSCFG_GDB #if __LINUX_ARM_ARCH__ >= 7 GDB_HANDLE OsDataAbortExcHandleEntry #endif #else SUB LR, LR, #8 @ LR offset to return from this exception: -8. STMFD SP, {R0-R7} @ Push working registers, but don`t change SP. MOV R5, LR MRS R1, SPSR MOV R0, #OS_EXCEPT_DATA_ABORT @ Set exception ID to OS_EXCEPT_DATA_ABORT. B _osExcPageFault @跳到缺页异常处理 #endif 软中断处理(swi) @ Description: Software interrupt exception handler _osExceptSwiHdl: @软中断异常处理 SUB SP, SP, #(4 * 16) @先申请16个栈空间用于处理本次软中断 STMIA SP, {R0-R12} @保存R0-R12寄存器值 MRS R3, SPSR @读取本模式下的SPSR值 MOV R4, LR @保存回跳寄存器LR AND R1, R3, #CPSR_MASK_MODE @ Interrupted mode 获取中断模式 CMP R1, #CPSR_USER_MODE @ User mode 是否为用户模式 BNE OsKernelSVCHandler @ Branch if not user mode 非用户模式下跳转 @ 当为用户模式时,获取SP和LR寄出去值 @ we enter from user mode, we need get the values of USER mode r13(sp) and r14(lr). @ stmia with ^ will return the user mode registers (provided that r15 is not in the register list). MOV R0, SP @获取SP值,R0将作为OsArmA32SyscallHandle的参数 STMFD SP!, {R3} @ Save the CPSR 入栈保存CPSR值 ADD R3, SP, #(4 * 17) @ Offset to pc/cpsr storage 跳到PC/CPSR存储位置 STMFD R3!, {R4} @ Save the CPSR and r15(pc) 保存LR寄存器 STMFD R3, {R13, R14}^ @ Save user mode r13(sp) and r14(lr) 保存用户模式下的SP和LR寄存器 SUB SP, SP, #4 PUSH_FPU_REGS R1 @保存中断模式(用户模式模式) MOV FP, #0 @ Init frame pointer CPSIE I @开中断,表明在系统调用期间可响应中断 BLX OsArmA32SyscallHandle /*交给C语言处理系统调用*/ CPSID I @执行后续指令前必须先关中断 POP_FPU_REGS R1 @弹出FP值给R1 ADD SP, SP,#4 @ 定位到保存旧SPSR值的位置 LDMFD SP!, {R3} @ Fetch the return SPSR 弹出旧SPSR值 MSR SPSR_cxsf, R3 @ Set the return mode SPSR 恢复该模式下的SPSR值 @ we are leaving to user mode, we need to restore the values of USER mode r13(sp) and r14(lr). @ ldmia with ^ will return the user mode registers (provided that r15 is not in the register list) LDMFD SP!, {R0-R12} @恢复R0-R12寄存器 LDMFD SP, {R13, R14}^ @ Restore user mode R13/R14 恢复用户模式的R13/R14寄存器 ADD SP, SP, #(2 * 4) @定位到保存旧PC值的位置 LDMFD SP!, {PC}^ @ Return to user 切回用户模式运行 硬中断处理(irq) OsIrqHandler: @硬中断处理,此时已切换到硬中断栈 SUB LR, LR, #4 /* push r0-r3 to irq stack */ STMFD SP, {R0-R3} @r0-r3寄存器入 irq 栈 SUB R0, SP, #(4 * 4)@r0 = sp - 16 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 /* push spsr and pc in svc stack */ STMFD SP!, {R1, R2} @实际是将 SPSR,和LR入栈,入栈顺序为 R1,R2,SP自增 STMFD SP, {LR} @LR再入栈,SP不自增 AND R3, R1, #CPSR_MASK_MODE @获取CPU的运行模式 CMP R3, #CPSR_USER_MODE @中断是否发生在用户模式 BNE OsIrqFromKernel @中断不发生在用户模式下则跳转到OsIrqFromKernel /* push user sp, lr in svc stack */ STMFD SP, {R13, R14}^ @sp和LR入svc栈 未定义异常处理(undef) @ Description: Undefined instruction exception handler _osExceptUndefInstrHdl:@出现未定义的指令处理 #ifdef LOSCFG_GDB GDB_HANDLE OsUndefIncExcHandleEntry #else @ LR offset to return from this exception: 0. STMFD SP, {R0-R7} @ Push working registers, but don`t change SP. MOV R0, #OS_EXCEPT_UNDEF_INSTR @ Set exception ID to OS_EXCEPT_UNDEF_INSTR. B _osExceptDispatch @ Branch to global exception handler. #endif 异常分发统一处理 _osExceptDispatch: @异常模式统一分发处理 MRS R2, SPSR @ Save CPSR before exception. MOV R1, LR @ Save PC before exception. SUB R3, SP, #(8 * 4) @ Save the start address of working registers. MSR CPSR_c, #(CPSR_INT_DISABLE | CPSR_SVC_MODE) @ Switch to SVC mode, and disable all interrupts MOV R5, SP EXC_SP_SET __exc_stack_top, OS_EXC_STACK_SIZE, R6, R7 STMFD SP!, {R1} @ Push Exception PC STMFD SP!, {LR} @ Push SVC LR STMFD SP!, {R5} @ Push SVC SP STMFD SP!, {R8-R12} @ Push original R12-R8, LDMFD R3!, {R4-R11} @ Move original R7-R0 from exception stack to original stack. STMFD SP!, {R4-R11} STMFD SP!, {R2} @ Push task`s CPSR (i.e. exception SPSR). CMP R0, #OS_EXCEPT_DATA_ABORT @是数据异常吗? BNE 1f @不是跳到 锚点1处 MRC P15, 0, R8, C6, C0, 0 @R8=C6(内存失效的地址) 0(访问数据失效) MRC P15, 0, R9, C5, C0, 0 @R9=C5(内存失效的状态) 0(无效整个指令cache) B 3f @跳到锚点3处执行 1: CMP R0, #OS_EXCEPT_PREFETCH_ABORT @是预取异常吗? BNE 2f @不是跳到 锚点2处 MRC P15, 0, R8, C6, C0, 2 @R8=C6(内存失效的地址) 2(访问指令失效) MRC P15, 0, R9, C5, C0, 1 @R9=C5(内存失效的状态) 1(虚拟地址) B 3f @跳到锚点3处执行 2: MOV R8, #0 MOV R9, #0 3: AND R2, R2, #CPSR_MASK_MODE CMP R2, #CPSR_USER_MODE @ User mode BNE 4f @不是用户模式 STMFD SP, {R13, R14}^ @ save user mode sp and lr 4: SUB SP, SP, #(4 * 2) @sp=sp-(4*2) 非常重要的ARM37个寄存器 详见 系列篇之寄存器篇 结尾 以上为异常接管对应的代码处理,具体每种异常发生的场景和代码细节处理,因内容太多,太复杂,系列篇后续将分篇一一分析.敬请关注! 参与贡献 访问注解仓库地址 Fork 本仓库 >> 新建 Feat_xxx 分支 >> 提交代码注解 >> 新建 Pull Request 新建 Issue 喜欢就大方 点赞+关注+收藏 吧 各大站点搜 "鸿蒙内核源码分析" ,快速找到组织, 欢迎转载,请注明出处. 百万汉字注解 >> 精读内核源码,中文注解分析, 深挖地基工程,大脑永久记忆,四大码仓每日同步更新< gitee | github | csdn | coding > 百篇博客分析 >> 故事说内核,问答式导读,生活式比喻,表格化说明,图形化展示,多站点每日同步更新< oschina | csdn | weharmony >

优秀的个人博客,低调大师

| 中文注解HarmonyOS源码 | v36.02

百万汉字注解 >> 精读内核源码,中文注解分析, 深挖地基工程,大脑永久记忆,四大码仓每日同步更新< Gitee | Github | CSDN | Coding > 百篇博客分析 >> 故事说内核,问答式导读,生活式比喻,表格化说明,图形化展示,多站点每日同步更新< OSCHINA | CSDN | WeHarmony > 系列篇硬件部分说明基于ARM720T.pdf文档. 本篇说清楚CPU的工作模式 读本篇之前建议先读鸿蒙内核源码分析(总目录)其他篇. 正如一个互联网项目的后台管理系统有权限管理一样,CPU工作是否也有权限(模式)? 一个成熟的软硬件架构,肯定会有这些设计,只是大部分人不知道,也不需要知道,老百姓就干好老百姓的活就行了,有工作能吃饱饭就知足了,宫的事你管那么多干嘛,你也管不了. 应用程序就只关注应用功能,业务逻辑相关的部分就行了,底层实现对应用层屏蔽的越干净系统设计的就越优良. 但鸿蒙内核源码分析系列篇的定位就是要把整个底层解剖,全部掰开,看看宫里究竟发生了么事.从本篇开始要接触大量的汇编的代码,将鸿蒙内核的每段汇编代码一一说明白.如此才能知道最开始的开始发生了什么,最后的最后又发生了什么. 七种模式 先看一张图,图来源于 ARM720T.pdf第43页,在ARM体系中,CPU工作在以下七种模式中: 用户模式(usr):属于正常的用户模式,ARM处理器正常的程序执行状态。 快速中断模式(fiq):用于处理快速中断,对高速数据传输或通道处理 外部中断模式(irq):对一般情况下的中断进行处理。 管理模式(svc):属于操作系统使用的保护模式,处理软件中断swi reset。 数据访问终止模式(abt):当数据或指令预取终止时进入该模式,可用于处理存储器故障、实现虚拟存储器和存储器保护。 系统模式(sys):运行具有特权的操作系统任务。 未定义指令中止模式(und):处理未定义的指令陷阱,当未定义的指令执行时进入该模式,可用于支持硬件协处理器的软件仿真。 除了用户模式外,其它六种均为特权模式或者叫异常模式。每种模式都有自己独立的入口和独立的运行栈空间.系列篇之CPU篇已介绍过只要提供了入口函数和运行空间,CPU就可以干活了.入口函数解决了指令来源问题,运行空间解决了指令的运行问题. 而且在多核情况下,每个CPU核的每种异常模式都有自己独立的栈空间.注意是异常模式下的栈空间,用户模式的栈空间是由用户(应用)程序提供的. 如何让这七种模式能流畅的跑起来呢? 至少需要以下解决三个基本问题. 栈空间是怎么申请的?申请了多大? 被切换中的模式代码放在哪里?谁来安排它们放在哪里? 模式之间是怎么切换的?状态怎么保存? 本篇代码来源于鸿蒙内核源码之reset_vector_mp.S,点击查看 这个汇编文件大概 500多行,非常重要,本篇受限于篇幅只列出一小部分,说清楚以上三个问题.系列其余篇中将详细说明每段汇编代码的作用和实现,可前往查阅. 1.异常模式栈空间怎么申请? 鸿蒙是如何给异常模式申请栈空间的 #define CORE_NUM LOSCFG_KERNEL_SMP_CORE_NUM //CPU 核数 #ifdef LOSCFG_GDB #define OS_EXC_UNDEF_STACK_SIZE 512 #define OS_EXC_ABT_STACK_SIZE 512 #else #define OS_EXC_UNDEF_STACK_SIZE 40 #define OS_EXC_ABT_STACK_SIZE 40 #endif #define OS_EXC_FIQ_STACK_SIZE 64 #define OS_EXC_IRQ_STACK_SIZE 64 #define OS_EXC_SVC_STACK_SIZE 0x2000 //8K #define OS_EXC_STACK_SIZE 0x1000 //4K @六种特权模式申请对应的栈运行空间 __undef_stack: .space OS_EXC_UNDEF_STACK_SIZE * CORE_NUM __undef_stack_top: __abt_stack: .space OS_EXC_ABT_STACK_SIZE * CORE_NUM __abt_stack_top: __irq_stack: .space OS_EXC_IRQ_STACK_SIZE * CORE_NUM __irq_stack_top: __fiq_stack: .space OS_EXC_FIQ_STACK_SIZE * CORE_NUM __fiq_stack_top: __svc_stack: .space OS_EXC_SVC_STACK_SIZE * CORE_NUM __svc_stack_top: __exc_stack: .space OS_EXC_STACK_SIZE * CORE_NUM __exc_stack_top: 代码解读 六种异常模式都有自己独立的栈空间 每种模式的OS_EXC_***_STACK_SIZE栈大小都不一样,最大是管理模式(svc)8K,最小的只有40个字节. svc模式为什么要这么大呢? 因为开机代码和系统调用代码的运行都在管理模式,系统调用的函数实现往往较复杂,最大不能超过8K. 例如:某个系统调用中定义一个8K的局部变量,内核肯定立马闪蹦.因为栈将溢出,处理异常的程序出现了异常,后面就再也没人兜底了,只能是死局. 鸿蒙是支持多核处理的,CORE_NUM表明,每个CPU核的每种异常模式都有自己的独立栈空间.注意理解这个是理解内核代码的基础.否则会一头雾水. 2.异常模式入口地址在哪? 再看一张图,图来源于 ARM720T.pdf 第56页 这就是一切一切的开始,指定所有异常模式的入口地址表,这就是规定,没得商量的.在低地址情况下.开机代码就是放在 0x00000000的位置, 触发开机键后,硬件将PC寄存器置为0x00000000,开始了万里长征的第一步.在系统运行过程中就这么来回跳. b reset_vector @开机代码 b _osExceptUndefInstrHdl @异常处理之CPU碰到不认识的指令 b _osExceptSwiHdl @异常处理之:软中断 b _osExceptPrefetchAbortHdl @异常处理之:取指异常 b _osExceptDataAbortHdl @异常处理之:数据异常 b _osExceptAddrAbortHdl @异常处理之:地址异常 b OsIrqHandler @异常处理之:硬中断 b _osExceptFiqHdl @异常处理之:快中断 以上是各个异常情况下的入口地址,在reset_vector_mp.S中都能找到,经过编译链接后就会变成 b 0x00000000 @开机代码 b 0x00000004 @异常处理之CPU碰到不认识的指令 b 0x00000008 @异常处理之:软中断 b 0x0000000C @异常处理之:取指异常 b 0x00000010 @异常处理之:数据异常 b 0x00000014 @异常处理之:地址异常 b 0x00000018 @异常处理之:硬中断 b 0x0000001C @异常处理之:快中断 不管是主动切换的异常,还是被动切换的异常,都会先跳到对应的入口去处理.每个异常的代码都起始于汇编,处理完了再切回去. 举个例子:某个应用程序调用了系统调用(比如创建定时器),会经过以下大致过程: swi指令将用户模式切换到管理模式(svc) 在管理模式中先保存用户模式的现场信息(R0-R15寄存器值入栈) 获取系统调用号,知道是调用了哪个系统调用 查询系统调用对应的注册函数 执行真正的创建定时器函数 执行完成后,恢复用户模式的现场信息(R0-R15寄存器值出栈) 跳回用户模式继续执行 各异常处理代码很多,不一一列出,本篇只列出开机代码,请尝试读懂鸿蒙内核开机代码,后续详细说明每行代码的用处. 开机代码 reset_vector: //开机代码 /* clear register TPIDRPRW */ mov r0, #0 @r0 = 0 mcr p15, 0, r0, c13, c0, 4 @c0,c13 = 0, C13为进程标识符 含义见 ARM720T.PDF 第64页 /* do some early cpu setup: i/d cache disable, mmu disabled */ @禁用MMU, i/d缓存 mrc p15, 0, r0, c1, c0, 0 @r0 = c1 ,c1寄存器详细解释见第64页 bic r0, #(1<<12) @位清除指令,清除r0的第11位 bic r0, #(1<<2 | 1<<0) @清除第0和2位 ,禁止 MMU和缓存 0位:MMU enable/disable 2位:Cache enable/disable mcr p15, 0, r0, c1, c0, 0 @c1=r0 /* r11: delta of physical address and virtual address */@物理地址和虚拟地址的增量 adr r11, pa_va_offset @将基于PC相对偏移的地址pa_va_offset值读取到寄存器R11中 ldr r0, [r11] @将R11的值给r0 sub r11, r11, r0 @r11 = r11 - r0 mrc p15, 0, r12, c0, c0, 5 /* r12: get cpuid */ @获取CPUID and r12, r12, #MPIDR_CPUID_MASK @r12经过掩码过滤 cmp r12, #0 @当前是否为0号CPU bne secondary_cpu_init @不是0号主CPU则调用secondary_cpu_init /* if we need to relocate to proper location or not */ adr r4, __exception_handlers /* r4: base of load address */ @r4获得加载基地址 ldr r5, =SYS_MEM_BASE /* r5: base of physical address */@r5获得物理基地址 subs r12, r4, r5 /* r12: delta of load address and physical address */ @r12=r4-r5 加载地址和物理地址的增量 beq reloc_img_to_bottom_done /* if we load image at the bottom of physical address */ /* we need to relocate image at the bottom of physical address */ ldr r7, =__exception_handlers /* r7: base of linked address (or vm address) */ ldr r6, =__bss_start /* r6: end of linked address (or vm address) */ sub r6, r7 /* r6: delta of linked address (or vm address) */ add r6, r4 /* r6: end of load address */ 异常的优先级 当同时出现多个异常时,该响应哪一个呢?这涉及到了异常的优先级,顺序如下 Reset (highest priority). Data Abort. FIQ. IRQ. Prefetch Abort. Undefined Instruction, SWI (lowest priority). 可以看出swi的优先级最低,swi就是软中断,系统调用就是通过它来实现的. 3.异常模式怎么切换? 写应用程序经常会用到状态,来记录各种分支逻辑,传递参数.这么多异常模式,相互切换,中间肯定会有很多的状态需要保存.比如:如何能知道当前运行在哪种模式下?怎么查?去哪里查呢? 答案是: CPSR 和 SPSR CPSR:程序状态寄存器(current program status register) (当前程序状态寄存器),在任何处理器模式下被访问。 SPSR:程序状态保存寄存器(saved program status register),每一种处理器模式下都有一个状态寄存器SPSR,SPSR用于保存CPSR的状态,以便异常返回后恢复异常发生时的工作状态。当特定 的异常中断发生时,这个寄存器用于存放当前程序状态寄存器的内容。在异常中断退出时,可以用SPSR来恢复CPSR。 这些寄存器: 保存有关最近执行的ALU操作的信息 控制中断的启用和禁用 设置处理器操作模式 喜欢就大方 点赞+关注+收藏 吧 各大站点搜 "鸿蒙内核源码分析" ,快速找到组织. 百万汉字注解 >> 精读内核源码,中文注解分析, 深挖地基工程,大脑永久记忆,四大码仓每日同步更新< Gitee | Github | CSDN | Coding > 百篇博客分析 >> 故事说内核,问答式导读,生活式比喻,表格化说明,图形化展示,多站点每日同步更新< OSCHINA | CSDN | WeHarmony >

优秀的个人博客,低调大师

| 中文注解HarmonyOS源码 | v36.01

百万汉字注解 >> 精读内核源码,中文注解分析, 深挖地基工程,大脑永久记忆,四大码仓每日同步更新< Gitee | Github | CSDN | Coding > 百篇博客分析 >> 故事说内核,问答式导读,生活式比喻,表格化说明,图形化展示,多站点每日同步更新< OSCHINA | CSDN | WeHarmony > 系列篇硬件部分说明基于ARM720T.pdf文档. 本篇说清楚CPU的工作模式 读本篇之前建议先读鸿蒙内核源码分析(总目录)其他篇. 正如一个互联网项目的后台管理系统有权限管理一样,CPU工作是否也有权限(模式)? 一个成熟的软硬件架构,肯定会有这些设计,只是大部分人不知道,也不需要知道,老百姓就干好老百姓的活就行了,有工作能吃饱饭就知足了,宫的事你管那么多干嘛,你也管不了. 应用程序就只关注应用功能,业务逻辑相关的部分就行了,底层实现对应用层屏蔽的越干净系统设计的就越优良. 但鸿蒙内核源码分析系列篇的定位就是要把整个底层解剖,全部掰开,看看宫里究竟发生了么事.从本篇开始要接触大量的汇编的代码,将鸿蒙内核的每段汇编代码一一说明白.如此才能知道最开始的开始发生了什么,最后的最后又发生了什么. 七种模式 先看一张图,图来源于 ARM720T.pdf 第43页,在ARM体系中,CPU工作在以下七种模式中: 用户模式(usr):属于正常的用户模式,ARM处理器正常的程序执行状态。 快速中断模式(fiq):用于处理快速中断,对高速数据传输或通道处理 外部中断模式(irq):对一般情况下的中断进行处理。 管理模式(svc):属于操作系统使用的保护模式,处理软件中断swi reset。 数据访问终止模式(abt):当数据或指令预取终止时进入该模式,可用于处理存储器故障、实现虚拟存储器和存储器保护。 系统模式(sys):运行具有特权的操作系统任务。 未定义指令中止模式(und):处理未定义的指令陷阱,当未定义的指令执行时进入该模式,可用于支持硬件协处理器的软件仿真。 除了用户模式外,其它六种均为特权模式或者叫异常模式。每种模式都有自己独立的入口和独立的运行栈空间.系列篇之CPU篇已介绍过只要提供了入口函数和运行空间,CPU就可以干活了.入口函数解决了指令来源问题,运行空间解决了指令的运行问题. 而且在多核情况下,每个CPU核的每种异常模式都有自己独立的栈空间.注意是异常模式下的栈空间,用户模式的栈空间是由用户(应用)程序提供的. 如何让这七种模式能流畅的跑起来呢? 至少需要以下解决三个基本问题. 栈空间是怎么申请的?申请了多大? 被切换中的模式代码放在哪里?谁来安排它们放在哪里? 模式之间是怎么切换的?状态怎么保存? 本篇代码来源于鸿蒙内核源码之reset_vector_mp.S,点击查看 这个汇编文件大概 500多行,非常重要,本篇受限于篇幅只列出一小部分,说清楚以上三个问题.系列其余篇中将详细说明每段汇编代码的作用和实现,可前往查阅. 1.异常模式栈空间怎么申请? 鸿蒙是如何给异常模式申请栈空间的 #define CORE_NUM LOSCFG_KERNEL_SMP_CORE_NUM //CPU 核数 #ifdef LOSCFG_GDB #define OS_EXC_UNDEF_STACK_SIZE 512 #define OS_EXC_ABT_STACK_SIZE 512 #else #define OS_EXC_UNDEF_STACK_SIZE 40 #define OS_EXC_ABT_STACK_SIZE 40 #endif #define OS_EXC_FIQ_STACK_SIZE 64 #define OS_EXC_IRQ_STACK_SIZE 64 #define OS_EXC_SVC_STACK_SIZE 0x2000 //8K #define OS_EXC_STACK_SIZE 0x1000 //4K @六种特权模式申请对应的栈运行空间 __undef_stack: .space OS_EXC_UNDEF_STACK_SIZE * CORE_NUM __undef_stack_top: __abt_stack: .space OS_EXC_ABT_STACK_SIZE * CORE_NUM __abt_stack_top: __irq_stack: .space OS_EXC_IRQ_STACK_SIZE * CORE_NUM __irq_stack_top: __fiq_stack: .space OS_EXC_FIQ_STACK_SIZE * CORE_NUM __fiq_stack_top: __svc_stack: .space OS_EXC_SVC_STACK_SIZE * CORE_NUM __svc_stack_top: __exc_stack: .space OS_EXC_STACK_SIZE * CORE_NUM __exc_stack_top: 代码解读 六种异常模式都有自己独立的栈空间 每种模式的OS_EXC_***_STACK_SIZE栈大小都不一样,最大是管理模式(svc)8K,最小的只有40个字节. svc模式为什么要这么大呢? 因为开机代码和系统调用代码的运行都在管理模式,系统调用的函数实现往往较复杂,最大不能超过8K. 例如:某个系统调用中定义一个8K的局部变量,内核肯定立马闪蹦.因为栈将溢出,处理异常的程序出现了异常,后面就再也没人兜底了,只能是死局. 鸿蒙是支持多核处理的,CORE_NUM表明,每个CPU核的每种异常模式都有自己的独立栈空间.注意理解这个是理解内核代码的基础.否则会一头雾水. 2.异常模式入口地址在哪? 再看一张图,图来源于 ARM720T.pdf 第56页 这就是一切一切的开始,指定所有异常模式的入口地址表,这就是规定,没得商量的.在低地址情况下.开机代码就是放在 0x00000000的位置, 触发开机键后,硬件将PC寄存器置为0x00000000,开始了万里长征的第一步.在系统运行过程中就这么来回跳. b reset_vector @开机代码 b _osExceptUndefInstrHdl @异常处理之CPU碰到不认识的指令 b _osExceptSwiHdl @异常处理之:软中断 b _osExceptPrefetchAbortHdl @异常处理之:取指异常 b _osExceptDataAbortHdl @异常处理之:数据异常 b _osExceptAddrAbortHdl @异常处理之:地址异常 b OsIrqHandler @异常处理之:硬中断 b _osExceptFiqHdl @异常处理之:快中断 以上是各个异常情况下的入口地址,在reset_vector_mp.S中都能找到,经过编译链接后就会变成 b 0x00000000 @开机代码 b 0x00000004 @异常处理之CPU碰到不认识的指令 b 0x00000008 @异常处理之:软中断 b 0x0000000C @异常处理之:取指异常 b 0x00000010 @异常处理之:数据异常 b 0x00000014 @异常处理之:地址异常 b 0x00000018 @异常处理之:硬中断 b 0x0000001C @异常处理之:快中断 不管是主动切换的异常,还是被动切换的异常,都会先跳到对应的入口去处理.每个异常的代码都起始于汇编,处理完了再切回去.举个例子: 某个应用程序调用了系统调用(比如创建定时器),会经过以下大致过程: swi指令将用户模式切换到管理模式(svc) 在管理模式中先保存用户模式的现场信息(R0-R15寄存器值入栈) 获取系统调用号,知道是调用了哪个系统调用 查询系统调用对应的注册函数 执行真正的创建定时器函数 执行完成后,恢复用户模式的现场信息(R0-R15寄存器值出栈) 跳回用户模式继续执行 各异常处理代码很多,不一一列出,本篇只列出开机代码,请尝试读懂鸿蒙内核开机代码,后续讲详细说明每行代码的用处. 开机代码 reset_vector: //开机代码 /* clear register TPIDRPRW */ mov r0, #0 @r0 = 0 mcr p15, 0, r0, c13, c0, 4 @c0,c13 = 0, C13为进程标识符 含义见 ARM720T.PDF 第64页 /* do some early cpu setup: i/d cache disable, mmu disabled */ @禁用MMU, i/d缓存 mrc p15, 0, r0, c1, c0, 0 @r0 = c1 ,c1寄存器详细解释见第64页 bic r0, #(1<<12) @位清除指令,清除r0的第11位 bic r0, #(1<<2 | 1<<0) @清除第0和2位 ,禁止 MMU和缓存 0位:MMU enable/disable 2位:Cache enable/disable mcr p15, 0, r0, c1, c0, 0 @c1=r0 /* r11: delta of physical address and virtual address */@物理地址和虚拟地址的增量 adr r11, pa_va_offset @将基于PC相对偏移的地址pa_va_offset值读取到寄存器R11中 ldr r0, [r11] @将R11的值给r0 sub r11, r11, r0 @r11 = r11 - r0 mrc p15, 0, r12, c0, c0, 5 /* r12: get cpuid */ @获取CPUID and r12, r12, #MPIDR_CPUID_MASK @r12经过掩码过滤 cmp r12, #0 @当前是否为0号CPU bne secondary_cpu_init @不是0号主CPU则调用secondary_cpu_init /* if we need to relocate to proper location or not */ adr r4, __exception_handlers /* r4: base of load address */ @r4获得加载基地址 ldr r5, =SYS_MEM_BASE /* r5: base of physical address */@r5获得物理基地址 subs r12, r4, r5 /* r12: delta of load address and physical address */ @r12=r4-r5 加载地址和物理地址的增量 beq reloc_img_to_bottom_done /* if we load image at the bottom of physical address */ /* we need to relocate image at the bottom of physical address */ ldr r7, =__exception_handlers /* r7: base of linked address (or vm address) */ ldr r6, =__bss_start /* r6: end of linked address (or vm address) */ sub r6, r7 /* r6: delta of linked address (or vm address) */ add r6, r4 /* r6: end of load address */ 异常的权限 当同时出现多个异常时,该响应哪一个呢?就涉及到了异常的权限,如下 Reset (highest priority). Data Abort. FIQ. IRQ. Prefetch Abort. Undefined Instruction, SWI (lowest priority). 可以看出swi的权限最低,swi就是软件中断,系统调用就是通过它来实现的. 3.异常模式怎么切换? 写应用程序经常会用到状态,来记录各种分支逻辑,传递参数.这么多异常模式,相互切换,中间肯定会有很多的状态需要保存.比如:如何能知道当前运行在哪种模式下?怎么查?去哪里查呢? 答案是: CPSR 和 SPSR CPSR:程序状态寄存器(current program status register) (当前程序状态寄存器),在任何处理器模式下被访问。 SPSR:程序状态保存寄存器(saved program status register),每一种处理器模式下都有一个状态寄存器SPSR,SPSR用于保存CPSR的状态,以便异常返回后恢复异常发生时的工作状态。当特定 的异常中断发生时,这个寄存器用于存放当前程序状态寄存器的内容。在异常中断退出时,可以用SPSR来恢复CPSR。 这些寄存器: 保存有关最近执行的ALU操作的信息 控制中断的启用和禁用 设置处理器操作模式 喜欢就大方 点赞+关注+收藏 吧 各大站点搜 "鸿蒙内核源码分析" ,快速找到组织. 百万汉字注解 >> 精读内核源码,中文注解分析, 深挖地基工程,大脑永久记忆,四大码仓每日同步更新< Gitee | Github | CSDN | Coding > 百篇博客分析 >> 故事说内核,问答式导读,生活式比喻,表格化说明,图形化展示,多站点每日同步更新< OSCHINA | CSDN | WeHarmony >

优秀的个人博客,低调大师

鸿蒙内核源码分析(互斥锁篇) | 互斥锁比自旋锁丰满多了 | 中文注解HarmonyOS源码 | v27.01

鸿蒙内核源码中文注解 >> 精读内核源码,中文注解分析, 深挖地基工程,大脑永久记忆, 四大源码仓每日同步更新< Gitee | Github | CSDN | Coding > 鸿蒙内核源码分析博客 >> 故事说内核,问答式导读,生活式比喻,表格化说明,图形化展示,主流站点每日同步更新< OSCHINA | CSDN | WeHarmony > 本篇说清楚互斥锁 读本篇之前建议先读鸿蒙内核源码分析(总目录)之自旋锁篇. 内核中哪些模块会用到互斥锁?看图: 图中是内核有关模块对互斥锁初始化,有文件,有内存,用消息队列等等,使用面非常的广.其实在给内核源码加注的过程中,会看到大量的自旋锁和互斥锁,它们的存在有序的保证了内核和应用程序的正常运行.是非常基础和重要的功能. 概述 自旋锁 和 互斥锁 虽都是锁,但解决的问题不同, 自旋锁解决用于CPU核间共享内存的竞争,而互斥锁解决线程(任务)间共享内存的竞争. 自旋锁的特点是死守共享资源,拿不到锁,CPU选择睡眠,等待其他CPU释放资源.所以共享代码段不能太复杂,否则容易死锁,休克. 互斥锁的特点是拿不到锁往往原任务阻塞,切换到新任务运行.CPU是会一直跑的.这样很容易会想到几个问题: 第一:会出现很多任务在等同一把锁的情况出现,因为切换新任务也可能因要同一把锁而被阻塞,CPU又被调去跑新新任务了.这样就会出现一个等锁的链表. 第二:持有锁的一方再申请同一把锁时还能成功吗? 答案是可以的,这种锁叫递归锁,是鸿蒙内核默认方式. 第三:当优先级很高的A任务要锁失败,主动让出CPU进入睡眠,而如果持有锁的B任务优先级很低, 迟迟等不到调度不到B任务运行,无法释放锁怎么办? 答案是会临时调整B任务的优先级,调到A一样高,这样B能很快的被调度到,等B释放锁后其优先级又会被打回原形.所以一个任务的优先级会看情况时高时低. 第四:B任务释放锁之后要主动唤醒等锁的任务链表,使他们能加入就绪队列,等待被调度.调度算法是一视同仁的,它只看优先级. 带着这些问题,进入鸿蒙内核互斥锁的实现代码,本篇代码量较大, 每行代码都一一注解说明. 互斥锁长什么样? enum { LOS_MUX_PRIO_NONE = 0, //线程的优先级和调度不会受到互斥锁影响,先来后到,普通排队. LOS_MUX_PRIO_INHERIT = 1, //当高优先级的等待低优先级的线程释放锁时,低优先级的线程以高优先级线程的优先级运行。 //当线程解锁互斥量时,线程的优先级自动被将到它原来的优先级 LOS_MUX_PRIO_PROTECT = 2 //详见:OsMuxPendOp中的注解,详细说明了LOS_MUX_PRIO_PROTECT的含义 }; enum { LOS_MUX_NORMAL = 0, //非递归锁 只有[0.1]两个状态,不做任何特殊的错误检,不进行deadlock detection(死锁检测) LOS_MUX_RECURSIVE = 1, //递归锁 允许同一线程在互斥量解锁前对该互斥量进行多次加锁。递归互斥量维护锁的计数,在解锁次数和加锁次数不相同的情况下,不会释放锁,别的线程就无法加锁此互斥量。 LOS_MUX_ERRORCHECK = 2, //进行错误检查,如果一个线程企图对一个已经锁住的mutex进行relock或对未加锁的unlock,将返回一个错误。 LOS_MUX_DEFAULT = LOS_MUX_RECURSIVE //鸿蒙系统默认使用递归锁 }; typedef struct { //互斥锁的属性 UINT8 protocol; //协议 UINT8 prioceiling; //优先级上限 UINT8 type; //类型属性 UINT8 reserved; //保留字段 } LosMuxAttr; typedef struct OsMux { //互斥锁结构体 UINT32 magic; /**< magic number */ //魔法数字 LosMuxAttr attr; /**< Mutex attribute */ //互斥锁属性 LOS_DL_LIST holdList; /**< The task holding the lock change */ //当有任务拿到本锁时,通过holdList节点把锁挂到该任务的锁链表上 LOS_DL_LIST muxList; /**< Mutex linked list */ //等这个锁的任务链表,上面挂的都是任务,注意和holdList的区别. VOID *owner; /**< The current thread that is locking a mutex */ //当前拥有这把锁的任务 UINT16 muxCount; /**< Times of locking a mutex */ //锁定互斥体的次数,递归锁允许多次 } LosMux; 这互斥锁长的明显的比自旋锁丰满多啦,还记得自旋锁的样子吗,就一个变量,单薄到令人心疼. 初始化 LITE_OS_SEC_TEXT UINT32 LOS_MuxInit(LosMux *mutex, const LosMuxAttr *attr) { //... SCHEDULER_LOCK(intSave); //拿到调度自旋锁 mutex->muxCount = 0; //锁定互斥量的次数 mutex->owner = NULL; //持有该锁的任务 LOS_ListInit(&mutex->muxList); //初始化等待该锁的任务链表 mutex->magic = OS_MUX_MAGIC; //固定标识,互斥锁的魔法数字 SCHEDULER_UNLOCK(intSave); //释放调度自旋锁 return LOS_OK; } 留意mutex->muxList,这又是一个双向链表, 双向链表是内核最重要的结构体,不仅仅是鸿蒙内核,在linux内核中(list_head)又何尝不是,牢牢的寄生在宿主结构体上.muxList上挂的是未来所有等待这把锁的任务. 三种申请模式 申请互斥锁有三种模式:无阻塞模式、永久阻塞模式、定时阻塞模式。 无阻塞模式:即任务申请互斥锁时,入参timeout等于0。若当前没有任务持有该互斥锁,或者持有该互斥锁的任务和申请该互斥锁的任务为同一个任务,则申请成功,否则立即返回申请失败。 永久阻塞模式:即任务申请互斥锁时,入参timeout等于0xFFFFFFFF。若当前没有任务持有该互斥锁,则申请成功。否则,任务进入阻塞态,系统切换到就绪任务中优先级最高者继续执行。任务进入阻塞态后,直到有其他任务释放该互斥锁,阻塞任务才会重新得以执行。 定时阻塞模式:即任务申请互斥锁时,0<timeout<0xFFFFFFFF。若当前没有任务持有该互斥锁,则申请成功。否则该任务进入阻塞态,系统切换到就绪任务中优先级最高者继续执行。任务进入阻塞态后,超时前如果有其他任务释放该互斥锁,则该任务可成功获取互斥锁继续执行,若超时前未获取到该互斥锁,接口将返回超时错误码。 如果有任务阻塞于该互斥锁,则唤醒被阻塞任务中优先级最高的,该任务进入就绪态,并进行任务调度。 如果没有任务阻塞于该互斥锁,则互斥锁释放成功。 申请互斥锁主函数 OsMuxPendOp //互斥锁的主体函数,由OsMuxlockUnsafe调用,互斥锁模块最重要的几个函数之一 //最坏情况就是拿锁失败,让出CPU,变成阻塞任务,等别的任务释放锁后排到自己了接着执行. STATIC UINT32 OsMuxPendOp(LosTaskCB *runTask, LosMux *mutex, UINT32 timeout) { UINT32 ret; LOS_DL_LIST *node = NULL; LosTaskCB *owner = NULL; if ((mutex->muxList.pstPrev == NULL) || (mutex->muxList.pstNext == NULL)) {//列表为空时的处理 /* This is for mutex macro initialization. */ mutex->muxCount = 0;//锁计数器清0 mutex->owner = NULL;//锁没有归属任务 LOS_ListInit(&mutex->muxList);//初始化锁的任务链表,后续申请这把锁任务都会挂上去 } if (mutex->muxCount == 0) {//无task用锁时,肯定能拿到锁了.在里面返回 mutex->muxCount++; //互斥锁计数器加1 mutex->owner = (VOID *)runTask; //当前任务拿到锁 LOS_ListTailInsert(&runTask->lockList, &mutex->holdList);//持有锁的任务改变了,节点挂到当前task的锁链表 if ((runTask->priority > mutex->attr.prioceiling) && (mutex->attr.protocol == LOS_MUX_PRIO_PROTECT)) {//看保护协议的做法是怎样的? LOS_BitmapSet(&runTask->priBitMap, runTask->priority);//1.priBitMap是记录任务优先级变化的位图,这里把任务当前的优先级记录在priBitMap OsTaskPriModify(runTask, mutex->attr.prioceiling);//2.把高优先级的mutex->attr.prioceiling设为当前任务的优先级. }//注意任务优先级有32个, 是0最高,31最低!!!这里等于提高了任务的优先级,目的是让其在下次调度中继续提高被选中的概率,从而快速的释放锁. return LOS_OK; } //递归锁muxCount>0 如果是递归锁就要处理两种情况 1.runtask持有锁 2.锁被别的任务拿走了 if (((LosTaskCB *)mutex->owner == runTask) && (mutex->attr.type == LOS_MUX_RECURSIVE)) {//第一种情况 runtask是锁持有方 mutex->muxCount++; //递归锁计数器加1,递归锁的目的是防止死锁,鸿蒙默认用的就是递归锁(LOS_MUX_DEFAULT = LOS_MUX_RECURSIVE) return LOS_OK; //成功退出 } //到了这里说明锁在别的任务那里,当前任务只能被阻塞了. if (!timeout) {//参数timeout表示等待多久再来拿锁 return LOS_EINVAL;//timeout = 0表示不等了,没拿到锁就返回不纠结,返回错误.见于LOS_MuxTrylock } //自己要被阻塞,只能申请调度,让出CPU core 让别的任务上 if (!OsPreemptableInSched()) {//不能申请调度 (不能调度的原因是因为没有持有调度任务自旋锁) return LOS_EDEADLK;//返回错误,自旋锁被别的CPU core 持有 } OsMuxBitmapSet(mutex, runTask, (LosTaskCB *)mutex->owner);//设置锁位图,尽可能的提高锁持有任务的优先级 owner = (LosTaskCB *)mutex->owner; //记录持有锁的任务 runTask->taskMux = (VOID *)mutex; //记下当前任务在等待这把锁 node = OsMuxPendFindPos(runTask, mutex);//在等锁链表中找到一个优先级比当前任务更低的任务 ret = OsTaskWait(node, timeout, TRUE);//task陷入等待状态 TRUE代表需要调度 if (ret == LOS_ERRNO_TSK_TIMEOUT) {//这行代码虽和OsTaskWait挨在一起,但要过很久才会执行到,因为在OsTaskWait中CPU切换了任务上下文 runTask->taskMux = NULL;// 所以重新回到这里时可能已经超时了 ret = LOS_ETIMEDOUT;//返回超时 } if (timeout != LOS_WAIT_FOREVER) {//不是永远等待的情况 OsMuxBitmapRestore(mutex, runTask, owner);//恢复锁的位图 } return ret; } 释放锁的主体函数 OsMuxPostOp //是否有其他任务持有互斥锁而处于阻塞状,如果是就要唤醒它,注意唤醒一个任务的操作是由别的任务完成的 //OsMuxPostOp只由OsMuxUnlockUnsafe,参数任务归还锁了,自然就会遇到锁要给谁用的问题, 因为很多任务在申请锁,由OsMuxPostOp来回答这个问题 STATIC UINT32 OsMuxPostOp(LosTaskCB *taskCB, LosMux *mutex, BOOL *needSched) { LosTaskCB *resumedTask = NULL; if (LOS_ListEmpty(&mutex->muxList)) {//如果互斥锁列表为空 LOS_ListDelete(&mutex->holdList);//把持有互斥锁的节点摘掉 mutex->owner = NULL; return LOS_OK; } resumedTask = OS_TCB_FROM_PENDLIST(LOS_DL_LIST_FIRST(&(mutex->muxList)));//拿到等待互斥锁链表的第一个任务实体,接下来要唤醒任务 if (mutex->attr.protocol == LOS_MUX_PRIO_INHERIT) {//互斥锁属性协议是继承会怎么操作? if (resumedTask->priority > taskCB->priority) {//拿到锁的任务优先级低于参数任务优先级 if (LOS_HighBitGet(taskCB->priBitMap) != resumedTask->priority) {//参数任务bitmap中最低的优先级不等于等待锁的任务优先级 LOS_BitmapClr(&taskCB->priBitMap, resumedTask->priority);//把等待任务锁的任务的优先级记录在参数任务的bitmap中 } } else if (taskCB->priBitMap != 0) {//如果bitmap不等于0说明参数任务至少有任务调度的优先级 OsMuxPostOpSub(taskCB, mutex);// } } mutex->muxCount = 1;//互斥锁数量为1 mutex->owner = (VOID *)resumedTask;//互斥锁的持有人换了 resumedTask->taskMux = NULL;//resumedTask不再等锁了 LOS_ListDelete(&mutex->holdList);//自然要从等锁链表中把自己摘出去 LOS_ListTailInsert(&resumedTask->lockList, &mutex->holdList);//把锁挂到恢复任务的锁链表上,lockList是任务持有的所有锁记录 OsTaskWake(resumedTask);//resumedTask有了锁就唤醒它,因为当初在没有拿到锁时处于了pend状态 if (needSched != NULL) {//如果不为空 *needSched = TRUE;//就走起再次调度流程 } return LOS_OK; } 总结 1.互斥锁解决的是任务间竞争共享内存的问题. 2.申请锁失败的任务会进入睡眠OsTaskWait,内核会比较持有锁的任务和申请锁任务的优先级,把持有锁的任务优先级调到尽可能的高,以便更快的被调度执行,早日释放锁. 3.释放锁的任务会在等锁链表中找一个高优先级任务,通过OsTaskWake唤醒它,并向调度算法申请调度.但要注意,调度算法只是按优先级来调度,并不保证调度后的任务一定是要唤醒的任务. 4.互斥锁篇关键是看懂 OsMuxPendOp 和 OsMuxPostOp 两个函数. 喜欢就请收藏吧 各大站点搜 "鸿蒙内核源码分析" ,快速找到组织. 鸿蒙内核源码中文注解 >> 精读内核源码,中文注解分析, 深挖地基工程,大脑永久记忆, 四大源码仓每日同步更新< Gitee | Github | CSDN | Coding > 鸿蒙内核源码分析博客 >> 故事说内核,问答式导读,生活式比喻,表格化说明,图形化展示,主流站点每日同步更新< OSCHINA | CSDN | WeHarmony >

优秀的个人博客,低调大师

| 中文注解HarmonyOS源码 | v23.01

鸿蒙内核源码注释 >> 精读内核源码,中文注解分析,深挖地基工程,大脑永久记忆,四大码仓每日同步更新 鸿蒙内核源码分析 >> 故事说内核,问答式导读,生活式比喻,表格化说明,图形化展示,主流站点每日同步更新 汇编如何传复杂的参数? 鸿蒙内核源码分析(汇编基础篇) | CPU在哪里打卡上班 ? < CSDN | OSCHINA | WeHarmony | 源动力 >中很详细的介绍了一段具有代表性很经典的汇编代码,有循环,有判断,有运算,有多级函数调用。但有一个问题没有涉及,就是很复杂的参数如何处理? 实际在开发过程中函数参数往往是很复杂的结构体,那复杂参数(比如结构体)怎么传递呢? 先看一段C语言和编译的汇编代码 #include <stdio.h> #include <math.h> struct reg{ int Rn[100]; int pc; }; int fp(reg cpu) { return cpu.Rn[0] * cpu.pc; } int main() { reg cpu; cpu.Rn[0] = 1; cpu.pc = 2; return fp(cpu); } //编译器: armv7-a gcc (9.2.1) fp(reg): sub sp, sp, #16 @申请栈空间 str fp, [sp, #-4]! @保护fp帧指针,等同于push {fp} add fp, sp, #0 @fp新值,同时也指向了栈顶 add ip, fp, #4 @定位到入栈口,让剩余参数依次入栈 stm ip, {r0, r1, r2, r3}@r0-r3入栈保存 ldr r3, [fp, #4] @取值cpu.pc = 2 ldr r2, [fp, #404] @取值cpu.Rn[0] = 1 mul r3, r2, r3 @cpu.Rn[0] * cpu.pc mov r0, r3 @返回值r0带回 add sp, fp, #0 @重置sp ldr fp, [sp], #4 @重置fp add sp, sp, #16 @归还栈空间 bx lr @跳回main函数 main: push {fp, lr} @入栈保存调用函数现场 add fp, sp, #4 @fp更新 sub sp, sp, #800 @分配800个栈空间给main mov r3, #1 @r3 = 1 str r3, [fp, #-408] @将1放置 fp-408处 mov r3, #2 @r3 = 2 str r3, [fp, #-8] @将2放置 fp-8处 mov r0, sp @r0 = sp sub r3, fp, #392 @r3 = fp - 392 mov r2, #388 @只拷贝388,剩下4个由寄存器传参 mov r1, r3 @保存由r1保存r3,用于memcpy bl memcpy @拷贝结构体部分内容,将r1的内容拷贝r2的数量到r0 sub r3, fp, #408 @定位到结构体剩余未拷贝处 ldm r3, {r0, r1, r2, r3} @将剩余结构体内容通过寄存器传参 bl fp(reg) @执行fp mov r3, r0 @返回值给r3 nop @用于程序指令的对齐 mov r0, r3 @再将返回值给r0 sub sp, fp, #4 @恢复SP值 pop {fp, lr} @出栈恢复调用函数现场 bx lr @跳回调用函数 两个函数对应两段汇编,干净利落,去除中间各项干扰,只有一个结构体reg,具体来看看汇编如何传递它,它在栈中的数据变化是怎样的? 入参方式 结构体中共101个栈空间(一个栈空间单位四个字节),对应就是404个字节地址. main上来就申请了 sub sp, sp, #800 @分配800个栈空间给main,即 200个栈空间 int main() { reg cpu; cpu.Rn[0] = 1; cpu.pc = 2; return fp(cpu); } 但main函数只有一个变量,只要101个栈空间,其他都算上也用不了200个的.为什么要这么做呢? 而且注意下里面的数字 388, 408, 392 这些都是什么意思? 看完main汇编能得到一个结论是 200个栈空间中除了存放了main函数本身的变量reg cpu外 ,还存放了 fp函数的参数cpu的部分值,存放了多少个?答案是 97个. 注意变量CPU没有共用,而是拷贝了一部份.如何拷贝的?继续看 memcpy汇编调用 mov r0, sp @r0 = sp sub r3, fp, #392 @r3 = fp - 392 mov r2, #388 @只拷贝388,剩下4个由寄存器传参 mov r1, r3 @保存由r1保存r3,用于memcpy bl memcpy @拷贝结构体部分内容,将r1的内容拷贝r2的数量到r0 sub r3, fp, #408 @定位到结构体剩余未拷贝处 ldm r3, {r0, r1, r2, r3} @将剩余结构体内容通过寄存器传参 看这段汇编拷贝,意思是从r1开始位置拷贝r2的数量到r0的位置,而且只拷贝了 388个,也就是 388/4 = 97个栈空间.剩余的4个通过寄存器传的参数.ldm代表从fp-408的位置将内存地址的值连续的给 r0 - r3寄存器 fp参数取用 fp(reg): sub sp, sp, #16 @申请栈空间 str fp, [sp, #-4]!@保护fp帧指针,等同于push {fp} add fp, sp, #0 @fp新值,同时也指向了栈顶 add ip, fp, #4 @定位到入栈口,让剩余参数依次入栈 stm ip, {r0, r1, r2, r3}@r0-r3入栈保存 ldr r3, [fp, #4] @取值cpu.pc = 2 ldr r2, [fp, #404]@取值cpu.Rn[0] = 1 mul r3, r2, r3 @cpu.Rn[0] * cpu.pc mov r0, r3 @返回值r0带回 add sp, fp, #0 @重置sp ldr fp, [sp], #4 @重置fp add sp, sp, #16 @归还栈空间 bx lr @跳回main函数 fp申请了4个栈空间就是用来存放四个寄存器值的,注意它和另外的reg cpu 97个栈空间是连续的. 同时 add fp, sp, #0 表示fp指向了栈顶位置 fp+404 和 fp+4 刚好取到了 cpu.Rn[0] 和 cpu.pc 的值. 如此完成了乘法运算. 总结 因为寄存器数量有限,所以只能通过这种方式来传递大的参数,也只能在main函数栈中保存,也必须确保数据的连续性. 一部分通过寄存器传,一部分通过拷贝的方式倒是挺有意思的. 喜欢就请注入源动力吧 各大站点搜 "鸿蒙内核源码分析",快速找到组织.或者更简单的,如图: 鸿蒙内核源码注释 >> 精读内核源码,中文注解分析,深挖地基工程,大脑永久记忆,四大码仓每日同步更新 鸿蒙内核源码分析 >> 故事说内核,问答式导读,生活式比喻,表格化说明,图形化展示,主流站点每日同步更新

优秀的个人博客,低调大师

鸿蒙内核源码分析(调度故事篇) | 西门和金莲的那点破事 | 中文注解HarmonyOS源码 | v8.05

鸿蒙内核源码注释 >> 精读内核源码,中文注解分析,深挖地基工程,大脑永久记忆,四大码仓每日同步更新 鸿蒙内核源码分析 >> 故事说内核,问答式导读,生活式比喻,表格化说明,图形化展示,主流站点每日同步更新 前言 本篇只讲一个故事,听完希望大家能把故事的场景过程记在脑子里,鸿蒙内核源码分析系列文章会反复的提这个故事,在后续具体源码解读过程中都会去用故事里的细节导读源码。如果看了也喜欢也请分享给更多的人知道这个故事。故事就得有个名字,就叫张大爷的故事吧,故事开始。 场馆介绍 某地有一个运动场馆,分成室内馆(400平米)和室外馆(4万平米),管理处在室内馆,那是工作人员办公的地方,非工作人员不得进入。 场馆的定位是供本地公司/组织/团体安排举办各种活动使用的,在舞台上表演(统称舞台剧),同时表演的舞台剧只能一个,但因为生意太好,申请人太多了,所以用馆要先申请->排队->上演.场馆里面有一座永远很准时,不会停的大钟表,每十分钟就自动响一次,场馆里有很多的资源,有篮球,酒馆,小卖部,桌椅,还有演员(人也算资源),反正就是应有尽有,但是数量有限. 资源由管理处统一管理,这些资源也得先申请才能使用.场地外有个大屏幕,屏幕实时对外公布场馆舞台剧情况,屏幕内容如下: 舞台剧名 优先级 状态 进行中节目 就绪节目 管理处 0 正在工作 打扫场地卫生 无 三国演义 19 已就绪 无 骂死王朗 淘宝直播 20 已就绪 无 薇娅9点直播 没错,场馆的内部工作也是个剧,只不过它的优先级最高.而且注意这里只展示正在和就绪的剧情节目,就绪是万事俱备,只欠登台表演的意思.例如上表中有两个剧都准备好了,组成了一个就绪队列,都等着管理处打扫完卫生后表演,但同时只能演一个剧,而三国演义的优先级更高(场馆规定越小的优先级越高),所以不出意外,下一个表演的节目就是三国演义之骂死王朗,这里请记住就绪队列,后续会反复的提它,很重要! 表演走什么流程? 用馆者需提交你舞台剧的剧本,剧本可以是玩游戏,拍电视剧,直播电商等等,反正精彩的世界任你书写,场馆内有专人(统称导演)负责跟进你的剧本上演。剧本由各种各样的场景剧组成(统称节目),比如要拍个水浒传的剧本. 被分成武松打虎,西门和金莲那点破事等等节目剧.申请流程是去管理处先填一张电子节目表,节目表有固定的格式,填完点提交你的工作就完成了,接下来就是导演的事了.节目表单格式如下. 剧本名称 节目章回 内容 优先级 所需资源 状态 水浒传 第18回 武松打虎 12 武松,老虎一只,酒18碗 未开始 水浒传 第28回 西门和金莲那点破事 2 西门庆,金莲,王婆,一个炕 未开始 水浒传 第36回 武松拳打蒋门神 14 武松,蒋门神,猪肉 未开始 故事写到这里,大家脑子里有个画面了吧,记住这两张表,继续走起。 西门大官人什么时候表演? 场馆都会给每个用馆单位发个标号代表你使用场馆的优先级,剧本中每个场景节目也有优先级,都是0级最高,31级最低,这里比如水浒传优先级为8,西门庆和金莲那点破事节目为2,节目资源是需要两位主角(西门,金莲)和王婆,一个炕等资源,这些资源要向场馆负责人申请好,节目资源申请到位了就可以进入就绪队列,如果你的剧本里没有一个节目的资源申请到了那对不起您连排号的资格都没有。这里假如水浒传审核通过,并只有西门大官人节目资源申请成功,而管理处卫生打扫完了,以上两个表格的内容将做如下更新 舞台剧名 优先级 状态 进行中节目 就绪节目 水浒传 8 正在进行 西门和金莲那点破事 无 三国演义 19 已就绪 无 骂死王朗 淘宝直播 20 已就绪 无 薇娅9点直播 注意虽然三国演义先来,但此时水浒传排在三国的前面,是因为它的优先级高,优先级分32级,0最高,31最低. 剧本名称 节目章回 内容 优先级 所需资源 状态 表演位置 水浒传 第18回 武松打虎 12 武松,老虎一只,酒18碗 未开始 暂无 水浒传 第28回 西门和金莲那点破事 2 西门,金莲,王婆,炕一个 正在进行 西门火急火燎的跑进金莲屋内 水浒传 第36回 武松拳打蒋门神 14 武松,蒋门神,猪肉 未开始 暂无 注意看表中状态的变化和优先级,一个是剧本的优先级,一个是同一个剧本中节目的优先级.而之前优先级最高的管理处,因为没有其他节目要运行,所以移出了就绪队列. 西门好事被破坏了怎么办了? 场馆会根据节目上的内容把节目演完。每个节目十分钟,时间到了要回去重新排队,如果还是你就可以继续你的表演。但这里经常会有异常情况发生. 比如上级领导给场馆来个电话临时有个更高优先级节目要插进来,没办法西门你的好事要先停止,please stop! 场地要让给别人办事,西门灰溜溜得回就绪队列排队去,但请放心会在你西门退场前会记录下来表演到哪个位置了(比如:西门官人已脱完鞋),以便回来时继续接着表演。高优先级的事处理完后,如果西门的优先级还是最高的就可以继续用场地,会先还原现场演到哪了再继续办事就完了,绝不重复西门前面的准备工作,否则西门绝不答应! 节目表演完所有资源要回收,这个节目从此消亡,如果你剧本里所有节目都表演完了,那你的整个剧本也可以拜拜了,导演回到导演组,又可以去接下一部戏了. 这里还原下当西门和金莲那点破事被场馆紧急电话打断后表的变化是怎样的,如下: 剧本名称 优先级 状态 进行中节目 就绪节目 管理处 0 正在工作 接听上级电话 无 水浒传 8 已就绪 无 西门和金莲那点破事 三国演义 19 已就绪 无 骂死王朗 淘宝直播 20 已就绪 无 薇娅9点直播 剧本名称 节目章回 内容 优先级 所需资源 状态 表演位置 水浒传 第18回 武松打虎 12 武松,老虎一只,酒18碗 未开始 暂无 水浒传 第28回 西门和金莲那点破事 2 西门庆,金莲,王婆,一个炕 就绪 西门官人脱完鞋 水浒传 第36回 武松拳打蒋门神 14 武松,蒋门神,猪肉 未开始 暂无 表演给谁看呢? 外面那些吃瓜观众啊,群众你我他,游戏公司设计了游戏的剧本,电商公司设计了电商剧本,西门大官人被翻拍了这么多次不就是都爱看嘛,场馆会按你的剧本来表演,当然也可以互动,表演的场景需要观众操作时,观众在外面可以操作,发送指令。想想你玩游戏输入名字登录的场景。场馆里面有三个团队,张大爷团队负责导演组演剧本,王场馆负责场地的使用规划的,李后勤负责搞搞后勤. 张大爷团队做什么的? 上面这些工作都是张大爷团队的工作,接待剧本的导演组,管理剧本清单,指派导演跟进,申请节目资源,调整剧本优先级,控制时间,以使舞台能被公平公正的被调度使用等等 王场馆是做什么的? 看名字能知道负责场地用度的,你想想这么多节目,场地只有这么点,同时只能由一个节目上演,怎么合理的规划才能即公平又效率最大化呢,这就是王场馆的工作,但咱王总也有两把刷子,会给用馆公司感觉到整个场馆都是自己在用,具体不在这个故事里说明,后续有专门讲王场馆如何高效的管理内外场地的故事篇. 李后勤是做什么的? 场馆每天的开业,歇业,场地清理,管理处的对外业务,接听电话,有人闹事了怎么处理,收钱开发票 等等也有很多工作统称为后勤工作要有专门的团队来对接,具体不在这里说明,后续也有专门讲这块的故事. 故事想说什么呢? 故事到底想说什么呢?这就是操作系统的调度机制,熟悉了这个故事就熟悉了鸿蒙系统内核任务调度的工作原理!操作系统就是管理场馆和确保工作人员有序工作的系统解决方案商,外面公司只要提供个剧本,就能按剧本把这台戏演好给广大观众观看。有了这个故事垫底,鸿蒙内核源码分析系列就有了一个非常好的开始基础。 真的是这样的吗?必须的。 内核和故事的关系映射 故事概念 内核概念 备注 剧本 程序 一个剧本一个负责人跟进,跑起来的程序叫进程 导演 进程 进程负责剧本整个运行过程,是资源管理单元,任务也是一种资源 节目 线程/任务 任务记录节目的整个运行过程,任务是调度的单元 西门被打断 保存现场 本质是保存寄存器(PC,LR,FP,SP)的状态 西门继续来 恢复现场 本质是还原寄存器(PC,LR,FP,SP)的状态 表演场地 用户空间 所有节目都在同一块场地表演 管理处 内核空间 管理处非工作人员不得入内 外部场地 磁盘空间 故事暂未涉及,留在内存故事中讲解 节目内容 代码段 任务涉及的具体代码段 管理处的服务 系统调用 软中断实现,切换至内核栈 场馆大钟 系统时钟 十分钟响一次代表一个节拍(tick) 节目20分钟 时间片 鸿蒙时间片默认 2个tick,20ms 上级电话 中断 硬中断,直接跳到中断处理函数执行 表演顺序 优先级 进程和线程都是32个优先级,[0-31],从高到低 张大爷 进程/线程管理 抢占式调度,优先级高者运行 王场馆 内存管理 虚拟内存,内存分配,缺页置换 == 李后勤 异常接管 中断,跟踪,异常接管 == 请牢记这个故事 当然还有很多的细节在故事里没有讲到,比如王场馆和李后勤的工作细节,还有后续故事一一拆解.太细不可能真的在一个故事里全面讲完,笔者想说的是框架,架构思维,要先有整体框架再顺藤摸瓜寻细节,层层深入,否则很容易钻进死胡同里出不来。读着读着就放弃了,其实真没那么难。当你摸清了整个底层的运作机制再看上层的应用,就会有了拨开云雾见阳光,神清气爽的感觉。具体的我们在后续的章节里一一展开,用这个故事去理解鸿蒙系统内核调度过程,没毛病,请务必牢记这个故事。 喜欢就请注入源动力吧 各大站点搜 "鸿蒙内核源码分析",快速找到组织.或者更简单的,如图: 鸿蒙内核源码注释 >> 精读内核源码,中文注解分析,深挖地基工程,大脑永久记忆,四大码仓每日同步更新 鸿蒙内核源码分析 >> 故事说内核,问答式导读,生活式比喻,表格化说明,图形化展示,主流站点每日同步更新

资源下载

更多资源
Mario

Mario

马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。

Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Rocky Linux

Rocky Linux

Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。

WebStorm

WebStorm

WebStorm 是jetbrains公司旗下一款JavaScript 开发工具。目前已经被广大中国JS开发者誉为“Web前端开发神器”、“最强大的HTML5编辑器”、“最智能的JavaScript IDE”等。与IntelliJ IDEA同源,继承了IntelliJ IDEA强大的JS部分的功能。

用户登录
用户注册