1. 中断概念与硬件基础
中断,说白了就是CPU正在忙着干活,突然被一个外部事件打断了。
我刚开始接触嵌入式时,总觉得中断是个很玄乎的东西。后来调试一个网络驱动,发现数据包老是丢,查了三天才发现是中断处理太慢导致的。嗯,从那以后,我对中断的理解就深刻多了。
1.1 什么是中断
中断是一种硬件机制。它让外设可以主动通知CPU:嘿,我有事找你!
没有中断的话,CPU只能轮询——不停地问每个外设:你有事吗?你有事吗?这效率,你想想看,得多低。
中断的核心流程其实很简单:
- 外设产生中断信号
- 中断控制器接收并转发给CPU
- CPU暂停当前任务,保存现场
- CPU跳转到中断服务程序(ISR)执行
- 执行完毕,恢复现场,继续原来的工作
我在项目中遇到过一个问题:某个传感器中断触发太频繁,导致系统响应变慢。后来我加了一个中断合并机制,把多次中断合并成一次处理,问题就解决了。
1.2 中断控制器——GIC
GIC,全称Generic Interrupt Controller,是ARM架构下标准的中断控制器。
你想想看,一个SoC里可能有几十上百个中断源。如果每个都直接连到CPU,那CPU的引脚根本不够用。GIC就是中间人,负责管理所有中断。
GIC的架构分为两部分:
- 分发器(Distributor):负责中断的使能、优先级、路由等配置
- CPU接口(CPU Interface):负责与CPU核交互,发送中断信号
GIC版本演进:
- GICv1:支持最多8个CPU核,每个核最多1020个中断
- GICv2:增加了虚拟化支持,广泛用于Cortex-A系列
- GICv3:支持更多CPU核,引入了独立的中断路由
- GICv4:进一步优化虚拟化中断性能
我个人习惯用GICv2,因为它在大多数嵌入式Linux平台上都支持得很好。调试起来也方便。
下面我用一张图来展示GIC的工作流程:
1.3 中断号
每个中断源都有一个唯一的中断号。在Linux里,中断号分两种:
- 硬件中断号:由芯片厂商定义,比如某个GPIO的中断号是32
- 软件中断号(IRQ number):Linux内核映射后的逻辑中断号
我记得第一次写驱动时,直接在代码里写死了硬件中断号。结果换了一个板子,中断号变了,驱动就炸了。后来我才知道,应该用platform_get_irq()这样的接口动态获取。
小技巧:在设备树里,中断号通常用三个字段描述:
interrupts = <GIC_SPI 42 IRQ_TYPE_EDGE_RISING>;
第一个字段是中断类型,第二个是中断号,第三个是触发方式。
1.4 中断触发方式
中断触发方式,说白了就是外设怎么告诉CPU:我有事!
| 触发方式 | 说明 | 典型应用 |
|---|---|---|
| 电平触发(Level-triggered) | 信号保持高电平或低电平 | 按键、网络中断 |
| 边沿触发(Edge-triggered) | 信号上升沿或下降沿触发 | GPIO中断、定时器 |
| 脉冲触发(Pulse-triggered) | 短脉冲信号 | DMA完成中断 |
这里有个坑,我必须要说。电平触发的中断,如果ISR没有及时清除中断源,中断会一直触发。我曾经调试一个网卡驱动,发现中断风暴把CPU占满了,查了半天才发现是电平中断没清干净。
避坑指南:
- 电平触发:一定要在ISR里清除中断源,否则会无限触发
- 边沿触发:可能会丢失中断,如果两次中断间隔太短
- 共享中断:多个设备共用一根中断线,ISR要判断是谁触发的
1.5 中断优先级与嵌套
GIC支持中断优先级。高优先级的中断可以打断低优先级的中断处理,这就是中断嵌套。
我个人习惯把实时性要求高的中断设高优先级,比如定时器、网络。把一些不那么紧急的中断设低优先级,比如按键、串口。
但要注意,中断嵌套太多会导致栈溢出。我在一个项目里就遇到过,三层中断嵌套直接把内核栈撑爆了,系统直接panic。后来我限制了嵌套层数,最多两层。
关键点总结:
- 中断是CPU与外设通信的高效机制
- GIC是ARM架构的标准中断控制器
- 中断号分硬件号和软件号,驱动里用软件号
- 触发方式选错了,调试起来很痛苦
- 优先级设置要合理,别让中断嵌套太深
好了,中断的硬件基础就聊到这里。这些概念搞清楚了,后面讲Linux中断子系统的实现时,你就能理解为什么代码要那么写了。