分享

理解进程CFS组调度

 mzsm 2015-10-06
作者:新浪微博(@NP等不等于P

计算机学习微信公众号(jsj_xx)

对于朴素的CFS调度而言,其控制粒度是一个进程,这样的粒度在很多场景是不合适的。比如,希望达到用户粒度,也就是希望每个用户占有相同的cpu时间。但是,在各个用户拥有的进程数量不同的情况下,显然朴素的CFS(请参考我们之前的《理解进程CFS调度》)会将cpu更多地分配给进程数量多的用户。组调度的引入,正是解决此问题的。

参考linux kernel souce code 4.0,本文分享我们(计算机学习微信公众号:jsj_xx)对组调度的理解(如有错误,还望指正,谢谢)。

组调度其实属于cgroup框架的cpu子系统,故需要开启cgroup:CONFIG_CGROUP_SCHED。同时,开启CFS组调度:CONFIG_FAIR_GROUP_SCHED。注意,本文仅讨论普通进程,不涉及实时进程。

1 组调度原理

我们先看task_group(本文中简称为tg)的shares的含义。

tg的shares值就是该tg(se)的权重。想想CFS中的进程,其权重需要从优先级转化而来,而tg就简单了:直接指定即可。另外,tg和task是两种完全不同的个体,由此引入se来抽象两者:tg和task。

以下是我对组调度(tg)的理解要点:

  • 根节点tg(init_task_group),是预先分配了cpu_nr(cpu个数)个se指针数组和cfs_rq指针数组。

  • 一个tg,它对应的se和cfs_rq都是基于cpu的,每个cpu都有一份。而对于task则仅有一份,不分cpu。

  • 对于se/cfs_rq这颗树而言,task(处于运行状态时)可能会在自己所属的tg的各个cpu之间移动。

  • 对于tg/cgroup这棵树而言,树结构层次是固定的,与/cgroup目录的树结构层次是一致的。

  • tg的默认shares是1024,也就是相当于一个1024优先级的进程。

组调度的原理,就是每个tg都拥有自己的cfs_rq以及融入上级的se,从而搭建出一颗se/cfs_rq树,通过tg的shares或者task的权重将所有节点(tg或task)管理起来。关键是,引入了平行于task的调度实体:tg。

2 tg数据结构

我们看下tg的数据结构:

  • 一个tg通过se指针数组与上级tg建立层次关系,从而搭建树结构层次(注意,不同于tg/cgroup那颗固定树,这棵树是灵活的)。

  • 一个tg通过cfs_rq指针数组建立自己的cfs红黑树。

  • 一个tg,针对某个cpu,se和cfs_rq是一一对应的。同时,se的两个字段(my_q和cfs_rq)使得自己成为cfs_rq树的中间节点。

  • 一个task,通过parent字段找到tg对应的(基于cpu的)se,通过sched_task_group字段找到tg。

简单地说,就是搭建了两颗树:tg/cgroup固定树和se/cfs_rq灵活树。

3 与cgroup的关系

一个tg,通过内嵌一个css(cgroup_subsys_state)的方式融入到cgroup框架里(cgroup框架,我们以后会有专题去谈)。涉及到cgroup中的cpu子系统,相关的注册函数如下:


我们主要看看cpu_cgroup_css_alloc(),创建css(也就是tg,因为css是内嵌于tg里的)的处理。

对于顶级tg:root_task_group,其css是预留的,无需分配。对于普通tg,需要分配tg(和内嵌的css)。这样,cgroup/tg的树结构层次,也就在这个处理中搭建出来了。

【参考cpu_cgroup_css_alloc()->sched_create_group()->alloc_fair_sched_group()】

另外,还可以看到,task加入tg的动作是支持批处理的。

【参考cpu_cgroup_attach()】

我们再看看设置shares时的处理,这是借助cgroup文件系统里的shares文件来实现的,其处理函数:

在sched_group_set_shares()里设置指定tg的shares值:

此处是组调度的核心处理,设置tg的shares。我们仔细分析此函数:

  • 将指定tg的shares设置为指定shares。

  • 自底向上的修正tg对应各个cpu的se的shares。原则是按照tg的所有成员在各个cpu上的load比重去切分tg的shares。

理解了这些,我们重新审视此tg的shares:

  • 此值对于该tg的成员来说,是一种划分的资源,其下成员(基于cpu)按比例划分。

  • 此值对于上级tg而言,是参与上级tg shares(基于cpu的)划分时的比重。

回到sched_group_set_shares(),继续分析。此处理中,最关键的是两个要点:

  • 基于每个cpu,是因为tg的下一级,不管是tg还是task,都可能分散于各个cpu。但是,在调度(包括SMP负载均衡)真实运作时,是分cpu处理的。

  • 自底(准确的说,这个底,其实是修改shares值的tg所在位置)向上的处理,这样就将tg的shares变化的效应向上扩散到最顶层,这是一发而动全身的效果!

那么,修改tg的shares时,如果不扩散呢?

我的理解是:tg的shares的修改,如果不做上述这种扩散处理,甚至不去利用各个tg在各个cpu上的load比重,这样,tg的shares在各个cpu上都是一样的(都等于tg的shares值),也是可以保证组调度的运行。但是,这种实现仅能保证了tg的粒度,tg内的task粒度却失衡了。。。

最后,要补充一下上述讨论的前提:各个cpu是负载均衡的。抛开均衡(SMP负载均衡涉及调度域,我们以后再谈)谈组调度是没有意义的,我觉得。毕竟tg的shares是一个值,而调度时所有cpu都是围绕该值运转,如果cpu负载不均,势必令调度失去公平。

4 与CFS的关系

CFS的红黑树存在于每一个tg里,也就是说每个tg的cfs_rq都在运作一个CFS调度(参考我们之前的《理解进程CFS调度》)。这样,在将一个task加入到某个tg时,需要自底(表示叶子的task)向上层次地做入队列处理:

自然,需要保证从该task到根节点是完整的路径,所以这个上溯动作直到碰到某层的tg(se)早已经存在于队列里才停止。

出队列也是如此。

task出队列,则沿着task所在的tg上溯,将沿途的只有一个成员的tg也做出队列操作。可见,上溯出队列操作会持续到碰到一个tg,该tg至少包含一个跟此task无关(此处的“无关”是指此成员不在此上溯路径里)的成员

注意,上述两个操作都是在se/cfs_rq树中。

至于各个(层)的tg或task,它们CFS调度涉及的vruntime,这些都是独立的。也就是说,层次之间的CFS运转是独立的,它们各自的周期时间计算只与自己的cfs_rq有关,与组调度(其它的cfs_rq)是毫无关系的。这样,可以想象,底层tg可能会由于上级tg的slice用完而reschedule,但其实自己的slice还有剩余,这是一种正常现象。因为底层的调度实体,周期以及各个slice的计算是独立与其它层的,但是调度控制还是受制于其它的(这里的“其它”是指自底向上,直至根节点的整个路径层次)。

5 与负载均衡的关系

在迁移进程时,也会针对迁移进程做上述扩散处理,相当于进程在tg内的迁移而已。只是,进程的选择需要大作文章,涉及调度域,这是另一个层次结构,与组调度的层次结构都无关。

6 总结

组调度将调度的粒度灵活化,并行保证了tg和task的粒度,这是由tg shares在cpu间划分贯穿树层次结构来实现的,同时伴以SMP负载均衡的辅佐。

新浪微博(@NP等不等于P

计算机学习微信公众号(jsj_xx)

原创技术文章,感悟计算机,透彻理解计算机!

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多