裸机中断流程

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

Linux处理异常流程

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

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

  

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

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

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

  

 void __init trap_init(void)
{
unsigned long vectors = CONFIG_VECTORS_BASE; /*CONFIG_VECTORS_BASE = 0xffff0000*/
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); //void *memcpy(void *dest, const void *src, size_t count)
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);
}

  作用:用来设置各种异常的处理向量,即将异常向量表复制到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文件中有定义。他们就是内核异常向量表的起始和结束地址。

     .equ    stubs_offset, __vectors_start + 0x200 - __stubs_start

     .globl    __vectors_start
__vectors_start:
swi SYS_ERROR0           //复位时,CPU将执行这条指令
b vector_und + stubs_offset   //未定义异常时,CPU执行这条指令
ldr pc, .LCvswi + stubs_offset //swi异常
b vector_pabt + stubs_offset   //指令预取中止
b vector_dabt + stubs_offset   //数据访问中止
b vector_addrexcptn + stubs_offset
b vector_irq + stubs_offset    //IRQ异常
b vector_fiq + stubs_offset    //FIQ异常 .globl __vectors_end
__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”为标号的一段代码,如下

 /*
* Undef instr entry dispatcher
* Enter in UND mode, spsr = SVC/USR CPSR, lr = SVC/USR PC
*/
vector_stub und, UND_MODE .long __und_usr @ (USR_26 / USR_32)
.long __und_invalid @ (FIQ_26 / FIQ_32)
.long __und_invalid @ (IRQ_26 / IRQ_32)
.long __und_svc @ (SVC_26 / SVC_32)
.long __und_invalid @
.long __und_invalid @
.long __und_invalid @
.long __und_invalid @
.long __und_invalid @
.long __und_invalid @
.long __und_invalid @ a
.long __und_invalid @ b
.long __und_invalid @ c
.long __und_invalid @ d
.long __und_invalid @ e
.long __und_invalid @ f .align

  vector_ 宏定义

 .macro    vector_stub, name, mode, correction=
.align 5  @将异常入口强制进行2^5字节对齐,即一个cache line大小对齐,出于性能考虑 vector_\name:    //und, UND_MODE ......
.if \correction  @correction=0 所以分支无效
sub lr, lr, #\correction
.endif @
@ Save r0, lr_<exception> (parent PC) and spsr_<exception>
@ (parent CPSR)
@
stmia sp, {r0, lr} @ save r0, lr
mrs lr, spsr
str lr, [sp, #] @ save spsr @
@ Prepare for SVC32 mode. IRQs remain disabled.
@
mrs r0, cpsr
eor r0, r0, #(\mode ^ SVC_MODE)
msr spsr_cxsf, r0 @
@ the branch table must immediately follow this code
@
and lr, lr, #0x0f
mov r0, sp
ldr lr, [pc, lr, lsl #]
movs pc, lr @ branch to handler in SVC mode
.endm

  

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

 vector_und:
@
3 @ 此时已进入UND_MOD,lr=上一个模式被打断时的PC值,下面三条指令是保护上个模式的现场
4 @
stmia sp, {r0, lr} @ save r0, lr
mrs lr, spsr @ 准备保存上个模式的cpsr值,因为他被放到了UND_MODE的spsr中
str lr, [sp, #] @ save spsr to stack
@
@ Prepare for SVC32 mode. IRQs remain disabled. 注意前面的“Prepare”,这里还不是真正切换到SVC,只是准备
@
mrs r0, cpsr @ r0=0x1b (UND_MODE)
eor r0, r0, #(\mode ^ SVC_MODE) @ 逻辑异或指令
msr spsr_cxsf, r0 @ cxsf是spsr寄存器的控制域(C)、扩展域(X)、状态域(S)、标志域(F),注意这里的spsr是UND管理模式的
@
@ the branch table must immediately follow this code 下一级跳转表必须要紧跟在这一段代码之后(这一点很重要)
@
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位的值来实现的
mov r0, sp @ 将SP值保存到R0是为了之后切换到SVC模式时将这个模式下堆栈中的信息转而保存到SVC模式下的堆栈中
ldr lr, [pc, lr, lsl #] @ LDR的稀有用法:将pc+lr*4的计算结果重新保存到lr中,我们知道pc是指向当前指令的下两条指令处的地址的,也就是指向了“.long __und_usr”
movs pc, lr @ branch to handler in SVC mode 前方高能!关键的地方来了!在跳转到第二级分支的同时CPU的工作模式从UND_MODE强制切换到SVC_MODE,这是由于MOVS指令在赋值的同时会将spsr的值赋给cpsr
ENDPROC(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 @
.long __und_invalid @
.long __und_invalid @
.long __und_invalid @
.long __und_invalid @
.long __und_invalid @ a
.long __und_invalid @ b
.long __und_invalid @ c
.long __und_invalid @ d
.long __und_invalid @ e
.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 异常处理体系结构:

字符设备驱动-------Linux异常处理体系结构的更多相关文章

  1. 字符设备驱动-----Linux中断处理体系结构

    一.中断处理体系结构的初始化 Linux内核将所有的中断统一编号,使用一个irq_desc结构数组来描述这些中断;每个数组项对应一个中断,也可能是一组中断,它们共用相同的中断号,里面记录了中断的名称. ...

  2. 1.字符设备驱动------Linux中断处理体系结构

    一.中断处理体系结构的初始化 Linux内核将所有的中断统一编号,使用一个irq_desc结构数组来描述这些中断;每个数组项对应一个中断,也可能是一组中断,它们共用相同的中断号,里面记录了中断的名称. ...

  3. Linux字符设备驱动框架

    字符设备是Linux三大设备之一(另外两种是块设备,网络设备),字符设备就是字节流形式通讯的I/O设备,绝大部分设备都是字符设备,常见的字符设备包括鼠标.键盘.显示器.串口等等,当我们执行ls -l ...

  4. 嵌入式Linux驱动学习之路(十)字符设备驱动-my_led

    首先贴上代码: 字符设备驱动代码: /** *file name: led.c */#include <linux/sched.h> #include <linux/signal.h ...

  5. Linux应用程序访问字符设备驱动详细过程【转】

    本文转载自:http://blog.csdn.net/coding__madman/article/details/51346532 下面先通过一个编写好的内核驱动模块来体验以下字符设备驱动 可以暂时 ...

  6. 深入理解Linux字符设备驱动

    文章从上层应用访问字符设备驱动开始,一步步地深入分析Linux字符设备的软件层次.组成框架和交互.如何编写驱动.设备文件的创建和mdev原理,对Linux字符设备驱动有全面的讲解.本文整合之前发表的& ...

  7. Linux字符设备驱动结构(一)--cdev结构体、设备号相关知识机械【转】

    本文转载自:http://blog.csdn.net/zqixiao_09/article/details/50839042 一.字符设备基础知识 1.设备驱动分类 linux系统将设备分为3类:字符 ...

  8. Smart210学习记录----beep linux字符设备驱动

    今天搞定了beep linux字符设备驱动,心里还是很开心的,哈哈...但在完成的过程中却遇到了一个非常棘手的问题,花费了我大量的时间,,,, 还是把问题描述一下吧,好像这个问题很普遍的,网上许多解决 ...

  9. Linux驱动设计——字符设备驱动(一)

    Linux字符设别驱动结构 cdev结构体 struct cdev { struct kobject kobj; struct module *owner; const struct file_ope ...

随机推荐

  1. android-继承BaseAdapter--自己定义适配器,getView运行多次的解决方法

    定义的getView运行多次的ListView布局: <ListView android:id="@+id/lv_messages" android:layout_width ...

  2. ddr sdram self-refresh & auto-refresh

    以下是EDD5116AFTA数据手册的摘录.不过看过了还是不太明白二者的区别. self-refresh:Self-refresh entry [SELF]This command starts se ...

  3. [BZOJ4026]dC Loves Number Theory 欧拉函数+线段树

    链接 题意:给定长度为 \(n\) 的序列 A,每次求区间 \([l,r]\) 的乘积的欧拉函数 题解 考虑离线怎么搞,将询问按右端点排序,然后按顺序扫这个序列 对于每个 \(A_i\) ,枚举它的质 ...

  4. 分析一下jquery中的ajax操作

    在web前端开发中,ajax是很重要的一项技术,用原生写起来很是麻烦,需要一大堆js代码,而到了jq里就被精简了许多,一起来看看: jquery中的ajax分为三种方式: 1.$.get(),get方 ...

  5. C# MVC js 跨域

    js 跨域: 第一种解决方案(服务端解决跨域问题): 跨域是浏览器的一种安全策略,是浏览器自身做的限制,不允许用户访问不同域名或端口或协议的网站数据. 只有域名(主域名[一级域名]和二级域名).端口号 ...

  6. OPENCV(7) —— HighGUI

    包括函数createTrackbar.getTrackbarPos.setTrackbarPos.imshow.namedWindow.destroyWindow.destroyAllWindows. ...

  7. XML获取节点信息值

    XmlDocument doc = new XmlDocument(); doc.LoadXml(sreturn); XmlNode xNode = doc.SelectSingleNode(&quo ...

  8. tac---反转输出文件

    tac命令用于将文件已行为单位的反序输出,即第一行最后显示,最后一行先显示.

  9. CentOS yum安装mcrypt详细图解教程

    CentOS yum安装mcrypt详细图解教程 在Linux的发行版CentOS 6.3 系统下,LAMP(Linux+Apache+Mysql+php)环境搭建好后发现PHPMyadmin提示 “ ...

  10. Swift学习笔记(11)--类与结构体

    类与结构是编程人员在代码中会经常用到的代码块.在类与结构中可以像定义常量,变量和函数一样,定义相关的属性和方法以此来实现各种功能. 和其它的编程语言不太相同的是,Swift不需要单独创建接口或者实现文 ...