1. 中断上下文与进程上下文:理解Linux内核两种执行上下文,以及它们对延迟工作的限制。

在Linux内核驱动开发中,理解“上下文”是编写正确、高效延迟工作代码的基石。中断上下文与进程上下文是内核中两种截然不同的执行环境,它们对可执行的操作、可调用的函数以及代码的延迟特性有着严格的限制。混淆这两种上下文是驱动开发中最常见的错误来源之一。

核心要点: 中断上下文是异步、不可阻塞的“快速通道”;进程上下文是可阻塞、可调度的“慢速通道”。延迟工作的选择(tasklet vs workqueue)本质上就是在这两种通道之间做权衡。

1.1 两种上下文的本质区别

下表从多个关键维度对比了中断上下文与进程上下文的核心差异:

对比维度 中断上下文 进程上下文
触发方式 硬件中断信号(异步) 系统调用、异常、内核线程(同步/异步)
关联进程 无(不隶属于任何用户进程) 有(current 宏有效,可访问进程描述符)
可睡眠/阻塞 绝对禁止 允许(可调用 schedule()、等待队列等)
可调用的内核API 仅限 GFP_ATOMIC 内存分配、spinlock、硬件寄存器操作 几乎全部内核API(包括 GFP_KERNEL、mutex、copy_from_user)
抢占性 不能被其他进程抢占(但可被更高优先级中断嵌套) 可被调度器抢占
执行时间要求 极短(微秒级),必须快速完成 相对宽松(毫秒级甚至更长)
典型场景 硬件事件响应、数据接收、清除中断标志 文件操作、网络协议栈、复杂数据处理

1.2 中断上下文:内核的“紧急响应通道”

当硬件设备(如网卡、磁盘控制器)需要内核立即关注时,会触发一个中断信号。CPU 会立即暂停当前正在执行的进程,保存其上下文,并跳转到预先注册的中断处理程序(Interrupt Service Routine, ISR)。此时,内核便进入了中断上下文。

中断上下文的核心约束是不可阻塞。因为中断处理程序不隶属于任何进程,没有进程控制块(PCB)可供调度器挂起和恢复。如果在中断上下文中尝试睡眠(例如调用 msleep() 或获取一个已被占用的 mutex),内核将直接崩溃(Kernel Panic)。

开发避坑: 在中断上下文中,绝对不要使用 kmalloc(GFP_KERNEL)。必须使用 kmalloc(GFP_ATOMIC),因为 GFP_KERNEL 分配可能触发内存回收而睡眠。同样,copy_from_user() 和 copy_to_user() 也可能因缺页中断而睡眠,在中断上下文中调用它们会导致死锁或崩溃。

下图展示了中断上下文的生命周期及其与进程上下文的关系:

中断上下文生命周期与进程上下文切换 进程上下文 用户进程 A 正在执行 可睡眠、可抢占 current 指向进程 A 中断上下文 硬件中断触发 不可睡眠、不可抢占 current 无意义 硬件中断信号 ISR 执行(极短) 保存寄存器、清除中断、调度延迟工作 恢复进程上下文 恢复寄存器、继续执行进程 A 中断返回 关键约束 中断上下文执行时间必须极短(通常 < 100μs),否则会阻塞其他中断和低优先级任务。 所有耗时操作必须推迟到 tasklet 或 workqueue 中执行。

1.3 进程上下文:内核的“常规工作通道”

进程上下文是内核执行系统调用、内核线程或异常处理时的环境。此时,内核代表一个特定的进程运行,全局变量 current 指向该进程的 task_struct。进程上下文拥有完整的进程控制块,因此可以睡眠、被抢占、参与调度。

在进程上下文中,驱动开发者可以使用绝大多数内核API,包括:

  • 内存分配: kmalloc(GFP_KERNEL)、vmalloc()
  • 同步机制: mutex_lock()、down_interruptible()
  • 用户空间访问: copy_from_user()、copy_to_user()
  • 调度相关: schedule()、wait_event()

下图展示了进程上下文在系统调用中的典型路径:

进程上下文:系统调用执行路径 用户空间 应用程序调用 read() 内核空间 VFS 层 sys_read() 驱动层 驱动 read() 回调 进程上下文特性(以驱动 read() 为例) ✅ 可以调用 copy_to_user() 将数据传回用户空间 ✅ 可以使用 mutex_lock() 保护共享数据结构 ✅ 如果数据未就绪,可以调用 wait_event_interruptible() 睡眠等待

1.4 上下文对延迟工作的限制

由于中断上下文对执行时间和可调用API的严格限制,驱动开发中通常采用“上半部/下半部”(Top Half / Bottom Half)机制:

  • 上半部(Top Half): 在中断上下文中执行,只做最紧急、最快速的工作(如读取硬件状态寄存器、清除中断标志位)。
  • 下半部(Bottom Half): 将耗时的数据处理工作推迟到更宽松的上下文中执行。Linux 提供了多种下半部机制,其中 tasklet 和 workqueue 是最常用的两种。

选择哪种下半部机制,直接取决于你希望它在哪种上下文中运行:

下半部机制 执行上下文 可睡眠 适用场景
Tasklet 中断上下文(软中断) ❌ 禁止 对时间要求极高、不可阻塞的快速处理(如网络数据包接收)
Workqueue 进程上下文(内核线程) ✅ 允许 需要睡眠、需要大量内存分配、需要访问用户空间的复杂操作
选择指南: 如果你不确定该用哪个,先问自己一个问题:“我的延迟工作是否需要睡眠?” 如果答案是“是”,请使用 workqueue;如果答案是“否”,且执行时间极短(微秒级),tasklet 是更轻量、更高效的选择。在后续章节中,我们将深入探讨这两种机制的具体实现与性能权衡。