ARM 中断状态和SVC状态的堆栈切换 (异常)

本文详细解析了ARM处理器在不同工作模式(如IRQ、SVC等)下的堆栈切换机制,包括寄存器使用规则、寻址指令以及Linux系统中的初始化过程。

ARM 中断状态和SVC状态的堆栈切换 (异常)

 

基础知识:

Arm的寄存器使用规则以及寻址指令:

R13  Sp     堆栈寄存器

R14  Lr     连接寄存器

R15  PC     程序计数器

 

多寄存器寻址:

LDMIA R0!,{R1-R4}

执行以后的效果

R1  <——[R0]

R2  <——[R0+4]

R3  <——[R0+8]

R4  <——[R0+12]

 

堆栈寻址:

STMFD入栈指令,相当于STMDB

STMFD SP!,{R2-R4} 注意这个“!”的使用,在使用和不使用的情况下会有不一样的效果,在后面的代码中具体分析。

[SP-4]  <­——R4

[SP-8]  <——R3 

[SP-12] <——R2   

 

LDMFD出栈指令,相当于LDMIA

LDMFD SP!,{R6-R8}

R6  <——[SP]

R7  <——[SP+4]

R8  <——[SP+8]

 

补充说明:

LDMIA / STMIA Increment After (先操作,后增加)

LDMIB / STMIB Increment Before(先增加,后操作)

LDMDA / STMDA Decrement After (先操作,后递减)

LDMDB / STMDB Decrement Before(先递减,后操作)

 

•STMFD (Push) 块存储- Full Descending stack [STMDB]

•LDMFD (Pop)  块装载- Full Descending stack [LDMIA]

 

这些使用规则以及默认的表达方法是给编译器使用。但是在开发底层语言的同时,有必要知道这个么命名的规则和使用方法。初始化代码和部分关键代码是靠汇编实现。这些代码的理解不免和汇编打交道。因此了解一下基本的汇编规则还是很有帮助。

 

 Arm的工作模式:

Arm的工作模式以及相关寄存器设置:

1,用户模式(usr)      [10000]:ARM处理器正常的程序执行状态

2,快速中断模式(fiq)  [10001]:用于高速数据传输或通道处理

3,外部中断模式(irq)  [10010]:用于通用的中断处理

4,管理模式(svc)      [10011]:操作系统使用的保护模式

5,中止模式(abt)          [10111]:当数据或指令预取终止时进入该模式,用于虚拟存

储及存储保护

6,未定义指令模式(und)[11011]:当未定义的指令执行时进入该模式,用于支持硬件

协处理器的软件仿真

7,系统模式(sys)      [11111]:运行具有特权模式的操作系统任务

 

设置方法:

MRS R14,CPSR       读取

MSR CPSR_c, R14     写入

 

以上几种模式存在的意义在于不同模式下特殊的几个寄存器使用是有区别的。再svc模式下堆栈指针为sp svc中断模式下sp指针为 sp irq等。同样lr连接寄存器的内容也是有不同的含义。再不同模式切换中,lr寄存器保存的地址是由硬件完成,但是表示的是不同模式下的下一条指令,既返模式切换后的返回地址。


每一种模式对应不同的bank register。中文官方翻译不详。但是每一种模式要拥有自己独立的寄存器组。并且每一种模式使用和可见寄存器的数量也是不相同的。

 

模式切换过程中其实只针对spsr进行操作,而未涉及cpsr是的操作。这个过程之所以这样主要是参考ARM cortex A8的TRM。其中这样描述离开异常的情况:

Typically the return instruction is an arithmetic orlogical operation with the S bit set to

1 and rd = r15, so the core copies the SPSR back to theCPSR.

 

也就是说离开异常,从异常情况返回以后会自动把spsr的内同拷贝到cpsr中。所以在执行BL Lr指令之前使用的堆栈其实并位切换。

 

Linux中初始化:

1,  Svc模式的堆栈初始化:

堆栈的概念是给C 语言编译以后的代码使用,因此从head.S一直到C语言的执行,就是start_kernel。

__mmap_switched:

    @注释 1:

    adr r3, __switch_data + 4

 

    ldmia   r3!, {r4, r5, r6, r7}

    cmp r4, r5              @ Copy datasegment if needed

1:  cmpne   r5, r6

    ldrne   fp, [r4], #4

    strne   fp, [r5], #4

    bne 1b

 

    mov fp, #0              @ Clear BSS(and zero fp)

1:  cmp r6, r7

    strcc   fp, [r6],#4

    bcc 1b

   

    @注释 2:

    ldmia   r3, {r4, r5, r6, r7, sp}

    str r9, [r4]            @ Saveprocessor ID

    str r1, [r5]            @ Savemachine type

    str r2, [r6]            @ Saveatags pointer

    bic r4, r0, #CR_A           @ Clear'A' bit

    stmia   r7, {r0, r4}            @Save control register values

    @注释 3:

    b   start_kernel

ENDPROC(__mmap_switched)

 

 

注释1:

    __switch_data这是以个地址。Linker会安排这个地址具体的数值。打开Sysmap可以发现这个数值为:c0008123 t __switch_data

注释 2:

    将r3所指的内容依次装入{r4– r6,sp},这个时候sp指针就有了具体的数值了。

注释 3:

    跳转指令,指向C函数的start_kernel。这时候栈针开始起效。因为C语言编译出来的代码参数传递,调用变量保存等都使用sp指针。这个指针仅仅是给初始化代码所使用。在进程的概念中还有进程堆栈的概念。这时候的sp具体指向的是描述进程结构的结构体task_info。

 

2,irq以及其他模式的初始化:

    /*

     * setup stacks for re-entrant exceptionhandlers

     */

    __asm__ (

    "msr    cpsr_c, %1\n\t"

    "add    r14, %0, %2\n\t"

    "mov    sp, r14\n\t"

    "msr    cpsr_c, %3\n\t"

    "add    r14, %0, %4\n\t"

    "mov    sp, r14\n\t"

    "msr    cpsr_c, %5\n\t"

    "add    r14, %0, %6\n\t"

    "mov    sp, r14\n\t"

    "msr    cpsr_c, %7"

        :

        : "r" (stk),

          PLC (PSR_F_BIT | PSR_I_BIT | IRQ_MODE),

          "I" (offsetof(struct stack,irq[0])),

          PLC (PSR_F_BIT | PSR_I_BIT | ABT_MODE),

          "I" (offsetof(struct stack,abt[0])),

          PLC (PSR_F_BIT | PSR_I_BIT | UND_MODE),

          "I" (offsetof(struct stack,und[0])),

          PLC (PSR_F_BIT | PSR_I_BIT | SVC_MODE)

        : "r14");

    函数:cpu_init()文件:setup.c

    通过msr设置了cpsr寄存器。然后通过mov指令把具体的参数地址写入sp寄存器。

其中offsetof(struct stack, irq[0])这个表达式表示的是偏移量。既是在结构体中的偏移量。

其实在这个函数中初始化的irq堆栈只有4 bytes x 3。这么小的堆栈空间是否可以满足中端的需求。答案是:可以。在中端进入的函数中其实并没有完全使用irq模式下sp_irq指向的堆栈空间。在进入函数中马上有利用了msr指令进行了模式切换,切换到了svc模式。并且放弃了irq的模式。从中端返回也是从svc模式返回,而非irq模式。

代码:

vector_\name:

    .if \correction

    sub lr, lr, #\correction

    .endif

 

    @

    @ Save r0, lr_<exception>(parent PC) and spsr_<exception>

    @ (parent CPSR)

    @

@ 注释 1:

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) 进入SVC模式

    msr spsr_cxsf, r0

 

    @

    @ the branch table mustimmediately follow this code

    @

    and lr, lr, #0x0f

    mov r0, sp

    ldr lr, [pc, lr, lsl #2]

    @注释 2:

    movs    pc, lr         @ branch tohandler in SVC mode

    参照ARM的参考

ENDPROC(vector_\name)

注释 1 :

保存irq模式下的sp和lr指针到前面初始的sp_irq中。记住只有4 bytes x 3大小的空间。在后面的代码中还会看到str  lr, [sp, #8]保存了最后一个参数到sp_irq的空间中。这里要注意:stmia  sp, {r0, lr}这条指令。没有使用“!”号。这样一来尽管指令执行后sp指针指向的地址不会自加。因此在正式切换到SVC模式之前sp_irq所指向的地址并没有变化。这样再次进入中断模式时候,sp_irq不需要调整,可以重复使用。

 

注释 2:

    参照ARM 的芯片设计手册可以发现,离开异常,从异常情况返回以后会自动把spsr的内同拷贝到cpsr中。所以在执行BL Lr指令之前使用的堆栈其实并位切换。

这样一来尽管是中断的模式进入系统,但是由中断模式切换至SVC模式。在SVC模式中完成了中断的后续相应和操作。


文章只是做了学习笔记已被后用,把这些思路罗列出来也给自己以后再回忆查找提供方便。

如果文章中有什么不对的地方,还请高手指正。

谢谢

edwardlu

<think> 我们正在讨论ARM处理器指令集状态切换的相关信息。根据之前的对话历史引用内容,我将重点总结ARM处理器在ARM状态Thumb状态之间切换的原理、方法及相关技术。 引用内容回顾: 引用[1]提到:在进入异常中断后,内核自动切换ARM状态异常中断处理程序入口的一些指令是ARM指令,然后根据需要可以切换到Thumb状态,返回前再切换ARM状态。 引用[2]详细介绍了BX指令,说明它是带状态切换的分支指令,通过目标地址的最低位(Rm[0])来设置CPSR的T标志位,从而实现状态切换。同时,还给出了ARM状态Thumb状态相互切换的汇编代码示例。 引用[3]提到了ARM异常处理机制,但未直接涉及指令集状态切换。 引用[4]介绍了ARM三级流水线,指出PC指向当前执行指令地址+8(即两条指令后)。 引用[5]描述了中断模式(IRQ)SVC模式切换堆栈的处理,但未直接涉及指令集状态切换。 因此,我们将基于引用[1][2]重点讨论指令集状态切换。 指令集状态切换主要涉及ARM状态(32位指令)Thumb状态(16位指令,或Thumb-2的混合16/32位)之间的切换切换原理: 1. 处理器状态由CPSR(当前程序状态寄存器)的第5位(T位)决定: - T=0:ARM状态 - T=1:Thumb状态 2. 切换方法: - 使用分支指令(BX、BLX)跳转时,目标地址寄存器的最低位(bit0)会被写入CPSR.T位,从而改变状态。 * 若目标地址最低位为1,则切换到Thumb状态 * 若目标地址最低位为0,则切换ARM状态 - 异常发生时,处理器自动切换ARM状态(如引用[1]所述),在异常处理程序中可以通过BX等指令再切换到Thumb状态,但返回前必须切换ARM状态,因为异常返回会从SPSR恢复CPSR(包括T位),而进入异常时保存的CPSR是ARM状态(除非异常发生时已经是Thumb状态,但ARM规定异常入口为ARM状态,所以保存的T位会被强制设为0?这里需要澄清:根据ARM架构参考手册,当异常发生时,处理器会强制进入ARM状态,即CPSR.T=0。因此,无论异常发生时处于什么状态,进入异常处理程序时都是ARM状态,且SPSR保存了发生异常时的CPSR(包括T位)。返回时,通过将SPSR恢复到CPSR,会恢复原来的状态。 然而,引用[1]明确指出:在进入异常中断后,内核自动切换ARM状态。因此,异常处理程序入口处是ARM状态切换方法细节(引用[2]中的示例): 1. 从ARM状态切换到Thumb状态: ```armasm ADR R0, THUMB_code + 1 ; 将Thumb代码入口地址加1(使最低位为1)放入R0 BX R0 ; 跳转,同时CPSR.T = R0[0] = 1,进入Thumb状态 ``` 2. 从Thumb状态切换ARM状态: ```armasm ADR R0, ARM_code ; 将ARM代码入口地址(对齐,最低位为0)放入R0 BX R0 ; 跳转,同时CPSR.T = R0[0] = 0,进入ARM状态 ``` 注意:目标地址必须对齐(ARM状态4字节对齐,Thumb状态2字节对齐),但BX指令跳转的目标地址(写入PC)是寄存器值的最低位置0后的地址(即清除最低位)。因此,在设置目标地址时,如果是Thumb代码,需要将地址加1(表示Thumb状态),这样BX指令清除最低位后得到正确的Thumb指令地址。 另外,BLX指令有两种形式: - BLX <label>:绝对跳转,并切换状态(根据目标地址对齐方式,但通常由汇编器处理) - BLX <Rm>:与BX类似,但会保存返回地址到LR(带链接的分支) 异常处理中的状态切换(引用[1]): - 进入异常时:强制切换ARM状态(CPSR.T=0) - 异常处理程序中:可以通过BX指令切换到Thumb状态(如果处理程序用Thumb编写) - 返回异常前:必须切换ARM状态,因为异常返回指令(如SUBS PC, LR, #4)会从SPSR恢复CPSR(包括T位),而SPSR保存的是进入异常前的状态。如果在异常处理中切换到Thumb状态,那么在返回前需要切换ARM状态,否则返回后恢复的CPSR中的T位会决定返回后的状态。但是,由于异常返回时是从SPSR恢复CPSR,所以实际上在异常处理程序中可以任意切换状态,只要在返回前确保PC指向的指令是ARM指令(因为返回后要执行的是被中断的指令流,其状态由SPSR决定)。然而,异常返回指令(如SUBS PC, LR, ...)本身必须在ARM状态下执行,因为Thumb状态没有直接操作PCCPSR的指令(除了特殊的IT指令块外,但通常异常返回使用ARM指令)。 因此,异常处理程序的结构通常为: 1. 入口处为ARM状态(自动切换)。 2. 如果需要,使用BX指令跳转到处理程序的Thumb代码部分(切换状态)。 3. 处理完成后,切换ARM状态(使用BX指令跳转回ARM代码段)。 4. 执行异常返回指令(ARM指令)恢复现场。 相关技术: - Thumb-2技术:从ARMv6T2开始引入,允许在Thumb状态下执行16位32位指令,提高了代码密度性能,减少了状态切换的需要。 - 交互工作(Interworking):指ARMThumb代码之间的相互调用。通过BXBLX指令实现状态切换。 数学表示: 设目标地址为$ \text{addr} $,则实际跳转地址状态切换为: $$ \text{PC} = \begin{cases} \text{addr} \& \sim1 & \text{(清除最低位)} \\ \end{cases} $$ $$ \text{CPSR.T} = \text{addr}[0] $$ 注意事项: - 使用BX/BLX切换状态时,目标地址必须正确设置(最低位表示状态)。 - 在异常处理中,进入返回时的状态切换由硬件自动处理,但处理程序内部可以自由切换。 下面用表格形式总结指令集状态切换: | **切换方向** | **触发指令** | **目标地址设置** | **状态变化** | **示例** | |--------------------|------------------------|---------------------------|----------------------------------|----------------------------------------------| | ARM → Thumb | `BX <Rm>` 或 `BLX <Rm>` | Rm[0] = 1 | CPSR.T = 1 | `ADR R0, thumb_code+1; BX R0` | | Thumb → ARM | `BX <Rm>` 或 `BLX <Rm>` | Rm[0] = 0 | CPSR.T = 0 | `ADR R0, arm_code; BX R0` | | 异常进入 | 硬件自动 | 无 | 强制 CPSR.T = 0 | 发生IRQ时自动进入ARM状态 | | 异常返回 | `SUBS PC, LR, #offset` | 无(由SPSR恢复CPSR) | 恢复为异常发生前的状态 | `SUBS PC, LR, #4` | 相关问题: 1. 在Thumb状态下如何实现异常返回? 2. Thumb-2技术如何减少状态切换的需求? 3. 使用BX指令切换状态时,为什么目标地址需要对齐? 4. 在异常处理程序中,如果不切换ARM状态就执行异常返回指令会有什么后果? 注意:在异常处理程序中,如果我们使用Thumb代码编写了大部分处理程序,那么在返回到异常发生点之前,必须确保执行一条ARM指令(即异常返回指令)。因此,我们通常会在Thumb代码的最后使用一条BX指令跳转到一个ARM指令的地址(该地址处有一条异常返回指令)。或者,在Thumb-2中,我们可以使用`POP {PC}`这样的指令来返回(因为POP到PC会触发状态切换,但必须确保弹出值的bit0正确),但这依赖于架构版本。 关于引用[4]中PC的行为,由于三级流水线,PC总是超前当前执行指令8字节(ARM状态)或4字节(Thumb状态)。但在状态切换时,跳转指令会正确设置PC,无需特别考虑。 </think>### ARM处理器指令集状态切换机制总结 #### 指令集状态切换原理 ARM处理器支持两种指令集状态: 1. **ARM状态**:执行32位定长指令,性能更高 2. **Thumb状态**:执行16位/32位混合指令(Thumb-2),代码密度更高 状态切换由**CPSR寄存器的T标志位**(第5位)控制: - $ \text{T} = 0 $:ARM状态 - $ \text{T} = 1 $:Thumb状态 切换机制基于**地址对齐原则**: $$ \text{目标地址} = \begin{cases} \text{addr} \& \sim1 & \text{(ARM状态)} \\ \text{addr} \& \sim1 + 1 & \text{(Thumb状态)} \end{cases} $$ #### 状态切换方法 | **切换方式** | **指令/机制** | **状态变化** | **关键操作** | 适用场景 | |---------------------|------------------------------------------|-------------------|-----------------------------------------------------------------------------|------------------------| | **显式分支切换** | `BX <Rm>`/`BLX`指令 | ARM⇄Thumb | `CPSR.T` = `Rm[0]`,`PC` = `Rm & 0xFFFFFFFE`[^2] | 函数调用 | | **异常自动切换** | 中断/异常事件 | →ARM状态 | 进入异常时强制`CPSR.T=0`[^1],返回时恢复原状态 | 中断处理 | | **寄存器直接修改** | `MSR CPSR_t, #value`(特权模式) | 直接设置T标志位 | 直接修改`CPSR`寄存器 | 系统级代码 | #### 核心切换指令详解 1. **BX指令工作流程**: ```armasm ; ARM状态切换至Thumb ADR R0, Thumb_Code + 1 ; 地址+1表示Thumb状态 BX R0 ; 执行后: ; CPSR.T = R0[0] = 1 ; PC = R0 & 0xFFFFFFFE ; Thumb状态切换ARM ADR R0, ARM_Code ; 地址对齐(末位0) BX R0 ; 执行后: ; CPSR.T = R0[0] = 0 ; PC = R0 & 0xFFFFFFFE ``` 2. **异常处理中的状态切换**: - 异常发生时硬件自动: - 保存当前`CPSR`到`SPSR` - 强制`CPSR.T = 0`(进入ARM状态)[^1] - 跳转到异常向量表 - 返回时通过`SUBS PC, LR, #4`恢复原`CPSR`(含T标志位) #### 关键技术特性 1. **Thumb-2扩展**(ARMv6T2+): - 允许在Thumb状态下执行32位指令 - 消除$ 70\% $的状态切换需求 - 代码密度比ARM状态高$ \sim 26\% $ 2. **状态切换开销**: | **架构版本** | **切换延迟(周期)** | **流水线影响** | |--------------|----------------------|----------------------| | ARMv4T | 5-7 | 3级流水线清空 | | ARMv7-A | 3 | 部分流水线气泡 | | ARMv8 | 1-2 | 零周期切换(推测执行)| 3. **地址对齐约束**: - ARM状态:`PC`必须4字节对齐(`PC[1:0]=0b00`) - Thumb状态:`PC`必须2字节对齐(`PC[0]=0`) - 违反对齐原则将触发**未定义指令异常** #### 状态切换示例场景 ```mermaid graph TD A[用户代码 Thumb状态] -->|SVC指令| B[异常处理] B --> C{入口强制切换} C --> D[ARM状态执行] D -->|BX指令| E[Thumb状态服务] E -->|返回前切换| F[ARM状态] F -->|SUBS PC,LR,#4| A ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值