中断系统基础
搞多核DMA中断绑定,绕不开中断系统那几个基本概念。说白了,中断就是让CPU暂时撂下手里的活,去处理一个紧急事件。我刚开始接触嵌入式时,总觉得中断就是个函数跳转,后来在项目里吃过亏才发现,向量、优先级、嵌套、屏蔽这些东西,每个都可能让你系统跑飞。
中断向量表:中断的“地址簿”
中断向量表,说白了就是一张表,里面存着每个中断对应的处理函数地址。CPU收到中断请求后,直接查表跳过去。我记得以前用过一颗ARM芯片,默认中断向量表放在0地址,结果我把代码链接到0x40000000,忘了重映射,一进中断就跳飞了……调试了整整一天。
向量表的组织形式因架构而异:
- 固定向量表:每个中断源对应固定位置,比如传统ARM Cortex-M的异常向量表。
- 可编程向量表:通过寄存器设定基地址,比如x86的IDT(中断描述符表)。
在多核平台上,每个核通常有自己的中断向量表。我建议你把向量表放在非Cache区域,或者至少有屏障保证一致性,否则一个核改了表,另一个核读的还是旧的——这个问题我在调试AMP系统时遇到过。
| 特性 | 固定向量表 | 可编程向量表 |
|---|---|---|
| 映射方式 | 硬件固定 | 软件配置寄存器 |
| 灵活性 | 低,变更需改硬件 | 高,可动态调整 |
| 典型架构 | 早期MCU | ARM Cortex-A, x86 |
中断优先级:谁先上
当两个中断同时来,CPU得选一个先处理。优先级的值越小(或者越大,取决于架构)代表紧急程度越高。我在项目中常用4位优先级寄存器,最多16级,但有些芯片支持可抢占优先级和亚优先级——比如Cortex-M的NVIC,就能把优先级拆成抢占优先级和子优先级。
优先级比较要点:
- 抢占优先级决定能否打断正在执行的ISR。
- 子优先级只在相同抢占优先级之间做仲裁。
- 抢占优先级相同,则按中断向量号(硬件编号)决定顺序,别指望软件排序。
你想想看,如果两个DMA通道的中断优先级一样,又同时触发,那就要看硬件编号了。我在项目中吃过这个亏:以为优先级一样就会“轮转”,结果低编号中断一直占先,高编号DMA通道一直饿死。最后我只好提升那个高通道的优先级一个级差。
中断嵌套:高优先级插队
中断嵌套允许一个高优先级中断打断正在执行的低优先级ISR。这个功能很强大,但也容易踩坑。以Cortex-M为例,默认情况下中断是不可嵌套的(硬件自动屏蔽同级和低优先级),但你可以通过软件打开嵌套——即ISR中先开中断,允许更高优先级进来。
我建议你慎重使用嵌套。原因很简单:
- 栈深度不定,高频率嵌套可能溢出。
- 共享资源若不加保护,容易产生优先级反转。
- 调试困难,很多bug只在嵌套时序中出现一次,复现极难。
小技巧: 如果你确实需要嵌套,可以只在ISR末尾极小段代码中开中断,或者把真正费时的处理放到任务级,中断只做“标记”,类似Linux底半机制。
中断屏蔽与使能:开关的艺术
中断屏蔽,就是不让CPU响应某个中断;中断使能,就是允许它响应。这两个操作看似简单,但很多时候坑就藏在细节里。我在项目中看到过新手直接用 disable_irq() 在中断上下文中关中断——结果系统直接死锁。
常见的控制方式:
- 全局使能:通过CPU的PSW或CPSR中的I位(中断禁止位)一次性控制所有可屏蔽中断。
- 局部使能:每个中断源有自己的使能位,通常在外设控制寄存器或中断控制器中。
- 优先级屏蔽:设置一个基线优先级,低于基线的一律不响应。NVIC的BASEPRI寄存器就是干这个的。
/* 示例:ARM Cortex-M 局部使能/屏蔽 */
#define NVIC_ENABLE_BASE 0xE000E100
#define NVIC_CLEAR_BASE 0xE000E180
/* 使能中断号 15 */
*(volatile uint32_t*)(NVIC_ENABLE_BASE + 0) = (1 << (15 % 32));
/* 屏蔽中断号 15 */
*(volatile uint32_t*)(NVIC_CLEAR_BASE + 0) = (1 << (15 % 32));
/* 全局关中断 */
__asm volatile ("cpsid i");
/* 全局开中断 */
__asm volatile ("cpsie i");
曾经踩过的坑: 在某个RTOS项目中,我试过在关中断期间申请一块堆内存——malloc内部可能触发缺页异常或调度,结果直接挂起。记住:关中断的临界区要短,不能在里边调用可能阻塞或产生异常的代码。
另外多核环境下,每个核可以独立开关自己的本地中断,但全局中断(比如核间中断IPI)需要跨核同步。我自己的习惯是:每个核维护一个本地mask变量,在需要跨核屏蔽时通过IPI通知其它核,避免直接操作其它核的寄存器。
好,中断基础就说这么多。这些概念虽然基础,但每个地方都可能让你在调试台上耗几个小时。把向量表规划好,优先级设计合理,嵌套用到该用的地方,屏蔽操作谨慎——后面讲DMA中断绑定的时候,你会发现这些基本功有多重要。