4. Linux 内核中断子系统基础

中断是硬件与内核交互的核心机制,DRM VBlank 中断的注册、处理与同步完全依赖中断子系统。理解中断描述符、处理流程以及上下半部机制,是优化 VBlank 响应延迟和 CPU 负载的前提。

4.1 中断描述符 (irq_desc)

内核使用 struct irq_desc 描述每个中断源,包含状态、处理函数链表、线程化处理、电源管理等信息。系统启动时根据硬件拓扑(如 GIC、APIC)静态或动态分配中断描述符。

字段 类型 说明
irq_data struct irq_data 中断号、芯片、触发类型、CPU 亲和性
action struct irqaction * 共享中断处理函数链表头
status_use_accessors unsigned int 中断状态标志(IRQ_INPROGRESS、IRQ_DISABLED 等)
threads_oneshot atomic_t 线程化中断一次性计数
depth unsigned int 中断禁用嵌套深度
name const char * 中断描述名称(如 vblank)
关键点:DRM 驱动通过 devm_request_irq() 或 request_irq() 注册 VBlank 中断时,内核会将该 irqaction 挂入对应 irq_desc 的 action 链表。共享中断(如 VBlank 与其他显示事件共用同一物理中断线)时,内核会遍历链表调用所有注册的处理函数。
中断描述符与设备树的关联:设备树中 interrupts = <GIC_SPI 53 IRQ_TYPE_EDGE_RISING> 指定中断控制器、中断号与触发类型。内核在解析设备树时,通过 irq_of_parse_and_map() 将设备树中断描述映射为 Linux 中断号,并初始化对应的 irq_desc。
中断描述符 (irq_desc) 结构 irq_desc irq_data action (链表头) status threads_oneshot depth / name 每个中断号对应一个 irq_desc VBlank 中断注册后挂入 action 链表 共享中断时遍历链表调用 handler

4.2 中断处理流程

当硬件中断信号到达 CPU 时,内核经历以下标准流程:

  1. 异常向量入口:CPU 跳转到 vector_irq 或 __irq_svc 汇编入口,保存现场。
  2. 通用中断处理:handle_arch_irq() 或 gic_handle_irq() 读取中断控制器寄存器,获取硬件中断号。
  3. 映射为 Linux 中断号:通过 irq_find_mapping() 将硬件中断号转换为 irq_desc 索引。
  4. 调用中断流控处理函数:如 handle_edge_irq()、handle_level_irq(),处理边沿/电平触发逻辑。
  5. 执行 action 链表:遍历 irq_desc->action,调用每个注册的 handler(即上半部)。
  6. 返回与恢复现场:中断处理完成后,CPU 恢复寄存器并返回被中断的上下文。
性能陷阱:VBlank 中断处理函数(上半部)中如果执行了耗时操作(如大量寄存器读写、DMA 状态轮询),会阻塞其他中断和实时任务。必须将非紧急工作推迟到下半部执行。
Linux 内核中断处理流程 硬件中断到达 CPU 异常向量入口 (vector_irq) 通用中断处理 (gic_handle_irq) 映射为 Linux 中断号 流控处理 (handle_edge_irq) 执行 action 链表 (上半部) VBlank 中断处理函数在此执行 必须快速完成,避免阻塞 通常只做: • 读取 VBlank 时间戳 • 调度下半部 (Tasklet/Workqueue)

4.3 上半部与下半部

中断处理分为上半部(Top Half)和下半部(Bottom Half),目的是在中断上下文中最小化关中断时间,将大量处理推迟到更宽松的上下文中执行。

特性 上半部 (Top Half) 下半部 (Bottom Half)
执行上下文 中断上下文(不可睡眠) 软中断 / 进程上下文(可睡眠)
中断状态 当前中断线被屏蔽(视流控类型) 中断已重新使能
允许操作 仅原子操作、寄存器读写、时间戳记录 内存分配、锁、文件操作、调度
典型耗时 微秒级(必须极短) 毫秒级甚至更长
DRM VBlank 示例 读取硬件 VBlank 计数器、记录 ktime_t 更新 CRTC 状态、唤醒等待队列、发送事件

4.3.1 Tasklet

Tasklet 是基于软中断(HI_SOFTIRQ / TASKLET_SOFTIRQ)的下半部机制,在中断返回前执行,保证顺序性(同一 Tasklet 不会在多个 CPU 上并发执行)。

/* 定义 Tasklet 处理函数 */
void vblank_tasklet_handler(unsigned long data)
{
    struct drm_crtc *crtc = (struct drm_crtc *)data;
    /* 更新 VBlank 统计、唤醒用户态等待 */
    drm_crtc_handle_vblank(crtc);
}

/* 声明并初始化 Tasklet */
DECLARE_TASKLET(vblank_tasklet, vblank_tasklet_handler, (unsigned long)crtc);

/* 在上半部中调度 */
void vblank_irq_handler(int irq, void *dev_id)
{
    /* 记录时间戳 */
    crtc->vblank_time = ktime_get();
    /* 调度 Tasklet */
    tasklet_schedule(&vblank_tasklet);
}
适用场景:Tasklet 适合对延迟敏感、执行时间短(< 1ms)的下半部任务。VBlank 事件处理中,如果只是更新状态和唤醒等待队列,Tasklet 是首选。

4.3.2 Workqueue

Workqueue 在进程上下文(内核线程)中执行,允许睡眠、持有锁、执行文件系统操作。适合需要较长处理时间或可能阻塞的任务。

/* 定义 Workqueue 处理函数 */
void vblank_work_handler(struct work_struct *work)
{
    struct drm_crtc *crtc = container_of(work, struct drm_crtc, vblank_work);
    /* 可能涉及 DRM 核心锁、事件发送 */
    drm_crtc_send_vblank_event(crtc);
}

/* 初始化 Workqueue */
INIT_WORK(&crtc->vblank_work, vblank_work_handler);

/* 在上半部中调度 */
void vblank_irq_handler(int irq, void *dev_id)
{
    /* 记录时间戳 */
    crtc->vblank_time = ktime_get();
    /* 调度 Workqueue */
    schedule_work(&crtc->vblank_work);
}
性能权衡:Workqueue 调度延迟比 Tasklet 高(因为涉及内核线程切换),但能处理更复杂的任务。在 VBlank 中断优化中,如果下半部需要获取 drm_mode_config.mutex 或发送 drm_event,必须使用 Workqueue 以避免在原子上下文中睡眠。
上半部与下半部分工(VBlank 中断示例) 上半部 (Top Half) 读取 VBlank 时间戳 (ktime_get) 调度下半部 (Tasklet/Workqueue) 下半部 (Bottom Half) 更新 CRTC VBlank 状态 唤醒用户态等待队列 调度 上半部必须极短(微秒级),下半部可执行毫秒级任务

4.4 中断线程化 (Threaded IRQ)

内核支持将中断处理函数直接运行在内核线程中(request_threaded_irq()),上半部只做最简单的硬件确认,下半部由内核线程自动执行。这简化了 Tasklet/Workqueue 的手动调度,但增加了线程切换开销。

/* 线程化中断示例 */
static irqreturn_t vblank_irq_handler(int irq, void *dev_id)
{
    /* 上半部:仅确认中断属于本设备 */
    struct drm_crtc *crtc = dev_id;
    if (!crtc->vblank_pending)
        return IRQ_NONE;
    return IRQ_WAKE_THREAD;  /* 唤醒线程化下半部 */
}

static irqreturn_t vblank_irq_thread_fn(int irq, void *dev_id)
{
    struct drm_crtc *crtc = dev_id;
    /* 线程上下文,可睡眠 */
    drm_crtc_handle_vblank(crtc);
    drm_crtc_send_vblank_event(crtc);
    return IRQ_HANDLED;
}

/* 注册线程化中断 */
request_threaded_irq(irq, vblank_irq_handler, vblank_irq_thread_fn,
                     IRQF_SHARED, "vblank", crtc);
优化建议:对于 VBlank 中断,如果下半部任务较重(如需要发送 DRM 事件、更新多个 CRTC 状态),推荐使用线程化中断 + Workqueue 的组合,避免 Tasklet 在软中断上下文中占用 CPU 时间过长,影响其他实时任务。

4.5 本章小结

  • 中断描述符是内核管理中断的核心数据结构,DRM 驱动通过 request_irq() 将 VBlank 处理函数挂入 action 链表。
  • 中断处理流程从硬件向量到流控处理再到 action 执行,上半部必须快速完成。
  • Tasklet 适合短小、不可睡眠的下半部任务,延迟低但不可阻塞。
  • Workqueue 适合可睡眠、耗时较长的下半部任务,调度延迟略高。
  • 线程化中断提供了一种自动上下半部分离的机制,适合 VBlank 事件处理。