1. 中断处理基础

1.1 中断的核心概念

中断(Interrupt)是硬件或软件向处理器发送的一种异步信号,用于通知处理器发生了需要立即关注的事件。中断机制是现代操作系统实现多任务、实时响应和外设管理的基石。

中断的本质:中断是一种硬件级别的“紧急呼叫”,它允许CPU暂停当前正在执行的程序流,转而执行一个专门的中断服务程序(ISR, Interrupt Service Routine),处理完后再恢复原来的执行流。

中断的主要分类如下:

分类 触发源 特点 典型示例
硬件中断 外部硬件设备 异步、随机发生、与CPU时钟无关 网卡收到数据包、键盘按键、磁盘I/O完成
软件中断 CPU指令执行 同步、由程序主动触发 系统调用(int 0x80)、异常(除零错误)
异常 CPU内部错误或特殊条件 同步、通常与当前指令相关 缺页异常、保护错误、调试断点

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

中断触发与处理流程 硬件设备 软件指令 CPU异常 中断控制器 (APIC / GIC) CPU核心 (保存/恢复上下文) 中断向量表 CPU收到中断信号后,根据中断号查询中断向量表,跳转到对应的中断服务程序(ISR)执行

1.2 中断上下文

中断上下文(Interrupt Context)是Linux内核中一个极为重要的概念。当CPU执行中断服务程序时,它运行在中断上下文中,与普通的进程上下文有本质区别。

对比维度 进程上下文 中断上下文
可睡眠 可以(通过调度器) 绝对不可以
可被抢占 可以 通常不可以(取决于内核配置)
持有锁 可以使用互斥锁(mutex) 只能使用自旋锁(spinlock)
访问用户空间 可以 绝对不可以
current宏有效 有效,指向当前进程 无效,指向被中断的进程
调度时机 主动调用schedule()或时间片耗尽 中断返回时检查need_resched标志
⚠️ 关键约束:中断上下文中不能调用任何可能导致睡眠的函数,包括kmalloc(GFP_KERNEL)、copy_from_user()、mutex_lock()等。违反此规则会导致内核死锁或系统崩溃。

内核通过in_interrupt()和in_irq()等宏来检测当前是否处于中断上下文:

/* 判断当前是否处于中断上下文 */
if (in_interrupt()) {
    pr_info("当前运行在中断上下文中,不能睡眠!\n");
    /* 只能使用自旋锁等原子操作 */
    spin_lock(&my_lock);
    /* ... 处理中断相关任务 ... */
    spin_unlock(&my_lock);
} else {
    pr_info("当前运行在进程上下文中,可以安全睡眠\n");
    /* 可以使用互斥锁 */
    mutex_lock(&my_mutex);
    /* ... 处理常规任务 ... */
    mutex_unlock(&my_mutex);
}

1.3 上半部与下半部机制

中断服务程序必须快速执行,因为它在执行期间会屏蔽同级别或更低级别的中断。然而,很多设备的中断处理工作非常复杂,例如网卡收到一个数据包后,需要将数据从硬件FIFO拷贝到内核缓冲区、解析协议头、将数据包交给协议栈、唤醒等待的进程等。如果所有这些工作都在ISR中完成,系统将长时间无法响应其他中断,导致严重的实时性问题。

为了解决这个问题,Linux内核将中断处理拆分为两个部分:

  • 上半部(Top Half):即中断服务程序本身,执行时间敏感、必须原子完成的操作,如清除硬件中断标志、拷贝关键数据、记录中断状态。上半部执行期间,当前中断线被屏蔽。
  • 下半部(Bottom Half):在中断使能的环境下执行剩余的非紧急处理工作,如协议栈处理、数据分发、唤醒进程。下半部可以被其他中断抢占。
上半部与下半部处理流程 中断发生 上半部 (Top Half) ISR 执行 中断屏蔽 · 快速 · 原子操作 下半部 (Bottom Half) Tasklet / Workqueue / Softirq 中断使能 · 可延迟 · 可抢占 上半部典型工作 • 保存硬件寄存器状态 • 清除中断挂起标志 • 从硬件FIFO拷贝关键数据 • 调度下半部执行 下半部典型工作 • 数据包协议栈处理 • 复杂的数据拷贝与校验 • 唤醒等待队列中的进程 • 执行定时器回调函数 时间轴 → 上半部必须快速完成(微秒级),下半部可以延迟执行(毫秒级甚至更晚)

1.4 Linux下半部机制的演进

Linux内核在发展过程中,下半部机制经历了多次演进,从最初的BH(Bottom Half)机制,到软中断(Softirq)、Tasklet,再到Workqueue,每种机制都有其适用的场景和性能特征。

下半部机制 引入版本 执行上下文 可睡眠 并发度 使用场景
BH (旧) Linux 1.x 中断上下文 否 单CPU串行 已废弃
Softirq Linux 2.3 中断上下文 否 多CPU并行 网络、块设备等高性能路径
Tasklet Linux 2.3 中断上下文 否 同类型串行 驱动中通用下半部
Workqueue Linux 2.5 进程上下文 是 多CPU并行 需要睡眠的复杂任务
💡 选择指南:
  • 如果下半部工作非常短且不需要睡眠 → 使用 Tasklet(基于Softirq的简化封装)
  • 如果下半部工作涉及大量计算、I/O操作或需要睡眠 → 使用 Workqueue
  • 如果对性能有极致要求且熟悉内核内部机制 → 直接使用 Softirq

1.5 中断处理的关键数据结构

在Linux内核中,中断处理涉及多个关键数据结构,理解它们之间的关系是掌握中断机制的基础:

/* 中断描述符结构体(简化版) */
struct irq_desc {
    struct irq_data        irq_data;      /* 中断号、触发方式等硬件信息 */
    struct irqaction      *action;        /* 中断处理函数链表 */
    unsigned int           status_use_accessors; /* 中断状态标志 */
    struct irq_chip       *chip;          /* 中断控制器操作函数集 */
    struct irq_domain     *domain;        /* 中断映射域 */
    unsigned int           depth;         /* 中断使能深度计数 */
    wait_queue_head_t      wait_for_threads; /* 等待队列 */
};

/* 中断处理动作结构体 */
struct irqaction {
    irq_handler_t          handler;       /* 上半部处理函数指针 */
    unsigned long          flags;         /* 中断标志(触发方式等) */
    const char            *name;          /* 设备名称 */
    void                  *dev_id;        /* 设备ID,用于共享中断识别 */
    struct irqaction      *next;          /* 链表下一个节点(共享中断) */
    int                    irq;           /* 中断号 */
    struct tasklet_struct  tasklet;       /* 关联的tasklet(可选) */
};

中断处理函数注册使用request_irq()接口,释放使用free_irq():

/* 注册中断处理函数 */
int request_irq(unsigned int irq,                /* 中断号 */
                irq_handler_t handler,           /* 上半部处理函数 */
                unsigned long flags,             /* 中断标志 */
                const char *name,                /* 设备名称 */
                void *dev_id);                   /* 设备ID */

/* 典型的中断处理函数原型 */
static irqreturn_t my_interrupt_handler(int irq, void *dev_id)
{
    /* 上半部:快速处理,保存必要数据 */
    struct my_device *dev = (struct my_device *)dev_id;
    
    /* 读取硬件状态寄存器 */
    u32 status = readl(dev->regs + STATUS_REG);
    
    /* 清除中断标志 */
    writel(status, dev->regs + CLEAR_REG);
    
    /* 调度下半部处理(例如使用tasklet) */
    tasklet_schedule(&dev->my_tasklet);
    
    return IRQ_HANDLED;
}
📌 核心要点回顾:
  1. 中断是异步事件,中断上下文不能睡眠、不能访问用户空间、只能使用自旋锁。
  2. 上半部执行时间极短(微秒级),负责硬件交互和关键数据保存。
  3. 下半部执行时间可长可短,负责剩余处理工作,根据需求选择Tasklet或Workqueue。
  4. Linux提供了多种下半部机制,从Softirq到Workqueue,覆盖从高性能到高灵活性的不同需求。