Linux---进程调度及CFS调度器
Linux的调度算法
(1)O(N)调度器
O(N)调度器发布于1992年,从就绪队列中比较所有进程的优先级,然后选择一个最高优先级的进程作为下一个调度进程。
- 优点:操作简单,便于理解。
- 缺点:时间消耗太大,在众多可运行进程或者多处理器的环境下都难以胜任
(2)O(1)调度器
O(1)调度器在Linux2.5系列内核开始至Linux2.6.23版本之前,优化了选择下一个进程的事件,为每个CPU维护一组进程优先级队列,每个优先级一个队列,这样在选择下一个进程时,只需要查询优先级队列相应的位图即可知道哪个队列中有就绪进程,所以查询时间为常数O(1)。
- 优点:适合于大型服务器的负载场景。
- 缺点:对处理桌面系统的交互式进程时存在问题,进程反应缓慢,并且对NUMA支持也不完善
(3)CFS调度器
Con Kolivas(麻醉师)提出了RSDL(Rotating Staircase Deadline Scheduler, 翻转楼梯最后期限调度算法),该算法吸收了队列理论算法,将公平调度的概念引入了Linux调度程序中,但没有被社区所采纳。Ingo Molnar(RedHat)借鉴了RSDL的思想,提出了CFS调度算法。CFS算法在Linux2.6.23后的版本使用。
进程的行为
- I/O密集型:进程的大部分时间用来提交I/O请求或是等待I/O请求。多数的用户界面程序(GUI)在多数的时间里等待鼠标或键盘的交互操作。
- CPU密集型:进程把时间大多数用在执行代码上,除非被抢占,否则一至不停地运行。例如编译代码、MATLAB等。
当然这种分法不是绝对的,进程可以同时展示这两种行为,比如X Window服务器及时I/O密集型,也是CPU密集型。进程也可以一会是I/O密集型,一会又是CPU密集型。通常I/O密集型的进程优先级比CPU密集型高。Linux作为通用系统,因此需要在调度策略上对I/O密集型、CPU密集型的两个矛盾中寻找平衡:进程响应迅速和最大系统利用率。
进程优先级
进程PCB描述符struct task_struct数据结构中有4个成员描述进程的优先级:prio、 static_prio、 normal_prio、 normal_prio。
- (1)prio保存着进程的动态优先级,是调度类考虑的优先级,有些情况下需要暂时提高进程优先级,例如实时互斥量等。
- (2)static_prio是静态优先级,在进程启动时分配。内核不存储nice值,取而代之的是static_prio,内核中的宏NICE_TO_PRIO()实现由nice值转换成static_prio,用户可以通过nice或者sched_setscheduler等系统调用来修改该值。
- (3)normal_prio是基于static_prio和调度策略计算出来的优先级,在创建进程时会继承父进程的normal_prio。对于普通进程,normal_prio等同于static_prio,对于实时进程,会根据rt_priority重新计算normal_prio。
- (4)rt_priority是实时进程的优先级。
优先级 |
限期进程 |
实时进程 |
普通进程 |
prio调度优先级 数值越小,优先级越高 |
大多数情况下prio等于normal_prio 特殊情况:如果进程a占有实时互斥锁,进程b正在等待锁,进程b的优先级比进程a的优先级高,那么把进程a的优先级临时提高到进程b的优先级,即进程a的prio值等于进程b的prio值 |
||
static_prio |
没有意义总是0 |
120+nice值 (nice -20至19) |
|
normal_prio |
-1 |
99-rt_priority |
static_prio |
rt_prio |
没有意义总是0 |
实时进程的优先级,范围1-99,数值越大,优先级越高 |
没有意义总是0 |
Linux内核支持的调度策略
- (1)限期进程使用限期调度策略
- (2)实时进程支持两种调度策略:先进先出调度、轮流调度
- (3)普通进程支持两种调度策略:标准轮流分时、空闲。
调度类 |
调度策略 |
调度算法 |
调度对象 |
停机调度类 stop_sched_class |
无 |
无 |
停机进程(迁移线程) |
限期调度类 dl_sched_class |
SCHED_DEADLINE |
最早期限优先 |
限期进程 |
实时调度类 rt_sched_class |
SCHED_FIFO |
先进先出 |
实时进程 |
SCHED_RR |
轮流调度 |
||
公平调度类 cfs_sched_class |
SCHED_NORMAL |
完全公平调度 |
普通进程 |
SCHED_IDLE |
|||
空闲调度类 idle_sched_class |
无 |
无 |
每个处理器上的空闲进程 |
优先级从高到低依次为:停机调度类>限期调度类>实时调度类>公平调度类>空闲调度类。
1、停机调度类
停机进程是优先级最高的进程,目前只有迁移线程属于停机调度类。迁移线程用来把进程从当前处理器迁移至其他处理器,对外伪装成优先级是99的先进先出实时进程。引入停机调度类的原因:能够抢占所有其他进程,快速处理调度器发出的迁移请求,把进程从当前处理器迁移到其他处理器。
2、限期调度类
使用最早期限优先算法,使用红黑树把进程按照绝对截止限期从小到大排序,每次调度时选择绝对截止期限最小的进程
3、实时调度类
实时调度类为每个调度优先级维护一个队列,其结构体如下:
- 位图bitmap用来快速查找第一个非空队列。
- 数组queue的下标是实时进程的调度优先级,下标越小,优先级越高。
每次调度,先找到优先级最高的一个非空队列,然后从队列中选择第一个进程。使用先进先出调度策略的进程没有时间片,如果没有优先级更好的进程,不主动让出处理器,则一直霸占处理器。使用轮流调度策略的进程有时间片,用完时间片以后,进程加入队列的尾部。可以通过文件“/proc/sys/kernel/ sched_rr_timeslice_ms”修改时间片。
4、公平调度类
完全公平调度算法使用红黑树把进程按虚拟运行时间从小到大排序,每次调度时会选择虚拟运行时间最小的进程。如果运行队列中的进程数量大于8,那么调度周期等于调度最小粒度成绩进程数量,否则调度周期是6ms。
进程时间片计算公式:进程的时间片=调度周期*进程的权重/运行队列中所有进程的权重总和
- 调度周期:在某个时间长度可以保证运行队列中的每个进程至少运行一次。
- 调度最小粒度:为了防止进程切换太频繁,进程被调度后应该至少运行一小段时间,称为调度最小粒度。可以通过文件“/proc/sys/kernel/ sched_min_granularity_ns”调整。
5、空闲调度类
每个处理器上有一个空闲进程,即0号线程。优先级最低,仅当没有其他进程可以调度的时候,才会调度空闲进程。
什么是公平调度
在理想的完美多任务处理系统中,每个进程都能获得1/n的处理器时间,n是指可运行进程的数量。同样我们可以调度给它们无限小的事件周期,所以在任何可测量周期内,我们给予n个进程中每个进程同样多的运行时间。
理想并非现实,由于处理器的性能不是那么非常强大,并且无限小的时间周期带来了更多的进程切换消耗,不同的进程情况不一样,处理器相同对待每个进程则会降低处理器的性能。
CFS允许每个进程运行一段时间、循环轮转、选择运行最少的进程作为下一个运行进程,每个进程按照其权重在全部可运行进程中所占比例的“时间片”来运行。那么权重是怎么来的,“时间片”是怎么计算的?
调度实体
进程的创建通过do_fork()函数完成,do_fork()在执行过程中就参与了进程调度相关的初始化。进程调度有一个重要的数据结构struct sched_entity,称为调度实体,含有参与调度所需要的信息。
- load:该调度实体的权重,
- run_node:该调度实体在红黑树中的节点
- on_rq:该调度实体是否在就绪队列中接受调度
- vruntime:表示虚拟运行时间
- exec_start、sum_exec_runtime和prev_sum_exec_runtime是计算虚拟时间需要的信息
- avg:该调度实体的负载信息
内核中调度器相关数据结构的关系如时图所示,看起来很复杂,其实它们是有关联的
权重计算
在调度实体的数据结构sched_entity中内嵌了load_weight结构体,用于描述调度实体的权重。
- (1)weight是调度实体的权重。
- (2)inv_weight是权重的一个中间计算结果,通过查表获取prio_to_wmult。
一个CPU密集型的进程nice值从0增加到1,那么它相对于其他nice值为0的进程将减少10%的CPU时间,因此进程每降低一个nice级别,优先级提高一个级别,相应的进程多获得10%的CPU时间;反之每提升一个nice级别,优先级则降低一个级别,相应的进程少获得10%的CPU时间。为了计算方便,内核约定nice值为0的权重值为1024,其他的nice值对应的权重值通过查表获取,prio_to_weight。10%的影响是相对及累加的,例如一个进程增加了10%的CPU时间,另外一个进程减少10%,那么差距大约是20%,因此使用系数1.25来计算。
内核使用set_load_weight函数来查询下述两个表
虚拟时钟
假设系统中只有3个进程A、B、C,它们的NICE都为0,也就是权重值都是1024,系统分配都是三分之一的运行时间。如果A、B、C三个进程的权重值不同呢?
内核中使用calc_delta_fair()函数进行vruntime计算虚拟时钟,推导过程如下
vruntime=(delta_exec*nice_0_weight*inv_weight)>>32,其中,
- vruntime表示进程虚拟的运行时间,
- delta_exec表示实际运行时间,
- nice_0_weight表示nice为0的权重值,
- weight表示该进程的权重值。
- inv_weight运用了prio_to_wmult表预先做了除法,作为系数使用
内核如何完成计算并更新?
所有与虚拟时钟有关的计算都在update_curr中执行, 该函数在系统中各个不同地方调用, 包括周期性调度器在内。update_curr的流程如下:
- (1)首先计算进程当前时间与上次启动时间的差值
- (2)通过负荷权重和当前时间模拟出进程的虚拟运行时钟
- (3)重新设置CFS的min_vruntime保持其单调性
进程调度的时机
- (1)阻塞操作:互斥量(mutex)、信号量(semaphore)、等待队列(waitqueue)等;
- (2)在中断返回前和系统调用返回用户空间时,去检查TIF_NEED_RESCHED标志位以判断是否需要调度。
- (3)将要被唤醒的进程(wakeups)不会马上调用schedule()要求被调度,而是会被添加到CFS就绪队列,并且设置TIF_NEED_RESCHED标志位。唤醒进程被调度需要分内核是否具有可抢占功能。
如果内核可抢占,则:
- 如果唤醒调用发生在系统调用或者异常处理上下文中,在下一次调用preempt_enable()时会检查是否需要抢占调度;
- 如果唤醒动作发生在硬中断处理上下文中,硬件中断处理返回前会检查是否需要抢占当前进程。
如果内核不可抢占,则:
- 当前进程调用cond_resched()时会检查是否需要调度;
- 主动调度调用schedule();
- 系统调用或者异常处理返回用户空间时;
- 中断处理完成返回用户空间时。
进程调度的方法
(1)主动调度
进程在用户模式下运行的时候,无法直接调用schedule()函数,只能通过系统调用进入内核模式,如果系统调用需要等待某个资源,例如互斥锁或信号量,就会把进程的状态设置为睡眠状态,然后调用schedule()函数来调度进程。进程也可以通过系统调用sched_yield()让出处理器,这种情况下进程不会睡眠。
在内核中,有以下3种主动调度方式:
- (1)直接调用schedule()函数来调度进程;
- (2)调用有条件重调度函数cond_resched()。
- (3)如果需要等待某个资源,例如互斥锁或信号量,就会把进程的状态设置为睡眠状态,然后调用schedule()函数来调度进程。
(2)周期调度
有些“流氓”进程不主动让出处理器,内核只能依靠周期性的时钟中断夺回处理器的控制权,时钟中断是调度器的脉搏。时钟中断处理程序检查当前进程的执行时间有没有超过限额,如果超过限额,设置需要重新调度调度的标志。
周期调度的函数是scheduler_tick()。
- (1)限期调度类的周期调度。
- (2)实时调度类的周期调度。
- (3)公平调度类的周期调度。
- (4)中断返回时的调度。
(3)唤醒进程时抢占
唤醒进程的时候,被唤醒的进程可能抢占当前进程。
- (1)如果被唤醒的进程和当前进程属于相同的调度类,那么调用调度类的check_preempt_curr方法以检查是否可以抢占当前进程。
- (2)如果被唤醒的进程所属调度类的优先级高于当前进程所属调度类的优先级,那么给当前进程设置需要重新调度的标志。
(4)创建新进程时抢占
使用系统调用fork、vfork、clone创建新进程的时候,新进程可能抢占当前进程。使用函数kernel_thread创建新的内核线程的时候,新的内核线程可能抢占当前进程
(5)内核抢占
内核抢占是指当进程在内核模式下运行的时候可以被其他进程抢占,需要打开配置宏CONFIG_PREEMPT。如果不支持内核抢占,当进程在内核模式下运行的时候,不会被其他进程抢占,带来的问题是:如果一个进程在内核模式下运行的时间很长,将导致交互式进程等待的时间很长,相应很慢,用户体验差。内核抢占就是为了解决这个问题。
支持抢占的内核称为抢占式内核,不支持抢占的内核称为非抢占式内核。个人计算机的桌面操作系统要求响应速度快,适合使用抢占式内核,服务器要求业务的吞吐率高,适合使用非抢占式内核。
Linux---进程调度及CFS调度器相关推荐
- 【Linux 内核】CFS 调度器 ③ ( 计算进程 “ 虚拟运行时间 “ )
文章目录 一.计算进程 " 虚拟运行时间 " 一.计算进程 " 虚拟运行时间 " 在上一篇博客 [Linux 内核]CFS 调度器 ② ( CFS 调度器 &q ...
- 【Linux 内核】CFS 调度器 ⑥ ( CFS 调度器就绪队列 cfs_rq | Linux 内核调度实体 sched_entity | “ 红黑树 “ 数据结构 rb_root_cached )
文章目录 一.CFS 调度器就绪队列 cfs_rq 二.Linux 内核调度实体 sched_entity 三." 红黑树 " 数据结构 rb_root_cached 一.CFS ...
- 【Linux 内核】CFS 调度器 ⑤ ( CFS 调度器类 fair_sched_class 源码 | next 赋值 | enqueue_task 赋值 | dequeue_task 赋值 )
文章目录 一.调度器类 sched_class 简介 二.CFS 调度器类源码 三.next 赋值 四.enqueue_task 赋值 五.dequeue_task 赋值 一.调度器类 sched_c ...
- 【Linux 内核】CFS 调度器 ④ ( 调度子系统组件模块 | 主调度器、周期性调度器 | 调度器类 )
文章目录 一.调度子系统组件模块 二.主调度器.周期性调度器 三.调度器类 一.调度子系统组件模块 调度器 需要对 被调度的进程 进行 排序 和 调度管理 , 进程管理过程需要 调度器 的 组件模块 ...
- 【Linux 内核】CFS 调度器 ① ( CFS 完全公平调度器概念 | CFS 调度器虚拟时钟 Virtual Runtime 概念 | 四种进程优先级 | 五种调度类 )
文章目录 一.CFS 调度器概念 ( 完全公平调度器 ) 二.CFS 调度器虚拟时钟概念 ( Virtual Runtime ) 三.进程优先级 ( 调度优先级 | 静态优先级 | 正常优先级 | 实 ...
- 【Linux 内核】CFS 调度器 ② ( CFS 调度器 “ 权重 “ 概念 | CFS 调度器调度实例 | 计算进程 “ 实际运行时间 “ )
文章目录 一.CFS 调度器 " 权重 " 概念 二.CFS 调度器调度实例 ( 计算进程 " 实际运行时间 " ) 一.CFS 调度器 " 权重 & ...
- 用c语言实现对n个进程采用“短进程优先”算法的进程调度_为什么Linux CFS调度器没有带来惊艳的碾压效果?...
文章转自公众号"人人都是极客" 但凡懂Linux内核的,都知道Linux内核的CFS进程调度算法,无论是从2.6.23将其初引入时的论文,还是各类源码分析,文章,以及Linux内核 ...
- 为什么Linux CFS调度器没有带来惊艳的碾压效果? | CSDN博文精选
任何领域,革命性的碾压式推陈出新并不是没有,但是概率极低,人们普遍的狂妄在于,总是认为自己所置身的环境正在发生着某种碾压式的变革,但其实,最终大概率不过是一场平庸. 作者 | dog250 责编 | ...
- (5)Linux进程调度-CFS调度器
目录 背景 1. 概述 2. 数据结构 2.1 调度类 2.2 rq/cfs_rq/task_struct/task_group/sched_entity 3. 流程分析 3.1 runtime与vr ...
- Linux进程调度 - CFS调度器 LoyenWang
背景 Read the fucking source code! --By 鲁迅 A picture is worth a thousand words. --By 高尔基 说明: Kernel版本: ...
最新文章
- 考察新人的两道c语言题目
- 洛谷 P3376 【模板】网络最大流
- HTML5 本地文件操作之FileSystemAPI整理(一)
- 数组中求子数组和最大
- 恢复VMware vSphere已孤立的虚拟机
- 28个MongoDB经典面试题
- mysqld 多线程 用pstree -p 显示
- AndroidStudio_开发工具的设置_代码编辑器使用_新特性---Android原生开发工作笔记73
- 你以为我在玩游戏?其实我在学 Java
- mini2440 SD卡脱机烧写恢复
- linux操作系统日志查看,linux 如何查看系统日志
- 常见的74系列集成电路
- 【工程光学】平面与平面系统
- 9. 广义表 - 广义表概念,存储结构,深度/长度,复制算法
- 华三路由器配置mstp多生成树协议
- 如何使用DDexec在Linux上隐蔽运行二进制文件
- 群晖nas免费内网穿透,实现外网异地远程访问
- 联想服务器重装系统按f几,联想笔记本电脑重装系统按F几
- lm2576使用注意
- 文件复制-字节输入输出流的使用