中断是嵌入式系统中最核心的异步事件响应机制。从硬件层面定义,中断是指CPU在执行当前程序的过程中,由内部或外部事件触发的一种信号,该信号迫使CPU暂停当前任务,转而去执行一段预先定义好的中断服务程序,处理完毕后再返回原程序继续执行。这个过程不是软件轮询,而是硬件级别的流程劫持。
理解中断,首先要区分中断与异常。在ARM体系结构中,广义的中断包含了狭义硬件中断和处理器异常两大类。硬件中断由外部设备发出,通过中断控制器送达CPU的中断请求引脚,典型来源包括定时器溢出、串口数据到达、GPIO电平变化、DMA传输完成等。处理器异常则是CPU自身在执行指令过程中检测到的异常状况,如未定义指令、数据中止、预取中止、软件中断等。两者的共性在于都会触发处理器进入异常模式、跳转到向量表对应的入口地址,区别在于触发源不同——中断来自片外,异常来自片内。
与中断相对的概念是轮询。轮询方式下,CPU周期性地逐一检查各外设的状态寄存器,判断是否有待处理事件。这种方式实现简单,但存在明显的效率瓶颈:CPU大量时间花在"问一圈没人理我"的空转上,实时性也难以保证,最坏情况下的响应延迟可达一个完整轮询周期。中断机制则实现了"按需响应"——外设有事件时主动通知CPU,CPU只在必要时介入处理。这种从"主动问"到"被动听"的范式转换,是嵌入式系统实现实时响应的硬件基础。
中断控制器在中断通路中扮演着路由与管理的角色。在ARM Cortex-M系列中,嵌套向量中断控制器NVIC集成了中断使能控制、优先级配置、向量表偏移、中断挂起与活跃状态跟踪等功能。NVIC最多支持240个外部中断通道,每个通道可独立配置优先级。在更复杂的ARM Cortex-A系列中,通用中断控制器GIC则承担了中断分发、优先级仲裁、多核中断路由等任务,支持中断分组、软件生成中断和虚拟中断等高级特性。控制器层是中断机制从"能用"到"好用"的关键一环。
中断向量表本质上是一个存放中断服务程序入口地址的数组。每个中断源在向量表中占据一个固定位置,当该中断被响应时,处理器硬件自动从向量表的对应偏移位置读取入口地址,然后跳转执行。这个过程全部由硬件完成,无需软件干预,因此响应速度极快。
在ARM Cortex-M3/M4处理器中,向量表的前16个条目被系统异常占用,从地址0x00000000开始依次为:初始栈指针值(0x00偏移)、复位向量(0x04偏移)、NMI不可屏蔽中断(0x08偏移)、硬故障HardFault(0x0C偏移)、内存管理MemManage(0x10偏移)、总线故障BusFault(0x14偏移)、用法故障UsageFault(0x18偏移),以及SVCall、PendSV、SysTick等系统服务向量。从0x40偏移开始是外部中断向量,依次对应IRQ0至IRQn,n的具体值取决于芯片厂商的实现。
向量表的基地址并非固定不可变。Cortex-M处理器提供了向量表偏移寄存器VTOR,允许在运行时将向量表重定位到SRAM或其他存储区域。这一特性对引导加载程序Bootloader至关重要:Bootloader运行时将向量表指向自己的中断向量,完成应用代码跳转后,再将VTOR修改为应用程序的向量表地址,实现中断向量的无缝切换。同时,SRAM中的向量表还支持运行时动态修改ISR入口,为在线升级和动态补丁提供了可能。
与Cortex-M的单级向量表不同,Cortex-A系列采用了两级向量表结构。第一级是异常向量表,固定在八个地址上(0x00000000或0xFFFF0000,取决于SCTLR.V位),每个异常类型对应一条跳转指令,通常是LDR PC, [PC, #offset]或B指令。第二级才是真正的处理函数入口地址表。LDR指令从当前PC附近的地址加载目标入口地址到PC,实现间接跳转。这种设计的意图是在有限的八条指令空间内完成最大的灵活性——修改二级表中的地址即可重定向处理函数。
两级向量表的另一个考量是虚拟内存支持。Cortex-A系列运行复杂操作系统时,向量表所在的高位地址(0xFFFF0000)通常映射到内核空间,这种设计既保证了异常入口的固定位置,又方便了操作系统对异常处理流程的接管。当Linux内核启动时,它会将异常向量表复制到高位虚拟地址,确保用户态进程无法直接访问和篡改异常处理逻辑。
当中断控制器将中断请求信号送达CPU后,处理器在每条指令执行完毕后检查中断信号。若中断被使能且优先级高于当前执行的中断(若有),CPU将进入中断响应序列。这一序列由硬件自动执行,程序员无法干预,理解其细节对排查中断相关的疑难杂症至关重要。
第一步是上下文保存。Cortex-M处理器自动将八个寄存器压入当前栈:xPSR、返回地址PC、链接寄存器LR、R12、R3、R2、R1、R0,总计32字节。压栈顺序由高地址到低地址,最后入栈的是xPSR。这个自动压栈动作无需任何汇编指令,纯硬件完成,是Cortex-M相比传统ARM核在中断延迟上的重大优化。Cortex-A系列则需要程序员在ISR入口手动保存关键寄存器,否则中断返回时上下文会被破坏。
第二步是向量表寻址。硬件根据中断号计算向量表偏移:偏移量等于中断号乘以4(每个向量占4字节)。CPU从向量表基地址加上偏移量的位置读取32位值,这个值就是ISR的入口地址。对于Cortex-M,若该中断的优先级分组中的抢占优先级高于当前中断,还需更新当前运行优先级寄存器。
第三步是更新处理器状态寄存器,标记当前进入中断上下文。Cortex-M将中断号写入xPSR的异常编号字段,将LR设置为特殊值(如0xFFFFFFF9表示从主栈返回线程模式使用MSP),便于中断返回时硬件识别返回目标。ARM的LR特殊编码是一种精巧的硬件设计——通过LR值的低位比特编码返回模式,省去了额外的状态寄存器。
第四步,PC被加载为ISR的入口地址,CPU开始执行中断服务程序。从硬件收到中断信号到ISR第一条指令开始执行的这段时间,称为中断延迟。Cortex-M3的典型中断延迟为12个时钟周期,包括寄存器压栈、向量表读取和流水线填充。若当前执行的指令是慢速指令(如多加载/存储指令),中断延迟可能更长。
中断返回同样由硬件自动化。当ISR执行完毕,遇到从LR加载PC的操作(如BX LR或POP PC),硬件识别LR中的特殊编码,自动执行出栈操作,恢复R0-R3、R12、LR、PC和xPSR,CPU回到被打断的线程继续执行。在这个过程中,若有更高优先级的中断被挂起,硬件也会在返回时自动进行尾链tail-chaining优化,跳过不必要的出栈再入栈动作,直接切换到下一个ISR。
Cortex-M处理器的优先级配置并非一个简单的数字大小比较。首先,芯片厂商在生产时配置了实际的优先级位宽,通常为3位(8级)到8位(256级),通过优先级寄存器PRIGROUP控制分组。优先级分组将优先级位宽分为两部分:抢占优先级字段和子优先级字段。抢占优先级决定了一个中断能否打断另一个正在执行的ISR,子优先级则在多个挂起的同级中断中决定先服务哪一个。
以3位优先级位宽为例,若设置分组为5(PRIGROUP=5,表示抢占优先级占3位,子优先级占0位),则有8个抢占等级,同一抢占优先级的中断之间不存在嵌套;若设置分组为4(抢占优先级占2位,子优先级占1位),则有4个抢占等级,每个抢占等级内部有2种子优先级,高抢占优先级的中断可以打断低优先级ISR,但相同抢占优先级的中断之间不能嵌套——子优先级仅决定挂起排队顺序。
实际优先级寄存器的值是左对齐的。8位优先级位宽时,值0x00是最高优先级,0xFF是最低优先级。但若芯片仅实现了4位优先级位宽,则只使用高4位,0x00仍是最高,0xF0是最低,写入0x0F的效果等同于0x00。这种左对齐设计使得不同实现之间的软件可移植性更好。
优先级反转是实时嵌入式系统中一个隐蔽而致命的问题。经典场景如下:低优先级任务L持有互斥锁正在访问共享资源,高优先级任务H随后需要同一资源而被阻塞,此时中等优先级任务M就绪并抢占L——M与锁无关却间接阻塞了H,导致H的实际优先级被反转为低于M。在任务多、嵌套深的应用中,H的最长阻塞时间可能无限大,违反实时系统的确定性要求。
针对优先级反转,学术界和工业界提出了两种主流解法。第一种是优先级继承协议:当高优先级任务H因锁被低优先级任务L持有而阻塞时,L临时继承H的高优先级,直至L释放锁。这使得中等优先级任务M无法打断L,L能尽快完成临界区释放锁,H的等待时间被限制在L的一个临界区长度内。优先级继承的实现对应用代码透明,由操作系统内核在互斥量获取和释放时自动调整任务优先级。
第二种是优先级天花板协议:为每个互斥锁分配一个天花板优先级,等于所有可能获取该锁的任务中的最高优先级。当任何任务获取锁时,该任务的优先级立即提升到
本篇完!