Linux中断

Linux中断:上半部 & 下半部
核心目的:上半部快进快出,避免长时间关中断、丢中断;耗时工作延后交给下半部
下半部不是必须,只有需要延后处理的时候才用。
优先级分层
- 最高:硬件中断(上半部)
外设触发,进入中断服务函数。
特点:
- 中断上下文,不能休眠
- 只做紧急活:清中断标志、接收简单数据、标记事件、调度下半部
- 上半部之间是平级,先到先得,高优先级硬件中断可以抢占低优先级
- 第二:下半部
两类:tasklet、workqueue
- tasklet(基于softirq软中断)
中断上下文,没有PCB,没有进程栈,禁止休眠 ;优先级高于workqueue。
适合短时、不阻塞的延后任务。
风险:中断频繁爆发时,tasklet一直抢占,会把普通进程饿死(典型:网络收包)。 - workqueue(工作队列)
进程上下文,依托内核线程,有PCB、独立进程栈,允许休眠、阻塞、等待 。
适合耗时、需要等待的任务;优先级低于tasklet。
-
第三:ksoftirqd 内核线程(兜底方案)
softirq触发太频繁(反复触发超过阈值),不能一直卡在中断上下文,内核交给
ksoftirqd内核线程去跑软中断,防止其他进程被饿死。 -
普通用户/内核进程
时间片轮转调度。
中断上下文 vs 进程上下文(面试背诵版)
| 对比项 | 中断上下文(上半部、softirq、tasklet) | 进程上下文(workqueue、ksoftirqd、普通进程) |
|---|---|---|
| 归属 | 不属于任何进程,没有task_struct(PCB) | 属于一个task,有独立PCB、进程描述符 |
| 栈 | 临时借用被打断进程的栈,没有专属栈 | 拥有自己独立进程栈 |
| 调度方式 | 不走调度器,中断返回前顺路执行;没有时间片 | 由调度器schedule()管理,时间片轮转、唤醒运行 |
| 能否休眠 | ❌ 禁止休眠、阻塞、等待锁 | ✅ 可以休眠、等待IO、等待互斥锁 |
| 抢占关系 | 优先级更高,可以抢占进程上下文任务 | 不能抢占中断上下文,来了中断就被打断 |
| 触发来源 | 硬件外设触发中断 | 内核主动唤醒、调度挑选 |
| 典型场景 | 清中断标志、简单数据搬运、标记软中断 | 读Flash、内存分配、等待信号、耗时业务 |
下文切换
一、中断上下文切换(硬件打断,不是进程切换!重点)
它是「打断正在跑的进程,临时进中断」,不会调用schedule()
流程:
- CPU正在运行某个进程(进程上下文)
- 外设发来中断信号 → CPU硬件自动动作:
- 保存当前现场(通用寄存器、程序计数器)到被打断进程的栈
- 跳转到中断向量表,进入内核中断入口
- 进入中断上下文(上半部 → 可能接着跑softirq/tasklet)
- 中断处理完毕,准备返回:
- 检查有没有pending软中断,有就执行
- 恢复之前保存的寄存器现场
- 回到刚才被打断的进程,继续跑
✅ 关键点:
没有换PCB,没有换栈,只是临时借栈跑一段代码,不是进程切换。
二、进程上下文切换(进程之间切换,调用schedule())
从一个task切到另一个task,是调度器主导
流程:
- 当前进程主动放弃CPU(休眠、时间片到、被抢占)
- 保存当前进程的全部现场(寄存器)到自己的PCB + 自己的进程栈
- 调度器挑选下一个就绪进程
- 拿到新进程的PCB,恢复它的寄存器、栈
- 跳转到新进程代码执行
✅ 关键点:更换PCB、更换独立进程栈
Linux内核时间把控 精简总结

源头:时钟中断,晶振产生周期性脉冲,是内核所有时间体系的基础。
- 忙等待(短延时,占用CPU)
udelay / mdelay:循环空转、消耗CPU,适合极短延时,CPU全程不释放。 - jiffies滴答计数
时钟中断触发计数,搭配time_before / time_after做时间对比判断,用来轮询等待超时。 - 内核定时器 timer_list
设置expires到期值,到期自动回调function函数,属于异步定时。 - 调度睡眠(长延时,释放CPU)
schedule():主动让出CPU,何时恢复由调度器决定,无固定超时schedule_timeout()/msleep():让出CPU,超时后自动唤醒schedule_timeout_interruptible():睡眠期间可以被信号打断
✅ 选型口诀:
短延时用 udelay/mdelay(忙等);
长等待优先睡眠调度,避免CPU空耗;
需要定时触发动作用timer_list;
需要轮询判断超时用jiffies比较。
中断禁用(屏蔽)
bash
local_irq_disable() //禁用本地中断的传递
//local_irq_enable 恢复本地中断的传递
local_irq_save(flags) //保存当前中断状态后,再禁用本地中断的传递 -> 比local_irq_disable更安全
//local_irq_restore(flags) 恢复本地中断的传递到指定状态
disable_irq(irq_num); //禁用指定中断
//enable_irq(irq_num) 使能指定中断
//注: 屏蔽中断的时间尽量短,否则会造成别的中断延时过久,甚至丢失,最好能采有屏蔽指定中断的方式