美文网首页嵌牛IT观察
Linux 异常处理的层次结构

Linux 异常处理的层次结构

作者: b88a0f2ae902 | 来源:发表于2018-07-22 22:12 被阅读0次

姓名:楼竞扬 学号:15020199020

5.1.1 Linux 异常处理的层次结构

异常的作用

异常,就是可以打断CPU正常运行流程的一些事情,如外部中断、未定义指令中断、试图修改只读的数据、执行swi指令(Software Interrupt Instruction)等。当异常发生时,CPU暂停当前的程序,先处理异常事件,然后再继续执行被中断的程序。

未定义指令异常,操作系统可以利用其来使用一些自定义的机器指令,它们在异常处理函数中实现。

数据访问中止异常,可以将一块数据设为只读,然后提供给多个进程共用,这压根可以节省内存。当某个进程试图修改其中的数据时,将出发“数据访问中止异常”,在异常处理函数中将这块数据复制出一份可写的副本,提供给这个进程使用。

当用户程序试图读写的数据或执行的指令不在内存中时,也会出发一个“数据访问中止异常”或“指令预取中止异常”,在异常处理函数中将这些数据或指令读入内存(内存不足时还可以将不用的数据、指令换出内存),然后重新执行被中断的程序。这样可以节省内存, 还使得操作系统可以运行这类程序:它们使用的内存远大于实际的物理内存。

当程序使用不对齐的地址访问内存时,也会触发“数据访问中止异常”,在异常处理程序中先使用多个对齐的地址读出数据;对于读操作,从中选取数据组合好后返回给被中断的程序;对于写操作,修改其中的部分数据后再写入内存。这使得程序(特别是应用程序)不用考虑地址对齐的问题。

用户程序可以通过 "swi" 指令触发 "swi异常",操作系统在 swi 异常处理函数中实现各种系统调用。

Linux 内核对异常的设置

内核在 start_kernel() 函数中调用 trap_init、init_IRQ这两个函数来设置异常的处理函数。

init/main.c:

asmlinkage void __init start_kernel(void)

{

...

    trap_init();

    init_IRQ();

... 

}

(1) trap_init()函数分析(linux-2.6.22.6\arch\arm\kernel\traps.c)

该函数用来设置各种异常的处理向量,包括中断向量。“向量”就是被放在固定位置的代码,当发生异常时,CPU会自动执行这些固定位置上的指令。ARM架构CPU的异常向量基址可以是 0x00000000 或 0xFFFF0000 ,Linux内核使用 0xFFFF0000 。trap_init() 函数就是将异常向量复制到基质处,代码如下:

721行中,vectors = CONFIG_VECTORS_BASE ,CONFIG_VECTORS_BASE 是一个配置项(内核配置选项),在Linux顶层目录下,文件.config,搜索CONFIG_VECTORS_BASE

故vectors = CONFIG_VECTORS_BASE = 0xffff0000。而地址 __vectors_start, __vectors_end之间的代码就是异常向量,在arch/arm/kernel/entry-armv.S中定义,它们被复制到地址 0xFFFF0000处。

异常向量的代码,大部分都是一些跳转指令。发生异常时,CPU自动执行这些指令,然后再跳转去执行更复杂的代码,比如保存被中断的执行环境,调用异常处理函数,恢复被中断程序的执行环境并重新运行程序。这部分”复杂代码“在地址__stubs_start, __stubs_end之间,它们在arch/arm/kernel/entry-armv.S中定义,第722行代码将其复制到地址0xFFFF0000+Ox200处。

异常向量的代码如下,其中的stubs_offset 用来重新定位跳转的位置。

.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:

stubs_offset的确定:

异常向量表和异常处理程序搬移前后对比

当汇编器看到 B 指令,把要跳转的标签转化为相对于当前PC的偏移量(±32 M )写入指令码。由于内核启动时中断向量表和 stubs 都发生了代码便宜,所以如果中断向量表中仍然写成b vector_irq,那么实际执行的时候就无法跳转到搬移后的vector_irq处,因为指令码里写的是原来的偏移量,所以需要把指令码中的偏移量写成偏移后的。设偏移后的偏移量为offset,则:

offset = L1 + L2

  = [0x200 - (irq_PC_x - _vector_start_x)] + (vector_irq_x - _stubs_start_x)

  =  0x200 - irq_PC + _vector_start + vector_irq - _stubs_start

  = vector_irq + (_vector_start + 0x200 - _stubs_start) - irq_PC

令:

  stubs_offset = _vector_start + 0x200 - _stubs_start

则:

  offset = vector_irq + stubs_offset - irq_PC

所以中断入口点的跳转指令为:

  b  vector_irq + stubs_offset

其中"- irq_PC"是由汇编器在编译时完成的

其中的vector_und、vector_pabt等表示要跳转去执行的代码。以vector_und为例,仍是在该文件中,通过 vector_stub宏来定义,代码如下:

/*

* Undef instr entry dispatcher

* Enter in UND mode, spsr = SVC/USR CPSR, lr = SVC/USR PC

*/

vector_stub und, UND_MODE

.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

.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

.align  5

这段代码表示在各个工作模式下执行未定义指令时,发生的异常的处理分支。如__und_usr表示在用户模式下, 执行未定义指令时,所发生的未定义异常将由它来处理;__und_svc表示在管理模式下执行未定义指令时,所发生的未定义异常由它来处理。在其它工作模式下不可能发生未定义指令异常,否则使用__und_invalid来处理错误。ARM架构CPU中使用4位数据来表示工作模式(目前只有7中工作模式),所以共有16个跳转分支。

vector_stub是一个宏,它根据后面的参数und、UND_MODE定义了以vector_und为标号的一段代码。这个宏为:

.macro  vector_stub, name, mode, correction=0

.align  5

vector_\name:

.if \correction

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, #8]        @ 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 #2]

movs    pc, lr          @ branch to handler in SVC mode

.endm

按参数展开为:

.macro  vector_stub, name, mode, correction=0

.align  5

vector_und:

.if 0

sub lr, lr, #0

.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, #8]        @ save spsr

@

@ Prepare for SVC32 mode.  IRQs remain disabled.

@

mrs r0, cpsr

eor r0, r0, #(UND_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 #2]

movs    pc, lr          @ branch to handler in SVC mode

.endm

这个vector_stub宏的功能是:计算处理完异常后的返回地址、保存一些寄存器(如r0、lr、spsr),然后进入管理模式,最后根据被中断的工作模式调用相应的跳转分支(如.long __und_usr)。当发生异常时,CPU会根据异常的类型进入某个工作模式,但是很快 vector_stub宏又会强制CPU进入管理模式,在管理模式下进行后续处理,这种方法简化了程序设计,使得异常发生前的工作模式要么是用户模式,要么是管理模式。

不同的跳转分支(__und_usr、__und_svc)只是在它们的入口处(比如保存被中断成都的寄存器)稍有差别,后续的处理大致相同,都是调用相应的C函数。比如未定义指令异常发生时,最终会调用do_undefinstr来进行处理。

各种异常的C处理函数可以分为5类,分布在不同的文件中:

arch/arm/kernel/traps.c

未定义指令异常的C处理函数,do_undefinstr

arch/arm/mm/fault.c

于内存相关访问异常的C处理函数,do_DataAbort、do_PrefetchAbort

arch/arm/kernel/irq.c

中断处理函数的在这个文件夹中定义,总入口函数asm_do_IRQ,它调用其它文件注册的中断处理函数

arch/arm/kernel/calls.S

swi 异常的处理函数指针被组织成一个表格:swi 指令机器码的位[23:0]被用来作为索引。通过不同的 swi index 指令调用不同的 swi 异常处理函数,被称为系统调用,如 sys_open、sys_read、sys_write等。

没有使用的异常

在Linux 2.6.22.6 中没有使用FIQ异常

trap_init()函数搭建了各类异常的处理框架。当发生异常时,各种C处理函数会被调用。这个C函数还要进一步细分异常发生的情况, 分别调用更具体的处理函数。

(2) init_IRQ()函数分析

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

init_IRQ()函数被用来初始化中断的处理框架,设置各种中断的默认处理函数。当发生中断时,中断入口函数asm_do_IRQ就可以调用这些函数做进一步处理。

总结

ARM架构Linux内核的异常处理体系结构:

ARM架构Linux内核的异常处理体系结构

相关文章

  • Linux 异常处理的层次结构

    姓名:楼竞扬 学号:15020199020 5.1.1 Linux 异常处理的层次结构 异常的作用 异常,就是可以...

  • Linux 异常处理的层次结构

    5.1.1 Linux 异常处理的层次结构 异常的作用异常,就是可以打断CPU正常运行流程的一些事情,如外部中断、...

  • SpringMvc异常

    异常处理类的介绍 类层次结构 异常处理类详解 1.AnnotationMethodHandlerException...

  • Python学习(八)

    异常处理 Python中的异常类型总结: Python内置异常类的层次结构: 异常检测 try-except语句 ...

  • Android 中的异常处理

    异常发生时,如果没有一个异常处理器来处理这个异常,程序会被中止。在 JVM 当中有一个预先定义好的异常处理层次结构...

  • JAVA 异常机制

    try-catch 异常机制的本质 异常处理方式 java异常类层次结构图 Error 表明系统JVM已经处于不可...

  • Java异常层次结构

    一、什么是Java异常? Java异常就是代码编译和运行中出现的一些特殊情况,通过异常我们可以查找出异常的代码进行...

  • java异常层次结构

    java异常层次结构说明 直接上图: 介绍如下: Error: 错误。指的是jvm遇到的重大的问题,如内存溢出、...

  • RH124|第三章 使用命令行管理文件

    描述linux文件系统的层次结构概念 目标 完成这一部分后,你能够描述Linux如何组织文件,以及文件系统层次结构...

  • Java异常

    Java异常类层次结构图: 通常,Java的异常(包括Exception和Error)分为 可查的异常(check...

网友评论

    本文标题:Linux 异常处理的层次结构

    本文链接:https://www.haomeiwen.com/subject/fsbrmftx.html