字符设备驱动-------Linux异常处理体系结构
裸机中断流程
- 外部触发
- CPU 发生中断, 强制的跳到异常向量处
- 跳转到具体函数
- 保存被中断处的现场(各种寄存器的值)
- 执行中断处理函数,处理具体任务
- 恢复被中断的现场
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异常处理体系结构的更多相关文章
- 字符设备驱动-----Linux中断处理体系结构
一.中断处理体系结构的初始化 Linux内核将所有的中断统一编号,使用一个irq_desc结构数组来描述这些中断;每个数组项对应一个中断,也可能是一组中断,它们共用相同的中断号,里面记录了中断的名称. ...
- 1.字符设备驱动------Linux中断处理体系结构
一.中断处理体系结构的初始化 Linux内核将所有的中断统一编号,使用一个irq_desc结构数组来描述这些中断;每个数组项对应一个中断,也可能是一组中断,它们共用相同的中断号,里面记录了中断的名称. ...
- Linux字符设备驱动框架
字符设备是Linux三大设备之一(另外两种是块设备,网络设备),字符设备就是字节流形式通讯的I/O设备,绝大部分设备都是字符设备,常见的字符设备包括鼠标.键盘.显示器.串口等等,当我们执行ls -l ...
- 嵌入式Linux驱动学习之路(十)字符设备驱动-my_led
首先贴上代码: 字符设备驱动代码: /** *file name: led.c */#include <linux/sched.h> #include <linux/signal.h ...
- Linux应用程序访问字符设备驱动详细过程【转】
本文转载自:http://blog.csdn.net/coding__madman/article/details/51346532 下面先通过一个编写好的内核驱动模块来体验以下字符设备驱动 可以暂时 ...
- 深入理解Linux字符设备驱动
文章从上层应用访问字符设备驱动开始,一步步地深入分析Linux字符设备的软件层次.组成框架和交互.如何编写驱动.设备文件的创建和mdev原理,对Linux字符设备驱动有全面的讲解.本文整合之前发表的& ...
- Linux字符设备驱动结构(一)--cdev结构体、设备号相关知识机械【转】
本文转载自:http://blog.csdn.net/zqixiao_09/article/details/50839042 一.字符设备基础知识 1.设备驱动分类 linux系统将设备分为3类:字符 ...
- Smart210学习记录----beep linux字符设备驱动
今天搞定了beep linux字符设备驱动,心里还是很开心的,哈哈...但在完成的过程中却遇到了一个非常棘手的问题,花费了我大量的时间,,,, 还是把问题描述一下吧,好像这个问题很普遍的,网上许多解决 ...
- Linux驱动设计——字符设备驱动(一)
Linux字符设别驱动结构 cdev结构体 struct cdev { struct kobject kobj; struct module *owner; const struct file_ope ...
随机推荐
- jquery中prop()方法和attr()方法
接着上一篇笔记的疑惑,找了下prop()方法和attr()方法的区别. 原来query1.6中新加了一个方法prop(),一直没用过它,官方解释只有一句话:获取在匹配的元素集中的第一个元素的属性值. ...
- Java基础算法
i++;++i; i--;--i; int a=5;int b=a++;++放在后面,表示先使用a的值,a再加1b=5,a=a+1,a=6 int c=5;int d=++c;++放在前面,表示先将c ...
- Hexo 自动同步
灵感 最近认证阿里云学生用户,参与ESC服务器9.9元/月的活动,准备先搭建一个博客网站,写写自已的心得以及经验.之前也搭建过网站,最后由于个人没时间(没时间是假的,就是懒.哈哈)的原因导致最后服务器 ...
- 【Linux下安装配置Jupyter】
""" 第一步 安装 """ pip3 install -i https://pypi.douban.com/simple jupyter ...
- 读MBA经历回想(下)做法决定结果——北漂18年(49)
上期聊了目的决定了手段,这次说说详细做法决定了最后的结果. 差额面试被淘汰的高分学员 2005年,是北京邮电大学工商管理学入学考试第一个差额淘汰的年份.意思是过分数线(165分)的人数超过了录取人数, ...
- Linux经常使用命令(十六) - whereis
whereis命令仅仅能用于程序名的搜索(程序安装在哪?).并且仅仅搜索二进制文件(參数-b).man说明文件(參数-m)和源码文件(參数-s). 假设省略參数,则返回全部信息. 和find相比.wh ...
- es65 跨模块常量
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
- js---07 js预解析,作用域---闭包
js解析器首先不会逐行读代码,这是第二部了. 首先 根据var找到变量,根据function找函数,找到变量var a = 1,js解析器只会读取等号前面的var a,并把a设置值未定义,并不会读取等 ...
- java接口理解(转载)
今天和同事好好的讨论了java接口的原理和作用,发现原来自己的对接口的理解仅仅是局限在概念的高度抽象上,觉得好像理解了但是不会变化应用其实和没有理解差不多.以前看一个帖子说学习一个东西不管什么时候都要 ...
- 湖南省第八届大学生计算机程序设计竞赛(A,B,C,E,F,I,J)
A 三家人 Description 有三户人家共拥有一座花园,每户人家的太太均需帮忙整理花园.A 太太工作了5 天,B 太太则工作了4 天,才将花园整理完毕.C 太太因为正身怀六甲无法加入她们的行列, ...