从零写 OS 内核-第十七篇:调度器 —— 让多任务真正“并发”起来!
“协作式多任务依赖程序主动让出 CPU,一旦死循环,系统就卡死。 今天,我们引入时钟中断,实现抢占式调度器,让多任务真正公平运行!”
在第六篇中,我们实现了 协作式多任务 ,进程通过 yield() 主动切换。 但这有个致命缺陷: 一个死循环的用户程序会让整个系统无响应 !
真正的操作系统必须能 强制剥夺 CPU ,在多个任务间公平分配时间—— 这就是 抢占式调度(Preemptive Scheduling) 。
今天,我们就来: ✅ 启用时钟中断(PIT) ✅ 实现时间片轮转(Round-Robin)调度器 ✅ 支持进程优先级与睡眠队列
让你的 OS 拥有 真正的并发能力 !
⏱️ 一、为什么需要抢占式调度?
协作式调度的痛点:
| 问题 | 后果 |
|---|---|
| 用户程序死循环 | 系统完全卡死,无法响应 |
| I/O 密集型任务 | 主动让出频繁,CPU 利用率低 |
| 实时性差 | 无法保证关键任务及时执行 |
抢占式调度的优势:
公平性
:每个进程获得固定时间片(如 10ms)
响应性
:即使有死循环,系统仍可响应
可控性
:通过优先级控制任务执行顺序
💡 调度器是操作系统的“交通警察”,决定谁在何时使用 CPU 。
🕰️ 二、时钟中断:调度器的心跳
x86 PC 通过 PIT(Programmable Interval Timer) 产生周期性中断。
PIT 初始化:
注册时钟中断处理程序:
🔑 每次时钟中断,都是调度器检查是否需要切换任务的机会 !
🔄 三、时间片轮转调度器
- 进程 PCB 增加调度字段
-
调度器核心:scheduler_tick
-
任务切换:schedule
✅ 时间片用完 → 当前进程排队 → 切换到下一个进程
🛌 四、睡眠与唤醒:支持阻塞操作
进程可能因 I/O(如读键盘)而 主动睡眠 ,此时不应占用 CPU。
- 睡眠队列
-
睡眠(sleep_on)
-
唤醒(wake_up)
-
系统调用集成:read 阻塞示例
💡 中断处理程序中调用 wake_up (如串口收到数据时)
🧪 五、测试:抢占式多任务
用户程序 1(CPU 密集型):
用户程序 2(I/O 密集型):
运行效果:
即使
cpu_hog
死循环,系统仍能响应键盘输入
两个进程交替运行,各占约 50% CPU
✅ 抢占式调度成功!
📊 六、调度策略扩展
- 优先级调度
-
动态优先级
I/O 密集型进程 → 提升优先级(提高响应性)
CPU 密集型进程 → 降低优先级(避免霸占 CPU) -
多级反馈队列(MLFQ)
多个优先级队列
时间片用完 → 降级到低优先级队列
I/O 阻塞后唤醒 → 升级到高优先级队列
💡 Linux 的 CFS(完全公平调度器)是更高级的实现 。
⚠️ 七、关键注意事项
- 中断上下文 vs 进程上下文
调度器
不能在中断处理程序中直接调用
switch_to_task
正确做法:设置
need_resched
,在中断返回前检查 -
临界区保护
访问运行队列/睡眠队列时,需
关闭中断
(
cli
/
sti
) -
空闲进程(idle task)
当无任务可运行时,执行
hlt
指令节省功耗 -
调度延迟
时间片太短 → 调度开销大
时间片太长 → 响应性差
通常 1~10ms 为宜
💬 写在最后
调度器是操作系统的 心脏 , 它决定了任务如何共享 CPU 这一稀缺资源。 从协作式到抢占式, 不仅是技术的演进,更是 设计理念的跃迁 。
今天你实现的 scheduler_tick , 正是 Linux 中 scheduler_tick 和 __schedule 的雏形。
🌟 公平、高效、响应迅速——这是调度器永恒的追求。
📬 动手挑战 : 实现一个简单的优先级系统,让 I/O 密集型进程获得更高优先级。 欢迎在评论区分享你的调度策略!
👇 下一篇你想看: 多处理器(SMP)支持 ,还是 进程间通信(管道/Pipe) ?
#操作系统 #内核开发 #调度器 #抢占式调度 #时钟中断 #PIT #多任务 #从零开始
评论