Cgroup核心代码分析

(未完待续,持续更新中)

 

目录

2 数据结构 

2.2 cgroup_subsys_state

2.2.1 概述

2.2.2 cgroup和css

2.3 css_set

2.3.1 概述

2.3.2  init_css_set

2.3.3 css_set_table

2.3.4 引用关系


1 散件箱子

这一小节用来放一些又散又短的内容;

1.1 cgroup_namespace

参考链接:cgroup_namespaces(7) - Linux manual pageicon-default.png?t=N6B9https://man7.org/linux/man-pages/man7/cgroup_namespaces.7.html

 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)

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值