zoukankan      html  css  js  c++  java
  • 字符设备驱动Linux异常处理体系结构

    裸机中断流程

    1. 外部触发
    2. CPU 发生中断, 强制的跳到异常向量处
    3. 跳转到具体函数
      1. 保存被中断处的现场(各种寄存器的值)
      2. 执行中断处理函数,处理具体任务
      3. 恢复被中断的现场

    Linux处理异常流程

      异常发生时,会去异常向量表找到入口地址,(这算异常发生之后跳转到第一个处理分支),进入异常模式,保护部分现场,强制进入SVC管理模式,根据异常发生前的工作模式,找到异常处理的第二级分支,在该模式下面接过异常模式堆栈中的信息,接着保存异常发生时异常模式还未保存的信息,准备好处理完毕返回处理程序的地址,调用异常处理函数,恢复现场。


     处理异常流程中的汇编处理流程:

      

    Linux内核对异常的初始化设置

      在内核启动时,内核会在start_kernel函数中调用trap_init,init_IRQ两个函数来设置异常的处理函数

    1. trap_init:(arch/arm/kernel/traps.c中定义)

      

     1 void __init trap_init(void)
     2 {
     3     unsigned long vectors = CONFIG_VECTORS_BASE;    /*CONFIG_VECTORS_BASE = 0xffff0000*/
     4     extern char __stubs_start[], __stubs_end[];
     5     extern char __vectors_start[], __vectors_end[];
     6     extern char __kuser_helper_start[], __kuser_helper_end[];
     7     int kuser_sz = __kuser_helper_end - __kuser_helper_start;
     8 
     9     /*
    10      * Copy the vectors, stubs and kuser helpers (in entry-armv.S)
    11      * into the vector page, mapped at 0xffff0000, and ensure these
    12      * are visible to the instruction stream.
    13      */
    14     memcpy((void *)vectors, __vectors_start, __vectors_end - __vectors_start);    //void *memcpy(void *dest, const void *src, size_t count)
    15     memcpy((void *)vectors + 0x200, __stubs_start, __stubs_end - __stubs_start);
    16     memcpy((void *)vectors + 0x1000 - kuser_sz, __kuser_helper_start, kuser_sz);
    17 
    18     /*
    19      * Copy signal return handlers into the vector page, and
    20      * set sigreturn to be a pointer to these.
    21      */
    22     memcpy((void *)KERN_SIGRETURN_CODE, sigreturn_codes,
    23            sizeof(sigreturn_codes));
    24 
    25     flush_icache_range(vectors, vectors + PAGE_SIZE);
    26     modify_domain(DOMAIN_USER, DOMAIN_CLIENT);
    27 }

      作用:用来设置各种异常的处理向量,即将异常向量表复制到0xffff0000处。Vectors = 0xffff0000, 地址_vectors_start ~~ __vectors_end之间的代码就是异常向量。

      所谓的异常向量就是被安放在固定位置的代码,只是一些跳转指令。发生异常,CPU自动执行这些指令,跳转执行更复杂的代码,比如:保存被中断程序的执行环境,调用异常处理函数,恢复被中断程序的执行环境并重新运行。这些“更复杂的代码”在地址 __stubs_start, ~ __stubs_end 之间,第15行,将他们复制到vectors + 0x200 处。

      到这里Linux内核异常向量设置的工作就算是完成了。可是想想:设置完这些异常向量之后,异常发生了,CPU是怎么一个处理过程???接着往下分析

      从异常向量表来开始入手,__vectors_start和__vectors_end在arch/arm/kernel/entry-armv.S文件中有定义。他们就是内核异常向量表的起始和结束地址。

     1     .equ    stubs_offset, __vectors_start + 0x200 - __stubs_start
     2 
     3     .globl    __vectors_start
     4 __vectors_start:
     5     swi    SYS_ERROR0           //复位时,CPU将执行这条指令
     6     b    vector_und + stubs_offset   //未定义异常时,CPU执行这条指令
     7     ldr    pc, .LCvswi + stubs_offset //swi异常
     8     b    vector_pabt + stubs_offset   //指令预取中止
     9     b    vector_dabt + stubs_offset   //数据访问中止
    10     b    vector_addrexcptn + stubs_offset
    11     b    vector_irq + stubs_offset    //IRQ异常
    12     b    vector_fiq + stubs_offset    //FIQ异常
    13 
    14     .globl    __vectors_end
    15 __vectors_end:

      以第一个调转指令“b  vector_und + stubs_offset”的分析为例,vector_und是个汇编宏定义,由vector_stub 及后面的参数定义,和c语言里面的宏定义特点是一样的,编译时宏在调用处的展开,就是用宏定义的实体部分去完全取代宏名称,可以直接在该处执行,以下是汇编宏定义规则:

           .macro    MACRO_NAME   PARA1   PARA2   ......

          ......实体---内容......

          .endm

    在文件中,找到了 vector_stub 这个宏(以下第二部分代码),它根据后面的参数“ und, UND_MODE”定义了以“vector_und”为标号的一段代码,如下

     1 /*
     2  * Undef instr entry dispatcher
     3  * Enter in UND mode, spsr = SVC/USR CPSR, lr = SVC/USR PC
     4  */
     5     vector_stub    und, UND_MODE
     6 
     7     .long    __und_usr            @  0 (USR_26 / USR_32)
     8     .long    __und_invalid            @  1 (FIQ_26 / FIQ_32)
     9     .long    __und_invalid            @  2 (IRQ_26 / IRQ_32)
    10     .long    __und_svc            @  3 (SVC_26 / SVC_32)
    11     .long    __und_invalid            @  4
    12     .long    __und_invalid            @  5
    13     .long    __und_invalid            @  6
    14     .long    __und_invalid            @  7
    15     .long    __und_invalid            @  8
    16     .long    __und_invalid            @  9
    17     .long    __und_invalid            @  a
    18     .long    __und_invalid            @  b
    19     .long    __und_invalid            @  c
    20     .long    __und_invalid            @  d
    21     .long    __und_invalid            @  e
    22     .long    __und_invalid            @  f
    23 
    24     .align    5

      vector_ 宏定义

     1 .macro    vector_stub, name, mode, correction=0
     2     .align    5  @将异常入口强制进行2^5字节对齐,即一个cache line大小对齐,出于性能考虑
     3 
     4 vector_\name:    //und, UND_MODE ......
     5     .if \correction  @correction=0 所以分支无效
     6     sub    lr, lr, #\correction
     7     .endif
     8 
     9     @
    10     @ Save r0, lr_<exception> (parent PC) and spsr_<exception>
    11     @ (parent CPSR)
    12     @
    13     stmia    sp, {r0, lr}        @ save r0, lr
    14     mrs    lr, spsr
    15     str    lr, [sp, #8]        @ save spsr
    16 
    17     @
    18     @ Prepare for SVC32 mode.  IRQs remain disabled.
    19     @
    20     mrs    r0, cpsr
    21     eor    r0, r0, #(\mode ^ SVC_MODE)
    22     msr    spsr_cxsf, r0
    23 
    24     @
    25     @ the branch table must immediately follow this code
    26     @
    27     and    lr, lr, #0x0f
    28     mov    r0, sp
    29     ldr    lr, [pc, lr, lsl #2]
    30     movs    pc, lr            @ branch to handler in SVC mode
    31     .endm

      

      以宏“vector_stub  und,   UND_MODE”为例代入,将其展开为:

     1 vector_und:   
     2     @
     3     @ 此时已进入UND_MOD,lr=上一个模式被打断时的PC值,下面三条指令是保护上个模式的现场
     4     @
     5     stmia    sp, {r0, lr}        @ save r0, lr
     6     mrs    lr, spsr              @ 准备保存上个模式的cpsr值,因为他被放到了UND_MODE的spsr中
     7     str    lr, [sp, #8]          @ save spsr to stack
     8     @
     9     @ Prepare for SVC32 mode.  IRQs remain disabled. 注意前面的“Prepare”,这里还不是真正切换到SVC,只是准备
    10     @
    11     mrs    r0, cpsr                     @ r0=0x1b  (UND_MODE)
    12     eor    r0, r0, #(\mode ^ SVC_MODE)  @ 逻辑异或指令
    13     msr    spsr_cxsf, r0                @ cxsf是spsr寄存器的控制域(C)、扩展域(X)、状态域(S)、标志域(F),注意这里的spsr是UND管理模式的
    14     @
    15     @ the branch table must immediately follow this code 下一级跳转表必须要紧跟在这一段代码之后(这一点很重要)
    16     @
    17     and    lr, lr, #0x0f     @ 执行这条指令之前:lr = 上个模式的cpsr值,现在取出其低四位--模式控制位的[4:0],关键点又来了:查看2440芯片手册可以知道,这低4位二进制值为十进制数值的 0-->User_Mode; 1-->Fiq_Mode; 
                        //2-->Irq_Mode; 3-->SVC_Mode; 7-->Abort_Mode; 11-->UND_Mode,明白了这些下面的处理就会恍然大悟,原来找到那些异常处理分支是依赖这4位的值来实现的
    18 mov r0, sp @ 将SP值保存到R0是为了之后切换到SVC模式时将这个模式下堆栈中的信息转而保存到SVC模式下的堆栈中 19 ldr lr, [pc, lr, lsl #2] @ LDR的稀有用法:将pc+lr*4的计算结果重新保存到lr中,我们知道pc是指向当前指令的下两条指令处的地址的,也就是指向了“.long __und_usr” 20 movs pc, lr @ branch to handler in SVC mode 前方高能!关键的地方来了!在跳转到第二级分支的同时CPU的工作模式从UND_MODE强制切换到SVC_MODE,这是由于MOVS指令在赋值的同时会将spsr的值赋给cpsr 21 ENDPROC(vector_und) 22 .long __und_usr    @ 0 (USR_26 / USR_32)运行用户模式下触发未定义指令异常 23 .long __und_invalid @ 1 (FIQ_26 / FIQ_32) 24 .long __und_invalid @ 2 (IRQ_26 / IRQ_32) 25 .long __und_svc    @ 3 (SVC_26 / SVC_32)运行用户模式下触发未定义指令异常 26 .long __und_invalid @ 4 其他模式下面不能发生未定义指令异常,否则都使用__und_invalid分支处理这种异常 27 .long __und_invalid @ 5 28 .long __und_invalid @ 6 29 .long __und_invalid @ 7 30 .long __und_invalid @ 8 31 .long __und_invalid @ 9 32 .long __und_invalid @ a 33 .long __und_invalid @ b 34 .long __und_invalid @ c 35 .long __und_invalid @ d 36 .long __und_invalid @ e 37 .long __und_invalid @ f

    代码注释出自:https://blog.csdn.net/clb1609158506/article/details/44348767

    小结:vector_stub的宏功能:计算处理完异常后的返回地址,保存一些寄存器(r0,lr, spsr),然后进入管理模式,最后根据被中断的工作模式调用第22行-37行中的某个跳转分支。发生异常时,CPU根据异常类型进入某个工作模式,但是很快 vector_stub又会强制CPU进入管理模式,在管理模式下进行后续处理。

      eg: 执行到“movs pc, lr”这一句,找到了branch table中的一项,现在我们继续往下分析,假设进入UND_MODE之前是User模式,那么接下来会到__und_usr分支去继续执行
    __und_usr标号也是在该文件中定义,

    分支跳转 __und_usr ------> b   __und_usr_unknown -----> b    do_undefinstr ----> 最终调用C函数进行复杂的处理 在arch/arm/kernel/traps.c中

     2.Init_IRQ函数分析

      中断也是一种异常,之所以单独列出来,是因为中断的处理与具体开发板密切相关,除了一些必须、共用的中断(比如系统时钟中断,片内外设UART中断)之外,必须由驱动开发者提供处理函数。内核提炼出中断处理的共性,搭建了一个极易扩充的中断处理体系。

     Init_IRQ函数(arch/arm/kernel/irq.c 中定义)被用来初始化中断的处理框架,设置各种中断的默认处理函数。当发生中断时,中断总入口函数 asm_do_IRQ 就可以调用这些函数作进一步处理。

     Linux 异常处理体系结构:

  • 相关阅读:
    Vue-CLI
    Vue生命周期函数
    构建之法阅读笔记之四
    大二下个人总结
    个人加分项
    对老师的建议
    学习进度条 第九十一-第一百零五天 vue+uniapp app开发学习笔记
    第15周作业
    二进制安装mysql 5.7.31 启动报错/etc/init.d/mysqld: line 239: my_print_defaults: command not found
    获取最小数字
  • 原文地址:https://www.cnblogs.com/y4247464/p/10091810.html
Copyright © 2011-2022 走看看