字符设备驱动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 异常处理体系结构:

原文地址:https://www.cnblogs.com/y4247464/p/10091810.html