GPIO中断优化:Linux驱动实时性与内存访问延迟调优 - 第2节
2.1 中断描述符:irq_desc 与 irq_chip
Linux 内核使用 struct irq_desc 描述一个中断源的完整状态,包括硬件属性、软件状态、处理函数链表和线程化信息。每个中断号对应一个 irq_desc 实例,存储在全局数组 irq_desc[] 中。
| 字段 | 类型 | 说明 |
|---|---|---|
irq_data |
struct irq_data |
硬件相关数据,包含 irq_chip 指针、中断号、触发类型 |
handle_irq |
irq_flow_handler_t |
高层级中断流控处理函数(如 handle_edge_irq) |
action |
struct irqaction * |
中断处理动作链表,指向注册的 request_irq 回调 |
threads_oneshot |
atomic_t |
标记正在运行的一次性线程化中断 |
depth |
unsigned int |
中断禁用嵌套深度,0 表示使能 |
istate |
unsigned int |
内部状态标志(IRQS_WAITING、IRQS_PENDING 等) |
struct irq_chip 封装了中断控制器的硬件操作函数,驱动开发者通过 irq_set_chip() 将其绑定到 irq_desc。
struct irq_chip {
const char *name;
void (*irq_mask)(struct irq_data *data);
void (*irq_unmask)(struct irq_data *data);
void (*irq_ack)(struct irq_data *data);
void (*irq_set_type)(struct irq_data *data, unsigned int flow_type);
int (*irq_set_affinity)(struct irq_data *data, const struct cpumask *dest, bool force);
};
关键点: 中断描述符是软硬件之间的桥梁。
irq_chip 控制硬件寄存器,irq_desc 管理软件状态与处理逻辑。实时调优中,depth 和 istate 的检查是排查中断丢失的第一入口。
2.2 中断处理流程:从硬件触发到 do_IRQ
当外设产生中断信号,CPU 响应后经过以下关键阶段:
- 硬件阶段:CPU 保存现场(PC、PSR 等),跳转到异常向量表入口。
- 汇编入口:架构相关代码(如 ARM 的
arch/arm/kernel/entry-armv.S)调用handle_arch_irq。 - 通用入口:
do_IRQ()获取irq_desc,调用handle_irq流控函数。 - 流控分发:根据中断类型调用
handle_edge_irq或handle_level_irq。 - 动作执行:遍历
action链表,执行注册的handler(上半部)。
性能提示: 在
do_IRQ() 中,内核会调用 irq_enter() 和 irq_exit() 来更新统计计数。实时系统应关注 irq_exit() 中是否触发软中断调度,这会影响下半部的响应延迟。
2.3 上半部(Top Half)与下半部(Bottom Half)机制
中断处理被拆分为两个阶段,以平衡响应速度与处理开销:
| 特性 | 上半部(Top Half) | 下半部(Bottom Half) |
|---|---|---|
| 执行上下文 | 中断上下文(不可睡眠) | 软中断 / 工作队列(可睡眠) |
| 中断禁用 | 当前 CPU 中断禁用 | 中断使能 |
| 执行时机 | 立即执行 | 延迟执行(调度点触发) |
| 典型任务 | ACK 硬件、拷贝数据、清除状态 | 协议解析、数据推送、唤醒等待队列 |
| 耗时要求 | 微秒级,必须极短 | 毫秒级,可容忍 |
2.3.1 下半部实现机制对比
| 机制 | 特点 | 适用场景 | 实时性 |
|---|---|---|---|
| 软中断(SoftIRQ) | 静态分配,同一类型可并行 | 网络 RX/TX、高吞吐场景 | 高(但需防饥饿) |
| Tasklet | 基于软中断,同一实例串行 | GPIO 中断、简单数据处理 | 中 |
| 工作队列(Workqueue) | 内核线程上下文,可睡眠 | I2C/SPI 通信、块设备操作 | 低(受调度影响) |
| 中断线程化(IRQ Thread) | 实时线程,可设置优先级 | PREEMPT_RT 实时系统 | 最高(可抢占) |
2.3.2 中断线程化与 PREEMPT_RT
在实时内核(PREEMPT_RT)中,所有中断(除少数时间关键型)都被线程化:
// 注册线程化中断
static irqreturn_t gpio_irq_thread_fn(int irq, void *dev_id)
{
struct gpio_dev *dev = dev_id;
// 可睡眠操作:I2C 读取、mutex 保护、schedule()
gpio_process_event(dev);
return IRQ_HANDLED;
}
static irqreturn_t gpio_irq_handler(int irq, void *dev_id)
{
// 上半部:仅 ACK 硬件,唤醒线程
struct gpio_dev *dev = dev_id;
writel(BIT(dev->irq_pin), dev->base + GPIO_ICR);
return IRQ_WAKE_THREAD;
}
// 注册时指定 IRQF_ONESHOT 防止中断重入
ret = request_threaded_irq(irq, gpio_irq_handler, gpio_irq_thread_fn,
IRQF_TRIGGER_RISING | IRQF_ONESHOT,
"gpio_int", dev);
实时调优警告: 使用
IRQF_ONESHOT 时,中断线在 thread_fn 返回前保持屏蔽。如果线程处理时间过长,会导致该中断源丢失后续中断。GPIO 边沿触发中断尤其容易丢失,建议配合硬件 FIFO 或使用 handle_edge_irq 的自动重试机制。
2.4 中断流控处理函数详解
内核提供四种标准流控函数,驱动通过 irq_set_handler() 选择:
| 流控函数 | 适用中断类型 | 行为特点 |
|---|---|---|
handle_simple_irq |
无需硬件 ACK 的中断 | 直接调用 action 链表,不操作硬件 |
handle_level_irq |
电平触发 | mask → handler → unmask,防止重复触发 |
handle_edge_irq |
边沿触发 | ack → handler,自动屏蔽新边沿直到处理完成 |
handle_fasteoi_irq |
EOI 控制器(如 GIC) | 只写 EOI 寄存器,不 mask/unmask |
GPIO 中断选型建议: 对于按键、传感器等短脉冲信号,使用
handle_edge_irq 配合 IRQF_TRIGGER_FALLING 或 IRQF_TRIGGER_RISING。对于电平型唤醒信号(如低功耗唤醒),使用 handle_level_irq 配合 IRQF_TRIGGER_LOW。实时系统优先选择 handle_fasteoi_irq 以减少 mask/unmask 开销。