第四章 进程调度
在前一章讲到内核是怎么管理进程的,进程的信息都是怎么在内存中保存和维护,这一章将会讨论这些进程是如何在操作系统的管理下有条不紊地运行的。
4.1 多任务
从操作系统的角度出发,多任务操作系统就是能同时并发地交互执行多个进程的操作系统。要明白的是,以单CPU系统为例,在某一特定时刻只会有一个进程真正被执行,都处于其他状态。
多任务系统弄个可以分为两类:非抢占式多任务(cooperative multitasking)和抢占式多任务(preemptive multitasking)。所有的类Unix系统都属于抢占式多任务,具体理解就是,系统的任务调度决定进程什么时间开始和结束,时间结束后系统会停止执行该进程并切换到别的进程。
4.2-4.4 Linux的进程调度算法
Linux的调度算法称为“完全公平调度算法”(CFS)
在讲述调度算法前,先要了解在算法中涉及到的一些概念和名词:
-
I/O消耗型和处理器消耗型进程
按字面意思理解,I/O消耗型指进程要消耗大部分时间在等待IO上,处理器消耗型指进程需要处理器进行大量运算。当然这两种类型的进程并不是绝对非黑即白的,也有进程同时是这两种类型。但是系统在处理这两类型的进程时,就需要取舍,如果偏重于IO进程的响应,那么处理器就不能高效运行,反之亦然。 -
进程优先级
Linux中有两种优先级的度量方式:
- nice值,[-20,19],默认为0。nice可以理解为对其他进程的友好程度,nice值越小优先级越高。
- 实时优先级,[0,99],数值越大优先级越高
- 时间片
时间片是一个数值,表示进程在被抢占前能持续运行的时间。时间片的大小对调度器的效率起到至关重要的作用。因为进程切换是需要时间的,时间片短进程就会来回切换,IO响应快但处理效率不高,反过来也是一样的。
CFS的原理
CFS的最基本概念就是,在给定的一段时间里,有n个进程,那么最理想的状态就是这段时间中每个进程获得处理请执行的时间是相等的。
但实际上,进程间的切换是有消耗的,因此CFS在这基础上做了一些调整:
- CFS允许每个进程运行一段时间、循环轮转,并选择运行“时间片”最少的进程作为下一个运行的进程。
- 上述提到每个进程运行一段时间,这段时间指的就是“时间片”,而进程的nice值不一样,它对应的时间片长短也会不一样。假设nice值为0的进程,时间片为100ms,那么可以得到其他nice值对应进程的时间片长短。
- 当然上述时间片的长短只是一个例子,处理器不会让程序连续运行如此“长”的时间。时间片的长短总是系统定时器的整数倍(具体介绍定时器在后面),而且会根据不同的情况灵活调整时间片长短。
- 为了避免过多的消耗在频繁的进程切换中,CFS保证每个进程获得的时间片最少为真实时间中的1ms。
CFS的实现
在Linux中,调度器是以模块实现的,这样我们就可以根据不同的业务选择不同的调度算法。Linux 5.13的代码中,把调度器相关的代码都放在kernel/sched文件夹中,CFS的调度算法实现在fair.c中。
但在讲CFS前,我觉得很有必要先了解一下Linux调度子系统的实现,包括数据结构,初始化和执行,毕竟CFS不常有而调度器常有。
调度组(struct task_group)
因为Linux是一个多用户系统,假设有两个用户,但这某个用户拥有的进程一个是高优先级,而另一个用户拥有的进程是低优先级的,这样就会导致拥有低优先级进程的用户得到很少的CPU时间,这样对用户来讲是不公平的。因此Linux提出一个概念——调度组,如果以用户来分组,那么这两个用户组得到的CPU时间应该是一样的。
linux可以以以下两种方式进行进程的分组:
- 用户ID
按照进程的USER ID进行分组,在对应的/sys/kernel/uid/目录下会生成一个cpu.share的文件,可以通过配置该文件来配置用户所占CPU时间比例。 - cgourp(control group)
生成组用于限制其所有进程,比如我生成一个组(生成后此组为空,里面没有进程),设置其CPU使用率为10%,并把一个进程丢进这个组中,那么这个进程最多只能使用CPU的10%,如果我们将多个进程丢进这