1. 中断处理概述

1.1 中断的基本概念

中断(Interrupt)是硬件或软件向处理器发送的一种异步信号,用于通知处理器发生了需要立即关注的事件。中断机制是现代操作系统响应外部事件的核心手段,它允许CPU在等待事件时执行其他任务,从而大幅提升系统吞吐量。

核心要点:中断的本质是“打断”CPU当前正在执行的指令流,强制跳转到一个预先定义好的处理函数(中断服务例程,ISR)中去执行。

根据中断来源,可将其分为以下两类:

中断类型 触发源 典型示例
硬件中断(Hardware IRQ) 外设控制器(网卡、磁盘、定时器) 网卡收到数据包、磁盘完成读写
软件中断(Software Interrupt) CPU指令主动触发(int指令、异常) 系统调用、缺页异常、除零错误

在Linux内核中,中断处理的核心数据结构是 irq_desc,每个中断号对应一个描述符,其中包含了该中断的触发方式、处理函数链表、状态标志等信息。

// include/linux/irqdesc.h (简化结构)
struct irq_desc {
    struct irq_data      irq_data;
    unsigned int         __percpu *kstat_irqs;
    irq_flow_handler_t   handle_irq;
    struct irqaction    *action;   // 中断处理函数链表
    unsigned int         status_use_accessors;
    const char          *name;
} ____cacheline_internodealigned_in_smp;

1.2 中断上下文(Interrupt Context)

当CPU进入中断处理时,执行环境从“进程上下文”切换到“中断上下文”。这是理解中断底半部机制的基础。

关键区分:中断上下文与进程上下文的最大区别在于——中断上下文中不可睡眠,不能调用可能引起阻塞的函数(如 kmalloc(GFP_KERNEL)、mutex_lock()),因为中断上下文没有关联的进程控制块(task_struct)可供调度器切换。

中断上下文具有以下特征:

  • 不可睡眠:中断处理程序必须快速执行,不能等待资源。
  • 不可被相同中断号抢占:同一IRQ线上的中断默认是禁止嵌套的。
  • 拥有独立的栈:每个CPU在初始化时会分配一个中断栈(通常为4KB或8KB),与进程内核栈分离。
  • 访问限制:只能使用 GFP_ATOMIC 标志分配内存。

以下SVG图展示了中断发生时CPU上下文的切换流程:

中断上下文切换流程 进程上下文 用户态/内核态代码 硬件中断信号 中断入口 保存现场 → 关中断 中断上下文 不可睡眠 · 栈独立 进入ISR 上半部(Top Half) 紧急操作 · 记录事件 执行ISR 恢复现场 返回进程

1.3 上半部(Top Half)与下半部(Bottom Half)的分工原则

中断处理程序必须快速执行,否则会导致系统响应延迟甚至丢数据。然而,很多设备在中断发生后需要完成大量处理工作(如网络协议栈解析、数据拷贝)。为此,Linux内核将中断处理拆分为两个阶段:

特性 上半部(Top Half) 下半部(Bottom Half)
执行时机 中断触发后立即执行 稍后调度执行(软中断/工作队列)
中断状态 关中断(当前IRQ线) 开中断(可被其他中断打断)
可睡眠 ❌ 不可睡眠 取决于机制(tasklet不可,工作队列可)
执行时长 极短(微秒级) 相对较长(毫秒级)
典型操作 读取硬件状态、清除中断标志、拷贝数据 协议处理、数据解析、唤醒等待进程
常见误区:初学者常将大量处理逻辑直接放在上半部中,导致中断延迟过高、丢包甚至系统崩溃。正确的做法是:上半部只做最紧急的硬件操作,其余工作推迟到下半部执行。

Linux内核提供了多种下半部机制,按优先级和复杂度排列如下:

  1. 软中断(SoftIRQ):内核预定义的有限集合(如NET_RX_SOFTIRQ、TIMER_SOFTIRQ),优先级高,不可睡眠。
  2. tasklet:基于软中断实现,动态注册,同一tasklet在同一CPU上串行执行。
  3. 工作队列(Workqueue):运行在进程上下文,可睡眠,适合耗时任务。

以下SVG图展示了上半部与下半部的协作关系:

上半部与下半部分工协作 硬件中断事件 上半部(Top Half) 读状态 · 清标志 · 记录数据 下半部(Bottom Half) 协议解析 · 数据拷贝 · 唤醒 调度下半部 下半部机制 • 软中断(最高优先级) • tasklet(基于软中断) • 工作队列(可睡眠) 返回进程上下文

1.4 中断处理的设计原则总结

在实际的嵌入式驱动开发中,遵循以下原则可以有效避免竞态条件和性能问题:

  • 最小化原则:上半部只做必须立即完成的操作,如读取硬件FIFO、清除中断挂起位。
  • 延迟原则:将数据解析、协议处理、I/O操作等推迟到下半部。
  • 同步原则:上下半部之间通过原子操作、自旋锁或内存屏障保护共享数据。
  • 优先级原则:紧急程度高的下半部使用软中断或tasklet,可容忍延迟的使用工作队列。
调试提示:在排查中断相关问题时,首先检查上半部是否执行了过多操作。可以通过 /proc/interrupts 查看中断计数,使用 ftrace 跟踪中断处理耗时。如果发现中断处理时间超过100微秒,通常意味着需要将部分工作移到下半部。