Control Group - 控制组

E.G.

先从一个例子出发,w表示的是weight,现在最顶层的 cfs_rq 中维护着俩个活跃的进程,现在假设它们在同一个 CPU 核运行,那么我们运行 top 的结果应该是怎么样的?

应该是这样的,b c俩个进程占用的CPU的资源之和应该是 另外一个 se 的 1/2,实际也是如此,而 b c 俩个进程公平竞争,Group B 得到的时间片。

b c 本身也是一个 sched_entity ,忘记在图片中提及。

说明一点,三个进程做的都是 while(true) 死循环,所以不存在 yield 的可能~

实现的过程

注意,所有的注释图,但凡涉及到 list_head 都是双向链表,当时没有注意到,所以读者得留心,大部分都是双向索引的~

实现的本质在组调度已经说明了,现在说的得是上层接口了。

上面使用的只是工具,完成的工作很简单,俩个 mkdir 指令

所以我们可以,手动进入这俩个目录使用 mkdir 命令,做的事情是一样的。

cpu 指的就是 之前说的 组调度, cpuset 是改变进程运行的所在 CPU 核

Cgroup 文件系统

代码出自内核 2.6.24,Cgroup v1.0 那时的代码

关键的数据结构

cgroup

说核心把,这个结构其实只起了连接的作用,所以不要觉得它能具体处理什么,它只是一个统筹的结构。当我们在 /sys/fs/cgroup/cpu 下 mkdir 的时候其实就创建了这么一个结构,它代表的就是一个控制单元,里面可能受到一个或多个 资源控制器 的限制。也可以理解为,它代表了一块受限的资源。

cgroupfs_root -- Hierarchy

每当用户挂载了一个新的 cgroup 文件系统的时候,就会创建这个结构,注意它其实内嵌了一个 cgroup,也可以称为最高层的 cgroup。

embedded cgroup

注意,用户的每一次 mount 操作,其实就创建了一个 Hierarchy,其实就是创建了一棵资源管理树,别的地方还是直译为层级,实际上非常难以理解。

值得一提的是,每个资源管理器( subsystem ),只能绑定在一个 Hierarchy ,比如 cpu 这个 资源管理器,如果你在 mount 的时候指定了 你所需要的 资源管理器,那么它就不能属于别的 资源管理树了。

这可以理解为,创建了一个名字叫 hier1 的 Hierarchy,然后它绑定了 cpu cpuset 俩个资源管理器(sub- system) ,所以保证的一点就是,这俩个 subsystems 没有被其它已经创建的 hierarchy 所使用。

值得一提的是,最开始初始化的时候,创建了一个虚拟的 root( dummy top ),用来挂载内核全部的资源管理器。

注意 dummytop 没有连接 subsystem

其实,最开始的创建的一个 dummy top 就可以认为是一个 Hierarchy,只是它连接了全部的资源管理器。

这里只画出了俩个 subsystems

当用户挂载一个新的 Hierarchy的时候,会指定它需要的 资源管理器(subsystem)。后来新建的 Hierarchy 和 最开始的 dummy top 是平级的,挂载在一个全局的链表上。

Hierarchy is a mount point ~

渐渐的把 资源管理器用 subsystem 替代,资源管理树用 Hierarchy 替代,读者必须理解的就是,subsystem 与 Hierarchy 的附属关系。

cgroup_subsys

刚才提及的 subsystem 登场了。

这么多指针函数,一看就是类定义把~,subsystem 这个词太容易让人误解了,以前的驱动架构里面也有这个说法,后来移除了,中文我习惯把它叫为 资源管理器,它就是管理资源的,但是国内很多人都直接直译了它的英文,实在让人难懂。前面我们提到的,cpu, cpuset... 都是一个具体的 ( subsystem )资源管理器。

当用户 mount 时,即创建了一个 Hierarchy,就会要求 资源管理器 和 它绑定,即此时就会从 dummy top 上脱离开来。

注意 cgfs_root 的 subsys_list 用来连接绑定的 subsystem,subsys 中则有 cgrootfs 的指针

来看看 cpu 的实现

都是具体指向的函数,对了,值得一提的是什么呢,内核设置了一个全局的数组指针,指向了目前的所有的 资源控制器。

macro 展开后,就是一个取址的操作了~

每个 subsystem 都有一个 unique id,对于 cpu 来说 这就是 cpu_cgroup_subsys_id,为的就是辨别,并且,在下面提到的全局数组,这个 id 其实就是它的 index

cgroup_subsys_state

在最开始接触 cgroup 的时候, 最容易弄混的俩个结构,就是它和 刚才提到的 subsystem,刚才我们说了,subsystem 是一个资源管理器,它是一个类,会绑定在一个 Hierarchy(资源管理树)上,表达的意思是,这棵上的进程都受这个资源管理器所限制,思考一下,Hierarchy 是可以有子树,假设我们最顶层的那个资源管理器,以cpuset为例子,我们给了它 1-4 的 CPU核,然后我创建了一个子树(子 Hier),我们现在想分配它 1个核。

现在是不是出现了资源不一样的情况,一个 subsystem 只能绑定在一个 Hierarchy,意味着它只能有一份,然后我们又需求的是 同一个 Hierarchy 也会有资源不一样的情况,所以有了它的存在。

我习惯把它理解为,一个具体的 “资源”,subsystem 只是强调了 资源管理器 的存在,而到底拥有多少资源,是这个结构说了算,通过 suffix “ state ”也能略晓一二,描述的是资源的状态。

以 cpu 这个 subsys 来说,就是同一个 Hierarchy 中的进程可能也不在一个 进程组,所以有了这个结构的存在。

注意它有一个 create 的接口,目的就是让一个资源管理器 创建 一个具体的 资源

cgroup 和这些 “资源挂钩”,用一个数组连接到具体的资源,index 就是之前介绍的 unique 的 subsys_id,用来区分具体是哪个资源。

前面我们说,默认存在的 dummy top 也是一个 Hierarchy ,所以在最开始初始化的时候,也会给他分配一个这样的 css( 简称 )。

css 和 cg 可以互相索引

当然,对于 dummy top,默认的资源当然是无限制,回想我们在组调度提到的,init_task_cgroup,一开始就是全部进程都在一个组内,所以它其实拥有全部的系统资源。当用户自己新建一个 Hierarchy 的时候,即 mount ,也不会调用 subsys->create() 函数,只有当用户新建一个子cgroup的时候,才会调用,以获取一个新的 css。

最开始,所有的进程都在一个组,emm,意思就是,最开始所有的 subsystem 都属于一个dummy top,当用户 mount 一个 hierarchy 时,这个 Hierarchy 就必须作为 最顶层 Cgroup 了,那它就必须保证,现在所有受到这个资源控制的进程都必须在最顶层的那一组,简单点理解,就是所有进程在没有添加到子 Hierarchy( Cgroup ) 时,都属于最高层的那一个cgroup,而初始化的时候,就已经全部添加到一个组内了,所以现在还不需要新建一个 css 。

具体的实现,就是 mount 时候,新建的 cgfs_root 内嵌的cg,会接替 dummy top 的 css

其实关键就在于,Hierarchy 必须要把之前 dummy top 那个组的全部的进程 接手,接手的过程就是接替那个 css,还有 subsystem,至于为什么能,就要看下面的 css_set 了。

mount 就是新建一个 Hierarchy

css_set

来看看,比较难懂的一个结构,我放在了最后解释它,它是 css( cgroup_subsys_state )_set

css_set 是个什么玩意儿呢?其实就是如它的名字所暗示的,它是一个集合,代表的是一个进程所受哪些具体的 资源 限制,现在看起来很复杂,现在要知道的就是,是一个 set!

  • Each task in the system has a reference-counted pointer to a css_set.

  • A css_set contains a set of reference-counted pointers to cgroup_subsys_state objects, one for each cgroup subsystem registered in the system. There is no direct link from a task to the cgroup of which it's a member in each hierarchy, but this can be determined by following pointers through the cgroup_subsys_state objects. This is because accessing the subsystem state is something that's expected to happen frequently and in performance-critical code, whereas operations that require a task's actual cgroup assignments (in particular, moving between cgroups) are less common. A linked list runs through the cg_list field of each task_struct using the css_set, anchored at css_set->tasks.

比如,我们最开始的例子,进程就受到 cpuset cpu 俩个 资源 控制,限制了它的 CPU 核 以及 CPU 的资源占用,那么可以说对于 b c 俩个进程,它们都在一个 css_set,如果还有其它进程也在跟他们在一个组内(受同样的 资源 控制),那么只需要增加 css_set 的一个引用计数就好了,它是为了节约资源才设计的。

可能还有一个进程,只受到 cpuset 的限制,但是它并不受到 另外一个组的 cpu 资源的限制,所以它们不在一个 set,为了应对各种复杂的情况,所以出现了这个 css_set

注意,我们常见的操作是,为每一个 subsystem 挂载一个 Hierarchy,也就是说, subsystem 对应一个 Hierarchy,然后,具体在根据子类分配资源。如下图,三个 task 只需要共用一个 css_set

css_set 和 cgroup 的连接比较隐晦,等会说明,图 A

内核也有默认的css_set,目的是为了连接 dummy top 上的 css 还有上面所有的进程,以及其余所有的 css_set,这里没有画出来,剩余的全部进程可能都在最开始的 css_set 上。

所以最一开始,init_css_set 是这样的:(假设只有俩个 subsystems )

图 B

所以,最开始所有的 task 都共用一个 css_set,css_set 会把全部的任务串连起来,图上很多指针没有画出来,因为太复杂了,不如突出重点。

css set 的存在,就是把拥有同一个 资源(state) 的任务连接在了一起,注意,不是 subsystem,可以参考上上张图,当创建了一个 cgroup( 注意,不是 mount,在 vfs 上体现为 mkdir ),就会创建一个 子css,这时候添加新的 task 进去,就会导致 进程脱离原来的 set。css_set 就是资源集合,在一个 set 上的进程共享相同的资源

现在来看看,cg_link 这个结构,目的是为了连接与它挂钩的 cgroup,我们现在要知道一点,首先,一个 cgroup可能连接多个 css_set,一个 css_set 可能与多个 cgroup 有关,这里我用 cgroup,实际上也可以理解为 css( 资源 ),对于 cgroup_roofs( top cgroup/ hierarchy )拥有全部的资源(最早 dummy top 建立的),而后来创建的可能只拥有部分。

以图 A(上上张图),为例子,会是这样的。

对于 list_head 箭头都是双向的,这里没有画出

从 css_set 的角度来说,cg_links字段,可以遍历全部的 cg_links,cg_links 的 cgrp_link_list 就指向的是对应的拥有 css(资源) 的那个 cgroup。

从 cgroup 的角度来说,css_sets 字段,可以遍历全部的 cg_links,代表的是 想要分享我资源的 css_set,着是非常重要的!!!

如果在加上,css ,那必然是,css_set1 会指向俩个资源,而 2,3分别拥有 cg1 2的资源,注意 cg_links 都是 二进二出。

下面还有一个指针会指向别的 cg

这张图应该更好理解,这也是比较复杂的结构了。总之,我们要知道,css_set 里进程,拥有相同的资源,这个结构是让进程更方便知道自己到底在哪个 cgroup,到底拥有多少资源。

现在 review 一下这个结构!

总结一下

怎么样,是不是够复杂!现在做个总结,

cgroup 就是 掌握若干 资源 的一个集合。

cgroupfs_root 是一个 Hierarchy,它内嵌了一个 cgroup,还可以创建子group,mount 操作会创建新的 Hierarchy,并且会绑定若干个 subsystem,(一个 subsystem 只能绑定一个 hier),当创建了子cgroup,cgroup 会调用,subsys->create() 生成新的 资源( css ),更重要的是,Hierarchy 会继承最开始为dunmmy top 创建的 css,就是说,接管了全部的资源(当然,指的是绑定了它的 subsystem的资源)。

因为最初的初始化,init_css_set 将所有的资源都掌握了,如今这里只需要改变了 css->cgroup 就可以保证,一个 Hierarchy 接管了原来掌握该资源所有的进程。

hier1_cpu 接管了全部 cpu 资源

subsystem 和 cgroup_subsys_state,一个是资源管理器,一个是具体的资源,前者提供了 create 接口,用以获取相关的资源。

css_set 连接若干的 css,忘了提示一点,set 中有 css 数组,这个数组指向的资源(css)是不会重复的,一个进程可以受多个 Hierarchy 控制,因为不同的 Hierarchy 对应的不同的资源类型,但是不可能说拥有俩个资源,这俩个资源都是在一个 Hierarchy,因为它们在数组的 index 是相同的,从常理上也理解不通,同样的资源如何还能分开计算呢?

css_set 和 cgroup 是多对多的关系,这很重要。上图没有画出,css 连接那俩个 cgroup,读者自己要心里有数,它们是靠 cgroup_links 实现的。

初始化与 mount 过程

本来还想提提 VFS 流程,想想都说烂了,而且这个篇幅,已经有点超出我的想象了,本来想讲的更详细一些,0.0,还是只提关键部分好吧,VFS 可以参考之前的文章。

下面就是最早的初始化代码,后续还有一个注册 proc 文件,就不贴出了。

此处,将全部的 subsystem 与 dummy top 连( 还没有绑定 ),并且调用 ss->create() 得到一个 css,同时init_css_set 通过 init_css_set_link 连接 top_cgroup( 内嵌在 cg_roofs ),并且 css_set 连接了全部 css。其中很关键的一步,就是将 init.cgroup 指向了init_ css_set ,这说明这个任务拥有所有的 “资源“,那么从它延伸出来的进程( fork 出来的 )都跟享受同样的资源。

emmm

mount

这里就不解释系统的注册了,这里不是重点~

后面版本的 cgroup 改为了 .mount 其实一样, VFS 可以保证 get_sb 被调用

比较长的一个函数,大致的过程有,创建 cgroup_rootfs( Hierarchy ),一个超级块( 常规的三板斧 sb inode dentry),rebind_subsystems 会将它需要的 subsystem 绑定(此时并不会创建 css ),并接替 dummy top 的 css,注意上述的代码还将 top_cgroup 与全部的 css_set 连接在一起,其实目前只有一个。

parse_cgroup_options 是解析参数的,mount -t cgroup -o opts,用来确定需要哪些 subsystem

cgroup_populate_dir 目的是为了生成一些配置文件,其实就是分配denty还有 inode,并设置它的 f_ops,下文详细解释一下就明白了。

CPU 资源管理器的实现

这一步执行的过程,就是上述 mount 的过程,建立一个 hierarchy ,绑定了 cpu 这个 subsystem,然后理所当然,继承了dummy top 的 css

在 mount 的时候,对于目录的 inode,进行了如下赋值

层层包装,最后新建了一个 cgroup 并且调用了 ss->create 得到了一个 css,最后调用了 populate_dir,我们现在来看看,到底干了什么事情。

首先是,ss->create,以 cpu 这个 subsystem 为例子,这个函数是

看到这,眼尖的读者肯定已经发现,现在这些函数,已经是在 sched.c 下了,这意味着这已经是进程管理方面的处理了。

现在在来看看,task_group 这个结构,上面调用的函数无非是分配了一个 task_group

注意它内嵌了一个css,典型的继承的思想

实际的将 task_group 作为一个 se,挂载在最高层的 cfs_rq,则是发生在添加到实际的任务pid 进去

至于这个文件怎么出现,其实就是 mount 的时候,自动生成的

这里不讲解 cftype 了,其实就是 dispatcher ,注意, 还调用了 ss->populate,对于 cpu 这个 subystem,还会生成一个 cpu.shares 文件!本质就是 se 的 weight

所以对 tasks 写会调用,common_file_write

这个函数如下,

关键在于,attach_task

这个函数非常的关键,调用了can_attach 和 attach 接口,前者做一些判断,后者真正的进行操作,以 cpu 为例,就是将刚才创建 task_group 的 se 添加到上层队列,并把进程的 se 添加到这个 task_group 的队列下。并且它更新了 css_set,假设最早期所有的进程都在一个 init_css_set,也就是说,现在是用户主动attach 的第一个进程,那么 css_set 会是:

new css_set 和 init_css_set 还会串连

并且 top_cgroup 也会串连全部 css_set,这里 cglinks 只是简单释义,值得注意是,task_group 内嵌了了一个 css,表明它就是 资源。

当改变了 se.cfs_rq 指针,调用 enqueue_task,就会添加到 task_group 对应的 cfs_rq 那里了。当然,此时的 task_group 的 se,也不在队列上

该函数会将se 一层层 加到自己的所在的 cfs_rq,通过 se->cfs_rq 来判断它的所属的队列

全局来看,大致就是这样。

回到最初的例子

那么,文章开头的例子,是怎么样的呢?这里也给出一张图,首先,全部进程都只能在一个核上运行,这意味着它们都受到一个 cpuset 的资源控制( css ),我们假设 cpuset 绑定在了一个 Hierarchy,同时,b,c被限制在一个组内,所以它们受 cpu 资源控制,注意进程 a 不受 cpu 控制,这意味着它还是拥有最初的 也就是 dummy top 创建的 css(cpu)的资源。

所以,不难得出下面这个图。

没有提及init_css_set 以及cg_links 还有subsystem

提几点, css_set_A 表明资源有 一个 CPU核,一个进程组(task_group)还有其他类型的全部资源( 比如内存,网卡,硬盘), css_set_B表明,一个CPU核,还有其他类型全部的资源( 最初的进程组,即在最高层的队列,内存等等)。

要强调的就是 css_set 表明的就是一个资源集合,然后解释为什么 cgroup css_set 都有css 指针数组。

Cgroup 也是一个资源集,但是它是固定的!css_set 是 cgroup 集,源代码注释把它称为,cgroup group,我觉得也不太好理解,总之,进程可以在多个 cgroup 中,比如 b,c 就同时在三个 cgroup 中,所以生成了一个css_set_A ,a 也是一样,但是不可能同时在有父子关系的俩个cgroup,原因就是在同一个 Hierarchy中,资源都是互斥的,也就说 数组的索引,会冲突,所以一定不能在,但是可以在父子之间移动。

现在来看看,是如何修改,task_group 的 se 的weight。

这便是,文件系统的威力,值得一提,刚才我们调用 ss->attach 的时候,其实只添加了 task_group 进入当前的 cpu 的核的队列,而不是每个 cpu 核的队列,那么到底其他队列是如何添加的。

答案是进程会在CPU之间,迁移

注释其实写的很明白,就是在 groups 或者 CPUs 之间迁移,中间调用可能有些复杂,但是本质就是这几个函数。

非常长的一篇笔记,因为实现起来确实有些复杂,其实还有 cpuset 这个资源管理器没有提到,但是本质不难猜测,就是控制进程的在 CPU 之间的迁移,中间加一些判断,就可以知道是否有目标CPU的资源,就实现了。

关键在于理解,Hierarchy Subsystem css css_set 这几个结构,接口由 VFS 实现~ 恩,暂时就到这,最近得忙编译器以及动态加载那方面的知识~ 容器就到这儿告一段落拉。

Last updated