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 下可能同时运行不同中断号的处理程序。
- 无进程上下文:无法访问用户空间内存,无法使用文件系统接口,无法调用可能睡眠的内核函数。
中断处理程序的限制清单
- 禁止睡眠:任何导致调度的操作(wait_event、mutex_lock、synchronize_rcu 等)均非法。
- 禁止长时间循环:中断处理程序占用 CPU 期间,同级或低优先级中断被屏蔽,影响系统实时性。
- 禁止使用浮点运算:内核默认不保存浮点寄存器状态,浮点操作会破坏用户进程的浮点上下文。
- 栈空间有限:中断栈通常只有 4KB~8KB,递归调用或大型局部变量可能导致栈溢出。
- 禁止调用可能触发缺页异常的函数:如
copy_from_user()、vmalloc()等。
经验法则:如果中断处理程序需要执行超过 100 条指令,或者涉及 I/O 等待、数据拷贝、协议解析等操作,就应该将大部分工作推迟到下半部(workqueue/tasklet)中执行。
1.4 中断上下文执行流程
下图展示了从硬件中断触发到上半部、下半部处理的完整流程:
1.5 中断上下文与 workqueue 的关联
本课程聚焦的 workqueue 是 Linux 内核中最常用的下半部机制之一。与 tasklet 运行在软中断上下文不同,workqueue 的工作函数运行在进程上下文(内核线程上下文)中,这意味着:
- workqueue 处理函数可以睡眠(使用 mutex、等待队列等)。
- workqueue 处理函数可以执行较长时间的操作(毫秒级甚至更长)。
- workqueue 处理函数可以被调度器抢占,不会长时间阻塞其他中断。
因此,中断上半部只做最紧急的硬件操作,将耗时的数据处理、协议解析、I/O 操作等通过 workqueue 延迟到进程上下文中执行,这是 Linux 驱动性能优化的核心策略。