从零写 OS 内核-第十七篇:调度器 —— 让多任务真正“并发”起来!

“协作式多任务依赖程序主动让出 CPU,一旦死循环,系统就卡死。 今天,我们引入时钟中断,实现抢占式调度器,让多任务真正公平运行!”

在第六篇中,我们实现了 协作式多任务 ,进程通过 yield() 主动切换。 但这有个致命缺陷: 一个死循环的用户程序会让整个系统无响应 !

真正的操作系统必须能 强制剥夺 CPU ,在多个任务间公平分配时间—— 这就是 抢占式调度(Preemptive Scheduling) 。

今天,我们就来: ✅ 启用时钟中断(PIT) ✅ 实现时间片轮转(Round-Robin)调度器 ✅ 支持进程优先级与睡眠队列

让你的 OS 拥有 真正的并发能力 !


⏱️ 一、为什么需要抢占式调度?

协作式调度的痛点:

问题 后果
用户程序死循环 系统完全卡死,无法响应
I/O 密集型任务 主动让出频繁,CPU 利用率低
实时性差 无法保证关键任务及时执行

抢占式调度的优势:

公平性

:每个进程获得固定时间片(如 10ms)
响应性

:即使有死循环,系统仍可响应
可控性

:通过优先级控制任务执行顺序

💡 调度器是操作系统的“交通警察”,决定谁在何时使用 CPU 。


🕰️ 二、时钟中断:调度器的心跳

x86 PC 通过 PIT(Programmable Interval Timer) 产生周期性中断。

PIT 初始化:

注册时钟中断处理程序:

🔑 每次时钟中断,都是调度器检查是否需要切换任务的机会 !


🔄 三、时间片轮转调度器

  1. 进程 PCB 增加调度字段

  2. 调度器核心:scheduler_tick

  3. 任务切换:schedule

✅ 时间片用完 → 当前进程排队 → 切换到下一个进程


🛌 四、睡眠与唤醒:支持阻塞操作

进程可能因 I/O(如读键盘)而 主动睡眠 ,此时不应占用 CPU。

  1. 睡眠队列

  2. 睡眠(sleep_on)

  3. 唤醒(wake_up)

  4. 系统调用集成:read 阻塞示例

💡 中断处理程序中调用 wake_up (如串口收到数据时)


🧪 五、测试:抢占式多任务

用户程序 1(CPU 密集型):

用户程序 2(I/O 密集型):

运行效果:

即使
cpu_hog
死循环,系统仍能响应键盘输入
两个进程交替运行,各占约 50% CPU

✅ 抢占式调度成功!


📊 六、调度策略扩展

  1. 优先级调度

  2. 动态优先级

    I/O 密集型进程 → 提升优先级(提高响应性)
    CPU 密集型进程 → 降低优先级(避免霸占 CPU)

  3. 多级反馈队列(MLFQ)

    多个优先级队列
    时间片用完 → 降级到低优先级队列
    I/O 阻塞后唤醒 → 升级到高优先级队列

💡 Linux 的 CFS(完全公平调度器)是更高级的实现 。


⚠️ 七、关键注意事项

  1. 中断上下文 vs 进程上下文

    调度器
    不能在中断处理程序中直接调用
    switch_to_task
    正确做法:设置
    need_resched
    ,在中断返回前检查

  2. 临界区保护

    访问运行队列/睡眠队列时,需
    关闭中断

    cli
    /
    sti

  3. 空闲进程(idle task)

    当无任务可运行时,执行
    hlt
    指令节省功耗

  4. 调度延迟

    时间片太短 → 调度开销大
    时间片太长 → 响应性差
    通常 1~10ms 为宜


💬 写在最后

调度器是操作系统的 心脏 , 它决定了任务如何共享 CPU 这一稀缺资源。 从协作式到抢占式, 不仅是技术的演进,更是 设计理念的跃迁 。

今天你实现的 scheduler_tick , 正是 Linux 中 scheduler_tick 和 __schedule 的雏形。

🌟 公平、高效、响应迅速——这是调度器永恒的追求。


📬 动手挑战 : 实现一个简单的优先级系统,让 I/O 密集型进程获得更高优先级。 欢迎在评论区分享你的调度策略!

👇 下一篇你想看: 多处理器(SMP)支持 ,还是 进程间通信(管道/Pipe) ?


#操作系统 #内核开发 #调度器 #抢占式调度 #时钟中断 #PIT #多任务 #从零开始

最后修改: 2026年7月1日

作者

评论

发表评论

您的邮箱地址不会被公开。