当前位置:首页 > 学习资源 > 讲师博文 > ARM处理器为什么需要不同工作模式?异常与中断机制基础解析

ARM处理器为什么需要不同工作模式?异常与中断机制基础解析 时间:2026-09-30      来源:华清远见

导读:很多初学 STM32 或 Linux 内核的同学都会背诵「ARM 有 7 种工作模式」,却很少有人认真想过:为什么一颗 CPU 要给自己设计这么多『人格』?本文从『保护』与『效率』两个动机出发,把 ARMv7-A 的模式体系、异常/中断的硬件响应流程、Cortex-M 的 NVIC 机制一次讲清楚,并附上 STM32 实操代码与 6 个高频 Bug 的排查方法。

一、先回答『为什么』:模式是硬件级的隔离与加速机制

设想一个没有模式区分的 CPU:用户程序、操作系统内核、设备驱动全部跑在同一个『身份』下,那么任何一个死循环、野指针都能直接改写页表、关掉 MMU、清空中断控制器——整个系统瞬间崩溃。软件约定(『请大家自觉不要碰这些寄存器』)在安全面前一文不值,因为约定无法对抗 Bug,更无法对抗恶意代码。

ARM 的解法是把『身份』做进硬件:CPU 在任意时刻都处于某个确定的工作模式(Mode),模式决定了两件事:

特权等级(Privilege Level):用户模式(User)下执行特权指令(如写 CPSR、配置 MMU/CP15)会直接触发异常,硬件不给你犯错的机会;

可见的寄存器集合:每种异常模式拥有一组私有(banked)的栈指针 R13、链接寄存器 R14 和保存程序状态寄存器 SPSR。

第二点是很多人忽略的『效率动机』。异常发生时 CPU 需要保存现场,如果所有模式共用一个栈指针,那么 FIQ 中断来了要先读内存切换栈、再压栈,全程多次访存;而有了 banked R13/R14,FIQ 模式拥有自己独立的 R13_fiq/R14_fiq,进入中断后立刻就能用,把上下文切换的访存次数压到最低——这正是当年 ARM 为快速中断设计的硬件加速。所以一句话总结:模式 = 特权隔离(安全)+ banked 寄存器(速度)。

1.1 ARMv7-A 的 7+1 种模式一览

注意:Cortex-A 系列才保留这套完整模式;到了 Cortex-M(STM32 所在的系列),ARM 把模式极度简化为两个——Thread 模式(跑主程序)和 Handler 模式(跑中断),特权级仍保留 User/Privileged 两档。简化不是倒退:嵌入式场景不需要 MMU 缺页、不需要系统调用指令,保留的只有『中断来了硬件自动切到 Handler 并压栈』这一核心能力。

二、异常与中断:一套硬件机制,两类触发源

ARM 文档里『异常(Exception)』是统称,指所有打断当前指令流的事件;『中断(Interrupt)』特指由外设异步触发的异常(IRQ/FIQ)。按触发时机可分为:

同步异常:由当前指令『自己』造成,如未定义指令、数据访问中止、SWI/SVC、断点。重新执行同一条指令必然再次触发;

异步异常(中断):与当前指令无关,由外部事件造成,如定时器到期、串口收到数据。

无论哪种异常,CPU 的响应骨架都是同一套四步曲(以 ARMv7-A 为例):

① 保存断点:把当前 PC 写入新模式的 R14(LR);

② 保存状态:把 CPSR 写入新模式的 SPSR;

③ 切换模式:修改 CPSR 的模式位、中断屏蔽位,必要时切到特权态;

④ 跳转向量:PC = 向量表中对应表项地址,开始执行处理程序。

2.1 向量表:两种架构,两种存法

图 1  ARMv7-A 与 Cortex-M 向量表结构对比:前者存『跳转指令』,后者直接存『函数地址』

ARMv7-A 的向量表里放的是指令(通常是一条 B 或 LDR PC),因为 A 系列异常发生时CPU 只是把 PC 指到表项地址,接下来『怎么跳、跳到哪』由表里的指令决定,这给了操作系统把向量表重定位到高端地址 0xFFFF0000 的灵活性。而 Cortex-M 把流程完全硬件化:向量表里直接存处理函数的地址,硬件取出地址直接送入 PC,连跳转指令都省了——这是 M 系列能做到 12 个周期完成中断进出的基础之一。

三、异常发生时硬件到底做了什么(Cortex-M 深度拆解)

图 2  Cortex-M 异常响应全流程:①②⑥ 由硬件自动完成,软件只需写第 ⑤ 步

对照上图,我们把 Cortex-M 的硬件动作拆到寄存器级别:

流水线冲刷:取消已经取指/译码但未完成的指令,保证异常响应点的精确性;

入栈(Stacking):硬件自动把 xPSR、PC、LR、R12、R3、R2、R1、R0 共 8 个字压入当前栈指针(MSP 或 PSP,由 CONTROL[1] 决定)。这 8 个字就是『自动现场』,其余寄存器(R4–R11)若被 ISR 使用,由编译器生成的 push 指令保存;

向量获取:用异常号 ×4 作为偏移,从 VTOR 指向的向量表中取出 ISR 地址;同时把一个特殊值 EXC_RETURN 写入 LR;

模式切换:进入 Handler 模式,SP 强制使用 MSP,特权级提升,并且『当前正在运行的优先级』被更新,从而屏蔽同级及更低优先级中断;

返回:ISR 末尾的 BX LR 发现 LR 是 EXC_RETURN(0xFFFFFFF1/FFF9/FFFD 等魔数),触发硬件自动出栈(Unstacking),恢复 8 个寄存器并回到被打断的线程。

两个让 M 系列『快上加快』的硬件优化值得记住:

尾链(Tail-chaining):ISR 刚要出栈时又来了新异常,硬件跳过『出栈再入栈』,直接切换向量执行新 ISR,省下约一次 8 字栈操作;

迟到(Late-arriving):入栈的 12 个周期里来了更高优先级异常,硬件复用已压好的栈帧,直接去执行高优先级 ISR。

【原理要点】理解『入栈/出栈是硬件做的』这一点,是理解后面所有中断 Bug 的钥匙:它解释了为什么 ISR 里不需要手写 push {r0-r3},也解释了 HardFault 时为什么能在栈上『挖』出出事前 8 个寄存器的值。

四、中断控制器 NVIC:让『多个中断』有序排队

CPU 同一时刻只能执行一个 ISR,但外设的中断是并发到达的。NVIC(Nested Vectored Interrupt Controller)就是硬件级的『调度员』,负责三件事:使能/屏蔽(ISER/ICER)、挂起登记(ISPR/ICPR)、优先级仲裁。优先级在 Cortex-M 上又被拆成两层:

图 3  NVIC 优先级分组:抢占位决定能否嵌套,子优先级决定同级排队顺序

抢占优先级(Preemption):数值更小者可以打断正在执行的 ISR,形成中断嵌套;

子优先级(Subpriority):两者抢占级相同时,同时到达则子优先级小者先执行,但正在执行者不会被打断。

STM32 库函数 NVIC_PriorityGroupConfig() 就是在配置 AIRCR.PRIGROUP 字段,决定 8 位优先级寄存器中『几位给抢占、几位给子优先级』。复位默认是 Group0——即所有中断都不可互相抢占,这是无数『中断不嵌套』玄学问题的根源(见 Bug 1)。

五、实操:STM32 外部中断与 SysTick(寄存器级代码)

下面以 STM32F103 为例,配置 PA0 按键下降沿中断。刻意用寄存器写法,因为每一步都对应前面讲的机制:

▍EXTI0 配置与 ISR(STM32F103 寄存器版)

再看启动文件中向量表的真容——它解释了『为什么函数名写错就进 HardFault / Default_Handler 死循环』:

▍启动文件中的向量表与 WEAK 符号

5.1 SysTick:操作系统的心跳

▍SysTick 1ms 心跳

六、常用 Bug 与排查

Bug 1:中断『不嵌套』——忘了配置优先级分组

现象:高优先级 ISR 无法打断低优先级 ISR。原因:复位后 AIRCR.PRIGROUP 默认为 Group0,8 位全部分给子优先级,任何中断都不可抢占。解决:系统初始化早期调用一次 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2),且全工程只允许设置一次(中途改分组会让已有优先级语义全部错乱)。

Bug 2:ISR 无限重入——忘记清挂起标志

现象:主程序几乎不运行,示波器上中断引脚『打满』。两层标志要分清:外设级(如 EXTI->PR、USART->SR)和 NVIC 级。外设标志不清,NVIC 会一直看到请求,退出 ISR 的瞬间(尾链)立刻再次进入。口诀:进 ISR 先看标志、处理完先清标志。

Bug 3:主循环看不到中断里改的变量——缺 volatile

现象:key_cnt 在调试器里在涨,主循环 while(key_cnt==0) 却死等。编译器在 -O2 下认为主循环里没有修改 key_cnt,把判断优化成了常量。给变量加 volatile 告诉编译器『它可能在编译视野之外被修改(中断/DMA/其它核)』。同类问题还有多字节变量的非原子读写:32 位以上变量在中断与主循环间共享时,主循环读取要临时关中断(__disable_irq()/__enable_irq())或用原子类型。

Bug 4:Bootloader 跳转后必 HardFault——向量表没搬家

现象:APP 单独烧录正常,从 Bootloader 跳转就死。APP 的向量表在 0x08004000,而 CPU 复位后 VTOR 默认指向 0x08000000,第一个中断就会取到 Bootloader 的向量。解决:APP 启动最开始执行 SCB->VTOR = 0x08004000;(同时确保跳转前关闭全局中断、还原外设时钟与栈指针 MSP)。

Bug 5:HardFault 玄学死机——用栈帧『验尸』

HardFault 发生时,硬件已把出事前一刻的 R0–R3、R12、LR、PC、xPSR 压入栈中。用下面的『验尸代码』可以取出 PC/LR,直接定位到出错指令地址:

▍HardFault 现场提取

Bug 6:低优先级中断『饿死』或丢节拍

现象:高优先级 ISR 里跑了长循环(如打印日志、等 SPI Flash),SysTick 被长时间压制,RTOS 节拍丢失、看门狗复位。原则:ISR 只做『登记』,把工作量甩给主循环或低优先级任务(中断下半部思想);需要屏蔽中断时用 BASEPRI 做『带阈值的临界区』而不是全局关中断,FreeRTOS 的 configMAX_SYSCALL_INTERRUPT_PRIORITY 正是此意。

七、总结

模式的本质是硬件级的『身份+隔离+加速』:特权位保护系统,banked 寄存器加速切换;

异常/中断共用一套硬件响应骨架:存断点、存状态、切模式、跳向量;

Cortex-M 把入栈/出栈、尾链、迟到全部硬件化,软件只需写好 ISR 与清标志;

NVIC 用『抢占+子优先级』两层语义管理并发中断,分组配置是全工程的总开关;

九成中断类 Bug 都落在六张清单里:分组、清标志、volatile、VTOR、栈帧、ISR 长度。

上一篇:Temperature、Top-K、Top-P有什么区别?大模型生成参数一次讲透

下一篇:机器学习中的过拟合和欠拟合是什么?为什么训练准确率高不代表模型好?

戳我查看嵌入式每月就业风云榜

点我了解华清远见高校学霸学习秘籍

猜你关心企业是如何评价华清学员的

干货分享
相关新闻
前台专线:010-82525158 企业培训洽谈专线:010-82525379 院校合作洽谈专线:010-82525379 Copyright © 2004-2026 北京华清远见科技发展有限公司 版权所有 ,京ICP备16055225号-5,京公海网安备11010802025203号

回到顶部