Linux 中的进程调度的基本概念。

即便是在多核处理器的计算机上,进程的数量通常也是远远多于处理器数量的。因此,宏观上并行运行的多个进程在微观上往往属于分时复用。进程调度的本质是怎么样让进程更好地分时复用处理器资源。概括地说,进程调度包括调度策略进程切换两个重要话题。

针对一个处理器来说,分时复用无非就是A进程的时间配额用完以后换到B进程的事情。在这个场景下,“如何选择B进程”就是调度策略,“如何运行B进程”就是进程切换。

在讨论进程调度策略之前有几个必须明确的重要概念:一个是时间片,一个是优先级,一个是抢占调度。另外,为了使用不同的调度策略满足不同的需求,此处还对进程进行了分类。

进程调度策略的几个概念

1. 时间片

时间片即TimeSlice,指的是分时复用过程中每个进程允许持续运行的最大时间配额单位。也就是说,如果A进程持续运行了一个TimeSlice,那么它必须考虑让出CPU资源给B进程。不过有两点值得注意:

1. 进程持续运行时间可以小于TimeSlice,比如当某个进程请求的资源得不到满足时,主动睡眠(因此上文描述中强调了最大);

2. 进程持续运行时间也可以大于TimeSlice,比如当某个进程时间片用完,考虑让出CPU时并没有别的可运行进程,那么这个进程会继续运行(因此上文描述中强调了单位)。在周期性计时模式中,一般时间片是一个节拍(一个tick,即1/HZ)的整数倍,在无节拍计时模式中,时间片的长度可以更加自由。

2. 优先级

优先级即Priority,指的是在所有进程中,谁更有资格优先获得处理器资源。一般来说,现代的进程调度器都是基于优先级的调度,也就是说都是倾向于先运行优先级高的进程,再运行优先级低的进程,同优先级之间轮转调度。不过凡事皆有例外,某些情况下确实会存在高优先级进程挂起而低优先级进程运行的情况,称为“优先级倒挂”

Linux内核中的优先级分静态优先级和动态优先级。静态优先级一般是进程创建时确定的,也可以通过特定的系统调用改变。动态优先级的初始值决定于静态优先级,但是随着进程的运行在不断地调整改变。调度策略选择进程时考虑的是动态优先级。

3. 抢占调度

抢占调度即Preemptible Scheduling,指的是一个高优先级进程是否可以强行夺取低优先级进程的处理器资源。如果可以强行夺取,就是可抢占的调度。但值得注意的是,抢占调度也并非一个简单的“是”与“否”的命题,而更多地表现为“某些时候可以抢占,某些时候不可以抢占”。因此,根据可抢占的程度,大致可以将抢占调度分为以下种类:

(1)不可抢占:完全不可抢占的调度器称之为协作式调度,A进程切换到B进程的唯一情况是A进程主动放弃。不可抢占常见于协程调度,协程是一种完全在用户态创建并管理的线程,没有内核参与。几乎不可能有完全不可抢占调度的现代操作系统内核,因为系统级的完全不可抢占是很危险的,一个简单的无限循环的应用程序就可以导致系统死机。

(2)用户态抢占:Linux-2.4以及更早的内核的调度器只允许用户态抢占。指的是当某个进程运行在用户态时,一个更高优先级的进程可以抢占该进程的时间片。然而,内核不可能随时随地检测是否有更高优先级的进程产生,而是在特定的时间点检测,这些时间点不妨称之为检查点(CheckPoint)。对于用户态抢占,其检查点是“异常、中断、或者系统调用处理完成后返回用户态的时候”。允许用户态抢占是现代操作系统内核设计的底线,Linux内核配置选项中的“不抢占”(CONFIG_PREEMPT_NONE)实际上指的是用户态抢占。

(3)内核态抢占:Linux-2.6以及更新的内核的调度器允许内核态抢占。指的是某个进程不管是运行在用户态还是内核态,一个更高优先级的进程都可以抢占该进程的时间片。同样,随时随地的检查是不可能的,允许内核态抢占无非是在用户态抢占的基础上,又增加了更多的抢占检查点而已。一般来说,内核态抢占的检查点是“异常、中断、或者系统调用处理完成后返回的时候”。请注意,异常中断或系统调用处理完成后,既可以返回到内核态,也可以返回到用户态,因此内核态抢占是用户态抢占的超集。

Linux的内核态抢占还可以细分成以下三种:

l 内核态自愿抢占:即内核配置选项中的CONFIG_PREEMPT_VOLUNTARY。采用白名单机制,在用户态基础上增加了一些特定的内核态检查点,通过显式调用might_resched()来决定要不要调度到更高优先级的进程。应该说这不算严格意义上的内核态抢占。

l 内核态完全抢占:即内核配置选项中的CONFIG_PREEMPT。采用黑名单机制,除非是在临界区里面,其他任意时刻都允许内核态抢占。临界区指的是关中断的区间(包括硬中断和软中断)以及用preempt_disable()和preempt_enable()所包含的区间。Linux内核态抢占通常指的是这一种。

l 内核态实时抢占:即内核配置选项中的CONFIG_PREEMPT_RT。这是最彻底的内核态抢占,取消了所有临界区,即便是在中断处理过程中也允许抢占。标准的Linux内核从5.3版本开始加入了这种模式初步支持,但到目前为止PREEMPT_RT尚不完整(需要更多的增强支持)。

内核抢占程度越高,对交互式应用的响应性越好;但它带来了更多的进程切换代价,对吞吐率有较大的影响。因此,一般桌面系统建议启用内核态抢占,而服务器系统建议关闭内核态抢占。

所有的调度器都围绕着这三个基本概念进行设计,调度器之间的区别,无非是时间片的长短定义不一样,优先级的计算以及围绕优先级对进程的组织不一样,以及允许抢占的程度不一样。

进程的分类

从调度器的角度来看,进程可以分成三大类:

1.交互式进程:此类进程有大量的人机交互,因此进程不断地处于睡眠状态,等待用户输入。典型的应用比如编辑器。此类进程对系统响应时间要求比较高,否则用户会感觉系统反应迟缓。

展开阅读全文

本文系作者在时代Java发表,未经许可,不得转载。

如有侵权,请联系nowjava@qq.com删除。

编辑于

关注时代Java

关注时代Java