4. Linux内核中断机制概述:中断向量、中断控制器(GIC)、中断号、中断上下文与进程上下文
中断机制是嵌入式Linux系统响应硬件事件的基石。对于I2C外设驱动开发而言,理解中断从硬件触发到内核软件处理的完整路径,是正确实现中断处理程序(ISR)和保证数据一致性的前提。本节将系统梳理中断向量表、通用中断控制器(GIC)、中断号映射以及中断上下文与进程上下文的本质区别。
4.1 中断向量与中断向量表
中断向量是处理器为每个中断源分配的固定入口地址。在ARM Cortex-A系列处理器中,中断向量表通常位于地址0x00000000或0xFFFF0000(由系统控制寄存器决定)。当CPU检测到中断信号时,硬件自动跳转到对应的向量地址执行预定义的跳转指令。
关键概念: 中断向量不是中断号,而是内存地址。每个向量对应一个异常或中断类型(复位、未定义指令、软中断、预取中止、数据中止、IRQ、FIQ)。
嵌入式要点: 在Linux启动过程中,内核会将中断向量表从ROM复制到RAM,并设置MMU映射,使向量表位于内核虚拟地址空间的高端(如0xFFFF0000)。这是为了支持动态修改异常处理函数。
4.2 中断控制器:GIC(Generic Interrupt Controller)
在ARM多核处理器系统中,GIC是管理中断路由、优先级、使能和分发的核心硬件单元。GIC负责将来自外设(如I2C控制器、GPIO、定时器)的中断信号,经过优先级仲裁后,分发到指定的CPU核心。
GIC架构分为两个主要逻辑单元:
| 单元 | 全称 | 功能 | 寄存器接口 |
|---|---|---|---|
| GICD | Distributor | 全局中断使能、优先级配置、中断分组、SPI/PPI路由 | GICD_* 寄存器组 |
| GICC | CPU Interface | 每个CPU核心独立的中断屏蔽、优先级掩码、中断确认(EOI) | GICC_* 寄存器组 |
驱动开发陷阱: 在编写I2C中断驱动时,必须确保GICD中对应的中断使能位已置位,且GICC的优先级掩码寄存器允许该中断通过。否则,即使外设产生了中断信号,CPU也不会收到IRQ。
4.3 中断号:从硬件中断线到Linux IRQ号
中断号是驱动开发者最常接触的抽象概念。在嵌入式Linux中,中断号经历了从硬件中断线到软件IRQ号的映射过程。
| 层级 | 名称 | 说明 | 示例(I2C控制器) |
|---|---|---|---|
| 硬件层 | HW IRQ ID | GIC为每个中断源分配的硬件ID,在GICD寄存器中索引 | SPI 62(GIC硬件ID) |
| DT层 | 中断描述符 | 设备树中interrupts = <GIC_SPI 62 IRQ_TYPE_LEVEL_HIGH> |
指定中断类型和触发方式 |
| 内核层 | Linux IRQ号 | 内核通过irq_of_parse_and_map()将DT描述符映射为虚拟IRQ号 |
返回IRQ号(如62或动态分配值) |
| 驱动层 | IRQ号 | 驱动调用request_irq(irq, handler, flags, name, dev)注册 |
dev->irq 存储的IRQ号 |
核心API: 在I2C控制器驱动中,通常通过以下方式获取中断号:
或
struct resource *res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);或
int irq = irq_of_parse_and_map(pdev->dev.of_node, 0);
4.4 中断上下文与进程上下文
这是嵌入式驱动开发中最容易出错的概念之一。中断上下文和进程上下文在可调用函数、可访问内存、调度行为等方面存在根本差异。
4.5 中断处理程序的分层设计:Top Half与Bottom Half
由于中断上下文不允许睡眠,且必须尽快完成(通常要求在微秒级),Linux内核将中断处理分为两个阶段:
- Top Half(上半部): 在中断上下文中立即执行,完成最紧急的硬件操作(如读取I2C状态寄存器、清除中断标志、拷贝FIFO数据到内存缓冲区)。必须快速返回。
- Bottom Half(下半部): 在稍后的安全时间执行,运行在软中断或进程上下文。完成耗时的数据处理(如解析I2C数据帧、唤醒等待队列、通知用户空间)。
I2C驱动实践: 在I2C控制器中断处理中,Top Half通常只做:
1. 读取中断状态寄存器,判断中断类型(TX_EMPTY / RX_FULL / ERROR)。
2. 如果FIFO深度小,直接搬运数据到内存缓冲区。
3. 返回IRQ_HANDLED或IRQ_WAKE_THREAD(如果使用线程化中断)。
耗时的I2C消息解析、DMA描述符管理、错误恢复逻辑应放在Bottom Half或中断线程中。
1. 读取中断状态寄存器,判断中断类型(TX_EMPTY / RX_FULL / ERROR)。
2. 如果FIFO深度小,直接搬运数据到内存缓冲区。
3. 返回IRQ_HANDLED或IRQ_WAKE_THREAD(如果使用线程化中断)。
耗时的I2C消息解析、DMA描述符管理、错误恢复逻辑应放在Bottom Half或中断线程中。
4.6 中断上下文检测与调试
在驱动开发中,判断当前是否处于中断上下文至关重要。内核提供了以下检测机制:
/* 检测当前是否在中断上下文中 */
#include <linux/preempt.h>
#include <linux/hardirq.h>
if (in_interrupt()) {
/* 当前在中断上下文或软中断上下文 */
pr_debug("Cannot sleep, using spinlock instead\n");
spin_lock(&dev->lock);
} else {
/* 当前在进程上下文 */
pr_debug("Can sleep, using mutex\n");
mutex_lock(&dev->mutex);
}
/* 更精细的检测 */
if (in_irq()) /* 仅在硬件中断上下文 */
if (in_softirq()) /* 仅在软中断上下文 */
if (in_atomic()) /* 在原子上下文(中断+持有自旋锁) */
死锁风险: 如果在中断上下文中调用了
mutex_lock()或kmalloc(GFP_KERNEL)(可能触发内存回收而睡眠),内核会触发BUG: scheduling while atomic错误,导致系统挂死或Oops。I2C驱动中常见的错误是在中断处理函数中直接调用i2c_transfer()——该函数可能睡眠等待硬件完成。
4.7 本章知识结构总结
| 概念 | 本质 | 对I2C驱动的影响 |
|---|---|---|
| 中断向量 | CPU硬件跳转地址 | 决定IRQ异常入口,驱动无需直接操作 |
| GIC | 中断路由与优先级控制器 | 需在设备树中正确配置中断类型和触发方式 |
| 中断号 | 从硬件ID到软件IRQ号的映射 | 驱动通过platform_get_resource获取 |
| 中断上下文 | 原子执行环境,不可睡眠 | Top Half中只能使用自旋锁,不能调用i2c_transfer |
| 进程上下文 | 可睡眠、可调度的执行环境 | Bottom Half或中断线程中完成I2C消息处理 |