这样解决CFS不公平,Linux之父高度赞扬

哈喽,我是子牙老师。今天咱们继续聊那个有趣的话题:算出Linux内核会调度哪个进程

Linux内核中的CFS调度器的调度实体有两种:单个进程、进程组。关于调度器如何调度进程,之前已写过一篇文章。公众号用户可直接查看《Linux内核如何调度进程》。非公众号用户关注公众号【硬核子牙】查看。本篇文章着重谈调度器如何调度进程组

你可能想问:为什么会出现组调度?试想如果是这样的场景:用户A创建了9个进程,用户B创建了1个进程,如果调度器只能调度单个进程,会出现什么样的结果?如果进程的nice值相同,那CFS调度器就会公平的分配CPU时间片,每个进程获得10%的CPU时间片。对于进程来说,这是公平的。但是对于用户来说,这是不公平的,用户A获得了90%的时间片,用户B只获得了10%的时间片

那如何解决这个问题呢?组调度应运而生!将用户A的所有进程打包成一个组作为调度实体,将用户B的所有进程打包成一个组作为调度实体。用户A调度实体与用户B调度实体平分CPU时间片,各50%。用户A调度实体中的9个进程再平分这50%的时间片,用户B调度实体中的那1个进程,就很幸运,一个人独享那50%

讲到这,你应该知道组调度存在的意义了。在Linux内核中,有两套组调度机制:一套是自动组机制,autogroup,这套机制的出现,让Linux之父linus大为赞叹;一套是可供用户配置的cgroups
在这里插入图片描述

关于组调度,本篇文章会讲到这些:

  1. 调度组在Linux内核中是如何存在的
  2. autogroup机制是什么
  3. cgroups机制是什么
  4. 进程是何时加入调度组的
  5. 调度组是何时加入CFS调度队列的
  6. 调度组的vruntime是如何计算的
  7. 调度组是如何被调度器调度到的
  8. 调度组中的进程如何分配CPU时间片

以下,enjoy~

调度组与进程

进程对应的结构体是task_struct,调度组对应的数组结构是task_group,它俩是如何关联的呢?
在这里插入图片描述

如何通过进程自己的task_struct找到它的调度组呢?通过属性sched_task_group
在这里插入图片描述

如何通过调度组task_group找到它下属的task_struct呢?每个task_group关联CPU个数个CFS调度队列cfs_rq,task_group下属的task_struct,都挂在这个cfs_rq中的红黑树上
在这里插入图片描述

这里面有两个问题需要探讨:一、task_group为什么关联的不是一个cfs_rq,而是CPU个数个?二、这个cfs_rq跟上篇文章提到的cfs_rq是一个意思吗?
在这里插入图片描述

先回答第一个问题,因为调度组task_group与进程task_struct是一对多的关系,而不同的进程会被不同的CPU调度,如何只有一个cfs_rq,为了防止一个进程被多个CPU调度,就需要加锁访问,为了避免锁开销,就使用CPU个数个cfs_rq解决,每个CPU一个cfs_rq,CPU调度进程的时候,从自己的cfs_rq中去找即可

第二个问题,这里先不讲,本文后面内容会讲到,接着往下看吧

两套组调度机制

autogroup、cgroups与task_group之间是如何关联的呢?
在这里插入图片描述

autogroup是什么?就是为shell终端设计的自动分组机制。打开一个新的shell终端,就会创建一个autogroup结构体。shell终端对应的bash,就是这个autogroup的第一个进程,在该shell终端上执行的所有命令,都是bash的子进程,挂在这个autogroup关联的task_group中的cfs_rq中的红黑树上
在这里插入图片描述

cgroups是什么?用于控制进程的硬件资源,比如能使用多少内存、IO带宽、网络带宽、绑定CPU运行、获得CPU时间片多少等。容器中用的比较多。比如docker就是使用cgroups限制容器进程使用的硬件资源
在这里插入图片描述

cgroups是如何创建的呢?是在目录/sys/fs/cgroups下创建新目录的时候
在这里插入图片描述

在cgroups目录下创建目录,为什么会调用到vfs_mkdir呢?因为在创建目录的时候,Linux内核会检测到这是个挂载目录
在这里插入图片描述

就会去vfs中找注册的相关函数去调用
在这里插入图片描述

至此,两套组调度机制与task_group之间的关系就讲完了。

注意!注意!注意!一个task_group不可能同时属于两套组调度机制。我只是为了方便,画在了一张图里

调度组与调度器

现在我们已经知道task_struct、task_group、cgroup、autogroup是如何关联的,但是目前还没有跟调度器关联,CPU是调度不到的。那task_group是如何与调度器关联的呢?
在这里插入图片描述

在Linux内核中,有一个根task_group,即root_task_group,它是后面创建的所有task_group的parent

task_group有两个非常重要的属性:se、cfs_rq。一般把task_struct中的se称为task se,把task_group中的se称为group se。se就是调度实体。se中的属性run_node就是调度队列cfs_rq中的红黑树中的节点

task_group中的se、cfs_rq,都不是一个,而是CPU个数个,原因前面已经解释过了。

root_task_group中的se数组为空,因为root_task_group不是调度实体。root_task_group中的cfs_rq指向默认cfs调度队列。即rq.cfs_rq = root_task_group.cfs_rq[cpuid]。如果不创建新的task_group,所有的进程都是root_task_group的组员,都挂在rq.cfs_rq中的红黑树上

因为rq.cfs_rq是默认调度队列,所以新创建的task_group.se要挂到rq.cfs_rq中的红黑树中才能被调度到,那什么时候挂上去呢?是task_group创建的时候?还是task_group中迎来第一个组员进程的时候?答案是迎来第一个组员进程的时候
在这里插入图片描述

总结一下,root_task_group的se数组为空,cfs_rq数组指向每个CPU默认的调度队列cfs_rq。其他task_group的se都要挂到CPU的默认调度队列中充当调度实体才能被调度到,只是不是创建的时候挂,而是往task_group中加入组员进程的时候挂。普通的task_group都有自己的cfs_rq数组,每个cfs_rq对应一个CPU

至此,与调度组相关的数据结构,及之间的关系就全部讲完了。接下来开始讲调度组的调度相关知识

举个例子

举个例子帮助大家理解前面讲的

如果你看到的进程树长这样,在Linux内核中是如何存储的呢?
在这里插入图片描述

Linux内核启动的时候,会创建一个root_task_group,然后对其初始化
在这里插入图片描述

Linux内核启动完成后,程序会不断的创建进程,新进程的sched_task_group都是root_task_group。为什么呢?因为1号进程的是,后面创建的进程都是copy自1号进程。这个sched_task_group可以更改
在这里插入图片描述

Linux内核会将进程systemd、clion、make都丢到CPU的默认调度队列cfs_rq中,即root_task_group.cfs_rq[cpuid]中的红黑树中

这时候用户A打开了一个shell终端,此时会创建一个autogroup及一个task_group,将task_group中的parent指向root_task_group,为该task_group创建一个cfs_rq,将shell终端对应的bash进程丢进去,然后将该task_group se挂到CPU默认队列cfs_rq上去。后续在这个shell终端中执行sleep会创建三个子进程,丢到该task_group关联的cfs_rq中的红黑树上

用户B也打开了一个shell终端,执行情况跟用户A一样

调度组的vruntime

vruntime的值决定了task_group se在cfs_rq中红黑树的位置,决定了何时会被调度到。那调度组的vruntime怎么算呢?很简单,取组员进程中vruntime最小的值,合情合理!

那调度组中的每个进程能获得多少CPU时间片呢?以开头的例子来说,用户A创建了9个进程,用户B创建了1个进程
在这里插入图片描述
在这里插入图片描述

上面的计算是基于理想情况,现实中需要考虑带宽、负载的影响,后面写文章详谈。关注公众号【硬核子牙】,第一时间获取文章动态

调度调度组

调度器是如何进行组调度的呢?看核心代码
在这里插入图片描述

这段代码的逻辑是:

  1. 从当前运行的进程所在的cfs_rq中,取vruntime最小的调度实体se
  2. 调用函数group_cfs_rq获取调度实体的cfs_rq
  3. 如果se是进程,cfs_rq为NULL,循环结束,该se关联的进程就是下一个要调度的进程
  4. 如果se是task_group,那cfs_rq就是该task_group关联的调度队列,while成立,调到步骤2,循环执行

是不是理解了各个数据结构之间的关系,理解这里就非常easy了

总结

Linux内核中有五种调度器,按优先级从高到低依次是:停机调度器、限期调度器、实时调度器、公平调度器、空闲调度器,与我们息息相关的是公平调度器CFS

CFS中的调度实体,可以是进程,也可以是进程组。你可能疑惑,怎么没提到线程?其实Linux内核层面没有线程的概念,线程的本质是轻量级进程,所以线程在Linux内核中也是task_struct,所以机制是一样的

基本上来说,你把我写得这两篇文章完全吃透,CFS源码你就可以轻轻松松读懂。CFS还剩边缘的知识,下一篇文章会全部讲到。关注公众号【硬核子牙】,第一时间获取文章动态

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值