开源鸿蒙内核源码分析系列 | 异常接管 | 社会很单纯 复杂的是人

开源鸿蒙内核源码分析系列 | 异常接管 | 社会很单纯 复杂的是人

为何要有异常接管?

拿小孩成长打比方,大人总希望孩子能健康成长,但在成长过程中总会遇到各种各样的问题,树欲静而风不止,成长路上有危险,有时是自己的问题有时是外在环境问题。就像抖音最近的流行口水歌一样,社会很单纯,复杂的是人啊,每次听到都想站起来扭几下。哎! 老衲到底做错什么了?

比如:老被其他小朋友欺负怎么弄?发现乱花钱怎么搞?青春期发育怎么应对?失恋要跳楼又怎么办?意思是超过他的认知范围,靠它自己解决不了了,就需要有更高权限,更高智慧的人介入进来,帮着解决,干擦屁股的事。

那么应用程序就是那个小孩,内核就是监护人,有更高的权限,更高的智慧。而且监护人还不止一个,而是六个,每个监护人对应解决一种情况,情况发生了就由它来接管这件事的处理,小朋友你就别管了哈,先把你关家里,处理好了外面安全了再把应用程序放出来玩去。

这六个人处理问题都自带工具,有标准的解决方案,有自己独立的办公场所,办公场所就是栈空间(独立的),标准解决方案就是私有代码段,放在固定的位置。而自带的工具就是 SPSR_***,SP_***,LR_***寄存器组。详见 系列篇之工作模式篇 ,这里再简单回顾下有哪些工作模式,包括小孩自己(用户模式)一共是七种模式。

七种工作模式

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

  • 用户模式(usr):该模式是用户程序的工作模式,它运行在操作系统的用户态,它没有权限去操作其它硬件资源,只能执行处理自己的数据,也不能切换到其它模式下,要想访问硬件资源或切换到其它模式只能通过软中断或产生异常。
  • 快速中断模式(fiq):快速中断模式是相对一般中断模式而言的,用来处理高优先级中断的模式,处理对时间要求比较紧急的中断请求,主要用于高速数据传输及通道处理中。
  • 普通中断模式(irq):一般中断模式也叫普通中断模式,用于处理一般的中断请求,通常在硬件产生中断信号之后自动进入该模式,该模式可以自由访问系统硬件资源。
  • 管理模式(svc):操作系统保护模式,CPU上电复位和当应用程序执行 SVC 指令调用系统服务时也会进入此模式,操作系统内核的普通代码通常工作在这个模式下。
  • 终止模式(abt):当数据或指令预取终止时进入该模式,中止模式用于支持虚拟内存或存储器保护,当用户程序访问非法地址,没有权限读取的内存地址时,会进入该模式,
  • 系统模式(sys):供操作系统使用的高特权用户模式,与用户模式类似,但具有可以直接切换到其他模式等特权,用户模式与系统模式两者使用相同的寄存器,都没有SPSR(Saved Program Statement Register,已保存程序状态寄存器),但系统模式比用户模式有更高的权限,可以访问所有系统资源。
  • 未定义模式(und):未定义模式用于支持硬件协处理器的软件仿真,CPU在指令的译码阶段不能识别该指令操作时,会进入未定义模式。

除用户模式外,其余6种工作模式都属于特权模式

  • 特权模式中除了系统模式以外的其余5种模式称为异常模式
  • 大多数程序运行于用户模式
  • 进入特权模式是为了处理中断、异常、或者访问被保护的系统资源
  • 硬件权限级别:系统模式 > 异常模式 > 用户模式
  • 快中断(fiq)与慢中断(irq)区别:快中断处理时禁止中断

每种模式都有自己独立的入口和独立的运行栈空间。只要提供了入口函数和运行空间,CPU就可以干活了。入口函数解决了指令来源问题,运行空间解决了指令的运行场地问题。而且在多核情况下,每个CPU核的每种特权模式都有自己独立的栈空间。注意是特权模式下的栈空间,用户模式的栈空间是由用户(应用)程序提供的。

异常接管的官方概念

异常接管是操作系统对运行期间发生的异常情况(芯片硬件异常)进行处理的一系列动作,例如打印异常发生时当前函数的调用栈信息、CPU现场信息、任务的堆栈情况等。异常接管作为一种调测手段,可以在系统发生异常时给用户提供有用的异常信息,譬如异常类型、发生异常时的系统状态等,方便用户定位分析问题。

开源鸿蒙的异常接管,在系统发生异常时的处理动作为:显示异常发生时正在运行的任务信息(包括任务名、任务号、堆栈大小等),以及CPU现场等信息。

进入和退出异常方式

异常接管切换需要处理好两件事:

一个是代码要切到哪个位置,也就是要重置PC寄存器,每种异常模式下的切换方式如图:

另一个是要恢复每种模式的状态,即 CPSR(1个) 和 SPSR(共5个) 的关系,对M[4:0]的修改,如图:

以下是M[4:0]在每种模式下具体操作方式:

栈帧

每个函数都有自己的栈空间,称为栈帧。调用函数时,会创建子函数的栈帧,同时将函数入参、局部变量、寄存器入栈。栈帧从高地址向低地址生长,也就是说栈底是高地址,栈顶是底地址。详见 系列篇之用栈方式 | CPU上班打卡的地方

以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)

快中断处理(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个寄存器

结尾

以上为异常接管对应的代码处理,具体每种异常发生的场景和代码细节处理,因内容太多,太复杂,系列篇后续将分篇一一分析。敬请关注!

百文说内核 | 抓住主脉络

子曰:“诗三百,一言以蔽之,曰‘思无邪’。”——《论语》:为政篇。百文相当于摸出内核的肌肉和器官系统,让人开始丰满有立体感,因是直接从注释源码起步,在开源鸿蒙内核源码加注释过程中,每每有心得处就整理,慢慢形成了以下文章。内容立足源码,常以生活场景打比方尽可能多的将内核知识点置入某种场景,具有画面感,容易理解记忆。说别人能听得懂的话很重要! 百篇博客绝不是百度教条式的在说一堆诘屈聱牙的概念,那没什么意思。更希望让内核变得栩栩如生,倍感亲切.确实有难度,自不量力,但已经出发,回头已是不可能的了。
百万汉字注解内核目的是要看清楚其毛细血管,细胞结构,等于在拿放大镜看内核。内核并不神秘,带着问题去源码中找答案是很容易上瘾的,你会发现很多文章对一些问题的解读是错误的,或者说不深刻难以自圆其说,你会慢慢形成自己新的解读,而新的解读又会碰到新的问题,如此层层递进,滚滚向前,拿着放大镜根本不愿意放手。与代码有bug需不断debug一样,文章和注解内容会存在不少错漏之处,请多包涵,但会反复修正,持续更新,v**.xx 代表文章序号和修改的次数,精雕细琢,言简意赅,力求打造精品内容。百篇博客系列思维导图结构如下:

根据上图的思维导图,我们未来将要和大家一一分享以上大部分关键技术点的博客文章。

百万汉字注解.精读内核源码

如果大家觉得看文章不过瘾,想直接撸代码的话,可以去下面四大码仓围观同步注释内核源码:

gitee仓

https://gitee.com/weharmony/kernel_liteos_a_note

github仓 :

https://github.com/kuangyufei/kernel_liteos_a_note

codechina仓

https://codechina.csdn.net/kuangyufei/kernel_liteos_a_note

coding仓

https://weharmony.coding.net/public/harmony/kernel_liteos_a_note/git/files

写在最后

我们最近正带着大家玩嗨OpenHarmony。如果你有用OpenHarmony开发的好玩的东东,或者有对OpenHarmony的深度技术剖析,想通过我们平台让更多的小伙伴知道和分享的,欢迎投稿,让我们一起嗨起来!有点子,有想法,有Demo,立刻联系我们:

合作邮箱:zzliang@atomsource.org