中断系统基础

搞多核DMA中断绑定,绕不开中断系统那几个基本概念。说白了,中断就是让CPU暂时撂下手里的活,去处理一个紧急事件。我刚开始接触嵌入式时,总觉得中断就是个函数跳转,后来在项目里吃过亏才发现,向量、优先级、嵌套、屏蔽这些东西,每个都可能让你系统跑飞。

中断处理核心流程 中断源 中断向量表 优先级仲裁 中断嵌套 屏蔽/使能 ISR执行 关键要点 • 中断源产生请求后,CPU通过向量表找到对应ISR入口 • 优先级仲裁决定多个同时中断时哪个先被响应 • 中断嵌套允许高优先级中断打断低优先级ISR • 屏蔽/使能精确控制每个中断的响应使能状态

中断向量表:中断的“地址簿”

中断向量表,说白了就是一张表,里面存着每个中断对应的处理函数地址。CPU收到中断请求后,直接查表跳过去。我记得以前用过一颗ARM芯片,默认中断向量表放在0地址,结果我把代码链接到0x40000000,忘了重映射,一进中断就跳飞了……调试了整整一天。

向量表的组织形式因架构而异:

  • 固定向量表:每个中断源对应固定位置,比如传统ARM Cortex-M的异常向量表。
  • 可编程向量表:通过寄存器设定基地址,比如x86的IDT(中断描述符表)。

在多核平台上,每个核通常有自己的中断向量表。我建议你把向量表放在非Cache区域,或者至少有屏障保证一致性,否则一个核改了表,另一个核读的还是旧的——这个问题我在调试AMP系统时遇到过。

特性固定向量表可编程向量表
映射方式硬件固定软件配置寄存器
灵活性低,变更需改硬件高,可动态调整
典型架构早期MCUARM Cortex-A, x86

中断优先级:谁先上

当两个中断同时来,CPU得选一个先处理。优先级的值越小(或者越大,取决于架构)代表紧急程度越高。我在项目中常用4位优先级寄存器,最多16级,但有些芯片支持可抢占优先级和亚优先级——比如Cortex-M的NVIC,就能把优先级拆成抢占优先级和子优先级。

优先级比较要点:

  • 抢占优先级决定能否打断正在执行的ISR。
  • 子优先级只在相同抢占优先级之间做仲裁。
  • 抢占优先级相同,则按中断向量号(硬件编号)决定顺序,别指望软件排序。

你想想看,如果两个DMA通道的中断优先级一样,又同时触发,那就要看硬件编号了。我在项目中吃过这个亏:以为优先级一样就会“轮转”,结果低编号中断一直占先,高编号DMA通道一直饿死。最后我只好提升那个高通道的优先级一个级差。

中断嵌套:高优先级插队

中断嵌套允许一个高优先级中断打断正在执行的低优先级ISR。这个功能很强大,但也容易踩坑。以Cortex-M为例,默认情况下中断是不可嵌套的(硬件自动屏蔽同级和低优先级),但你可以通过软件打开嵌套——即ISR中先开中断,允许更高优先级进来。

我建议你慎重使用嵌套。原因很简单:

  1. 栈深度不定,高频率嵌套可能溢出。
  2. 共享资源若不加保护,容易产生优先级反转。
  3. 调试困难,很多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中断绑定的时候,你会发现这些基本功有多重要。