1. 中断基础概念:中断向量、中断优先级、中断嵌套、中断上下文
在网关驱动开发中,中断是CPU与外部设备(如以太网PHY、USB控制器、PCIe桥片)交互的核心机制。理解中断的基础概念,是排查“中断丢失”与“DMA超时”问题的前提。本节从四个维度展开:中断向量、中断优先级、中断嵌套与中断上下文。
1.1 中断向量
中断向量是中断服务程序(ISR)的入口地址索引。当外设触发中断时,CPU通过中断控制器(如ARM GIC、x86 APIC)查找向量表,跳转到对应的处理函数。
| 概念 | 说明 | 典型值/范围 |
|---|---|---|
| 中断向量号 | 每个中断源分配的唯一数字标识 | 0~255(x86),16~1019(ARM GICv3) |
| 向量表基地址 | 存放中断向量入口地址的内存起始位置 | 0xFFFF0000(ARM),IDT(x86) |
| 共享中断 | 多个设备共用同一个中断向量号 | 常见于PCIe MSI/MSI-X |
排查要点:中断丢失常因向量号冲突或向量表被意外修改导致。使用
cat /proc/interrupts 可查看当前系统中每个中断号的触发次数与绑定CPU。
ARM64 中断向量表示例(Linux 内核代码片段):
// arch/arm64/kernel/entry.S
.align 11
SYM_CODE_START(vectors)
ventry el1t_sync_invalid // Synchronous EL1t
ventry el1t_irq_invalid // IRQ EL1t
ventry el1t_fiq_invalid // FIQ EL1t
ventry el1t_error_invalid // Error EL1t
ventry el1h_sync // Synchronous EL1h
ventry el1h_irq // IRQ EL1h
ventry el1h_fiq_invalid // FIQ EL1h
ventry el1h_error_invalid // Error EL1h
// ... 更多向量入口
SYM_CODE_END(vectors)
1.2 中断优先级
中断优先级决定了当多个中断同时到达时,CPU优先响应哪一个。在网关设备中,高优先级中断通常分配给实时性要求高的外设(如PTP时间同步、看门狗),低优先级中断分配给批量数据传输(如DMA完成中断)。
| 中断控制器 | 优先级范围 | 优先级配置方式 | 典型应用 |
|---|---|---|---|
| ARM GICv2/v3 | 0~255(数值越小优先级越高) | ICC_PMR / GICD_IPRIORITYR | 网关SoC(i.MX8, MT7621) |
| x86 APIC | 0~15(数值越大优先级越高) | IOAPIC RTE 寄存器 | x86网关(Intel Celeron) |
| RISC-V PLIC | 0~7 | PLIC_PRIORITY 寄存器 | RISC-V 网关(GD32V) |
常见陷阱:在网关驱动中,若DMA完成中断优先级低于网络接收中断,当网络流量大时,DMA中断可能被长时间延迟,导致DMA超时。排查时应检查中断优先级分配是否合理。
1.3 中断嵌套
中断嵌套是指一个中断服务程序执行过程中,被另一个更高优先级的中断打断。在网关驱动中,合理的中断嵌套可以保证紧急事件及时处理,但过度嵌套会导致栈溢出或优先级反转。
中断嵌套的三种模式:
- 完全嵌套:高优先级中断可以打断任何低优先级中断。ARM GIC 默认模式。
- 组嵌套:只有属于更高优先级组的中断才能打断当前中断。常用于安全关键系统。
- 禁止嵌套:所有中断按顺序执行,不互相打断。适用于简单嵌入式系统。
| 模式 | 优点 | 缺点 | 网关适用场景 |
|---|---|---|---|
| 完全嵌套 | 实时性最好 | 栈深度大,调试困难 | 多网口网关,需快速响应 |
| 组嵌套 | 安全隔离性好 | 配置复杂 | 工业网关,需功能安全 |
| 禁止嵌套 | 实现简单,栈可控 | 实时性差 | 低端IoT网关 |
1.4 中断上下文
中断上下文是CPU在执行中断服务程序时所处的特殊运行环境。与进程上下文不同,中断上下文不能睡眠、不能调度、不能访问用户空间。理解中断上下文是编写安全、高效ISR的基础。
| 对比项 | 进程上下文 | 中断上下文 |
|---|---|---|
| 可睡眠 | 是(通过 schedule()) | 否(会导致内核崩溃) |
| 可访问用户空间 | 是 | 否(mm_struct 可能无效) |
| 持有锁 | 互斥锁、自旋锁 | 仅自旋锁(且需关中断) |
| 栈空间 | 进程内核栈(8KB~16KB) | 中断栈(4KB~8KB,独立或共用) |
| 典型耗时 | 无限制 | 应 < 100μs(否则触发软中断/工作队列) |
排查要点:中断丢失的常见原因之一是ISR中执行了耗时操作(如打印、内存分配),导致后续中断被屏蔽或丢失。应使用
in_irq() 宏判断当前是否在中断上下文中,并避免调用可能睡眠的函数。
Linux 内核中断上下文检测代码:
#include <linux/preempt.h>
#include <linux/hardirq.h>
void my_isr_handler(int irq, void *dev_id)
{
// 检测是否在中断上下文中
if (in_interrupt()) {
pr_debug("当前在中断上下文中 (hardirq=%d, softirq=%d)\n",
in_irq(), in_softirq());
}
// 错误示例:在中断上下文中调用可能导致睡眠的函数
// kmalloc(size, GFP_KERNEL); // 错误!GFP_KERNEL 可能睡眠
// 正确做法:使用 GFP_ATOMIC
void *buf = kmalloc(64, GFP_ATOMIC);
if (!buf) {
pr_err("中断上下文内存分配失败\n");
return;
}
// 处理中断...
kfree(buf);
}