ARMv7-A小系统实践(六):异常入口、返回地址与独立中断栈
本章记录什么
这一章解释异常入口与返回,并建立任务私有的 IRQ 异常帧。它要解决的不是“进入了哪个 C 函数”,而是任意指令执行到一半时,怎样保存足够的信息,使处理结束后能够像中断从未发生一样继续执行;如果调度器选择了另一个任务,又怎样改为恢复另一个任务的现场。
在这一章之前做了什么
上一章的协同切换发生在函数调用边界,因此可借助 AAPCS 只保存 callee-saved 状态。IRQ 没有这个便利:r0-r3、r12、条件标志和返回地址都可能正处于有效计算中。硬件只完成模式切换、CPSR 到 SPSR 的保存以及返回地址写入 banked lr,其余现场必须由入口汇编建立。
本章先分清硬件动作和软件动作,再说明为什么返回 PC 需要修正、为什么异常帧应归属于被打断任务、为什么 C handler 可以使用独立中断栈但恢复现场不能长期停留在共享栈上。
系列目录 · 上一篇:协同任务与普通上下文切换
本章的阅读顺序
先读基础概念,确认每个术语解决什么问题;再看设计推导,理解实现为什么采用这种保存集合、初始化顺序或状态机;随后逐段阅读代码和执行轨迹;最后用验证方法与错误案例检查理解。文中保留寄存器名、指令名和标准缩写,叙述部分统一使用简体中文。
动手前的基础:异常入口、现场与返回
日期:2026-07-28
状态:本专题已完成并通过;只代表异常入口与返回完成,不代表整个第四目标完成
专题范围:CPU 看见异常以后发生什么
后续专题:GIC、设备中断号、定时器、acknowledge/EOI
一、本节学完要形成的完整链
1 | |
本节最重要的不是背指令,而是分清三方责任:
| 责任方 | 负责什么 |
|---|---|
| CPU 硬件 | 切模式、保存旧 CPSR 到 SPSR、生成 LR、进入向量 |
| 异常入口汇编 | 修正返回地址、保存通用寄存器、构造帧、满足 AAPCS |
| C handler / OS | 判断原因、处理事件、记录故障、决定是否调度或终止 |
二、“中断”和“异常”是什么关系
在 ARMv7-A 里,异常是更大的概念:
1 | |
- Undefined、SVC、Abort 通常与当前执行指令直接相关。
- IRQ/FIQ 通常来自外部设备或中断控制器,可以异步打断当前代码。
- GIC 负责汇总和仲裁许多设备中断,再向 Cortex-A9 发出 IRQ/FIQ 信号。
- CPU 收到 IRQ 信号后,才执行本节学习的“IRQ 异常进入”。
因此:
1 | |
本节先掌握最后一段;下一轮再补设备和 GIC。
三、异常向量表
ARMv7-A 的普通异常向量有 8 个槽,每槽 4 字节:
| 向量偏移 | 异常 | 目标模式 |
|---|---|---|
0x00 |
Reset | SVC |
0x04 |
Undefined Instruction | UND |
0x08 |
SVC | SVC |
0x0C |
Prefetch Abort | ABT |
0x10 |
Data Abort | ABT |
0x14 |
Reserved | — |
0x18 |
IRQ | IRQ |
0x1C |
FIQ | FIQ |
向量表不是 8 个完整 handler,只是 8 个入口槽。常见写法:
1 | |
后面再放真正地址:
1 | |
为什么不能直接把整个 handler 写在向量槽中:相邻槽只相差 4 字节,一个槽只容纳一条 32 位 ARM 指令,所以通常先跳转。
Cortex-A9 可以通过 VBAR 设置普通向量表基址。RTEMS 启动代码中可定点观察:
1 | |
它使用 8 条 ldr pc, ... 组成向量表,并在启动阶段设置向量基址。
四、异常发生时,硬件自动做什么
以原程序处于 System 模式、IRQ 到来为例。
异常前:
1 | |
CPU 接受 IRQ 后,概念上完成:
1 | |
这里再次体现第一主题的 banked registers:
- 原来的
SP_sys/LR_sys没有被复制或覆盖; - 切到 IRQ 模式后,同名
sp/lr映射成SP_irq/LR_irq; SPSR_irq保存的是异常前完整 CPSR,里面包含旧模式、T 位、I/F 位和条件标志等。
硬件没有自动做的事
CPU 不会自动:
- 把
r0-r12压入栈; - 生成操作系统定义的
ExceptionFrame结构; - 调用 C 函数;
- 查询 GIC 中断号;
- 清除设备中断源;
- 决定是否切换任务。
这些都属于软件。
五、为什么必须同时保存返回 PC 和旧 CPSR
只恢复 PC 不够。
假设 IRQ 前:
1 | |
IRQ 期间:
1 | |
返回时必须恢复:
1 | |
恢复 CPSR 后:
- 模式回到 System;
- 当前可见 SP/LR bank 回到
SP_sys/LR_sys; - ARM/Thumb 状态恢复;
- IRQ 屏蔽状态恢复;
- 条件标志恢复。
这正是第一主题“模式 bank”和本主题“异常返回”的闭环。
六、LR 为什么还要修正
异常进入时的 LR 是架构规定的“返回信息”,不保证对所有异常都等于最终继续 PC。不同异常与流水线语义不同,常见 ARM-state 返回方式如下:
| 异常 | 常见返回/重试形式 | 意义 |
|---|---|---|
| SVC | movs pc, lr |
从 SVC 后一条继续 |
| Undefined | movs pc, lr |
跳过未定义指令 |
| IRQ/FIQ | subs pc, lr, #4 |
回到被打断程序的继续位置 |
| Prefetch Abort | subs pc, lr, #4 |
修复原因后重试取指 |
| Data Abort | subs pc, lr, #8 |
修复原因后重试数据访问 |
这张表用于建立差异意识,不要求现在死背。实际返回还取决于:
- 希望跳过故障指令还是重试;
- 异常前是 ARM 还是 Thumb;
- 使用
SUBS/MOVS PC,LR、RFE,还是先构造统一异常帧; - 系统是否把 LR 预先修正成最终 PC。
RTEMS 的 IRQ 入口先做:
1 | |
把 LR_irq 预先变成最终继续 PC,然后使用 SRS/RFE 形式统一保存和返回。
七、异常入口为什么和普通上下文不同
普通任务切换:
1 | |
IRQ:
1 | |
例如:
1 | |
如果 IRQ 的 C handler 改写 r0,回来后 str 就会写入错误数据。
但这不意味着每个 IRQ 入口一定机械保存所有 r0-r15:
- 入口汇编自身和被调用 C 函数会破坏的状态必须保存;
r4-r11若完全交给符合 AAPCS 的 C handler,C handler 自己必须保持它们;- 如果入口汇编把某些 r4-r11 当临时寄存器,就必须额外保存;
- PC/CPSR 通过 LR/SPSR 或异常帧专门保存;
- VFP/NEON 是否保存取决于系统策略。
正确问题不是“是不是全保存”,而是:
从异常点返回时,原程序仍然需要的每一项状态,是否都能得到原值?
八、一条简化的 IRQ 入口路径
下面是教学用骨架,不包含 SMP、嵌套中断、独立中断栈和 VFP:
1 | |
逐步解释:
sub lr,lr,#4:将 IRQ 架构 LR 修正为最终继续地址。SRS:保存 Return State,也就是返回 PC 与旧 CPSR。CPS #0x13:切换到 SVC 模式,使用 SVC 栈运行公共处理逻辑。PUSH:保护任意异常点上可能活跃、又会被 C 调用破坏的寄存器。BL:进入 C 后重新受 AAPCS32 约束。POP:恢复被中断程序的易失寄存器。RFE:同时恢复 PC/CPSR;CPSR.M 恢复后重新看到原模式的 banked SP/LR。
这里 6 个寄存器共 24 字节,SRS 的返回状态占 8 字节;如果入口 SVC SP 原本 8 字节对齐,则公共调用前仍保持 8 字节对齐。
实际 RTEMS 代码位于:
1 | |
可只观察这些关键点:
1 | |
其余嵌套计数、SMP、VFP、临时中断栈和调度逻辑暂时忽略。
九、SRS/RFE SUBS PC,LR` 是什么关系
简单 handler
如果整个 IRQ handler 一直留在 IRQ 模式,并把 LR_irq/SPSR_irq 保持好,可以:
1 | |
这里 S 与写 PC 的组合完成异常返回语义:
1 | |
统一异常帧
复杂 OS 往往要:
- 换到 SVC 栈或独立中断栈;
- 允许嵌套;
- 调用 C;
- 可能在 IRQ 尾部调度;
- 让不同异常共享一套帧。
因此先把返回 PC 和 CPSR 存进内存,最后使用:
1 | |
从内存同时恢复它们。
二者最终目标相同:
1 | |
只是返回状态保存在寄存器还是内存、处理过程简单还是复杂。
十、异常帧是什么
异常帧是 OS 在内存中定义的数据结构,不是 ARM 硬件统一规定的 C 结构体。
示意:
1 | |
它的作用:
- 让 C handler 查看异常现场;
- 让调试器打印寄存器;
- 修改返回值或返回地址;
- 支持异常尾部调度;
- 最终由汇编按相同偏移恢复。
ARM 规定的是异常入口和返回的架构语义;帧字段顺序由 OS 决定。但一旦汇编执行 BL c_handler:
1 | |
仍必须遵守 AAPCS32。
十一、IRQ 的完整分层
先建立全图,下一轮再展开中间部分:
1 | |
“CPU 进入 IRQ 模式”并不能告诉软件到底是 UART、定时器还是网卡触发。真正的中断号来自 GIC acknowledge,这就是下一轮的核心。
十二、常见错误与 Debug 现象
1. 没有初始化 SP_irq
现象:第一次 IRQ 一压栈就访问错误地址,常表现为 Data Abort 或直接死机。
2. 忘记修正 IRQ LR
现象:返回后跳过或重复一条指令,可能持续出现难以解释的逻辑错误。
3. 没保存 r0-r3/r12 就调用 C
现象:中断返回后普通代码变量随机改变。
4. SP 未按 8 字节对齐就 BL
现象:简单 handler 似乎可运行,稍微优化、增加 64 位访问或换编译器后崩溃。
5. 只恢复 PC,没恢复 CPSR
现象:仍处于异常模式、IRQ 一直被屏蔽、栈 bank 错误或 ARM/Thumb 状态错误。
6. 没清设备 pending 或没写 EOI
现象:返回后立刻再次进入 IRQ,形成中断风暴。此项将在下一轮展开。
十三、自检题与解析
完成这一章后,可以不看正文回答下面六个问题。
硬件进入 IRQ 时自动保存什么?
硬件把旧 CPSR 保存到
SPSR_irq,按异常类型生成LR_irq,切换到 IRQ 模式并跳到VBAR+0x18。它不会自动保存r0-r12,也不会替操作系统建立 C 结构体形式的异常帧。为什么只恢复 PC 不够?
异常前的模式、I/F/T 位和条件标志都在旧 CPSR 中。正确返回必须同时恢复 PC 与 CPSR,否则即使地址正确,处理器也可能留在错误模式或使用错误指令状态。
为什么 IRQ 入口要保护
r0-r3和r12?IRQ 可发生在任意指令边界,这些寄存器可能含有尚未消费的中间值;入口又要调用遵循 AAPCS 的 C 函数,而 C 函数有权改写 caller-saved 寄存器。
SRS与RFE分别做什么?SRS把当前异常模式的返回地址和 SPSR 放入目标模式栈;RFE从匹配布局中同时装回 PC 与 CPSR。它们是一组适合构造统一异常帧的指令,但仍需软件保存通用寄存器。独立 IRQ 栈和任务私有 frame 是否矛盾?
不矛盾。任务私有 frame 保存将来恢复任务所需的状态;独立 IRQ 栈承载 C handler 的调用深度。前者解决现场所有权,后者隔离中断处理过程的栈消耗。
怎样口述一次完整 IRQ?
设备提出请求,GIC 交付 IRQ,处理器保存 CPSR/LR 并进入向量,汇编修正返回地址并建立 frame,C 分发器读取 IAR、处理设备、清源并写 EOIR,IRQ 尾部选择最终 frame,最后恢复通用寄存器并以
RFE或等价序列恢复 PC/CPSR。
十四、自检结论
如果能够独立解释硬件与软件的责任边界、返回地址修正、异常帧所有权和 PC/CPSR 成对恢复,就具备继续连接 GIC 与设备中断的基础。
架构手册规则怎样落到 IRQ frame
第一部分:ARM 架构异常入口与返回
3. 异常与中断不是完全同义
ARM 异常包括同步和异步来源:
| 异常 | 典型原因 | 同步性 |
|---|---|---|
| Reset | 复位 | 特殊 |
| Undefined Instruction | 指令不支持/编码非法 | 同步 |
| SVC | 软件执行 SVC |
同步 |
| Prefetch Abort | 取指失败 | 同步 |
| Data Abort | 数据访问失败 | 同步 |
| IRQ | 普通外部中断请求 | 异步 |
| FIQ | 快速外部中断请求 | 异步 |
因此:
1 | |
GIC 只负责外部中断分发,不负责修复 Data Abort,也不决定 SVC 的语义。
4. 向量表
普通 AArch32 异常向量偏移:
| 偏移 | 向量 |
|---|---|
0x00 |
Reset |
0x04 |
Undefined Instruction |
0x08 |
SVC |
0x0C |
Prefetch Abort |
0x10 |
Data Abort |
0x14 |
Reserved |
0x18 |
IRQ |
0x1C |
FIQ |
向量表每槽只有 4 字节,通常放一条跳转或装载 PC 指令:
1 | |
实际可使用 VBAR 指向对齐的向量基址,也可能受高向量配置影响。移植时必须确认:
- 向量内容位于 CPU 可执行地址;
- VBAR 设置的是运行地址,不是错误的链接/加载地址;
- 写 VBAR 后按架构要求使用同步措施;
- 每个异常入口符号和指令状态正确;
- IRQ 栈在开放 IRQ 前已经初始化。
向量与 GIC ID 再次区分
所有 GIC 普通 IRQ 都进入 +0x18。向量表没有 1024 个槽。设备 ID 是在 IRQ 软件入口读取 GICC_IAR 后才得到。
5. 异常入口:硬件做什么
以普通 IRQ 进入 IRQ 模式为主线,架构效果可理解为:
1 | |
硬件通常没有替软件完成:
1 | |
所以“异常发生时 CPU 自动保存全部寄存器”是错误认识。
为什么要尽快保存 LR_irq 与 SPSR_irq
它们是当前异常的关键返回状态。同模式再次嵌套或入口代码误改可能覆盖它们。成熟入口会尽快将其搬到受控栈帧。
6. 返回地址为什么不能统一写死
不同异常的 LR_mode 语义不同,处理目标也不同:
1 | |
常见 A32 形式:
1 | |
但实际返回必须结合:
- 异常类型;
- ARM/Thumb 状态;
- 是重试故障指令还是跳过;
- 入口是否已经提前修正 LR;
- 使用
RFE还是经典带S返回。
最稳妥的方法是依据 ARM ARM B1.8.3 的 Table B1-6/B1-7,而不是背一句“所有异常 LR 都减 4”。
7. 软件入口怎样建立异常帧
一种可读的逻辑布局:
1 | |
入口的职责:
- 修正或保存原始
LR_irq。 - 保存
SPSR_irq。 - 保存会被入口和 C handler 改写的通用寄存器。
- 保证 frame 布局与 C 定义一致。
- 保证调用 C 时 SP 8 字节对齐。
- 处理 IRQ 栈、SVC 栈或 per-CPU interrupt stack 的切换策略。
- 记录中断嵌套状态。
SRS/RFE 路线
支持相应 ARMv7 指令时,可用:
1 | |
SRS 把异常返回状态保存到指定模式栈;RFE 从内存同时恢复 PC 与 CPSR。具体寻址后缀必须与栈布局匹配。
经典路线
也可以:
1 | |
这只是示意。实际代码若把状态搬到 SVC 栈、支持嵌套或调度,布局会更复杂。
为什么 handler 不能直接普通 return
C 的 return 只遵循 AAPCS 返回到调用它的汇编入口,并不会自动:
- 把
SPSR_irq恢复到 CPSR; - 从 IRQ 模式回到原模式;
- 选择异常返回语义;
- 重新装载完整异常现场。
真正异常返回仍由汇编出口完成。
8. 异常返回的本质
无论使用哪种指令形式,最终必须原子地实现逻辑:
1 | |
恢复 CPSR 带来的效果包括:
- 原模式恢复;
- 原
I/F/A屏蔽状态恢复; - 原 ARM/Thumb 状态恢复;
- 原条件标志恢复;
- 当前可见 SP/LR bank 回到原模式。
如果只恢复 PC、不恢复 CPSR,代码可能从正确地址以错误模式或错误指令状态执行,通常很快崩溃。
从普通 context 走到任务私有 IRQ frame
实现步骤四:建立真正的 IRQ 异常帧
1 | |
为什么要和普通上下文分开
普通切换发生在函数调用边界,受 AAPCS 保护;IRQ 可以发生在任意指令之间,因此r0-r3/r12 也可能保存着仍然有效的中间值,不能丢。
先看什么
1 | |
官方资料重点:
- IRQ 自动执行
CPSR → SPSR_irq; LR_irq的返回地址语义;- IRQ 模式有 banked SP/LR;
subs pc, lr, #4或 RFE 类返回如何恢复 CPSR。
必须先回答的问题
- 硬件自动保存了哪些内容,哪些必须由软件保存?
- 为什么 IRQ 入口要保存
r0-r12? - 为什么调用 C dispatcher 前 SP 必须仍为 8 字节对齐?
- 为什么保存的是
SPSR_irq,而不是只保存 handler 当前 CPSR? - 为什么普通
bx lr不能完成异常返回?
本实现的 64 字节异常帧
1 | |
r0-r12,lr 共 56 字节,再为 SPSR 和 padding 留 8 字节,总计 64 字节,C 调用前仍满足
8 字节对齐。
返回顺序
1 | |
这一步仍不主动打开 IRQ。目的是把“异常保存/返回合同”与“中断控制器配置”拆开。
从规则走到实际实现
下面回到本章对应的小系统实现。这里不再假定读者已经打开源码,而是把关键地址、数据结构、指令序列和返回路径直接放在文章中解释。
异常是总称,IRQ 只是其中一种
在这里采用的经典 ARM 向量布局中:
| 偏移 | 异常 | 常见原因 |
|---|---|---|
| 0x00 | Reset | 复位入口 |
| 0x04 | Undefined | 不能执行的指令或相应扩展不可用 |
| 0x08 | SVC | 软件执行 SVC 指令 |
| 0x0c | Prefetch Abort | 取指访问失败 |
| 0x10 | Data Abort | 数据访问失败 |
| 0x14 | Reserved | 保留 |
| 0x18 | IRQ | 普通中断请求 |
| 0x1c | FIQ | 快速中断请求 |
SVC、Undefined、精确 Abort 与当前指令有明确关系;IRQ/FIQ 来自异步请求。Data Abort 还要区分精确与非精确情形,不能把所有异常都看成“当前 PC 的一条指令出错”。
CPU 的 IRQ 向量只有一个。UART 和 Timer 的 IRQ 并不会分别跳到向量表中第 UART 号、第 Timer 号的位置;具体设备号要进入后读取 GIC。
硬件自动做了什么,没做什么
对本工程从 SVC 进入 IRQ 的路径,硬件完成的核心动作是:
1 | |
它不会自动把 r0-r12、任务 LR 和所有局部变量压进内存。也不会创建 C 栈帧、调用 irq_dispatch,或查找哪个任务最该运行。这些都是入口软件的责任。
IRQ 模式有自己的 SP/LR,可以让入口先取得一组独立寄存器,但普通通用寄存器仍然可能包含被打断代码的活跃数据。在保存前拿 r0 当临时变量,就可能破坏任务。
LR_irq 为什么减 4
异常 LR 是架构定义的返回信息,不能当成普通 BL 留下的 LR。对当前 IRQ 处理方式,入口执行:
1 | |
得到适合恢复的返回 PC,再把它放入异常帧。后面的 RFE 已使用修正后的值,不应再次减 4。
在本系列 ARM 状态的常见处理约定中,可以这样理解:
| 异常 | 常见返回位置处理 | 限定条件 |
|---|---|---|
| IRQ/FIQ | LR 减 4 后回到被打断执行流 | 采用经典 PL1 返回语义 |
| SVC | 通常使用异常 LR,继续下一条 | 不重复执行 SVC |
| Undefined | 要看模拟、跳过还是终止 | 不能统一套一个修正常量 |
| Prefetch Abort | 若修复后重试,常用 LR 减 4 | 先确认异常与执行状态 |
| 精确 Data Abort | 若修复后重试,常用 LR 减 8 | 不是对所有 Data Abort 都可恢复 |
Thumb 指令宽度、异常种类和期望重试行为都会影响推理。系统故障诊断应保存原始 LR/SPSR,并据异常类型解释,不能把 IRQ 的减 4 抄遍所有 handler。
基础版把完整现场留在任务自己的栈上
基础版 src/start.S 的 IRQ 主体:
1 | |
不要一口气读完。它其实有四个阶段。
把 IRQ bank 中的返回信息转移到 SVC 栈
SRSDB sp!, #0x13 使用当前异常模式的 LR/SPSR,把它们存到指定目标模式 SVC 的栈,并更新那个目标 SP。此时 CPU 尚在 IRQ 模式。
接着 cps #0x13 切到 SVC,名字 SP 便能看到刚刚更新后的 SVC 栈位置,名字 LR 看到的是原任务的 LR_svc。随后保存 r0-r12 和 LR_svc。
这里严格依赖“被打断任务使用 SVC 栈”的设计。如果以后任务在 User 模式,SVC 栈不再自动等于该用户任务自己的栈,必须另外安排用户 SP/LR 和内核栈的归属。
得到 64 字节的任务私有 frame
1 | |
总计 16 个字,64 字节。r13 没作为字段保存,因为 frame 地址加上帧大小就是这段恢复序列应还原的 SP;PC 与 CPSR 来自额外的两个返回字。
对应 C 定义是 r[13]、svc_lr、return_pc、cpsr。工程用 offsetof 和 sizeof 静态断言核对 52/56/60/64 这些数字,避免 C 布局与汇编脱节。
把 C handler 移到独立栈
frame 指针放在 r0,作为 irq_dispatch 的参数。随后入口按 MPIDR 选本核中断栈顶,再执行 BL。
此时 CPU 处于 SVC 模式。当前 SP 是 SVC bank 的寄存器,只是它的值落在名为 irq_stack 的内存区。因此“中断里读 SP 就一定是 sp_irq”在这个实现里不成立。
两种“IRQ 栈”概念必须分开:
| 对象 | 含义 |
|---|---|
| banked sp_irq | IRQ 模式下名为 SP 的硬件寄存器 |
| 独立软件中断栈 | 专门留给 handler 调用链使用的内存 |
| 任务私有异常帧 | 被打断任务未来恢复所需的内存状态 |
一块内存可以在启动时作为 sp_irq 的初始值,之后又被 SVC 模式用作 C 处理栈;这不改变 banked 寄存器的架构定义。
从分发器选择的现场返回
irq_dispatch 返回一个 frame 指针。它可以是原任务 A 的 frame,也可以是任务 B 的 frame。
1 | |
先恢复普通寄存器和任务 LR,再从剩下的两个字同时恢复 PC/CPSR。RFE 不是普通 BX:它携带异常状态恢复语义,使任务模式、中断屏蔽与条件标志一起回到保存时的状态。
为什么不能把两个任务的现场一直留在同一 IRQ 栈
假定 A 被打断,现场保存在共享中断栈;IRQ 尾部切到 B。下一次 B 被打断又从同一个中断栈顶压入现场,A 尚未恢复的数据就可能被覆盖。
于是出现一种典型假象:第一次切换成功,第二次返回就跳飞。寄存器保存集合可能完全正确,错误来自内存所有权。
本实现的分工是:
1 | |
任务可恢复状态随任务保留;中断栈只承担当前中断处理的临时工作。
不支持嵌套,是当前实现的重要约束
每次入口都把 handler SP 直接设到同一个本核 IRQ 栈顶。如果处理中再次允许 IRQ,新入口会覆盖第一层 handler 的调用链。因此当前代码保持 IRQ 屏蔽,不支持重入式嵌套。
真要加嵌套,至少要处理:
- 异常 LR/SPSR 已经转移到安全内存;
- 仅最外层切到中断栈,内层沿当前中断栈继续压栈;
- 嵌套深度和 dispatch-disable 状态;
- GIC 优先级与 EOIR 的对应顺序;
- 只在允许的最外层返回点调度。
RTEMS 参考路径中多出的这些计数和检查都有具体用途,并不是可以随手删除的冗余代码。
栈对齐与栈容量要分别验证
被打断时任务 SP 可能只按 4 字节对齐,64 字节保存并不会改变它的模 8 余数。当前入口在调用 C 前换到 8 字节对齐的中断栈,从而满足公共接口要求。
中断栈底有四个哨兵字,handler 还记录见到的最小 SP。哨兵能发现部分越界,最小 SP 能观察到采样点处的使用量,但不能证明任意更深调用从未达到更低地址。完整高水位分析需要填充整个栈并扫描,或在更多位置记录。
小系统没有为每个任务提供完善的 guard page。扩大 printf 调用、局部数组或递归以后,仍然要重新计算栈预算。
Abort 的记录比盲目返回更重要
当前 fatal 路径输出 vector、异常 LR、SPSR,以及 DFSR/DFAR/IFSR/IFAR,然后停止。它没有实现缺页修复和用户异常信号。
诊断顺序应是:先用 vector 判断异常类别,再解码对应 FSR,结合有效 FAR、SPSR 和反汇编定位。对于某些非精确异常,FAR 或 LR 不足以精确定位出错的那条访存,不能机械使用“打印地址减常量”。
异常规则可查 ARM ARM 的 B1.8,尤其是 Exception return addresses 与 Exception return。结合源码画出上面的 frame,比只记住一个 SUBS 返回公式更可靠。
重点回看
IRQ 可发生在任意指令边界,硬件只保存少量控制状态,软件必须建立完整可恢复 frame。返回 PC 需要按异常类型修正,C handler 可以运行在独立 IRQ 栈上,但任务的可恢复现场必须有稳定所有者。恢复 PC 和 CPSR 是一个不可拆开的动作。
本章总结
IRQ 可发生在任意指令边界,硬件只保存少量控制状态,软件必须建立完整可恢复 frame。返回 PC 需要按异常类型修正,C handler 可以运行在独立 IRQ 栈上,但任务的可恢复现场必须有稳定所有者。恢复 PC 和 CPSR 是一个不可拆开的动作。
下一章把安全的 IRQ 入口接到 GIC 与 Private Timer,建立可重复中断的状态闭环。
ARMv7-A小系统实践(六):异常入口、返回地址与独立中断栈
https://goko-son626.github.io/post/armv7a-05-exception-frames.html

