3、调度时机:进程状态切换、中断发生、系统调用返回
调度时机,说白了就是操作系统什么时候会去“看一眼”当前该不该换人。我刚开始学操作系统时,总觉得调度是随时随地在发生的——其实不是。调度是有触发条件的,而且这些条件非常明确。
我个人习惯把调度时机归纳为三大类:进程状态切换、中断发生、系统调用返回。这三类时机,覆盖了绝大多数操作系统的调度触发点。
3.1 进程状态切换
进程在运行过程中,状态不是一成不变的。它会从运行态变成就绪态,或者从运行态变成阻塞态。每一次状态变化,都是调度的好机会。
举个例子:一个进程正在跑,突然它要读磁盘数据。这时候它就会从“运行态”变成“阻塞态”。操作系统一看,嗯,你既然要等磁盘,那CPU不能闲着,赶紧把另一个就绪进程拉上来跑。
我在项目中遇到过一个问题:某个嵌入式系统里,进程频繁地在运行态和阻塞态之间切换,导致调度器被频繁触发,上下文切换开销巨大。后来我们做了个优化——把短时间的等待改成忙等待,减少了状态切换次数,性能反而上去了。
3.2 中断发生
中断是硬件给操作系统发的“信号”。比如网卡收到数据包了,键盘被按下了,定时器到点了。这些信号一来,CPU会暂停当前工作,去处理中断。
中断处理完了之后呢?要不要回到原来的进程?不一定。这时候调度器会重新评估:当前这个进程还配不配继续跑?
我曾经踩过一个坑:一个实时系统里,定时器中断频率设得太高,每次中断处理完都要触发调度。结果调度开销占了CPU时间的30%以上。后来我把定时器中断频率降下来,改用累积计数的方式,才把性能拉回来。
这里要特别提一下时钟中断。它是抢占式调度的核心驱动力。没有时钟中断,抢占式调度就无从谈起。你想想看,如果一个进程死循环了,没有时钟中断来“打断”它,其他进程永远没机会跑。
3.3 系统调用返回
进程主动调用系统函数(比如read、write、sleep),这叫系统调用。系统调用执行完毕后,要返回到用户态。在返回之前,操作系统会问自己一个问题:要不要趁机换个进程跑?
为什么要在系统调用返回时调度?因为系统调用执行过程中,进程可能释放了资源,或者改变了优先级。比如一个进程调用了sleep(1),它主动让出了CPU,那返回时当然要看看有没有更合适的进程来跑。
我记得有一次调试一个网络服务器,发现某个进程总是占用CPU时间过长。后来定位到问题:这个进程频繁调用系统调用,但每次系统调用返回时,调度器都把它又选上了。原因很简单——它的优先级太高了。后来我们调整了优先级策略,才解决了这个问题。
3.4 三种时机的对比
| 调度时机 | 触发方式 | 典型场景 | 抢占式 | 非抢占式 |
|---|---|---|---|---|
| 进程状态切换 | 进程主动或被动改变状态 | 进程等待I/O、进程退出 | ✔ | ✔ |
| 中断发生 | 硬件信号 | 时钟中断、I/O中断 | ✔ | ✘ |
| 系统调用返回 | 进程主动调用 | read、write、sleep | ✔ | ✔ |
从表格里能看出来:中断发生是抢占式调度独有的。非抢占式调度只会在进程主动让出CPU时触发调度。这也是两种调度模式最本质的区别。
3.5 调度时机的核心逻辑图
下面这张图,我把三种调度时机和它们之间的关系画出来了。你看一眼就能明白整个流程。
这张图把整个调度流程串起来了。进程在运行中,遇到三种情况之一,就会触发调度器决策。调度器根据当前策略,决定是继续跑还是换人。
总结一下:调度时机就这三种。进程状态切换是“被动触发”,中断是“硬件触发”,系统调用返回是“主动触发”。理解清楚这三种时机,你就能明白操作系统什么时候会“插手”进程调度了。
公众号:蓝海资料掘金营,微信deep3321