(未完待续,持续更新中)
目录
1 散件箱子
这一小节用来放一些又散又短的内容;
1.1 cgroup_namespace
When a process creates a new cgroup namespace using clone(2) or unshare(2) with the CLONE_NEWCGROUP flag, its current cgroups directories become the cgroup root directories of the new namespace
当我们进入一个容器的时候,查看/sys/fs/cgroup/cpu下的内容时,看到的实际上是该容器对应的cgroup的目录的内容;搞过容器的同学一定都知道这个。这个现象就是通过cgroup_namespace实现的。
cgroup namespace的实现是为了模拟/proc/self/cgroup;在没有cgroup namespace之前,在容器中,看到的/proc/self/cgroup中包含了root视角下目录部分;相关的代码实现,我们这里暂时略过;接下来看下cgroup的目录部分;
copy_cgroup_ns() //CLONE_NEWCGROUP
---
/* It is not safe to take cgroup_mutex here */
spin_lock_irq(&css_set_lock);
cset = task_css_set(current);
get_css_set(cset);
spin_unlock_irq(&css_set_lock);
new_ns = alloc_cgroup_ns();
...
new_ns->user_ns = get_user_ns(user_ns);
new_ns->ucounts = ucounts;
new_ns->root_cset = cset;
---
cgroup_init_fs_context()
---
ctx->ns = current->nsproxy->cgroup_ns;
get_cgroup_ns(ctx->ns);
---
cgroup_do_get_tree()
---
/*
* In non-init cgroup namespace, instead of root cgroup's dentry,
* we return the dentry corresponding to the cgroupns->root_cgrp.
*/
if (!ret && ctx->ns != &init_cgroup_ns) {
struct dentry *nsdentry;
struct super_block *sb = fc->root->d_sb;
struct cgroup *cgrp;
mutex_lock(&cgroup_mutex);
spin_lock_irq(&css_set_lock);
cgrp = cset_cgroup_from_root(ctx->ns->root_cset, ctx->root);
spin_unlock_irq(&css_set_lock);
mutex_unlock(&cgroup_mutex);
nsdentry = kernfs_node_dentry(cgrp->kn, sb);
dput(fc->root);
...
fc->root = nsdentry;
}
---
总结起来,一句话,在CLONE_NEWCGROUP之后挂载cgroup目录,root inode会变成该任务所在的cgroup目录。
2 数据结构
本小节主要说明cgroup核心中的几个关键数据结构,即cgroup、cgroup_subsys_state和css_set,它们扮演的角色和之间的引用关系。
2.2 cgroup_subsys_state
2.2.1 概述
cgroup通过各个子系统的控制器,去限制一组任务的资源,css对应的就是一个控制器的实例。每个控制器实例可能需要包含以下数据:
- 资源限制,包括限制多少及如何限制;
- 资源统计,当前使用了多少资源;
- 接入cgroup,即接入cgroup核心代码,还有层级关系,比如,子层级不能突破父层级的限制;
css通常会被嵌入到一个控制器自己的结构体中,资源限制和统计由各个控制器负责,而接入cgroup由css负责。
2.2.2 cgroup和css
首先,每个cgroup中都内嵌了一个叫self的css,用于维护cgroup结构的层级关系和生命周期;参考以下函数:
static inline struct cgroup *cgroup_parent(struct cgroup *cgrp)
{
struct cgroup_subsys_state *parent_css = cgrp->self.parent;
if (parent_css)
return container_of(parent_css, struct cgroup, self);
return NULL;
}
static inline void cgroup_get(struct cgroup *cgrp)
{
css_get(&cgrp->self);
}
cgroup自身css和控制器css之间识别的方式是cgroup_subsys_state.ss指针,控制器的css.ss指向对应子系统的cgroup_subsys,而cgroup的css.ss为空。
cgroup对应的控制器实例的css在创建cgroup时创建,参考代码:
cgroup_apply_control_enable()
---
cgroup_for_each_live_descendant_pre(dsct, d_css, cgrp) {
for_each_subsys(ss, ssid) {
struct cgroup_subsys_state *css = cgroup_css(dsct, ss);
if (!(cgroup_ss_mask(dsct) & (1 << ss->id)))
continue;
if (!css) {
css = css_create(dsct, ss);
...
}
...
}
}
---
一个cgroup需要创建的css,取决于其cgroup_ss_mask(),也就是这个cgroup上使能的控制器;
(1) 对于cgroup v2,这个是需要配置的,即cgroup.subtree_control;
(2) 对于cgroup v1,则取决于所属的目录树,具体代码可以参考cgroup_control()和
cgroup1_mount(),顶层目录会使用root->subsys_mask,然后一级一级的继承下去;
css和cgroup之间的关系
- css引用cgroup并抓取一个引用计数,参考函数init_and_link_css();此后,便可以通过cgroup_css()访问对应的css了;对于cgroupv2,子目录没有使能某控制器时,会使用父目录的,参考函数cgroup_e_css();
- cgroup引用css,并增加其online_cnt,参考函数online_css(),另外,每一个css还会增加一个它的parent css的online_cnt;
css和cgroup关系的解除
- kill_css(),删除cgroup目录或者关闭相关控制器时(cgroupv2),percpu_ref css.refcnt会被kill,然后减掉css的一个online_cnt;
注: percpu_ref是一个percpu、atomic和rcu的组合体,当一个percpu_ref被kill掉之后,会转换到atomic模式,通过rcu机制保证kill操作被所有的cpu看到之后,confirm回调会被调用,也就是css_killed_ref_fn()
- offline_css(),在css.online_cnt清零之后调用,cgroup->subsys[]的对一个slot会被清除
- css_free_rwork_fn(),css本身会被释放,同时,释放cgroup的引用计数
至此,两者关系彻底解除。
注:以上各个过程都通过kwork做的异步,所以,在我们删除cgroup之后,其相关的css和cgroup结构可能会依然存在很长时间。
2.3 css_set
2.3.1 概述
css_set代表的是一组任务和对它们进行资源限制的控制器;引入css_set是因为cgroupv1中每个子系统都有一个单独层级树。
cpu-cg-root ' mem-cg-root ' blkio-cg-root
/ \ ' / \ ' / \
cpu-cg-0 cpu-cg-1 ' mem-cg-0 mem-cg-1 ' blkio-cg-0 blkio-cg-1
/ \ ' / \ ' / \
cpu-cg-2 cpu-cg-3' mem-cg-2 mem-cg-3' blkio-cg-2 blkio-cg-3
' '
在cgroup v1中,每个子系统都有一个单独的树,每个任务可以被attach到不同的树里。
css_set的作用就是将任务和子系统的css组合起来。
cset会将task、cgroup和css连接起来,如下:
blkio mem sched
/ \ / \ / \
io0 io1 m0 m1 s0 s1
(t0 t1) (t0 t2) (t1 t2)
/ t0 in cset(css_io0, css_m0)
cgrp_io0
\ t1 in cset(css_io0, css_s0)
/ t0 in cset(css_io0, css_m0)
cgrp_m0
\ t2 in cset(css_m0, css_s0)
/ t1 in cset(css_io0, css_s0)
cgrp_s0
\ t2 in cset(css_m0, css_s0)
- css_set.subsys[]中包含一个控制器css集合;
- css_set.tasks连接起的是在这组控制器限制下任务;任务则通过task_struct.cgroups获取其所属的css_set;
- css_set和cgroup通过cgrp_cset_link实现多对多联系;cgroup想要遍历其下所有的任务,需要首先遍历其下css_set,然后再遍历css_set中的task;
2.3.2 init_css_set
这是系统的初始css_set; init_css_set中的css设置在cgroup_subsys_init(),不过,此时这个css来自cgrp_dfl_root;在cgroup1的目录被mount起来之后,会执行rebind_subsys(),将css切换到cgroup1的目录树root cgroup,参考如下代码:
cgroup_init_subsys()
---
ss->root = &cgrp_dfl_root;
css = ss->css_alloc(cgroup_css(&cgrp_dfl_root.cgrp, ss));
init_and_link_css(css, ss, &cgrp_dfl_root.cgrp);
...
init_css_set.subsys[ss->id] = css;
---
cgroup1_mount()
-> cgroup_setup_root()
-> rebind_subsystems()
---
do_each_subsys_mask(ss, ssid, ss_mask) {
struct cgroup_root *src_root = ss->root;
struct cgroup *scgrp = &src_root->cgrp;
struct cgroup_subsys_state *css = cgroup_css(scgrp, ss);
/* rebind */
RCU_INIT_POINTER(scgrp->subsys[ssid], NULL);
rcu_assign_pointer(dcgrp->subsys[ssid], css);
ss->root = dst_root;
css->cgroup = dcgrp;
...
}
---
init_task的task_struct.cgroups会被赋值为init_css_set,之后,新fork的任务会获得父任务的cset,参考以下代码:
cgroup_post_fork()
---
if (use_task_css_set_links) {
struct css_set *cset;
spin_lock_irq(&css_set_lock);
cset = task_css_set(current); //注意,这里是current,而不是child
if (list_empty(&child->cg_list)) {
get_css_set(cset);
cset->nr_tasks++;
css_set_move_task(child, NULL, cset, false);
}
spin_unlock_irq(&css_set_lock);
}
---
css_set_move_task()会将cset付给cgroup->cgroups,并将任务task_cgroup.cg_list链接到css_set.tasks中。
注:此处的操作都是在css_set_lock中,一个全局spin_lock,在不挂载cgroup目录的情况下,use_task_css_set_links是false
- 所有新创建的任务,都会获得其父任务的css_set,并抓取一个引用计数,同时挂入css_set.task list;
- 所有任务的默认css_set是init_css_set
2.3.3 css_set_table
系统中所有的css_set都保存在哈希表css_set_table中,一个任务再被attach到一个cgroup之后,需要选择一个匹配的css_set,匹配规则如下:
- 匹配条件主要有两个:css模板和dest cgroup;css模板是在任务原css_set.subsys[]基础上,使用dest cgroup的cgroup_e_css()覆盖对应子系统的css;参考函数,find_existing_css_set();举个例子,一个在root下的任务被放到blkio:test下,它的css模板就是(css_blkio_test, css_sched_root, css_mem_root...),原来的css_blkio_root被css_blkio_test覆盖了;
- css模板的必须匹配,也就是说css_set.subsys[]必须是(css_blkio_test, css_sched_root, css_mem_root...);
- css对应的cgroup也必须相同,在cgroupv2下,如果子cgroup目录没有使用某个控制器,其会使用父目录的,这就导致一个css会被多个cgroup共享;dest cgroup自然必须被考虑在内,如下:
compare_css_sets()
---
if (memcmp(template, cset->subsys, sizeof(cset->subsys)))
return false;
l1 = &cset->cgrp_links;
l2 = &old_cset->cgrp_links;
while (1) {
...
l1 = l1->next;
l2 = l2->next;
link1 = list_entry(l1, struct cgrp_cset_link, cgrp_link);
link2 = list_entry(l2, struct cgrp_cset_link, cgrp_link);
cgrp1 = link1->cgrp;
cgrp2 = link2->cgrp;
//如果root相同就代表位于相同的层级中,此时使用dest cgroup比较
if (cgrp1->root == new_cgrp->root) {
if (cgrp1 != new_cgrp)
return false;
} else {
if (cgrp1 != cgrp2)
return false;
}
}
---
2.3.4 引用关系
我们已经知道cset中集合了cgroup、css和task,他们之间引用关系何时建立何时解除?
- task引用css_set,参考函数cgroup_post_fork()和cgroup_migrate_execute(),task_struct.cgroups保存cset,并抓取一个cset计数;这个计数会在任务退出(cgroup_free)或者任务迁移cgroup(css_set_move_task)时释放;css_set.tasks与task_struct.cg_list之间的链接关系在cgroup_release()解除;
- css_set引用css,参考函数find_css_set()和put_css_set_locked(),css_set在建立和销毁过程中会将获取和释放css的引用计数;
- css_set引用cgroup,参考find_css_set()->link_css_set()和put_css_set_locked(),css_set和cgroup会通过css_set_link建立链接,css_set会持有一个cgroup的引用计数;
综上,他们之间的引用关系,是这样的:
/ (blkio_css, mem_css, sched_css...)
task_struct.cgrpups - css_set
\ cgroup
cgroup - css (online_cnt)