1. 中断基础:ARM Cortex-A 中断控制器(GIC)架构与中断处理流程
中断,说白了就是让CPU从手头工作里跳出来,去处理一件更紧急的事。在汽车嵌入式Linux里,中断处理的好坏直接决定了系统的实时性。我刚开始做驱动时,总觉得中断就是配个IRQ号、写个handler就完事了,结果在实车上踩了不少坑——中断延迟过高导致CAN报文丢失,那叫一个头疼。
今天咱们就从GIC(Generic Interrupt Controller)架构入手,把中断的整个处理流程掰开揉碎了讲清楚。
1.1 GIC到底长什么样?
ARM Cortex-A系列处理器用的中断控制器叫GIC,目前主流是GICv2和GICv3。GICv2在Cortex-A7/A15上很常见,GICv3则出现在Cortex-A53/A72等64位核上。两者的核心思想一致,只是GICv3支持了更多中断源和更灵活的路由。
GIC的逻辑架构分为两大块:
- Distributor(分发器):负责中断的使能、优先级配置、触发方式设置,以及将中断分发到指定的CPU接口。
- CPU Interface(CPU接口):每个CPU核独享一个接口,负责向核发送中断信号,并处理中断的应答和结束。
我画了张图,帮你快速建立整体印象:
1.2 中断类型:三种中断,各有各的脾气
GIC把中断分成三类,咱们一个一个说:
| 中断类型 | 中断ID范围 | 典型用途 | 我踩过的坑 |
|---|---|---|---|
| SPI(共享外设中断) | 32~1019 | UART、I2C、SPI、CAN等外设 | 多个核共享中断时,没配好亲和性导致中断风暴 |
| PPI(私有外设中断) | 16~31 | 每个核私有的定时器、PMU | 误以为PPI是全局的,结果只在一个核上生效 |
| SGI(软件生成中断) | 0~15 | 核间通信(IPI)、CPU唤醒 | SGI优先级没设对,导致死锁 |
1.3 中断优先级与分组:别让低优先级的事耽误了刹车
GICv2支持256级优先级(0最高,255最低)。但实际实现中,芯片厂商可能只用了高4位或高5位。我建议你查一下芯片手册里的GIC_PRIORITY寄存器描述,确认到底支持多少级。
优先级分组(Priority Group)是GICv2引入的概念,把中断分成两组:
- Group 0:安全中断,通常给TrustZone使用,以FIQ方式触发。
- Group 1:非安全中断,Linux跑在非安全世界,用的就是Group 1。
为什么要分组?说白了就是为了隔离。安全世界的代码(比如TEE)不能被Linux打断,所以安全中断必须走FIQ,优先级高于所有非安全IRQ。
1.4 中断处理流程:从硬件到软件,一步都不能少
咱们把整个流程拆成6个步骤,配合代码来看:
- 外设触发中断:外设拉高中断请求线。
- Distributor仲裁:检查中断是否使能、优先级是否足够高,然后选一个目标CPU。
- CPU Interface发送信号:向目标CPU核发送IRQ或FIQ信号。
- CPU保存上下文:硬件自动保存CPSR、PC到SVC模式下的栈,跳转到异常向量表。
- 读取中断ID:软件读GICC_IAR寄存器,获取中断号,然后查表调用对应的handler。
- 结束中断:handler处理完后,写GICC_EOIR寄存器,通知GIC中断处理完毕。
在Linux里,第5步和第6步被封装成了通用的中断框架。驱动开发者只需要注册一个中断处理函数:
/* 驱动中注册中断的典型写法 */
static irqreturn_t my_irq_handler(int irq, void *dev_id)
{
/* 处理中断事件 */
/* 注意:这里不能睡,不能调度 */
return IRQ_HANDLED;
}
static int my_probe(struct platform_device *pdev)
{
int irq = platform_get_irq(pdev, 0);
if (irq < 0)
return irq;
return request_irq(irq, my_irq_handler,
IRQF_TRIGGER_RISING,
"my_device", dev);
}
1.5 中断亲和性:把中断绑到正确的核上
在多核系统里,中断可以路由到任意一个核。但如果不加控制,所有中断都往CPU0上涌,CPU0忙死,其他核闲死。这就是所谓的「中断风暴」。
Linux提供了/proc/irq/{irq_num}/smp_affinity接口,可以设置中断的CPU亲和性:
# 把中断42绑定到CPU0和CPU1(位掩码0x03)
echo 3 > /proc/irq/42/smp_affinity
在汽车场景里,我一般这样分配:
- CPU0:运行实时控制任务,只绑高优先级中断(如CAN、安全相关)。
- CPU1:处理普通外设中断(UART、I2C)。
- CPU2/CPU3:跑Linux网络栈、文件系统等非实时任务。
1.6 中断延迟:汽车场景下的生死线
中断延迟 = 硬件延迟 + 软件延迟。硬件延迟主要看GIC的仲裁时间,一般几十ns到几μs。软件延迟才是大头——关中断的时间、中断嵌套、cache miss都会拖慢响应。
我整理了一个典型的中断延迟分布表:
| 环节 | 典型耗时 | 优化方向 |
|---|---|---|
| GIC仲裁 | ~100ns | 硬件固定,无法优化 |
| CPU保存上下文 | ~200ns | 减少异常向量表跳转次数 |
| 读GICC_IAR | ~50ns | 确保IAR在L1 cache中 |
| 查中断描述符表 | ~500ns | 使用基数树(radix tree)加速查找 |
| 执行handler | 几μs~几百μs | 把耗时操作推到下半部 |
| 写GICC_EOIR | ~50ns | 确保EOIR写操作不阻塞 |
1.7 小结:中断基础,决定了你的车机稳不稳
GIC架构是ARM中断处理的基石。理解Distributor和CPU Interface的分工,搞清楚SPI/PPI/SGI的区别,掌握中断优先级和亲和性的配置,你就能写出靠谱的驱动。