嵌入式系统Linux内核开发实战指南(ARM平台)
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

第7章 ARM处理器工作模式与异常中断处理

7.1 ARM处理器工作模式

ARM处理器共有7种工作模式,除用户(user)模式之外的其他6种处理模式统称为特权模式(Privileged Modes)。在这些模式下,程序可以访问所有的系统资源,也可以任意切换处理器模式。特权模式中除系统(system)模式之外的其他5种模式又统称为异常模式。

处理器模式可以通过软件控制进行切换,也可以通过外部中断或内部异常处理过程进行切换。运行在用户模式下的程序不能访问一些受操作系统保护的系统资源,也不能直接进行处理器模式切换,如果要想进行模式切换,可以产生异常处理,在异常处理过程中进行处理器模式的切换,这种体系架构可以使操作系统控制整个系统资源。

每一种异常模式中都有一组寄存器供异常处理程序使用,这样可以保证在进入异常模式时,用户模式下的寄存器不被破坏。

系统模式不是通过异常过程进入的,它和用户模式具有完全相同的寄存器。系统模式属于特权模式,可以访问所有系统资源,也可以直接进行处理器模式切换,主要供操作系统内核进程使用;通常操作系统内核进程需要访问所有的系统资源,同时该进程仍然使用用户模式下的寄存器组,而不是异常模式下的寄存器组,这样可以保证当异常发生时内核进程状态不被破坏。

ARM处理器的7种工作模式如表7-1所示。

表7-1 ARM处理器的7种工作模式

对应于以上7种模式,ARM处理器有7组通用寄存器,如表7-2所示。

表7-2 ARM处理器各种工作模式下的寄存器

说明:

✧ ARM处理器共有37个寄存器,被分为若干个组(BANK),这些寄存器包括31个通用寄存器和6个状态寄存器,其中通用寄存器包括程序计数器(PC指针)及所有寄存器

(均为32位)。

✧ ARM处理器中有用户模式、系统模式、特权模式、数据访问终止模式、未定义指令终止模式、IRQ中断模式、FIQ中断模式7种工作模式,每种工作模式都有R0~R15及CPSR共17个通用寄存器,而在5种异常中断(exception)模式中又各自拥有一个独立的SPSR寄存器用于存放当前状态寄存器的值。这些通用寄存器对于各种工作模式或部分工作模式有些是公用的,如表7-2所示。

✧ R8~R12对于快速中断FIQ模式之外的其他模式都是公用的,而FIQ模式另外有一套自己寄存器R8_fiq~R12_fiq,这样是为了在处理FIQ中断时不必保护R8~R12寄存器,从而提高响应速度。

✧ R13和R14除了用户模式和系统模式公用外,其他每种异常模式都有一套自己独立的寄存器:R13_svc、R14_svc/R13_abt、R14_abt/R13_und、R14_und/R13_irq、R14_irq/R13_fiq、R14_fiq,即R13和R14有6套物理寄存器实现。

✧ R13/sp寄存器在ARM中除了可用作通用寄存器外还用作堆栈指针sp,而且每一种异常(exception)模式都有自己独立的R13物理寄存器实现。在子程序中R13只能用作sp,sp在进入子程序的值和退出子程序的值必须相等。

✧ R14/lr寄存器在ARM中除了可以用作通用寄存器外,还可用作连接寄存器lr,用来保存子程序的返回地址,如果子程序保存了返回地址,R14也可以用作其他用途。每一种异常(exception)模式都有自己独立的R14/lr物理寄存器实现。

✧ R15寄存器在ARM中用作程序计数器pc,虽然R15也可以用作通用寄存器,但要注意有一些特殊限制,如果违反了这些限制,指令执行的结果将是不可预料的。

✧ 子程序通过寄存器R0~R3来传递参数,这时,R0~R3可以记作A1~A4。如果参数超过4个,可以将剩余的参数送到数据栈中,入栈的顺序与参数的顺序相反,即最后一个参数字先入栈。

✧ 在子程序中用R4~R11来保存局部变量,这时,R4~R11可以记作V1~V8;而在Thumb程序中,通常只能使用寄存器R4~R7。

✧ 寄存器R12用作子程序间的scratch寄存器,记作ip,在子程序间的连接代码中常有这种使用规则。

7.2 ARM处理器异常中断向量表和优先级

异常(Exception)是指由处理器执行指令导致原来运行程序的中止,异常与指令运行相关,是CPU执行程序产生的,是同步的,可分为精确异常和非精确异常。异常处理遵守严格的程序顺序,不能嵌套,只有当第一个异常处理完并返回后才能处理后续的异常。

中断(Interrupt)是异步产生的,不是由CPU执行程序产生的,中断属于异常的一种,中断是唯一与CPU运行无关的异常。所以我们用异常中断来统称Exception和Interrupt。

ARM处理器中的异常种类及其向量表和优先级说明如表7-3所示。

表7-3 ARM处理器的异常向量表及优先级

说明:

✧ 高端向量是ARM架构可选配置,可以通过硬件外部输入管脚来配置是低端向量还是高端向量,不能通过指令来改变向量的位置,但如果ARM芯片内部有标准ARM协处理器,那么协处理器CP15的寄存器C1的bit13可以用来切换低端和高端向量地址,等于0时为低端向量,等于1时为高端向量。

✧ ARM的例外优先级从高到低依次为Reset→Data abort→FIQ→IRQ→Prefetch abort→Undefined instruction/SWI。

7.3 ARM处理器异常中断处理

我们主要介绍如何进入异常中断处理以及如何退出异常中断处理。

7.3.1 进入异常中断处理

ARM处理器发生异常中断,则ARM处理器进入如下异常中断自动处理过程(假设发生的异常中断对应的模式为mode):

1)将当前程序状态寄存器CPSR的值保存到SPSR_mode中;

2)将CPSR中的模式位设置成mode模式,将CPSR中的bit7(I)设置为1,禁止IRQ中断,如果是FIQ中断,则再将CPSR中的bit6(F)设置为1,禁止FIQ中断;

3)将返回地址传给lr_mode;

4)将该异常中断的向量地址传给程序计数器pc,从而进入异常中断处理程序。

上述操作步骤可以用如下伪代码来描述:

        R14_<exception_mode> = return link
        SPSR_<exception_mode> = CPSR
        CPSR[4:0] = exception mode number
        CPSR[5] = 0;切换到ARM状态
        If <exception_mode> == Reset or FIQ then
            CPSR[6] = 1;禁止FIQ中断
        CPSR[7] = 1;禁止IRQ中断
        pc = exception vector address

7.3.2 退出异常中断处理

当要从异常中断处理程序中返回时,要做以下两步操作(假设发生的异常中断对应的模式为mode):

1)将保存在SPSR_mode中的值恢复到当前程序状态寄存器CPSR中;

2)返回到发生异常中断的指令的下一条指令处执行,也就是将lr_mode寄存器的值适当地返回到程序计数器pc中。

但程序员只需做好上述第二步即可,第一步在完成第二步的同时由处理器自动完成,所以我们下面讲解从各种异常中断处理返回的编程接口。

■ 退出复位异常中断处理(Reset)

复位异常中断处理程序不需要返回,所以不需要这个接口。

■ 退出未定义指令异常中断处理(Undefined Instruction)

未定义指令异常中断由当前执行的指令自身产生,当未定义指令异常中断产生时,程序计数器pc的值还未更新,它指向当前指令后面第2条指令(对于ARM指令,它指向当前指令地址加8字节的位置;对于Thumb指令,它指向当前指令地址加4字节的位置),当未定义指令异常中断发生时,处理器将值(pc-4)保存到lr_und中,此时(pc-4)指向当前指令的下一条指令,所以从未定义指令异常中断返回可以通过如下指令来实现:

        mov pc, lr

该指令将寄存器lr_mode中的值复制到程序计数器pc中,实现程序返回,同时将SPSR_mode寄存器中的值复制到当前程序状态寄存器CPSR中。

如果要在异常中断处理中使用数据栈,那么可以在进入异常中断处理程序时保存被中断程序的执行现场,在退出异常中断处理程序时恢复被中断程序的执行现场,编程如下:

        stmfd sp!, {register_list, lr}     ;保存被中断程序的执行现场
        ; . . .
        ldmfd sp!, {register_list, pc}^    ;恢复被中断程序的执行现场

上面的register_list,是异常中断处理程序中使用的寄存器列表,标识符^表示要将SPSR_mode寄存器中的值复制到当前程序状态寄存器CPSR中。

■ 退出软中断指令(SWI)异常中断处理(Undefined Instruction)

SWI异常中断和未定义异常中断指令一样,也是由当前执行的指令自身产生,当SWI指令执行时,pc的值还未更新,它指向当前指令后面第2条指令(对于ARM指令,它指向当前指令地址加8字节的位置;对于Thumb指令,它指向当前指令地址加4字节的位置),当未定义指令异常中断发生时,处理器将值(pc-4)保存到lr_svc中,此时(pc-4)指向当前指令的下一条指令,所以从SWI异常中断处理返回的实现方法与从未定义指令异常中断处理返回一样:

        mov pc, lr

使用数据栈的方法与未定义指令异常中断处理中的方法也一样:

        stmfd sp!, {register_list, lr}     ;保存被中断程序的执行现场
        ; . . .
        ldmfd sp!, {register_list, pc}^    ;恢复被中断程序的执行现场

■ 退出指令预取中止异常中断处理(Prefetch Abort)

在指令预取时,如果目标地址是非法的,该指令被标记成有问题的指令,这时,流水线上该指令之前的指令继续执行,当执行到该被标记成有问题的指令时,处理器产生指令预取中止异常中断。发生指令预取异常中断时,程序要返回到该有问题的指令处,重新读取并执行该指令,因此指令预取中止异常中断应该返回到产生该指令预取中止异常中断的指令处,而不是当前指令的下一条指令。

指令预取中止异常中断由当前执行的指令自身产生,当指令预取中止异常中断发生时,程序计数器pc的值还未更新,它指向当前指令后面第2条指令(对于ARM指令,它指向当前指令地址加8字节的位置;对于Thumb指令,它指向当前指令地址加4字节的位置)。此时处理器将值(pc-4)保存到lr_abt中,它指向当前指令的下一条指令,所以返回操作可以通过下面指令实现:

        subs pc, lr, #4

该指令将lr中的值减4后传给程序计数器pc中,实现程序返回,同时将SPSR_abt寄存器的内容复制到当前程序状态寄存器CPSR中。

如果要在指令预取中止异常中断处理中使用数据栈,可以用以下方法保护、恢复被中断程序的执行现场:

        subs lr, lr, #4
        stmfd sp!, {register_list, lr}     ;保存被中断程序的执行现场
        ; . . .
        ldmfd sp!, {register_list, pc}^    ;恢复被中断程序的执行现场

上面的register_list是异常中断处理程序中使用的寄存器列表,标识符^表示要将SPSR_abt寄存器中的值复制到当前程序状态寄存器CPSR中。

■ 退出数据访问中止异常中断处理(Data Abort)

发生数据访问异常中断时,程序要返回到该有问题的指令处,重新访问该数据,因此数据访问异常中断应该返回到产生该数据访问中止异常中断的指令处,而不是当前指令的下一条指令。

数据访问异常中断由当前执行的指令自身产生,当数据访问异常中断发生时,程序计数器pc的值已经更新,它指向当前指令后面第3条指令(对于ARM指令,它指向当前指令地址加12字节的位置;对于Thumb指令,它指向当前指令地址加6字节的位置)。此时处理器将值(pc-4)保存到lr_abt中,它指向当前指令后面第2条指令,所以返回操作可以通过下面指令实现:

        subs pc, lr, #8

该指令将lr中的值减8后传给程序计数器pc中,实现程序返回,同时将SPSR_abt寄存器内容复制到当前程序状态寄存器CPSR中;

如果要在数据访问异常中断处理中使用数据栈,可以用以下方法保护、恢复被中断程序的执行现场:

        subs lr, lr, #8
        stmfd sp!, {register_list, lr}     ;保存被中断程序的执行现场
        ; . . .
        ldmfd sp!, {register_list, pc}^    ;恢复被中断程序的执行现场

上面的register_list是异常中断处理程序中使用的寄存器列表,标识符^表示要将SPSR_abt寄存器中的值复制到当前程序状态寄存器CPSR中。

■ 退出IRQ异常中断处理程序(IRQ)

通常处理器执行完当前指令后,查询IRQ中断引脚,并查看是否允许IRQ中断,如果某个中断引脚有效,并且系统允许该中断产生,处理器将产生IRQ异常中断,当IRQ异常中断产生时,程序计数器pc的值已经更新,它指向当前指令后面第3条指令(对于ARM指令,它指向当前指令地址加12字节的位置;对于Thumb指令,它指向当前指令地址加6字节的位置),当IRQ异常中断产生时,处理器将值(pc-4)保存到IRQ异常模式下的寄存器lr_irq中,它指向当前指令之后的第2条指令,因此返回操作可以通过下面指令实现:

        subs pc, lr, #4

该指令将lr中的值减4后传给程序计数器pc中,实现程序返回,同时将SPSR_irq寄存器的内容复制到当前程序状态寄存器CPSR中。

如果要在IRQ异常中断处理中使用数据栈,可以用以下方法保护、恢复被中断程序的执行现场:

        subs lr, lr, #4
        stmfd sp!, {register_list, lr}     ;保存被中断程序的执行现场
        ; . . .
        ldmfd sp!, {register_list, pc}^    ;恢复被中断程序的执行现场

上面的register_list是异常中断处理程序中使用的寄存器列表,标识符^表示要将SPSR_irq寄存器中的值复制到当前程序状态寄存器CPSR中。

■ 退出FIQ异常中断处理程序(FIQ)

与IRQ异常中断一样,处理器执行完当前指令后,查询FIQ中断引脚,并查看是否允许FIQ中断,如果中断引脚有效,并且系统允许该中断产生,处理器将产生FIQ异常中断,当FIQ异常中断产生时,程序计数器pc的值已经更新,它指向当前指令后面第3条指令(对于ARM指令,它指向当前指令地址加12字节的位置;对于Thumb指令,它指向当前指令地址加6字节的位置),当FIQ异常中断产生时,处理器将值(pc-4)保存到IRQ异常模式下的寄存器lr_fiq中,它指向当前指令之后的第2条指令,因此返回操作可以通过下面指令实现:

        subs pc, lr, #4

该指令将lr中的值减4后传给程序计数器pc中,实现程序返回,同时将SPSR_fiq寄存器的内容复制到当前程序状态寄存器CPSR中。

如果要在FIQ异常中断处理中使用数据栈,可以用以下方法保护、恢复被中断程序的执行现场:

        subs lr, lr, #4
        stmfd sp!, {register_list, lr}     ;保存被中断程序的执行现场
        ; . . .
        ldmfd sp!, {register_list, pc}^    ;恢复被中断程序的执行现场

上面的register_list是异常中断处理程序中使用的寄存器列表,标识符^表示要将SPSR_fiq寄存器中的值复制到当前程序状态寄存器CPSR中。

7.4 ARM处理器的中断(IRQ或FIQ)

前面提到的ARM处理器的IRQ和FIQ异常中断都是由处理器内部或外部中断源引起的。ARM处理器支持多个内部或外部中断源,如定时器中断、UART中断、网络收发中断等,ARM处理器一般用中断使能寄存器、中断屏蔽寄存器、中断停靠寄存器、中断状态寄存器、中断模式寄存器来管理这些中断,这些寄存器都是32位的,各寄存器中对应的一位管理一个中断源,所以ARM处理器支持一般不超过32个中断源,要了解处理器有哪些中断源请参看各处理器芯片手册。

上述寄存器中的中断使能寄存器用来使能中断,寄存器中的位为1时,其对应的中断被使能,否则中断不被使能。中断屏蔽寄存器用来禁止中断,寄存器中的位为1时,其对应的中断被禁止。中断停(pending)靠寄存器是只读寄存器,如果其中的某位为1,表示当前系统中发生了该位对应的中断,如果系统中同时产生了多个中断,那么该寄存器中就有多个相应的位被置1。中断状态寄存器也是只读寄存器,这个寄存器的值相当于使能寄存器与停靠寄存器相与得到的值,也就是说只有那些被使能并且当前确实产生了的中断对应该寄存器中的位才会被设置为1。中断模式寄存器用来分别将每个中断源配制成IRQ中断模式或者FIQ中断模式,IRQ是普通中断模式,处理速度比较慢,优先级较低,而FIQ处理速度表较快,优先级较高。