详解Linux内核异常处理体系结构

本节内容:Linux内核异常处理的的初始化过程和异常发生时的处理流程。

【首先来区分一下两个概念:中断(Interrupt)和异常(Exception)。中断属于异常的一种,就拿2440开发板来说,他有60多种中断源,例如来自DMA控制器、UART、IIC和外部中断等。2440有一个专门的中断控制器来处理这些中断,中断控制器在接收到这些中断信号之后就需要ARM920T进入IRQ或FIQ模式进行处理,这两种模式也是中断异常的仅有模式。而异常的概念要广的多,它包括复位、未定义指令、软中断、IRQ等等。还有一点知识就是,中断这种异常在响应之前到来之前是需要程序员进行什么优先级、是否要屏蔽信号之类的初始化的,而其他比如未定义指令是不用的,只要发生了就跳到异常向量入口取址执行。因此下面初始化内容中的第(2)点是针对中断这种异常的设置的】

一、初始化设置:

(1)异常向量相关的设置:start_kernel()–>setup_arch()–>early_trap_init()函数来担任这个任务。在arch/arm/kernel/traps.c文件件中定义:这个函数很有分量,值得细细分析!!!

void __init early_trap_init(void){unsigned long vectors = CONFIG_VECTORS_BASE;extern char __stubs_start[], __stubs_end[];extern char __vectors_start[], __vectors_end[];extern char __kuser_helper_start[], __kuser_helper_end[];int kuser_sz = __kuser_helper_end – __kuser_helper_start;/* * 看下面这段英文注释,代码就一目了然了,就是把异常向量表、和异常处理那部分代码复制到指定的地址处 * Copy the vectors, stubs and kuser helpers (in entry-armv.S) * into the vector page, mapped at 0xffff0000, and ensure these * are visible to the instruction stream. */memcpy((void *)vectors, __vectors_start, __vectors_end – __vectors_start); memcpy((void *)vectors + 0x200, __stubs_start, __stubs_end – __stubs_start); memcpy((void *)vectors + 0x1000 – kuser_sz, __kuser_helper_start, kuser_sz); /* * Copy signal return handlers into the vector page, and * set sigreturn to be a pointer to these. */memcpy((void *)KERN_SIGRETURN_CODE, sigreturn_codes,sizeof(sigreturn_codes));flush_icache_range(vectors, vectors + PAGE_SIZE);modify_domain(DOMAIN_USER, DOMAIN_CLIENT);}

详细函数分析:将异常向量表复制到vectors地址处,,vectors在函数的第一句就被赋值为“CONFIG_VECTORS_BASE”,经验告诉我们它是个内核编译配置项,去内核的顶层目录里边的“.config”文件搜索就出来,果然就有“CONFIG_VECTORS_BASE=0xffff0000”这么一句话。好,同样问题就来了,我们之前了解过的中断向量是放到0x00000000地址开始处,把中断向量放到0xffff0000 异常触发时cpu还能自动找到?答案是能!在ARM920T的使用手册里边有涉及相关的内容:协处理控制寄存器CP15的C1寄存器的第[13]位就是用来设置异常向量的存放位置的,该位为0存放到0x0000000开始处,为1存放到0xffff0000开始处。

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

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

………globl__vectors_start__vectors_start:swiSYS_ERROR0 ;arm在复位异常发生时来这里执行bvector_und + stubs_offsetldrpc, .LCvswi + stubs_offsetbvector_pabt + stubs_offsetbvector_dabt + stubs_offsetbvector_addrexcptn + stubs_offsetbvector_irq + stubs_offsetbvector_fiq + stubs_offset.globl__vectors_end……..下面以第一个调转指令“bvector_und + stubs_offset”的分析为例,发现怎么在源码里面都找不到vector_und这个东东,各种查资料之后发现特么是个汇编宏定义,干!展开来解解气,建议先熟悉一下汇编宏定义规则。

.macro MACRO_NAME PARA1 PARA2 ……

……内容……

.endm

同样在这个文件中找到了vector_stub这个宏:

.macrovector_stub, name, mode, correction=0.align5@将异常入口强制进行2^5字节对齐,即一个cache line大小对齐,出于性能考虑vector_\name:.if \correction @correction=0 所以分支无效sublr, lr, #\correction.endif.endif………..movspc, lr@ branch to handler in SVC modeENDPROC(vector_\name).endm

以宏“vector_stubund, UND_MODE”为例将其展开为:

vector_und:@@ 此时已进入UND_MOD,lr=上一个模式被打断时的PC值,下面三条指令是保护上个模式的现场@stmiasp, {r0, lr}<span style="white-space:pre"></span>@ save r0, lrmrslr, spsr<span style="white-space:pre"></span>@ 准备保存上个模式的cpsr值,因为他被放到了UND_MODE的spsr中strlr, [sp, #8]@ save spsr to stack@@ Prepare for SVC32 mode. IRQs remain disabled. 注意前面的“Prepare”,这里还不是真正切换到SVC,只是准备!!不要紧张@mrsr0, cpsr@ r0=0x1b (UND_MODE)eorr0, r0, #(\mode ^ SVC_MODE)<span style="white-space:pre"></span>@ 逻辑异或指令msrspsr_cxsf, r0@ cxsf是spsr寄存器的控制域(C)、扩展域(X)、状态域(S)、标志域(F),注意这里的spsr是UND管理模式的@@ the branch table must immediately follow this code 下一级跳转表必须要紧跟在这一段代码之后(这一点很重要)@andlr, 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位的值来实现的movr0, sp@ 将SP值保存到R0是为了之后切换到SVC模式时将这个模式下堆栈中的信息转而保存到SVC模式下的堆栈中ldrlr, [pc, lr, lsl #2] @ 我第一次遇到LDR的这种用法,找了一下LDR的资料发现是这个意思:将pc+lr*4的计算结果重新保存到lr中,我们知道pc是指向当前指令的下两条指令处的地址的,也就是指向了“.long__und_usr”movspc, lr@ branch to handler in SVC mode 前方高能!关键的地方来了!在跳转到第二级分支的同时CPU的工作模式从UND_MODE强制切换到SVC_MODE,这是由于MOVS指令在赋值的同时会将spsr的值赋给cpsrENDPROC(vector_und).long__und_usr@ 0 (USR_26 / USR_32)运行用户模式下触发未定义指令异常.long__und_invalid@ 1 (FIQ_26 / FIQ_32).long__und_invalid@ 2 (IRQ_26 / IRQ_32).long__und_svc@ 3 (SVC_26 / SVC_32)运行用户模式下触发未定义指令异常.long__und_invalid@ 4 其他模式下面不能发生未定义指令异常,否则都使用__und_invalid分支处理这种异常.long__und_invalid@ 5.long__und_invalid@ 6.long__und_invalid@ 7.long__und_invalid@ 8.long__und_invalid@ 9.long__und_invalid@ a.long__und_invalid@ b.long__und_invalid@ c.long__und_invalid@ d.long__und_invalid@ e.long__und_invalid@ f

【附加注释:在arch\arm\include\asm\ptrace.h中有:#define SVC_MODE 0x00000013 和 #define UND_MODE 0x0000001b

未经一番寒彻骨,焉得梅花扑鼻香

详解Linux内核异常处理体系结构

相关文章:

你感兴趣的文章:

标签云: