Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >linux 进程调度器(下) -- 调度器演进

linux 进程调度器(下) -- 调度器演进

作者头像
用户3147702
发布于 2022-09-23 00:29:05
发布于 2022-09-23 00:29:05
2.3K00
代码可运行
举报
运行总次数:0
代码可运行

1. 引言

通过此前的两篇文章,我们系统介绍了 linux 操作系统中的调度算法与其演进:

linux 操作系统的进程调度(上) -- 进程调度的基本概念

linux 操作系统的进程调度(中) -- 进程调度算法的演进

本文,我们就来介绍 Linux 操作系统实际使用的进程调度器以及它们的演进。

2. O(n) 调度器

在早期的 linux 操作系统中,2.4 版本到 2.6 版本之间,linux 采用了实现起来十分简单的 O(n) 调度器。

O(n) 调度器只有一个全局的任务队列,即使有多个 CPU,它们也同样共享这个全局的队列。每当有进程就绪,就会被添加到队列,一旦进程运行结束,操作系统就会从队列中删除它。

由于每个进程都拥有自己的优先级,所以每当一个 CPU 执行完一个时间片或空闲时,它只需要遍历整个任务队列,找到优先级最高的一个并执行即可,由于这一遍历过程的时间复杂度为 O(n),所以这个算法实现的调度器就被命名为 O(n) 调度器。

O(n) 调度器将进程标记为两种基本的类型:

  1. 实时进程;
  2. 非实时进程。

2.1 实时进程的调度

对于实时进程来说,进程必须以高于实时进程的优先级被调用,并且用户不能用 nice 值对这一优先级进行修改。

为了保证实时进程能够得到最高的优先级,操作系统的实现中固定地使用 1000 + 进程的实际优先级来实现对优先级数值的提升,这是一个简单有效的方案。

2.2 非实时进程的调度

对于绝大多数用户进程来说,它们都是非实时进程,因此,非实时进程的调度是操作系统中最为普遍的。

正如上一篇文章中介绍的,调度器会按照进程的类型,即它是 CPU 密集型还是 IO 密集型来为进程分配优先级,同时,为了应对进程类型的动态变化以及防止进程为了提升优先级而进行的作弊,操作系统会周期性地重置所有任务的优先级到最高,然后再进行动态调整。

在此基础上,用户还可以为进程分配 nice 值来对优先级进行一定程度上的调整。

这里提到了“周期”,那么这个周期以及动态调整的优先级是怎么实现的呢?它们的答案是同一个东西 -- 时间片。每当一个尚未分配时间片的进程出现在队列中时,调度器都会为这个进程分配固定数量的时间片。而在执行过程中,剩余时间片越多,说明进程 IO 越多,也就说明这个进程是一个 IO 密集型进程,它的调度优先级也就相应的越高。当进程的时间片使用完毕,重复这一过程,意即开启下一周期即可。

而实际上,非实时进程的调度优先级为:

时间片剩余数量 + 20 - nice

2.3 O(n) 调度器的缺点

显而易见,上述调度算法存在以下问题:

  1. 随着时间片的切换,进程会在不同的 CPU 上执行,因此对 CPU 的缓存利用率很低;
  2. 由于多个 CPU 共享全局队列,因此,当队列中的进程进行增、删、更新时,需要加锁,这显然会对整体运行效率造成较大的影响;
  3. 在队列中,进程无序存放,即使是实时进程也同样混合其中,因此每次寻找下一个执行的进程都需要遍历整个队列,性能较低。

3. O(1) 调度器

在 linux 内核采用 O(n) 调度器的 4 年后,Linux2.6.0 采纳了 Rad Hat 公司设计的 O(1) 调度算法,这是一个基于上一篇文章中介绍的多级反馈队列算法的调度器实现。

3.1 O(1) 调度器的实现

首先,O(1) 调度器最明显的改进是为每个 CPU 都实现了一套队列,并且实现了一套负载均衡算法,每当新的任务到来时,这个负载均衡器会动态决定将进程分配到哪个 CPU 的队列中。

针对每个 CPU,都有两组链表组成两个 hash 表,分别是 active RunQueue 和 expire RunQueue。而每个哈希表中,都通过拉链法维护了 140 个链表,每个槽代表一个优先级,每个链表中的所有任务优先级都相同,因此,调度器可以以 O(1) 时间时间复杂度获取到优先级最高的进程,而为了进一步提升这一过程的执行效率,调度器还通过一个 bitmap 来存储 active 队列各个优先级是否存在任务。

为什么哈希表要拥有 140 个槽呢?因为他们对应了 0~139 这 140 个进程优先级。在 O(1) 调度器中,进程优先级数字越低,实际优先级越高,而 0~99 为实时进程优先级,100~139 为非实时进程优先级。

3.2 O(1) 调度器的执行

当 active 队列中某一个进程完成执行,它就会被移动到队列尾部;当队列全部任务都执行过指定时间片以后,bitmap 该优先级对应的位就会被置为 0,当整个 bitmap 全部被置 0 后,调度器指向 active 队列和 expired 队列的指针就会交换,并且重新对已执行过的进程进行优先级重估,并且添加到全新的 active 队列中,开启新的一轮执行。

3.3 O(1) 执行器的缺点

当然了,O(1) 执行器也存在一些缺点:

  1. IO 密集型任务的识别准确率欠佳,尤其是与 O(n) 简单粗暴的实现方式相比,并且随着时间的推移,这类问题暴露的也越来越多,直到到了积重难返的地步;
  2. avtive 队列和 expire 队列交换的过程虽然简单快捷,但重新评估优先级仍然存在一定的耗时。

4. CFS 调度器

由于 O(1) 调度器的上述问题,很快在 Linux 2.6.23 版本,就有一款新的调度器 -- CFS 被内核采用,它是由匈牙利程序员 Ingo Molnar 在澳大利亚外科医生 Con Kolivas 提出的楼梯调度算法基础上改进实现的。

这其中还有一个有趣的轶事,作为外科医生的 Con Kolivas 在完成他的楼梯调度算法的设计后,开发实现了一款名为 RSDL 的调度器,意即公平策略调度器(The Rotating Staircase Deadline Schedule),然而,这个调度器被 Linus 无情地拒绝了,这让 Con Kolivas 十分不满,他愤而退出了 Linux 内核开发社区,并且在此后开发了 BFS,意即“脑残调度器”(Brain Fuck Scheduler)来发泄自己的不满。

4.1 调度器分层思想

而事实证明,在公平策略调度器基础上改进设计的 CFS 确实是一款优秀的调度器,它的思想是将调度器进行模块化,从而让操作系统中可以有多种调度器以不同的策略和优先级来执行。

这样一来,CFS 再也不用去关心实时进程了,它只需要专注于普通进程即可,这也就是“让最适合的调度器,去做最适合的事”。

操作系统中,调度器由此分为四层:

  1. DL 调度器:采用 sched_deadline 策略;
  2. RT 调度器:采用 sched_rr 和 sched_fifo 策略;
  3. CFS 调度器:采用 sched_normal 和 sched_batch 策略;
  4. IDEL 调度器:采用 sched_idle 策略。

4.2 CFS 调度器的实现

CFS 调度器的思想是“完全公平”,可是显然,不同优先级的进程实际执行的物理时间是不同的,那么,怎么算是公平的呢?

CFS 调度器提出了“虚拟运行时间”的概念:

虚拟运行时间 = 物理运行时间 * nice 值对应的权重 / 优先级对应的权重

这里有一个“权重”的概念,在 CFS 调度器中,维护了一个与普通进程优先级 100~139 一一对应的 40 个权重组成的列表:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const int sched_prio_to_weight[40] = {
	88761,     71755,     56483,     46273,     36291,
	29154,     23254,     18705,     14949,     11916,
	 9548,      7620,      6100,      4904,      3906,
	 3121,      2501,      1991,      1586,      1277,
	 1024,       820,       655,       526,       423,
	  335,       272,       215,       172,       137,
	  110,        87,        70,        56,        45,
	   36,        29,        23,        18,        15,
};

在保证公平,即所有进程虚拟运行时间相同的前提下,通过上述计算,优先级对应的权重值越大,实际的物理运行时间也就越长。

这个算法让物理运行时间在不同优先级的进程中发生不同程度的膨胀,从而实现了虚拟运行时间上的完全公平,这样一来,在系统负载高时,任务可以仍然在保证公平的前提下对其物理执行时间进行伸缩,这是 O(1) 调度器和 O(n) 调度器这类通过分配固定时间片的调度器所不能实现的。

4.3 CFS 调度器的 pick-next 算法

CFS 调度器的执行过程与其选取下一个将要执行的任务的 pick-next 算法所依赖的数据结构息息相关,那就是 -- 红黑树。

红黑树拥有很强的自适应性,我们知道,有序的二叉树都有一个致命的弱点,那就是增、删、更新操作时,需要进行 rebalance,这是一个十分耗时的操作,例如在 AVL 树中,删除节点时,整个树结构的旋转次数都是 O(logN) 量级的,而红黑树则在最坏情况下只需要进行三次旋转,而增加节点时,红黑树则只需要至多进行两次旋转。

同时,红黑树虽然并不保证平衡,但它保证了有序,在 CFS 调度器中,pick-next 的红黑树中,key 是任务已经执行过的虚拟运行时间,这样一来,在公平原则的前提下,调度器只需要每次都选取最左子树的左叶子结点进行执行,也就是每次都去执行已经运行虚拟运行时间最少的进程,这当然就是最公平的。

5. 后记

本文介绍了 linux 操作系统中的调度器和调度算法的演进,这当然是非常大略的介绍,有兴趣还是建议去阅读相关的内核源码,这里包括对操作系统调度器实际使用的辅助性的数据结构的缺省,都是为了提高文章可读性的需要,实际上,在操作系统中仍然有大量的细节,和许多调度器实际遇到的问题的解决值得我们深入地进一步研究

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-07-02,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 小脑斧科技博客 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
写给吃瓜群众的 Linux 进程调度剖析
在上一篇文章中介绍了 Linux 内核是如何对进程进行管理的,这篇将阐述内核是如何对进程进行调度。因为这篇文章努力用简单的语言把进程调度这件事情描述清楚,所以文章篇幅略长,建议收藏慢看。也欢迎关注公众号 CS 实验室 ,目前在写一些开发中常用但不常了解细节的东西,比如 Linux 内核、Python 进阶。
CS实验室
2021/03/22
5930
写给吃瓜群众的 Linux 进程调度剖析
调度器及CFS调度器
调度:就是按照某种调度的算法设计,从进程的就绪队列中选择进程分配CPU,主要是协调进程对CPU等相关资源的使用。
laputa
2022/11/21
1.2K0
Linux进程调度学习!
进程调度决定了将哪个进程进行执行,以及执行的时间。操作系统进行合理的进程调度,使得资源得到最大化的利用。
用户6280468
2022/04/18
2K0
Linux进程调度学习!
Linux进程调度策略的发展和演变--Linux进程的管理与调度(十六)
调度器面对的情形就是这样, 其任务是在程序之间共享CPU时间, 创造并行执行的错觉, 该任务分为两个不同的部分, 其中一个涉及调度策略, 另外一个涉及上下文切换.
233333
2018/11/20
2.3K0
Linux内核调度分析(进程调度)
本文是《Linux内核设计与实现》第四章的阅读笔记,代码则是摘自最新的4.6版本linux源码(github),转载请注明出处。
Marky Lumin
2018/01/23
15.2K0
聊聊Linux内核进程调度下篇
进程优先级 Linux内核中进程优先级一般分为动态优先级和静态优先级,动态优先级是内核根据进程的nice值、IO密集行为或者计算密集行为以及等待时间等因素,设置给普通的进程;静态优先级是用户态应用设置给实时进程。在调度中静态优先级的进程优先级更高。 一般应用分为IO密集型和计算密集型;I/O密集型是进程执行I/O操作时候等待资源或者事件时候,数据读取到后恢复进程的运行,这样基本出于等待IO和运行之间进行交替,由于具有这样的特性,进程调度器通常会将短的CPU时间片分配给I/O密集型进程。计算密集型是进
用户4700054
2022/08/17
1.3K0
聊聊Linux内核进程调度下篇
Linux进程调度之 - O(1)调度算法
Linux是一个支持多任务的操作系统,而多个任务之间的切换是通过 调度器 来完成,调度器 使用不同的调度算法会有不同的效果。
用户7686797
2020/08/24
5.1K0
Linux进程调度之 - O(1)调度算法
Linux进程调度器的设计--Linux进程的管理与调度(十七)
调度器面对的情形就是这样, 其任务是在程序之间共享CPU时间, 创造并行执行的错觉, 该任务分为两个不同的部分, 其中一个涉及调度策略, 另外一个涉及上下文切换.
233333
2018/12/04
3.7K0
Linux进程调度器的设计--Linux进程的管理与调度(十七)
Linux Kernel调度器的过去,现在和未来
Linux Kernel Development 一书中,关于 Linux 的进程调度器并没有讲解的很全面,只是提到了 CFS 调度器的基本思想和一些实现细节;并没有 Linux 早期的调度器介绍,以及最近这些年新增的在内核源码树外维护的调度器思想。所以在经过一番搜寻后,看到了这篇论文 A complete guide to Linux process scheduling,对 Linux 的调度器历史进行了回顾,并且相对细致地讲解了 CFS 调度器。整体来说,虽然比较啰嗦,但是对于想要知道更多细节的我来说非常适合,所以就有了翻译它的冲动。当然,在学习过程也参考了其它论文。下面开启学习之旅吧,如有任何问题,欢迎指正~
刘盼
2020/04/20
2.7K0
Linux Kernel调度器的过去,现在和未来
Linux进程调度器概述--Linux进程的管理与调度(十五)
调度器面对的情形就是这样, 其任务是在程序之间共享CPU时间, 创造并行执行的错觉, 该任务分为两个不同的部分, 其中一个涉及调度策略, 另外一个涉及上下文切换.
233333
2018/10/09
3.7K0
Linux进程调度器概述--Linux进程的管理与调度(十五)
【操作系统 OS】什么是Linux CFS?完全公平调度器是什么?
Linux 中 CFS 的全称是 Completely Fair Scheduler,完全公平调度器,是 Linux 内核中的一种进程调度算法。
Lokinli
2024/07/31
6650
【Linux进程】初悉进程
在Linux中,进程是最基本的执行单位。进程调度在整个操作系统中属于核心地位,是操作系统实现多任务处理的关键操作,确保每个进程在有限的CPU资源下有序的完成相应操作。
小文要打代码
2025/01/09
3870
【Linux进程】初悉进程
linux进程调度
进程可以分为实时进程和普通进程,对于这两种不同类型的进程肯定有不同的调度策略,task_struct中的policy就用来表示调度策略。
你的益达
2020/08/12
8.2K0
linux进程调度
吐血整理 | 肝翻 Linux 进程调度所有知识点
前面我们重点分析了如何通过 fork, vfork, pthread_create 去创建一个进程或者线程,以及后面说了它们共同调用 do_fork 的实现。现在已经知道一个进程是如何创建的,但是进程何时被执行,需要调度器来选择。所以这一节我们介绍下进程调度和进程切换的详情。
刘盼
2021/12/13
2.1K0
吐血整理 | 肝翻 Linux 进程调度所有知识点
进程调度算法
在多道程序系统中,进程的数量往往多于处理机的个数,因此进程争用处理机的情况在所难免。处理机调度是对处理机进行分配,即从就绪队列中按照一定的算法(公平、高效)选择一个进程并将处理机分配给它运行,以实现进程并发执行。
薄荷冰
2024/11/14
2570
进程调度算法
进程调度:我太难了!
为了实现切换,我们提供一个API,这两个程序执行一会儿就主动调用一下这个API,然后在这个API内部实现任务的切换。
轩辕之风
2022/05/17
3880
进程调度:我太难了!
Linux 完全公平调度算法
之前我写过一篇分析 O(1)调度算法 的文章:O(1)调度算法,而这篇主要分析 Linux 现在所使用的 完全公平调度算法。
用户7686797
2021/02/24
1.4K0
linux 操作系统的进程调度(上) -- 进程调度算法的演进
上一篇文章中,我们介绍了内核调度的基本概念,知道了调度器设计中最核心的两个指标 -- 周转时间与响应时间:
用户3147702
2022/06/27
1.9K0
linux 操作系统的进程调度(上) -- 进程调度算法的演进
【Linux系统内核探索】进程调度
进程调度是操作系统内核的核心功能之一,负责在多个进程之间分配CPU时间,使得系统能够同时运行多个进程。因为计算机的CPU资源有限,操作系统需要决定在任何时刻哪个进程能够使用CPU执行任务,这个过程就是进程调度。 Linux进程调度经历了多个阶段的优化,目前主流的Linux内核使用的是完全公平调度器。CFS调度器的核心思想是通过精确计算每个进程的“虚拟运行时间”来决定调度的公平性。CFS调度器不会简单依赖于时间片,而是通过调度树来快速查找下一个应运行的进程。 现代的Linux调度主要依赖于Linux的CFS调度器,在2.6版本之前主要用的是Linux内核O(1)调度算法,这次我们的重点在于Linux内核O(1)调度算法。
用户11305458
2024/11/21
1710
【Linux系统内核探索】进程调度
常用进程调度算法_进程调度算法例题
所谓进程调度方式,是指当某个进程正在处理机上执行时,若有某个更为重要或紧迫的进程需要处理,即有优先权更高的进程进入就绪队列,此时应如何分配处理机。通常有以下两种进程调度方式:
全栈程序员站长
2022/11/10
1.5K0
常用进程调度算法_进程调度算法例题
相关推荐
写给吃瓜群众的 Linux 进程调度剖析
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验