3、Linux中断子系统:中断描述符、中断请求(IRQ)号、中断处理流程(上半部/下半部)

在UART驱动开发中,中断处理是决定吞吐量和响应延迟的核心环节。本章深入剖析Linux中断子系统的三大支柱:中断描述符的数据结构、IRQ号的映射机制,以及上半部/下半部的分工流程。理解这些概念是优化UART中断延迟的前提。

3.1 中断描述符 (irq_desc)

Linux内核使用 struct irq_desc 来描述每一个中断源。该结构体是中断子系统的中枢,串联了硬件中断控制器、中断处理函数以及下半部机制。每个IRQ号都对应一个 irq_desc 实例。

关键字段说明: 中断描述符中 action 链表挂载了所有注册的中断处理函数;irq_data 关联底层硬件控制器;threads_handled 等字段用于统计和调试。
struct irq_desc 核心结构图 struct irq_desc irq_data (硬件抽象) action (处理函数链表) threads_handled (统计) handle_irq (入口函数) kstat_irqs (中断计数) pending_mask (待处理位图)

3.2 中断请求(IRQ)号与映射机制

IRQ号是软件层面识别中断源的逻辑编号。在UART场景中,串口控制器通常占用一个专用的硬件中断线,通过中断控制器(GIC/IOAPIC)映射为唯一的IRQ号。

概念 说明 UART示例
硬件中断号 中断控制器引脚编号,由SoC设计固定 UART0 硬件中断线 = 32
IRQ号 内核使用的逻辑编号,通过 irq_domain 映射 request_irq(irq=66, ...)
irq_domain 硬件中断号 → IRQ号的映射表 设备树中 interrupts = <0 32 4>
调试技巧: 使用 cat /proc/interrupts 查看当前系统中所有IRQ号的分配情况,确认UART中断是否被正确注册以及触发次数。
IRQ号映射流程 硬件中断线 (HW IRQ) 中断控制器 (GIC) irq_domain 映射 IRQ号 (逻辑编号) irq_desc 实例 request_irq() 注册处理函数

3.3 中断处理流程:上半部与下半部

Linux将中断处理拆分为两个阶段,以平衡实时性与吞吐量:

  • 上半部 (Top Half): 在关闭本地中断的上下文中执行,要求极短且不可睡眠。典型操作:读取硬件状态寄存器、清除中断标志、将数据搬运到FIFO或临时缓冲区。
  • 下半部 (Bottom Half): 在开启中断的上下文中执行,允许延迟处理。UART驱动中常用的下半部机制包括:tasklet、workqueue 以及 threaded IRQ。
UART性能陷阱: 如果在UART上半部中执行过多的数据拷贝或复杂的逻辑判断,将直接阻塞其他中断,导致串口丢帧或系统响应延迟飙升。务必遵循“上半部最小化”原则。
中断处理流程:上半部与下半部 硬件中断发生 上半部 (Top Half) 关中断 · 快速 · 不可睡眠 调度下半部 (return IRQ_WAKE_THREAD) tasklet (软中断上下文) workqueue (进程上下文) threaded IRQ (内核线程) 开中断,继续处理

3.4 UART驱动中的中断处理函数注册

在UART驱动中,中断处理函数的注册通常发生在端口启动时。以下是一个典型的注册代码片段:

/* UART中断处理函数 - 上半部 */
static irqreturn_t uart_isr(int irq, void *dev_id)
{
    struct uart_port *port = dev_id;
    unsigned int iir;

    /* 读取中断标识寄存器,判断中断类型 */
    iir = readl(port->membase + UART_IIR);
    if (iir & UART_IIR_NO_INT)
        return IRQ_NONE;  /* 非本设备中断 */

    /* 处理接收中断:将数据从RX FIFO搬入环形缓冲区 */
    if (iir & UART_IIR_RX_TIMEOUT || iir & UART_IIR_RX_AVAIL) {
        /* 上半部只做必要的硬件操作 */
        uart_handle_rx(port);
    }

    /* 调度下半部处理数据解析与协议栈递交 */
    return IRQ_WAKE_THREAD;  /* 触发 threaded IRQ 下半部 */
}

/* UART中断处理函数 - 下半部 (threaded IRQ) */
static irqreturn_t uart_thread_fn(int irq, void *dev_id)
{
    struct uart_port *port = dev_id;

    /* 在进程上下文中完成数据解析、流量控制、唤醒等待队列等 */
    uart_process_rx(port);

    return IRQ_HANDLED;
}

/* 注册中断 */
int uart_startup(struct uart_port *port)
{
    int ret;

    ret = request_threaded_irq(port->irq, uart_isr, uart_thread_fn,
                               IRQF_TRIGGER_HIGH | IRQF_SHARED,
                               "uart_irq", port);
    if (ret)
        dev_err(port->dev, "Failed to request IRQ %d\n", port->irq);

    return ret;
}
优化要点: 使用 request_threaded_irq() 将上半部与下半部分离,上半部仅处理硬件寄存器操作,下半部在进程上下文中执行,允许睡眠和更复杂的逻辑,从而显著降低中断关闭时间。

3.5 中断响应延迟分析

中断响应延迟是指从硬件中断信号产生到上半部处理函数开始执行的时间间隔。主要影响因素包括:

延迟来源 典型耗时 优化方向
中断控制器仲裁 ~1-5 µs 配置中断优先级,减少共享中断
内核中断入口/出口 ~2-10 µs 使用 IRQF_NO_THREAD 减少上下文切换
上半部执行时间 ~5-50 µs 精简上半部逻辑,仅做必要硬件操作
中断嵌套与屏蔽 ~10-100 µs 避免在中断中调用可能导致睡眠的函数
测量方法: 使用 trace_irq_handler_entry 和 trace_irq_handler_exit 内核跟踪点,配合 ftrace 或 perf 精确测量中断处理函数的执行时间分布。
中断响应延迟时间线 硬件中断 中断控制器 上半部入口 上半部出口 下半部开始 仲裁延迟 入口/出口开销 上半部执行

3.6 本章小结

  • 中断描述符 是内核管理中断的核心数据结构,每个IRQ号对应一个 irq_desc 实例。
  • IRQ号 通过 irq_domain 从硬件中断号映射而来,是驱动开发中使用的逻辑编号。
  • 上半部 关中断执行,必须极短;下半部 开中断执行,适合复杂处理。
  • UART驱动中推荐使用 request_threaded_irq() 分离上下半部,以降低中断关闭时间。
  • 中断响应延迟由仲裁、入口/出口开销和上半部执行时间三部分构成,可通过内核跟踪点精确测量。