Linux内核设计与实现——第4章:进程调度
- 多任务
- 抢占式和非抢占式
- Linux的进程调度
- 2.5前的O(n)调度
- 2.5的O(1)调度
- 2.6的 完全公平调度算法(CFS)
- 策略
- 进程分类:I/O 和 CPU消耗型进程区别
- 进程优先级:nice和priority
- 实时进程和普通进程
- Linux调度类
- 调度器:主调度器+周期调度器
- 调度策略:实时(FIFO + RR)/ 普通(NORMAL)
- CFS 完全公平调度算法
- 分配规则
- vruntime体现公平性
- 数据结构
- 抢占和上下文切换
- 上下文切换
- 用户抢占和内核抢占的区别
4.1 多任务
多任务操作系统:多进程并发执行,可以划分为两类
- 非抢占式多任务(cooperative multitasking):由调度程序来决定何时停止一个进程的运行,通常与时间片(timeslice)搭配使用
- 抢占式多任务(preemptive multitasking):进程一直执行,直到它完成或主动让步,然后处理器切换到另一个进程。绝大多数操作系统基本不用这个
4.2 Linux的进程调度
(1)历史演变
- 2.5之前:复杂度为O(n)的调度算法,从1991年Linux的第1版到后来的2.4,Linux调度程序都很简陋
- 2.5开始:引入O(1)调度程序,特点是在拥有数以十计的多CPU环境下,性能完美。缺点是对于交互程序体验不佳
- 2.6开始,Linux的“完全公平调度算法”(简称CFS)代替了O(1)算法,该算法基于“反转楼梯最后期限调度算法”理论(Rotating Staircase Deadline scheduler)
(2)调度算法:
- O(n)算法
- pick next算法,遍历runqueue中所有进程,选出优先级最高的进程调用
- 缺点很明显,进程越来越多时,处理器消耗大量时间
- O(1)算法
- 进程优先级范围是0到139,共140个。因此该算法为每个优先级都设置了一个runqueue,即包含140个可运行状态的进程链表
- 用一个bitmap记录这140个进程链表,因此需要5个32位int来表示,每个进程占一个bit。
- 初始bitmap中的所有位都被置0,当某个优先级的进程处于可运行状态时,该优先级所对应的位就被置1
- 选取过程可以理解为:遍历bitmap,找到对应位为1 且 优先级最高的那个
- CFS算法
- 楼梯调度算法 staircase scheduler
- RSDL旋转楼梯(Rotating Staircase Deadline Schelduler)
- CFS 完全公平调度器
- 该算法吸取了上面两个的思想,最终被内核采纳
- 详细见下面4.4
- BFS调度器
- 又称“脑残调度器”,没啥用,见Reference
(3)Reference
- Linux进程调度策略的发展和演变--Linux进程的管理与调度(十六):https://blog.csdn.net/gatieme/article/details/51701149
4.3 策略
(1)进程的分类
- 第一种分类方式:对于这种进程,调度器应该尽量降低它们的调度频率,延长运行时间
类型 | 描述 | 示例 | 评价 |
I/O消耗型,更注重响应时间 | 进程大多数时间用来提交或等待I/O请求 | 数据库服务器, 文本编辑器 | 真正运行时间短,多数时间用于阻塞 |
CPU消耗型,更注重吞吐量 | 大多数时间用于执行代码上 | 深度学习训练模型 | 对于这种进程,调度器应该尽量降低它们的调度频率,延长运行时间 |
- 第二种分类方式
类型 | 描述 | 示例 |
交互式进程(interactive process) | 此类进程经常与用户进行交互, 因此需要花费很多时间等待键盘和鼠标操作. 当接受了用户的输入后, 进程必须很快被唤醒, 否则用户会感觉系统反应迟钝 | shell, 文本编辑程序和图形应用程序 |
批处理进程(batch process) | 此类进程不必与用户交互, 因此经常在后台运行. 因为这样的进程不必很快相应, 因此常受到调度程序的怠慢 | 程序语言的编译程序, 数据库搜索引擎以及科学计算 |
实时进程(real-time process) | 这些进程由很强的调度需要, 这样的进程绝不会被低优先级的进程阻塞. 并且他们的响应时间要尽可能的短 | 视频音频应用程序, 机器人控制程序以及从物理传感器上收集数据的程序 |
(2)进程优先级的概念
- 传统操作系统:优先级高的进程先运行,低的后运行,相同优先级的进程按轮询挨个调度
- Linux:优先级决定了该进程在所有可用进程中的时间占比,优先级高的进程获得时间片长
(3)Linux进程优先级属性:nice(NI值)和Priority(PR值)
- nice值
- 范围是 -20 到 +19,默认值为0
- nice值越大,优先级越低,获得的处理器时间越少
- 也称静态优先级,即当nice值设定好了之后,除非我们用renice去改它,否则它是不变的
- nice值是所有Unix系统的标准化的概念,不同的Unix系统nice值运用方式有所差异:在Mac OS中,nice值代表分配给进程时间片的绝对值,是绝对的。而Linux中,nice代表时间片的比例,是相对的
- priority
- 实时优先级,也称动态优先级,因为priority的值在之前内核的O1调度器上表现是会变化的
- 默认情况下范围从0到99(包括0和99)
- priority值越大,优先级越高
- 重要Reference:https://blog.51cto.com/frankch/1773621
(4)实时进程和非实时进程(普通进程)
- 在内核中,进程优先级的取值范围是通过一个宏定义的,这个宏的名称是MAX_PRIO,它的值为140。而这个值又是由另外两个值相加组成的,一个是代表nice值取值范围的NICE_WIDTH宏(-20到+19,共40),另一个是代表实时进程(realtime)优先级范围的MAX_RT_PRIO宏(0到99,共100)
- 说白了就是,Linux实际上实现了140个优先级范围,取值范围是从0-139,这个值越小,优先级越高
- 实时进程:优先级值在0-99范围内的,都是实时进程,可选择的调度策略:SCHED_FIFO、SCHED_RR(Round Robin)
- 非实时进程 / 普通进程:100-139范围内的是非实时进程,对应策略有:SCHED_NORMAL、SCHED_OTHER、SCHED_IDLE
- 重要Reference:https://blog.51cto.com/frankch/1773621
(5)时间片
- 时间片太长,影响交互;时间片太短,容易造成上下文切换的消耗
- 对于Linux来说,nice值影响每个进程的时间片
(6)一个例子
- 假设存在两个进程:文字编辑进程(I/O消耗型进程) 和 视频编码进程(CPU消耗进程)
- 对于文字编辑(I/O)进程:希望有更高的处理器时间(在Linux中即优先级更高),这并非因为它需要更多的处理器时间,而是我们希望在它需要时总能优先得到处理器
- 理想的打算:分配给 IO进程 和 CPU进程 相同的nice值,因此它们时间片各占50%。每次使用时,IO进程使用时间不到50%会提前释放时间片,CPU进程用满50%。对于IO进程来说,它拥有更高的优先级;对于CPU进程来说,它拥有更长的使用时间。显然,这是公平的
4.4 Linux调度相关
(1)调度器:Linux有两个调度器一起工作
- 主调度器 scheduler:一种是直接的, 比如进程打算睡眠或出于其他原因放弃CPU
- 周期性调度器 scheduler_tick:另一种是通过周期性的机制, 以固定的频率运行, 不时的检测是否有必要
(2)调度策略(算法):六种
- 实时进程:SCHED_FIFO,SCHED_RR,SCHED_DEADLINE(新支持的策略,基于EDF算法)
- 普通进程:SCHED_NORMAL,SCHED_BATCH(也是基于CFS的,分化版本)
- IDLE进程(可作了解,不重要):SCHED_IDLE(系统空闲时才跑这个调度算法)
(3)调度器类:五个
调度器类 | 所属进程优先级 | 描述 | 对应调度策略 |
---|---|---|---|
stop_sched_class | 最高 | 优先级最高的线程,会中断所有其他线程,且不会被其他任务打断作用 | 无, 不需要调度普通进程 |
dl_sched_class | 较高 | 采用EDF最早截至时间优先算法调度实时进程 | SCHED_DEADLINE |
rt_sched_class | 高 | 采用提供 Roound-Robin算法或者FIFO算法调度实时进程 | SCHED_FIFO, SCHED_RR |
fair_sched_clas | 中 | 采用CFS算法调度普通的非实时进程 | SCHED_NORMAL, SCHED_BATCH |
idle_sched_class | 低 | 采用CFS算法调度idle进程, 每个cup的第一个pid=0线程:swapper,是一个静态线程 | SCHED_IDLE |
(4)总结
调度器类 | 调度策略 | 调度策略对应的调度算法 | 调度实体 | 调度实体对应的调度对象 |
---|---|---|---|---|
stop_sched_class | 无 | 无 | 无 | 特殊情况, 发生在cpu_stop_cpu_callback 进行cpu之间任务迁移migration或者HOTPLUG_CPU的情况下关闭任务 |
dl_sched_class | SCHED_DEADLINE | Earliest-Deadline-First最早截至时间有限算法 | sched_dl_entity | 采用DEF最早截至时间有限算法调度实时进程 |
rt_sched_class |
SCHED_RR
SCHED_FIFO |
Roound-Robin时间片轮转算法
FIFO先进先出算法 |
sched_rt_entity | 采用Roound-Robin或者FIFO算法调度的实时调度实体 |
fair_sched_class |
SCHED_NORMAL
SCHED_BATCH |
CFS完全公平懂调度算法 | sched_entity | 采用CFS算法普通非实时进程 |
idle_sched_class | SCHED_IDLE | 无 | 无 | 特殊进程, 用于cpu空闲时调度空闲进程idle |
(5)Reference
Linux进程调度器概述--Linux进程的管理与调度(十五):https://blog.csdn.net/gatieme/article/details/51699889
4.5 CFS 完全公平调度算法
(1)CFS公平调度设计思路
- 根据进程优先级权重(nice)分配运行时间
- 分配给进程的运行时间 = 调度周期 * 进程权重 / 所有进程权重之和
- 调度周期很好理解,就是将所有处于TASK_RUNNING态进程都调度一遍的时间,差不多相当于O(1)调度算法中运行队列和过期队列切换一次的时间
- 举个例子,比如只有两个进程A, B,权重分别为1和2,调度周期设为30ms,那么分配给A的CPU时间为:30ms * (1/(1+2)) = 10ms;而B的CPU时间为:30ms * (2/(1+2)) = 20ms。那么在这30ms中A将运行10ms,B将运行20ms
(2)公平体现在哪——vruntime
- 其实公平是体现在另外一个量上面,叫做virtual runtime(vruntime),它记录着进程已经运行的时间,但是并不是直接记录,而是要根据进程的权重将运行时间放大或者缩小一个比例
- vruntime = 实际运行时间 * 1024 / 进程权重
- 为了不把大家搞晕,这里我直接写1024,实际上它等于nice为0的进程的权重,代码中是NICE_0_LOAD。也就是说,所有进程都以nice为0的进程的权重1024作为基准,计算自己的vruntime增加速度。
- 还以上面AB两个进程为例,B的权重是A的2倍,那么B的vruntime增加速度只有A的一半
(3)CFS的内部实现,主要由四个部分组成,位于 kernel/sched_fair.c 中
- 时间记账
- CFS不再有时间片的概念,由vruntime变量来记录该进程的运行时间
- 进程选择
- 当CFS需要选择下一个运行进程时,它会挑一个具有最小vruntime的进程
- CFS使用红黑树来组织可运行进程队列,迅速找到具有最小vruntime的进程(即最左侧的叶子节点)
- 调度器入口
- 在文件 kernel/sched.c 中,进程调度的主要入口点函数 schedule():在可运行队列中,选择哪个进程可以运行,何时投入运行
- schedule()通常都需要和一个调度类相关联:找到优先级最高的调度类,询问该调度类谁是下一个该运行的进程
- 睡眠和唤醒
- 休眠(被阻塞)的进程处于一个不可执行状态,常见原因比如read()等。
- 进入休眠时,内核把该进程标记成休眠状态,从可执行红黑树中移除。唤醒进程则相反
- 第三章曾讨论过,休眠对应两种状态:TASK_INTERRUPTIBLE和TASK_UNINTERRUPTIBLE,区别在于
- TASK_INTERRUPTIBLE:如果接收到一个信号,会被提前唤醒并响应该信号
- TASK_UNINTERRUPTIBLE:如果接收到一个信号,会忽略
- 休眠的进程在内核中用等待队列wake_queue_head_t来表示,需要休眠的进程进入该队列,被唤醒后再出来
- 唤醒操作通过函数wake_up()进行,负责唤醒指定的等待队列上的所有进程
(4)Reference
- https://www.cnblogs.com/tianguiyu/articles/6091378.html
- https://blog.csdn.net/weixin_42092278/article/details/83959440
- https://blog.csdn.net/gatieme/article/details/51699889
4.6 抢占和上下文切换
(1)上下文切换
- 概念:从一个可执行进程切换到另一个可执行进程
- 对应函数:kernel/sched.c 中的 context_switch() 函数
- 1. 调用switch_mm(), 把虚拟内存从一个进程映射切换到新进程中
- 2. 调用switch_to(),从上一个进程的处理器状态切换到新进程的处理器状态。这包括保存、恢复栈信息和寄存器信息
(2)抢占:分为用户抢占和内核抢占
- 用户抢占
- 内核即将返回用户空间时(无论是【系统调用返回】还是【中断处理返回】),都会检查need_resched标志,如果它被设置了,内核会选择一个(更适合的)进程投入运行
- 发生时机有两种
- 从【系统调用】返回【用户空间】
- 从【中断处理程序】返回【用户空间】
- 内核抢占
- 只要重新调度是安全的(没有锁),内核就能进行抢占
- 每个进程的thread_info中有个preempt_count计数器,使用锁时+1,释放锁时-1。只有为0时才能安全抢占
- 发生时机
- 当从【中断处理程序】正在执行,且返回内核空间之前。当一个【中断处理例程】退出,在返回到内核态时(kernel-space)。这是隐式的调用schedule()函数,当前任务没有主动放弃CPU使用权,而是被剥夺了CPU使用权
- 当内核代码再一次具有可抢占性的时候,如解锁(spin_unlock_bh)及使能软中断(local_bh_enable)等, 此时当kernel code从不可抢占状态变为可抢占状态时(preemptible again)。也就是preempt_count从正整数变为0时。这也是隐式的调用schedule()函数
- 如果内核中的任务显式的调用schedule(), 任务主动放弃CPU使用权
- 如果内核中的任务阻塞(这同样也会导致调用schedule()), 导致需要调用schedule()函数。任务主动放弃CPU使用权
- Reference
- Linux进程调度器概述--Linux进程的管理与调度(十五):https://blog.csdn.net/gatieme/article/details/51699889
4.7 实时调度策略
见4.4的调度策略
Reference
- Linux进程调度器概述--Linux进程的管理与调度(十五):https://blog.csdn.net/gatieme/article/details/51699889
- Linux进程调度策略的发展和演变--Linux进程的管理与调度(十六):https://blog.csdn.net/gatieme/article/details/51701149
Linux内核设计与实现——第4章:进程调度相关推荐
- 《linux内核设计与实现》第一章
第一章Linux内核简介 一.unix 1.Unix的历史 Unix是现存操作系统中最强大和最优秀的系统. --1969年由Ken Thompson和Dernis Ritchie的灵感点亮的产物. - ...
- Linux内核设计与实现 第18章 调试
调试工作艰难是内核级开发区别于用户级开发的一个显著特点.相比于用户级开发,内核调试的难度确实要艰苦得多.更可怕的是,它带来的风险比用户级别更高,内核的一一个错误往往立刻就能让系统崩溃. 驾驭内核调试的 ...
- Linux内核设计与实现 第19章 可移植性
Linux是一个可移植性非常好的操作系统,它广泛支持许多不同体系结构的计算机.可移植性是指代码从一种体系结构移植到另外一种不同的体系结构上的方便程度.我们都知道Linux是可移植的,因为它已经能够在各 ...
- linux内核双向循环队列,读书笔记之linux内核设计与实现(2)进程调度
调度程序是内核的组成部分,它负责选择下一个要运行的进程.进程调度程序可看作在可运行态进程之间分配有限的处理器时间资源的内核子系统. 多任务操作系统就是能够同时并发的交互执行多个进程的操作系统.多任务系 ...
- 《linux内核设计与实现》读书笔记第一、二章
第一章 Linux内核简介 1.1 Unix的历史 1971年,Unix被移植到PDP-11型机中. 1973年,Unix操作系统用C语言改写--为Unix系统的广泛移植铺平了道路. 1977年, ...
- Linux内核设计与实现(13)第十三章:虚拟文件系统
Linux内核设计与实现(13)第十三章:虚拟文件系统 1. 文件系统 1.1 文件系统定义: 1.2 文件系统分类 1.3 标准文件系统:Ext文件系统族 1.4 VFS 1.4.1 VFS 背景 ...
- Linux内核设计与实现(1)第一章:Linux内核简介
Linux内核设计与实现(1)第一章:Linux内核简介 1. linux历史及与Unix关系 2. 内核组成 3. 用户空间和内核空间 4. 系统调用 5. 中断 6. Unix强大的原因 7. L ...
- 跟我一起玩《linux内核设计的艺术》第1章(四)——from setup.s to head.s,这回一定让main滚出来!(已解封)
看到书上1.3的大标题,以为马上就要见着main了,其实啊,还早着呢,光看setup.s和head.s的代码量就知道,跟bootsect.s没有可比性,真多--这确实需要包括我在内的大家多一些耐心,相 ...
- 读《Linux内核设计与实现》我想到了这些书
从题目中可以看到,这篇文章是以我读<Linux内核设计与实现>而想到的其他我读过的书,所以,这篇文章的主要支撑点是<Linux内核>. 开始读这本书已经 ...
最新文章
- pku The Windy's KM最小权匹配 or 最小费用最大流
- letswave7中文教程2:脑电数据预处理-通道位置分配
- libpython2.7.so.1.0 cannot open的解决方法
- Python将DataFrame的某一列作为index
- 限制EditText 输入的字节数
- 云服务器磁盘挂载_云小课 | 磁盘容量不够用?小课教你来扩容!
- win8系统的计算机共享在哪里设置方法,win10系统设置与win8系统局域网文件共享的方案...
- python拨号_python 拨号代码(win10 系统亲测有效)
- Linux_《Linux命令行与shell脚本编程大全》第十章学习总结
- “嫌贫爱富”之人,从一顿饭局当中便可看出
- 利用hdparm工具配合crontab使硬盘不用时休眠
- Android 反编译Apk (Mac)
- immunedeconv估算免疫细胞比例
- 身居乱世之中,重新审视“活法
- 为什么贝叶斯统计如此重要?
- 马虎词汇教程16-20(转载)
- 银川清华计算机技术培训,银川有没有本地IT技能培训?
- 零基础用Unity制作你的第一个游戏(1)
- 1270: Wooden Sticks [贪心]
- 规范化、标准化、归一化、正则化