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 的检查是排查中断丢失的第一入口。
中断描述符核心结构 struct irq_desc irq_data (chip + hwirq) handle_irq (流控函数) action → irqaction 链表 threads_oneshot (原子计数) depth (禁用嵌套深度) istate (内部状态标志) struct irq_chip irq_mask / irq_unmask irq_ack (应答) irq_set_type (触发类型) irq_set_affinity (CPU亲和) irqaction handler thread_fn dev_id flags

2.2 中断处理流程:从硬件触发到 do_IRQ

当外设产生中断信号,CPU 响应后经过以下关键阶段:

  1. 硬件阶段:CPU 保存现场(PC、PSR 等),跳转到异常向量表入口。
  2. 汇编入口:架构相关代码(如 ARM 的 arch/arm/kernel/entry-armv.S)调用 handle_arch_irq。
  3. 通用入口:do_IRQ() 获取 irq_desc,调用 handle_irq 流控函数。
  4. 流控分发:根据中断类型调用 handle_edge_irq 或 handle_level_irq。
  5. 动作执行:遍历 action 链表,执行注册的 handler(上半部)。
Linux 中断处理完整流程 ① 外设中断触发 ② CPU 异常向量表 ③ handle_arch_irq ④ do_IRQ() ⑤ handle_edge_irq ⑤ handle_level_irq
性能提示: 在 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 实时系统 最高(可抢占)
上半部与下半部执行时序 t 中断触发 上半部 下半部(软中断) 线程化中断处理 中断禁用 中断使能 中断使能 上半部执行期间当前 CPU 中断被硬件屏蔽,必须极短 下半部在中断使能环境下执行,可被更高优先级中断抢占

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
handle_edge_irq 与 handle_level_irq 对比 handle_edge_irq 1. irq_ack() — 应答硬件 2. 检查 IRQS_PENDING 标志 3. 遍历 action 链表执行 handler 4. 若 PENDING 则重新触发 特点:自动屏蔽新边沿,防丢失 handle_level_irq 1. irq_mask() — 屏蔽中断线 2. 遍历 action 链表执行 handler 3. 检查是否仍有电平信号 4. irq_unmask() — 解除屏蔽 特点:电平持续则立即重触发
GPIO 中断选型建议: 对于按键、传感器等短脉冲信号,使用 handle_edge_irq 配合 IRQF_TRIGGER_FALLING 或 IRQF_TRIGGER_RISING。对于电平型唤醒信号(如低功耗唤醒),使用 handle_level_irq 配合 IRQF_TRIGGER_LOW。实时系统优先选择 handle_fasteoi_irq 以减少 mask/unmask 开销。