cgroup内核实现

Time: 二月 20, 2015
Category: cgroup

cgroup模块里比较重要的数据结构是:struct cgroup, struct css_set, struct cg_cgroup_link,搞清楚这三个数据结构之间的关系,基本就可以了解内核是怎么管理cgroup的了。关于cgroup的一些资料,可以参考redhat写的介绍:

>> Relationships Between Subsystems, Hierarchies, Control Groups and Tasks

具体来说:

  • 一个struct cgroup结构对应hierarchy树中一个cgroup结点
  • struct css_set是一个进程共享的数据结构,用来描述一个进程attach了哪些cgroup。很明显,通常情况下,某一组进程都会attach到相同的cgroup子系统里去,所以为了加快进程fork/exit的速度,没有必要每个进程都单独维护一个css_set。
  • struct cg_cgroup_link结构用来描述struct css_set和struct cgroup之间的关系,因为一个进程可能attach到不同的cgroup,同时一个cgroup里也会有不同的进程,所以css_set和cgroup之间是多对多的关系。

另外还有几个关键的数据结构,struct cgroup_subsys_state和struct cgroup_subsys,其中struct cgroup_subsys为所有子系统定义了一个统一的接口,这个接口要求子系统提供create()/destroy()/attach()等基本方法,例如一个freezer子系统的接口声明如下:

struct cgroup_subsys freezer_subsys = {
	.name		= "freezer",
	.create		= freezer_create,
	.destroy	= freezer_destroy,
	.populate	= freezer_populate,
	.subsys_id	= freezer_subsys_id,
	.can_attach	= freezer_can_attach,
	.attach		= NULL,
	.fork		= freezer_fork,
	.exit		= NULL,
};

而struct cgroup_subsys_state结构则描述了一个cgroup下某个子系统相关的状态,因为cgroup上可能有N个子系统,所以通常struct cgroup和struct cgroup_subsys_state是1对多的关系。struct cgroup结构包含一个指向struct cgroup_subsys_state的指针:

struct cgroup {
        /* ... */

	/* Private pointers for each registered subsystem */
	struct cgroup_subsys_state *subsys[CGROUP_SUBSYS_COUNT];

        /* ... */
};

1、初始化

首先:

1、子进程会默认继承父进程的cgroup

2、子cgroup会默认继承父cgroup的所有属性

当内核刚启动的时候,还没有任何进程,也没有任何子系统被挂载,内核会初始化以下几个数据:

  1. init_task,初始进程
  2. rootnode,初始的hierarchy根节点,包含所有的cgroup子系统
  3. init_css_set,初始的struct css_set结构,关联init_task和rootnode

做完这三件事情,内核为cgroup的所有操作提供了一个统一的视图。

2、构建hierarchy,cgroup子系统挂载

每一个hierarchy都是一个文件系统

static struct file_system_type cgroup_fs_type = {
	.name = "cgroup",
	.mount = cgroup_mount,
	.kill_sb = cgroup_kill_sb,
};

要创建一个hierarchy,执行mount命令:

mount -t cgroup -o cpu,memory cpu_and_mem /cgroup/cpu_and_mem

这个命令创建了一个叫cpu_and_mem的hierarchy,包含cpu和memory子系统。

cgroup_mount函数的执行流程是:

  1. 调用 parse_cgroupfs_options() 分析文件系统参数,主要是要知道挂载哪些cgroup子系统
  2. cgroup_get_rootdir() 创建一个root节点,和系统boot时初始化的rootnode一样
  3. 调用 rebind_subsystems() 初始化hierarchy相关联的cgroup子系统。rebind_subsystems()是一个通用的子函数,cgroup_mount/cgroup_remount都要用到它,所以它需要处理两个情况,一个是需要增加哪些子系统,又有哪些子系统是需要删除的。比如我可以通过remount将原本包含cpu/memory子系统的hierarchy变成只包含cpu的一个hierarchy
  4. 将cgroup与当前全局变量css_set_table里的所有css_set关联(创建cgrp_css_link)。这也是为什么当一个hierarchy构建后,你能通过tasks文件看到系统的所有进程

因为hierarchy是一个文件系统,所以cgroup_get_rootdir()得到的节点是文件系统的根目录:

static int cgroup_get_rootdir(struct super_block *sb)
{
	static const struct dentry_operations cgroup_dops = {
		.d_iput = cgroup_diput,
		.d_delete = cgroup_delete,
	};

	struct inode *inode =
		cgroup_new_inode(S_IFDIR | S_IRUGO | S_IXUGO | S_IWUSR, sb);

	if (!inode)
		return -ENOMEM;

	inode->i_fop = &simple_dir_operations;
	inode->i_op = &cgroup_dir_inode_operations;
	/* directories start off with i_nlink == 2 (for "." entry) */
	inc_nlink(inode);
	sb->s_root = d_make_root(inode);
	if (!sb->s_root)
		return -ENOMEM;
	/* for everything else we want ->d_op set */
	sb->s_d_op = &cgroup_dops;
	return 0;
}

这个目录的操作函数是cgroup_dir_inode_operations,这个函数在后续的创建cgroup时候需要用到:

static const struct inode_operations cgroup_dir_inode_operations = {
	.lookup = cgroup_lookup,
	.mkdir = cgroup_mkdir,
	.rmdir = cgroup_rmdir,
	.rename = cgroup_rename,
	.setxattr = cgroup_setxattr,
	.getxattr = cgroup_getxattr,
	.listxattr = cgroup_listxattr,
	.removexattr = cgroup_removexattr,
};

3、创建/删除cgroup

有了hierarchy之后,创建cgroup是很简单的事情,只需要在hierarchy下面mkdir即可,这个操作会触发cgroup_mkdir函数,cgroup_mkdir()函数的执行流程是:

  1. 创建struct cgroup
  2. 为每一个子系统创建一个struct cgroup_subsys_state,这里的子系统是继承过来的。同一个hierarchy里的所有cgroup包含相同的子系统。
  3. 调用 cgroup_create_file() 为当前目录创建一个inode,这个新的inode定义了当前目录下的文件和目录操作接口
    1. 文件: cgroup_file_operations/cgroup_file_inode_operations
    2. 目录:cgroup_dir_inode_operations/simple_dir_operations,这个和hierarchy根目录的操作函数是一致的。因为对于一个hierarchy而言,mkdir本身就是创建一个cgroup
  4. 调用cgroup_populate_dir() 创建一些必须的cgroup文件,例如cgroup.procs/tasks等等。子系统的文件由子系统模块创建,内核通过调用子系统的接口来完成

创建cgroup_subsys_state的过程如下,通过init_cgroup_css()函数将css和cgrp关联起来:

	for_each_subsys(root, ss) {
		struct cgroup_subsys_state *css;

		css = ss->css_alloc(cgrp);
		if (IS_ERR(css)) {
			err = PTR_ERR(css);
			goto err_free_all;
		}
                // 这里将css和cgrp关联起来
                init_cgroup_css(css, ss, cgrp);
		if (ss->use_id) {
			err = alloc_css_id(ss, parent, cgrp);
			if (err)
				goto err_free_all;
		}
	} 

其中cgroup_file_operations的定义如下:

static const struct file_operations cgroup_file_operations = {
    .read = cgroup_file_read,
    .write = cgroup_file_write,
    .llseek = generic_file_llseek,
    .open = cgroup_file_open,
    .release = cgroup_file_release,
};

这些函数主要实现cgroup文件的读写,子系统各种参数的设置和读取都是通过这些函数来完成的。

删除cgroup的过程主要由cgroup_rmdir()函数完成,事实上大部分工作都在cgroup_destroy_locked()函数里处理掉了:

  1. 检查是否存在子cgroup,被删除的cgroup必须在hierarchy的叶子节点上
  2. 删除所有的struct cgroup_subsys_state,如上,在cgroup创建时创建了这些数据结构
  3. 删除cgroup,解除所有的依赖关系

4、遍历进程

我们知道通过cgroup的tasks文件能够遍历当前cgroup里所有attached的进程。但是cgroup本身并不存储这些信息,而是通过相当复杂的引用关系,和最上图一样,每个cgroup包含一个cg_cgroup_link链表,而每个cg_cgroup_link关联一个css_set结构,因为前面我们说过,css_set结构是进程共享的,css_set通过维护一个进程链表。

unnamed0

cgroup的文件定义原型是struct cftype,tasks文件的定义是:

static struct cftype files[] = {
	{
		.name = "tasks",
		.open = cgroup_tasks_open,
		.write_u64 = cgroup_tasks_write,
		.release = cgroup_pidlist_release,
		.mode = S_IRUGO | S_IWUSR,
	},
}

从cgroup_task_open函数分析最终我们可以知道,进程的遍历过程如下:

  1. pidlist_array_load()函数,得到所有的进程列表。函数使用了一个特殊的迭代器cgroup_iter来完成上图这种复杂关系的遍历,和普通的链表迭代不同
  2. cgroup_pidlist_seq_operations 将tasks文件作为seq_file输出给用户

简单介绍一下cgroup_iter迭代的方法:遍历css_sets列表上的每个css_set(通过cg_cgroup_link关联),对每个css_set,遍历其上的每个进程。

struct task_struct *cgroup_iter_next(struct cgroup *cgrp,
					struct cgroup_iter *it)
{
	struct task_struct *res;
	struct list_head *l = it->task;
	struct cg_cgroup_link *link;

	/* If the iterator cg is NULL, we have no tasks */
	if (!it->cg_link)
		return NULL;
	res = list_entry(l, struct task_struct, cg_list);
	/* Advance iterator to find next entry */
	l = l->next;
	link = list_entry(it->cg_link, struct cg_cgroup_link, cgrp_link_list);
	if (l == &link->cg->tasks) {
		/* We reached the end of this task list - move on to
		 * the next cg_cgroup_link */
		cgroup_advance_iter(cgrp, it);
	} else {
		it->task = l;
	}
	return res;
}

Leave a Comment