O(1)调度器:Linux2.6版本的核心算法

news2024/9/22 4:18:37

上一章学习了O(n)调度器的设计,以及它的核心算法,其主要思路如下:

  • O(n)调度器采用一个Runqueue运行队列来管理所有可运行的进程,在主调度schedule函数中选择一个优先级最高,也就是时间片最大的进程来运行,同时也会对喜欢睡眠的进程做奖励,去增加此类进程的时间片
  • 当Runqueue运行队列中无进程可选择时,则会对系统中所有的进程进行依次重新计算时间片的操作

针对这个调度,虽然简单,但是也存在缺点:

  • 时间复杂度为O(n),当系统中就绪队列中的进程数目增多,那么调度器的运算量就会线性增长,为每个进程计算其时间片的过程太耗费时间
  • 多核处理器扩展问题,多核处理器的进程在同一个就绪队列中,因此调度器对它的所有操作都会因为全局自旋锁而导致系统各个处理器之间的等待,使的就绪队列称为明显的瓶颈
  • 实时进程不能及时调度,内核不可抢占,如果某个进程,一旦进入内核状态,那么再高优先级的进程都无法剥夺,只有等进程返回用户态的时候才可以被调度。

针对以上问题,Linux2.6做了较大的改进,针对多处理器问题,为每个CPU设置一个就绪队列,实现了时间复杂度为O(1)的调度算法,本章重点学习以下内容:

  • O(1)调度器是什么?
  • O(1)调度算法如何实现?

O(1)调度算法介绍

Ingo Molnar在linux2.6版本的内核中加入了全新的调度算法,它能够在常数时间内调度任务,因此被称为O(1)调度器,它主要引入了一些新的特性:

  • 全局优先级,范围为0~139,数值越低,优先级越高
  • 将进程拆分成实时进程(0 ~ 99)和普通进程(100 ~ 139),更高优先级任务获得更多的时间片
  • 支持抢占,当任务状态变成TASK_RUNNING时,内核会检查其优先级是否比当前任务的优先级更高,如果是的话,则抢占当前正在运行的任务,切换到该任务
  • 实时进程使用静态优先级
  • 普通进程使用动态优先级,任务优先级会在其使用完自己的时间片后重新计算,内核会考虑它过去的行为,决定它的交互等级,交互型任务更容易得到调度

对于O(n)调度器会在所有进程的时间片用完后,才会重新计算任务的优先级。而O(1)调度器则是在每个进程时间片用完后,就重新计算优先级。对于O(1)调度器为每个CPU维护了两个队列

  • active队列:存放的是时间片尚未用完的任务
  • expired队列:**存放的是时间片已经耗尽的任务

当一个队列的时间片用完后,就会被转到expired队列,而且会重新计算它的优先级,当active队列任务全部转移到expired队列后,会交换二者,使得active队列指向expired队列,expired队列指向active队列。可以看到,优先级的计算,队列切换都和任务数量多寡无关,能够在O(1)的时间复杂度下完成。其基本的思路如下图所示:

  1. active中的任务时间片用完,那么就会被移动到expired中。
  2. active中已经没有任务可以运行,就把expiredactive交换,从而expired中的任务可以重新被调度。

O(1)调度算法数据结构

为了减小多核CPU之间的竞争,所以每个CPU都需要维护一份本地的优先级队列,因为如果使用全局的优先级,那么多核CPU就需要对全局优先队列进行上锁,从而导致性能下降。runqueue结构主要维护调度相关的信息,其定义如下:

接下来,我们看看prio_array_t

  • nr_active:所有优先级队列中的数任务数
  • bitmap:位图,每个位对应一个优先级的任务队列,用于记录哪个任务队列不为空,能通过Bitmap快速找到不为空的任务队列
  • queue:优先级队列数组,每个元素维护一个优先级队列,比如索引为0的元素维护着优先级为0的任务队列

对于以上的,每个优先级是一个链表,同时还维护了一个由101 bit组成的Bitmap,其中实时进程的优先级是0~00,占100bit,当某个优先级上有进程被插入链表时,响应的比特位就被置位。在进度算法中通常用sched_find_first_bit来查询该bitmap,它返回当前被置位的最高优先级的数组下表,由于使用位图,查找一个任务来执行所需的时间并不依赖于任务的个数,而是依赖于优先级的数量,所以该调度器是一个O(1)调度器

资料直通车:最新Linux内核源码资料文档+视频资料

内核学习地址:Linux内核源码/内存调优/文件系统/进程管理/设备驱动/网络协议栈

bitmap 的第2位和第6位为1(红色代表为1,白色代表为0),表示优先级为2和6的任务队列不为空,也就是说 queue 数组的第2个元素和第6个元素的队列不为空。

实时进程和普通进程

O(1)调度算法 把140个优先级的前100个(0 ~ 99)作为 实时进程优先级,而后40个(100 ~ 139)作为 普通进程优先级。实时进程被放置到实时进程优先级的队列中,而普通进程放置到普通进程优先级的队列中。

实时进程

实时进程分为FIFO(先进先出)和RR(时间片轮转)两种算法,其调度算法比较简单,如下:

  • 先进先出实时进程调度:如果调度器在执行某个先进先出的实时进程,那么调度器就会一直运行这个进程,直到其主动放弃运行权(退出进程或者sleep等)
  • 时间片轮转实时进程调度:如果调度器在执行某个时间片轮询的实时进程,那么调度器会判断当前进程的时间片是否用完,如果用完的话,那么重新分配时间片给它,并且重新放置会active队列中,然后调度其他同优先级或者优先级跟高的实时进程

普通进程

每个进程都有一个动态优先级和静态优先级,静态优先级不会变化,进程创建时被设置;而动态优先级会随着进程的睡眠时间而发生变化。

调度算法实现

与之前的内核一样,内核会设置一个时钟tick,在时钟tick中会进入时钟中断,最终会触发调用scheduler_tick函数

由于前面的理论知识,那么我们可以猜想在里面可能会做如下的工作

如果时间片用完,那么把进程从active队列移动到expired队列中

如果当前 runqueue 的 active 队列为空,那么把 active 队列与 expired 队列进行交换

对于实时进程和普通进程优先级的处理

重点看一下scheduler_tick的实现

重点的工作来了

该函数后面的就开始做进程切换了,不在本文的考虑范围之内,我们重点来看看O(1)的核心算法,我们可以直接看

这三行就是算法的核心,首先去从runqueue的active队列中的bitmap找到一个下标,这个下标就是对应的优先级,然后获取到对应优先级的链表,然后从中获取一个next进程。后面的操作就是执行进程切换,调度了。

当系统中无可运行进程时,也就是进程的时间片都耗光了,则需要重新给进程设置时间片,只需要切换active和expried的指针即可

5. 静态优先级和动态优先级

进程的优先级分为静态优先级和动态优先级。普通优先级是进程创建时默认设置的优先级,动态优先级会在进程运行时经过动态的调整。

对于O(1)的静态优先级,其定义如下:

  • 实时进程保存在进程描述符rt_priority成员中,取值范围是1(优先级最低) ~ 99(优先级最高)
  • 普通进程保存在static_pro成员中,取值范围是100(优先级最高) ~ 139(优先级最低),分别对应ncice值为-20~19

O(1)调度器中所有进程的动态优先级为p->prio

在系统运行中,会通过此函数来重新计算进程的动态优先级,实时进程值需要返回对应的p->prio。而普通进程则需要进行赏罚分明,通过进程的睡眠时间sleep_avg来计算进程是否需要赏罚。当一个进程经常睡眠,则会增加它的优先级,当一个进程常占用CPU,则需要惩罚,降低其优先级。

总结

O(1)调度器的引入主要是为了解决O(n)调度器的不足,O(1)调度器比O(n)调度器考虑的因素更多,更复杂,不像O(n)调度器那样直接考虑时间片的大小来调度,同时也有共同点,那就是爱睡眠进程增大优先级,增大时间片的机制来获取更多的运行时间。

O(1)调度器,为了减小锁的竞争,每个CPU维护一个自己的就绪队列。就绪队列由两个优先级组成,分别是active优先级数组和expired优先级数组,每个优先级数组包含140个优先级,就是每个优先级对应一个队列,其中前100个对应于实时进程,后40个对应普通进程。

但是单论效率,似乎已经没有能够超过O(1)的了,不过O(1)调度器在根据"nice"值确定时间片的算法上,存在一些瑕疵。它所使用的的规则大致是这样的:"nice"为0的任务可以运行100ms,"nice"值每增加1,可运行时间将减少5ms,照此推算,"nice"为+19的任务可以运行5ms。

如果一个任务"nice"是0,另一个是1,那么可运行时间分别是100ms和95ms,差别不大,但如果一个是18,另一个是19,那么可运行时间分别是10ms和5ms,差了一倍。此外,前一种场景的任务切换每105ms发生一次,而后一种场景则是每15ms一次,调度周期的长度并不固定。内核演变就出现了完全公平的调度算法,后面继续学习中…

对于O(n)调度器会在所有进程的时间片用完后,才会重新计算任务的优先级。而O(1)调度器则是在每个进程时间片用完后,就重新计算优先级。对于O(1)调度器为每个CPU维护了两个队列

  • active队列:存放的是时间片尚未用完的任务
  • expired队列:**存放的是时间片已经耗尽的任务

当一个队列的时间片用完后,就会被转到expired队列,而且会重新计算它的优先级,当active队列任务全部转移到expired队列后,会交换二者,使得active队列指向expired队列,expired队列指向active队列。可以看到,优先级的计算,队列切换都和任务数量多寡无关,能够在O(1)的时间复杂度下完成。其基本的思路如下图所示:

  1. active中的任务时间片用完,那么就会被移动到expired中。
  2. active中已经没有任务可以运行,就把expiredactive交换,从而expired中的任务可以重新被调度。

O(1)调度算法数据结构

为了减小多核CPU之间的竞争,所以每个CPU都需要维护一份本地的优先级队列,因为如果使用全局的优先级,那么多核CPU就需要对全局优先队列进行上锁,从而导致性能下降。runqueue结构主要维护调度相关的信息,其定义如下:

接下来,我们看看prio_array_t

  • nr_active:所有优先级队列中的数任务数
  • bitmap:位图,每个位对应一个优先级的任务队列,用于记录哪个任务队列不为空,能通过Bitmap快速找到不为空的任务队列
  • queue:优先级队列数组,每个元素维护一个优先级队列,比如索引为0的元素维护着优先级为0的任务队列

对于以上的,每个优先级是一个链表,同时还维护了一个由101 bit组成的Bitmap,其中实时进程的优先级是0~00,占100bit,当某个优先级上有进程被插入链表时,响应的比特位就被置位。在进度算法中通常用sched_find_first_bit来查询该bitmap,它返回当前被置位的最高优先级的数组下表,由于使用位图,查找一个任务来执行所需的时间并不依赖于任务的个数,而是依赖于优先级的数量,所以该调度器是一个O(1)调度器

bitmap 的第2位和第6位为1(红色代表为1,白色代表为0),表示优先级为2和6的任务队列不为空,也就是说 queue 数组的第2个元素和第6个元素的队列不为空。

实时进程和普通进程

O(1)调度算法 把140个优先级的前100个(0 ~ 99)作为 实时进程优先级,而后40个(100 ~ 139)作为 普通进程优先级。实时进程被放置到实时进程优先级的队列中,而普通进程放置到普通进程优先级的队列中。

实时进程

实时进程分为FIFO(先进先出)和RR(时间片轮转)两种算法,其调度算法比较简单,如下:

  • 先进先出实时进程调度:如果调度器在执行某个先进先出的实时进程,那么调度器就会一直运行这个进程,直到其主动放弃运行权(退出进程或者sleep等)
  • 时间片轮转实时进程调度:如果调度器在执行某个时间片轮询的实时进程,那么调度器会判断当前进程的时间片是否用完,如果用完的话,那么重新分配时间片给它,并且重新放置会active队列中,然后调度其他同优先级或者优先级跟高的实时进程

普通进程

每个进程都有一个动态优先级和静态优先级,静态优先级不会变化,进程创建时被设置;而动态优先级会随着进程的睡眠时间而发生变化。

调度算法实现

与之前的内核一样,内核会设置一个时钟tick,在时钟tick中会进入时钟中断,最终会触发调用scheduler_tick函数

由于前面的理论知识,那么我们可以猜想在里面可能会做如下的工作

如果时间片用完,那么把进程从active队列移动到expired队列中

如果当前 runqueue 的 active 队列为空,那么把 active 队列与 expired 队列进行交换

对于实时进程和普通进程优先级的处理

重点看一下scheduler_tick的实现

重点的工作来了

该函数后面的就开始做进程切换了,不在本文的考虑范围之内,我们重点来看看O(1)的核心算法,我们可以直接看

这三行就是算法的核心,首先去从runqueue的active队列中的bitmap找到一个下标,这个下标就是对应的优先级,然后获取到对应优先级的链表,然后从中获取一个next进程。后面的操作就是执行进程切换,调度了。

当系统中无可运行进程时,也就是进程的时间片都耗光了,则需要重新给进程设置时间片,只需要切换active和expried的指针即可

5. 静态优先级和动态优先级

进程的优先级分为静态优先级和动态优先级。普通优先级是进程创建时默认设置的优先级,动态优先级会在进程运行时经过动态的调整。

对于O(1)的静态优先级,其定义如下:

  • 实时进程保存在进程描述符rt_priority成员中,取值范围是1(优先级最低) ~ 99(优先级最高)
  • 普通进程保存在static_pro成员中,取值范围是100(优先级最高) ~ 139(优先级最低),分别对应ncice值为-20~19

O(1)调度器中所有进程的动态优先级为p->prio

在系统运行中,会通过此函数来重新计算进程的动态优先级,实时进程值需要返回对应的p->prio。而普通进程则需要进行赏罚分明,通过进程的睡眠时间sleep_avg来计算进程是否需要赏罚。当一个进程经常睡眠,则会增加它的优先级,当一个进程常占用CPU,则需要惩罚,降低其优先级。

总结

O(1)调度器的引入主要是为了解决O(n)调度器的不足,O(1)调度器比O(n)调度器考虑的因素更多,更复杂,不像O(n)调度器那样直接考虑时间片的大小来调度,同时也有共同点,那就是爱睡眠进程增大优先级,增大时间片的机制来获取更多的运行时间。

O(1)调度器,为了减小锁的竞争,每个CPU维护一个自己的就绪队列。就绪队列由两个优先级组成,分别是active优先级数组和expired优先级数组,每个优先级数组包含140个优先级,就是每个优先级对应一个队列,其中前100个对应于实时进程,后40个对应普通进程。

但是单论效率,似乎已经没有能够超过O(1)的了,不过O(1)调度器在根据"nice"值确定时间片的算法上,存在一些瑕疵。它所使用的的规则大致是这样的:"nice"为0的任务可以运行100ms,"nice"值每增加1,可运行时间将减少5ms,照此推算,"nice"为+19的任务可以运行5ms。

如果一个任务"nice"是0,另一个是1,那么可运行时间分别是100ms和95ms,差别不大,但如果一个是18,另一个是19,那么可运行时间分别是10ms和5ms,差了一倍。此外,前一种场景的任务切换每105ms发生一次,而后一种场景则是每15ms一次,调度周期的长度并不固定。内核演变就出现了完全公平的调度算法,后面继续学习中…

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/356171.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

2019蓝桥杯真题完全二叉树的权值 C语言/C++

题目描述 给定一棵包含 N个节点的完全二叉树,树上每个节点都有一个权值,按从 上到下、从左到右的顺序依次是 A_1, A_2, A_N,如下图所示: 现在小明要把相同深度的节点的权值加在一起,他想知道哪个深度的节点 权值之和最…

力扣2241. 设计一个 ATM 机器

力扣上的一个中等难度的题,之所以写一篇博客记录下来,是因为貌似触发了力扣的彩蛋,第一次遇见,感觉挺有意义的。 题目如下: 一个 ATM 机器,存有 5 种面值的钞票:20 ,50 &#xff0c…

手把手教你使用gdb调试器

所谓调试,指的是对编好的程序用各种手段进进行查错和排非错的过程。进行这种查错处理时,下面将讲解如何使用gdb进行程序的调试。 gdb 简介 gdb是一个功能强大的调试工具,可以用来调试C程序或C程序。在使用这个工具进行程序调试时&#xff0…

nodejs学习-4:nodejs连接mongodb和相关操作

1. express生成器生成express模板 前提需要首先下载好:express-generator,命令如下(全局安装) npm install -g express-generator生成模板命令如下: express 项目名称 --viewejs // --view 参数表示前端界面使用的引擎,这里使用…

Java线程池运行原理,线程池源码解读【Java线程池学习二】

一、前奏 有了上一篇博文的学习,相信你对于线程池的使用这块已经不在存在什么问题了,日常开发和面试也都足够了。 线程池最优使用策略【Java线程池学习一】 但随着时间的推移在闲下来的时候我突然想,当任务进入了队列之后是怎么取出来的呢…

linux系统根文件系统构建

根文件系统构建 一、根文件系统简介 根文件系统是 Linux 内核启动以后挂载(mount)的第一个文件系统,从根文件系统中读取初始化脚本,比如 rcS,inittab 等。根文件系统和 Linux 内核是分开的,单独的 Linux 内核是没法正常工作的&a…

快捷获取GDI+绘图参数的两种经验方案

文章目录一、使用系统的枚举二、专用枚举1、颜色Color2、字体Font3、字体名称4、笔刷Brush5、笔Pen6、矩形Rectangle7、点Point8、大小Size文章出处: https://blog.csdn.net/haigear/article/details/129085403在绘图中,常常需要给出颜色,字体…

目标检测各常见评价指标详解

注:本文仅供学习,未经同意请勿转载 说明:该博客来源于xiaobai_Ry:2020年3月笔记 对应的PDF下载链接在:待上传 目录 常见的评价指标 准确率 (Accuracy) 混淆矩阵 (Confusion Matrix&#xff…

SpringBoot实现统一返回接口(除AOP)

起因 关于使用AOP去实现统一返回接口在之前的博客中我们已经实现了,但我突然突发奇想,SpringBoot中异常类的统一返回好像是通过RestControllerAdvice 这个注解去完成的,那我是否也可以通过这个注解去实现统一返回接口。 正文 这个方法主要…

Django框架之模型视图--HttpResponse对象

HttpResponse对象 视图在接收请求并处理后,必须返回HttpResponse对象或子对象。HttpRequest对象由Django创建,HttpResponse对象由开发人员创建。 1 HttpResponse 可以使用django.http.HttpResponse来构造响应对象。 HttpResponse(content响应体, con…

【opencv源码解析0.2】opencv库源码编译

如何编译opencv库源码 大家好,我是周旋,感谢大家学习【opencv源码解析】系列,本系列首发于公众号【周旋机器视觉】。 上篇文章我们介绍了如何配置opencv环境,搞清了opencv的包含目录include、静态库链接以及动态库链接的作用。 【…

(考研湖科大教书匠计算机网络)第五章传输层-第四节:TCP流量控制

获取pdf:密码7281专栏目录首页:【专栏必读】考研湖科大教书匠计算机网络笔记导航 文章目录一:流量控制概述二:流量控制举例三:拓展阅读(可不看)(1)TCP流量控制完整例子&a…

马上卸载这个恶心的软件!

大家好,我是良许。 春节已经过完了,但在这喜庆的日子里,又有一个小丑在上窜下跳了。 没错,这个不要脸的小丑依然还是 Notepad 的作者。 好好的一个开发者,为何老喜欢整一些有得没得的东西?好好搬砖写代码…

pygame8 扫雷游戏

一、游戏规则: 1、点击方格,如果是地雷,游戏失败,找到所有地雷游戏胜利 2、如果方块上出现数字,则表示在其周围的八个方块中共有多少颗地雷 二、游戏主逻辑: 主要逻辑即调用run_game, 然后循环检测事件…

云计算|OpenStack|社区版OpenStack---基本概念科普(kvm的驱动类别和安装)

前言: 云计算里基本都是基于kvm技术作为底层支撑,但,该技术是比较复杂的,首先,需要硬件的 支撑,表现在物理机上,就是需要在BIOS中调整设置虚拟化功能,这个虚拟机功能通常是interVT或…

Fastjson2基础使用以及底层序列化/反序列化实现探究

1 Fastjson2简介 Fastjson2是Fastjson的升级版,特征: 协议支持:支持JSON/JSONB两种协议部分解析:可以使用JSONPath进行部分解析获取需要的值语言支持:Java/Kotlin场景支持:Android8/服务端其他特性支持&a…

python基础知识有哪些需要背(记住是基础知识)我是初学者

大家好,小编来为大家解答以下问题,一个有趣的事情,一个有趣的事情,今天让我们一起来看看吧! 1、python基础知识有哪些需要背(记住是基础知识)我是初学者 或看好Python的广阔前景,或…

RabbitMQ 入门到应用 ( 五 ) 应用

6.更多应用 6.1.AmqpAdmin 工具类 可以通过Spring的Autowired 注入 AmqpAdmin 工具类 , 通过这个工具类创建 队列, 交换机及绑定 import org.springframework.amqp.core.AmqpAdmin; import org.springframework.amqp.core.Binding; import org.springframework.amqp.core.Di…

python基于django微信小程序的适老化老人健康预警小程序

随着信息技术和网络技术的飞速发展,人类已进入全新信息化时代,传统管理技术已无法高效,便捷地管理信息。为了迎合时代需求,优化管理效率,各种各样的管理系统应运而生,各行各业相继进入信息管理时代, 适老化老人健康预警微信小程序就是信息时代变革中的产物之一。 任何系统都要遵…

Spring国际化实现

Java国际化 Java使用Unicode来处理所有字符。 Locales 国际化主要涉及的是数字、日期、金额等。 有若干个专门负责格式处理的类。为了对格式进行控制,可以使用Locale类。它描述了: 一种语言一个位置(通常包含)一段脚本(可选,自Java SE7开…