3、调度时机:进程状态切换、中断发生、系统调用返回

调度时机,说白了就是操作系统什么时候会去“看一眼”当前该不该换人。我刚开始学操作系统时,总觉得调度是随时随地在发生的——其实不是。调度是有触发条件的,而且这些条件非常明确。

我个人习惯把调度时机归纳为三大类:进程状态切换中断发生系统调用返回。这三类时机,覆盖了绝大多数操作系统的调度触发点。

3.1 进程状态切换

进程在运行过程中,状态不是一成不变的。它会从运行态变成就绪态,或者从运行态变成阻塞态。每一次状态变化,都是调度的好机会。

举个例子:一个进程正在跑,突然它要读磁盘数据。这时候它就会从“运行态”变成“阻塞态”。操作系统一看,嗯,你既然要等磁盘,那CPU不能闲着,赶紧把另一个就绪进程拉上来跑。

我在项目中遇到过一个问题:某个嵌入式系统里,进程频繁地在运行态和阻塞态之间切换,导致调度器被频繁触发,上下文切换开销巨大。后来我们做了个优化——把短时间的等待改成忙等待,减少了状态切换次数,性能反而上去了。

核心要点:进程状态切换是调度最自然的触发点。只要进程从运行态离开(无论去阻塞还是就绪),调度器就会介入。

3.2 中断发生

中断是硬件给操作系统发的“信号”。比如网卡收到数据包了,键盘被按下了,定时器到点了。这些信号一来,CPU会暂停当前工作,去处理中断。

中断处理完了之后呢?要不要回到原来的进程?不一定。这时候调度器会重新评估:当前这个进程还配不配继续跑?

我曾经踩过一个坑:一个实时系统里,定时器中断频率设得太高,每次中断处理完都要触发调度。结果调度开销占了CPU时间的30%以上。后来我把定时器中断频率降下来,改用累积计数的方式,才把性能拉回来。

这里要特别提一下时钟中断。它是抢占式调度的核心驱动力。没有时钟中断,抢占式调度就无从谈起。你想想看,如果一个进程死循环了,没有时钟中断来“打断”它,其他进程永远没机会跑。

我的建议:在设计系统时,时钟中断的频率要权衡。频率太高,调度开销大;频率太低,响应不及时。一般服务器系统用100-1000Hz,实时系统可能用到10000Hz以上。

3.3 系统调用返回

进程主动调用系统函数(比如read、write、sleep),这叫系统调用。系统调用执行完毕后,要返回到用户态。在返回之前,操作系统会问自己一个问题:要不要趁机换个进程跑?

为什么要在系统调用返回时调度?因为系统调用执行过程中,进程可能释放了资源,或者改变了优先级。比如一个进程调用了sleep(1),它主动让出了CPU,那返回时当然要看看有没有更合适的进程来跑。

我记得有一次调试一个网络服务器,发现某个进程总是占用CPU时间过长。后来定位到问题:这个进程频繁调用系统调用,但每次系统调用返回时,调度器都把它又选上了。原因很简单——它的优先级太高了。后来我们调整了优先级策略,才解决了这个问题。

注意:系统调用返回时的调度,是非抢占式调度的重要实现方式。在非抢占式系统里,进程只有主动通过系统调用让出CPU,其他进程才有机会运行。

3.4 三种时机的对比

调度时机 触发方式 典型场景 抢占式 非抢占式
进程状态切换 进程主动或被动改变状态 进程等待I/O、进程退出
中断发生 硬件信号 时钟中断、I/O中断
系统调用返回 进程主动调用 read、write、sleep

从表格里能看出来:中断发生是抢占式调度独有的。非抢占式调度只会在进程主动让出CPU时触发调度。这也是两种调度模式最本质的区别。

3.5 调度时机的核心逻辑图

下面这张图,我把三种调度时机和它们之间的关系画出来了。你看一眼就能明白整个流程。

调度时机核心逻辑图 进程运行中 进程状态切换 中断发生 系统调用返回 调度器决策 是否切换进程? 继续运行当前进程 切换其他进程 注:中断发生是抢占式调度独有的触发条件

这张图把整个调度流程串起来了。进程在运行中,遇到三种情况之一,就会触发调度器决策。调度器根据当前策略,决定是继续跑还是换人。

一个小技巧:调试调度问题时,先看触发时机对不对。很多时候调度异常,不是因为调度算法有问题,而是触发时机没把握好。我曾经排查过一个bug,折腾了两天,最后发现是时钟中断被意外屏蔽了——调度器根本没机会被触发。

总结一下:调度时机就这三种。进程状态切换是“被动触发”,中断是“硬件触发”,系统调用返回是“主动触发”。理解清楚这三种时机,你就能明白操作系统什么时候会“插手”进程调度了。


公众号:蓝海资料掘金营,微信deep3321