1. 中断上下文基础

1.1 中断概念

中断(Interrupt)是硬件或软件向处理器发送的一种异步信号,用于通知处理器发生了需要立即关注的事件。在嵌入式 Linux 系统中,中断是驱动与硬件交互的核心机制——当外设完成数据传输、发生错误或需要服务时,通过中断线向 CPU 发出请求,CPU 暂停当前执行流,转而执行中断服务程序(ISR)。

核心要点:中断机制避免了 CPU 轮询外设状态带来的忙等待开销,是系统实时性和响应能力的基础保障。

1.2 中断上下文 vs 进程上下文

理解中断上下文与进程上下文的区别,是编写正确驱动代码的前提。下表从多个维度对比两者的关键差异:

对比维度 进程上下文 中断上下文
执行主体 用户进程或内核线程 中断处理程序(ISR)
可睡眠 是(可调用 schedule()) 否(不可睡眠、不可阻塞)
可访问用户空间 是(通过 copy_from/to_user) 否(无进程关联)
持有锁类型 互斥锁(mutex)、信号量等可睡眠锁 仅限自旋锁(spinlock)等非睡眠锁
调度状态 可被调度器抢占 不可被调度(原子上下文)
栈空间 进程内核栈(通常 8KB/16KB) 中断栈(通常 4KB/8KB,与进程栈隔离)
典型执行时间 毫秒级甚至更长 微秒级(必须极短)
常见陷阱:在中断上下文中调用 kmalloc(GFP_KERNEL) 会导致睡眠,应使用 GFP_ATOMIC 标志。同样,down_interruptible()、mutex_lock() 等可睡眠锁在中断上下文中绝对禁止使用。

1.3 中断处理程序的特点与限制

Linux 内核将中断处理程序分为两部分:上半部(Top Half)和下半部(Bottom Half)。上半部是注册到 request_irq() 的处理函数,直接响应硬件中断;下半部(如 tasklet、workqueue、软中断)则延迟执行非紧急处理逻辑。

上半部(硬中断处理程序)的特点

  • 原子性:执行期间当前 CPU 禁止本地中断(若未设置 IRQF_DISABLED 则可能允许同级中断嵌套,但现代内核默认不嵌套)。
  • 短小精悍:只完成最必要的硬件操作(如读取状态寄存器、清除中断标志、拷贝数据到缓冲区),耗时应在几十微秒以内。
  • 不可重入:同一中断号的处理程序在单 CPU 上不会并发执行,但多 CPU 下可能同时运行不同中断号的处理程序。
  • 无进程上下文:无法访问用户空间内存,无法使用文件系统接口,无法调用可能睡眠的内核函数。

中断处理程序的限制清单

  1. 禁止睡眠:任何导致调度的操作(wait_event、mutex_lock、synchronize_rcu 等)均非法。
  2. 禁止长时间循环:中断处理程序占用 CPU 期间,同级或低优先级中断被屏蔽,影响系统实时性。
  3. 禁止使用浮点运算:内核默认不保存浮点寄存器状态,浮点操作会破坏用户进程的浮点上下文。
  4. 栈空间有限:中断栈通常只有 4KB~8KB,递归调用或大型局部变量可能导致栈溢出。
  5. 禁止调用可能触发缺页异常的函数:如 copy_from_user()、vmalloc() 等。
经验法则:如果中断处理程序需要执行超过 100 条指令,或者涉及 I/O 等待、数据拷贝、协议解析等操作,就应该将大部分工作推迟到下半部(workqueue/tasklet)中执行。

1.4 中断上下文执行流程

下图展示了从硬件中断触发到上半部、下半部处理的完整流程:

中断上下文执行流程 硬件中断触发 CPU 保存现场 (寄存器压栈、关本地中断) 上半部 ISR 执行 (读状态、清标志、记录数据) 下半部调度 (标记 workqueue/tasklet 待执行) 下半部执行 (进程上下文 / 软中断上下文)

1.5 中断上下文与 workqueue 的关联

本课程聚焦的 workqueue 是 Linux 内核中最常用的下半部机制之一。与 tasklet 运行在软中断上下文不同,workqueue 的工作函数运行在进程上下文(内核线程上下文)中,这意味着:

  • workqueue 处理函数可以睡眠(使用 mutex、等待队列等)。
  • workqueue 处理函数可以执行较长时间的操作(毫秒级甚至更长)。
  • workqueue 处理函数可以被调度器抢占,不会长时间阻塞其他中断。

因此,中断上半部只做最紧急的硬件操作,将耗时的数据处理、协议解析、I/O 操作等通过 workqueue 延迟到进程上下文中执行,这是 Linux 驱动性能优化的核心策略。