📚 Linux 驱动核心笔记:上下文、中断与休眠
一、 核心概念对比:进程上下文 vs 中断上下文
要理解为什么不能在中断处理函数中里调用休眠函数让出CPU,首先必须搞清楚代码是在什么环境(上下文)下运行的。
1. 进程上下文 (Process Context)
- 定义 :当用户程序(APP)通过系统调用(如
open,read,write,ioctl)陷入内核态时,内核正在代表这个 APP 执行代码。 - 特点 :
- 有身份 :拥有对应的
task_struct(进程控制块),记录了 PID、打开的文件、内存页表等信息。 - 有行李 :因为有
task_struct,它可以保存当前的运行状态(寄存器、堆栈)。 - 可休眠:因为有地方保存状态,内核可以把它挂起(Sleep),把 CPU 让给别人,稍后再恢复它。
- 有身份 :拥有对应的
- 场景 :应用进入到内核态后,一般会调用驱动里的
xxx_read,xxx_write,xxx_open函数,而这些函数通常都运行在进程上下文。
2. 中断上下文 (Interrupt Context)
- 定义:当硬件触发信号(如按键按下、网卡收包),CPU 强行打断当前正在运行的任务,跳转到中断处理函数(ISR)执行。
- 特点 :
- 无身份 :中断不是进程,它没有自己的
task_struct。它是一个"不速之客",借用当前正在运行的那个倒霉进程的内核栈来运行。 - 无行李:它没有地方保存"暂停时的状态"以便被调度器管理。
- 不可休眠:这是铁律。
- 无身份 :中断不是进程,它没有自己的
- 场景 :驱动里通过
request_irq注册的handler函数(如gpio_key_isr)。
3. 直观对比表
| 特性 | 进程上下文 (Process Context) | 中断上下文 (Interrupt Context) |
|---|---|---|
| 代表人物 | APP (通过系统调用进入内核) | 硬件中断 (ISR) |
| 持有凭证 | task_struct (身份证) |
无 (借用别人的) |
| 能否休眠 | 能 (msleep, copy_to_user) |
绝对不能 |
| 能否被调度 | 受调度器管理 | 优先级极高,不可被调度器抢占 |
| 执行目标 | 服务于特定的 APP 请求 | 响应硬件紧急事件 |
二、 深度解析:为什么中断里不能休眠?
1. 根本原因:找不到回家的路
休眠的本质是**"让出 CPU"并触发"调度器"**。
- 过程 :当进程休眠时,内核把它的状态存入
task_struct,然后切到别的进程。 - 中断的困境 :中断没有
task_struct。- 如果在中断里调用
msleep,调度器会试图把"当前状态"保存起来。但"当前状态",即上下文,属于被中断的那个无辜进程,而不是中断本身。 - 如果真的切换走了,调度器再也找不到这个中断处理函数了(因为它不在调度队列里)。中断一旦挂起,硬件就会一直等待,或者数据丢失,系统逻辑彻底崩塌。
- 如果在中断里调用
2. 内核的反应:Kernel Panic
Linux 内核设计有检测机制。如果在中断上下文中检测到调度行为(如调用了 msleep 或 schedule),内核会立即抛出致命错误:
Panic: scheduling while atomic
(在原子上下文中尝试调度)
这意味着系统崩溃,通常会死机或重启。
三、中断中可以延时吗?
答案:可以,但不能使CPU休眠。
1. msleep 的机制
msleep不管延时多短(哪怕 1 纳秒),它的实现逻辑都是:- 设置定时器。
- 调用
schedule()让出 CPU。 - 进程进入睡眠队列。
- 只要涉及 "让出 CPU" 和 "调用 schedule()",在中断里就是死罪。
2. 如果非要延时怎么办?
在中断中如果必须延时(例如复位硬件需要维持 5us 低电平),只能使用 "忙等待" 函数:
udelay(us)或mdelay(ms)- 原理 :CPU 在原地空转(执行死循环指令),不让出 CPU。
- 代价:这期间 CPU 100% 占用,不能处理其他任务。所以只能用于极短时间的延时(通常微秒级)。
四、 扩展名词解释 (Glossary)
为了更好地阅读内核代码,以下是几个必须掌握的专业术语:
1. 原子上下文 (Atomic Context)
- 解释 :指一段必须连续执行、不可被分割、不可被挂起的代码区域。
- 包含 :
- 所有中断处理程序(硬中断、软中断)。
- 持有 自旋锁 (Spinlock) 的代码段。
- 规则 :在原子上下文中,禁止任何可能导致休眠或调度的操作。
2. 抢占 (Preemption)
- 解释:高优先级的任务强行打断低优先级的任务,夺取 CPU 使用权。
- 内核抢占 :Linux 是可抢占内核,意味着即便驱动程序在内核态跑着(进程上下文),如果有更高优先级的进程醒了,也可以打断它。但在中断上下文中,通常禁止抢占。
3. 用户空间与内核空间 (User Space vs Kernel Space)
- 用户空间:APP 运行的地方,权限受限,不能直接访问硬件寄存器。
- 内核空间:操作系统内核和驱动运行的地方,拥有最高权限。
- copy_to_user / copy_from_user :这是两界之间的桥梁。
- 注意 :这两个函数通过虚拟内存访问用户数据,可能会缺页异常(Page Fault)导致休眠。因此,这两个函数也不能在中断里用!
4. 忙等待 (Busy Wait)
- 解释:CPU 不去休息,而是通过死循环来消磨时间。
- 比喻:相当于你盯着微波炉倒计时(忙等待),而不是定个闹钟去睡觉(休眠)。
- 对应函数 :
ndelay,udelay,mdelay。