[知其然不知其所以然-15] cgroup概述
2016-01-01 00:30
751 查看
cgroup提出的背景是对进程分组,然后以组为单位占用资源并调度。
学习一个内核功能的捷径是通过sysfs文件。我们这里先不管cgroup内部复杂的数据结构,
直接给出cgroup重要的几个操作。
首先cgroup是被mount后才能使用的:
mount一旦完成,第一个cpuset(也就是根cgroup)默认管理所有cpu,
我们看到这个条目是以cpuset.开头的,说明这个条目是cpuset特有的字段。
在cpuset下还有一些通用字段,不管mount的是cpuset,还是memory等子系统,
都会有的字段,比如进程:
可以在cpuset下通过mkdir创建目录,例如创建一个名字为test的目录:
通过对这些条目的修改,可以指定该cgroup(即test目录)可以管理哪些cpu。
下面分别来看mount,mkdir时发生了什么。
先看mount:
(rw,nosuid,nodev,noexec,relatime,cpuset,clone_children)
则parse_cgroupfs_options会以逗号为分割,获取各个用户参数。
这些参数中最重要的就是subsys_mask,也就是cpuset,表示将要生成的
新cgroup会启用cpuset这个subsystem。
接下来的for_each_root循环是完备性检查,主要就是看用户提供的subsys_mask(cpuset)
是否已经被别的cgroup目录挂载了,如果是的话就返回-EBUSY退出。否则,通过了检查,
就可以新生成一个cgroup根了,这个根比较特殊,是一个包含了cgroup的结构体,因为需要
保存整个树的其他信息,因此被封装成了cgroup_root结构体,接下来对这个cgroup根做进一步的
初始化,调用cgroup_setup_root(root, opts.subsys_mask)的意思是,把系统中对应opts.subsys_mask
的所有subsys系统,都attach到root指向的cgroup根上。
subsys系统,也就是cpuset,memory,devices等,在我们的例子里是cpuset,然后cpuset的初始化
要提前于cgroup的初始化,其实就是一个静态全局变量:
注意,任意一个subsys,都只能和一个
cgroup 根/tree结合,所以如果subsys_mask对应的subsys已经被绑定到其他的cgroup_root了,那么需要先解除
绑定,再绑定到本次指定的root上,也就是cgroup_setup_root要完成的事:
跟这个回调相关的函数集,来源是subsys的回调,对cpuset来说,这些回调来源于上面定义的cpuset_cgrp_subsys
的legacy_cftypes文件。
那么用的是默认的cgroup_dfl_base_files或者cgroup_legacy_base_files,然后添加到cgroup的对象;
如果css->ss已经赋值了,那么在此基础上还要添加跟子系统相关的css的回调,例如cpuset.开头的成员。
虽然我们整个流程说的是mount流程,为了说明cgroup_setup_root的使用,我们先提一下cgroup初始化函数,
因为这个函数里才会出现css->ss没有赋值的情况:
我们看到cgroup_init一来就调用了cgroup_setup_root,而mask为0,说明只是想
初始化一下cgrp_dfl_root这个cgroup根的回调,并不想跟具体的子系统挂钩。
接下来cgroup_init_subsys(ss, false);会初始化所有的subsys,即为每个subsys分配css,
并将css->ss指回所属的subsys:
接下来是尝试把该subsys对应回调,添加到该subsys包含的css中去,
对cpuset来说,只有一个legacy_ctypes回调,因此将尝试把legacy_cftype添加
到subsys,即添加到ss的cfts链表:
的时候,就会把subsys的cfts添加到cgroup的sysfs成员里了。
总结一下,但系统在cgroup初始化时,先调用cgroup_setup_root(&cgrp_dfl_root, 0);,
则生成默认的回调cgroup_dfl_base_files,包含cgroup.controllers,cgroup.events等子条目;
cgroup初始化完毕,用户mount子系统时,例如的cpuset,则子sysfs条目再加上加上cpuset自己的条目,
即cpuset.cpus,cpuset.mems等。
再回到mount流程,还是cgroup_setup_root函数,我们再把这个函数列出来:
在mount 流程中,css_populate_dir执行完毕后,cpuset目录已经生成了必要的sysfs条目,接下来,
是redind_subsystems。这个函数的作用是什么?是用新生成的这个cgroup根,接管系统里指定ss_mask
的子系统。注意,在cgroup_init_subsys中,是subsys(ss)和css(cgroup subsys state)关联起来了,但跟新生成的
cgroup还没有联系起来。具体和subsys子系统的绑定是靠rebind_subsystems:
首先我们强调了很多次,rebind函数的意思是把submask对应的子系统,全部重新绑定到指定根cgroup上,
假设有两个变量A和B,和一个公用变量C,初始C保存了A的信息,我们要把C的值改成B,并清理A的数据。
于是我们可以分三步,先把A的值拷贝给B,然后可以安全的销毁A,最后把C指向B,这就是rebind函数的思想。
遍历当前系统中的指定待接管subsys,将他们的参数(即各子系统css下面的sysfs回调)赋值给将要替换的cgroup的sysfs,
接着第二次遍历这些子系统,将他们的css下的sysfs条目清空,接着将css应该包含的条目指向新cgroup,最后
将原src_root 即cgroup根的相应子系统掩码去掉,进而由新cgroup根来接管(比如,本来dir A管辖cpuset和memory,然后
dir B要求接管cpuset后,dir A就必须把他子目录里cpuset开头的条目删除,让dir B来生成并管理)这就完成了一次
rebind过程。
还是最早的那个例子,我们曾看到mount的结果:
cgroup on /sys/fs/cgroup/systemd type cgroup (rw,nosuid,nodev,noexec,relatime,xattr,release_agent=/lib/systemd/systemd-cgroups-agent,name=systemd)
root@Surface-Pro-3:/sys/fs/cgroup/systemd# ls
cgroup.clone_children cgroup.procs cgroup.sane_behavior notify_on_release release_agent system.slice tasks user.slic确实都是跟subsys子系统无关的条目,再来看看cpuset的目录:
root@Surface-Pro-3:/sys/fs/cgroup/cpuset# ls
cgroup.clone_children cpuset.cpus cpuset.mem_hardwall cpuset.memory_spread_page cpuset.sched_relax_domain_level tasks
cgroup.procs cpuset.effective_cpus cpuset.memory_migrate cpuset.memory_spread_slab notify_on_release user.slice
cgroup.sane_behavior cpuset.effective_mems cpuset.memory_pressure cpuset.mems release_agent
cpuset.cpu_exclusive cpuset.mem_exclusive cpuset.memory_pressure_enabled cpuset.sched_load_balance system.slice
可以看出多了一部分cpuset的条目。
学习一个内核功能的捷径是通过sysfs文件。我们这里先不管cgroup内部复杂的数据结构,
直接给出cgroup重要的几个操作。
首先cgroup是被mount后才能使用的:
tmpfs on /sys/fs/cgroup type tmpfs (rw,mode=755) cgroup on /sys/fs/cgroup/systemd type cgroup (rw,nosuid,nodev,noexec,relatime,xattr,release_agent=/lib/systemd/systemd-cgroups-agent,name=systemd) time) cgroup on /sys/fs/cgroup/memory type cgroup (rw,nosuid,nodev,noexec,relatime,memory) cgroup on /sys/fs/cgroup/cpu,cpuacct type cgroup (rw,nosuid,nodev,noexec,relatime,cpu,cpuacct) cgroup on /sys/fs/cgroup/net_cls type cgroup (rw,nosuid,nodev,noexec,relatime,net_cls) cgroup on /sys/fs/cgroup/cpuset type cgroup (rw,nosuid,nodev,noexec,relatime,cpuset,clone_children) cgroup on /sys/fs/cgroup/hugetlb type cgroup (rw,nosuid,nodev,noexec,relatime,hugetlb,release_agent=/run/cgmanager/agents/cgm-release-agent.hugetlb) cgroup on /sys/fs/cgroup/devices type cgroup (rw,nosuid,nodev,noexec,relatime,devices) cgroup on /sys/fs/cgroup/freezer type cgroup (rw,nosuid,nodev,noexec,relatime,freezer) cgroup on /sys/fs/cgroup/blkio type cgroup (rw,nosuid,nodev,noexec,relatime,blkio) cgroup on /sys/fs/cgroup/perf_event type cgroup (rw,nosuid,nodev,noexec,relatime,perf_event,release_agent=/run/cgmanager/agents/cgm-release-agent.perf_event)主要关注cpuset:
mount一旦完成,第一个cpuset(也就是根cgroup)默认管理所有cpu,
chenyu@Surface-Pro-3:/$ cat /sys/fs/cgroup/cpuset/cpuset.cpus 0-3
我们看到这个条目是以cpuset.开头的,说明这个条目是cpuset特有的字段。
在cpuset下还有一些通用字段,不管mount的是cpuset,还是memory等子系统,
都会有的字段,比如进程:
chenyu@Surface-Pro-3:/$ cat /sys/fs/cgroup/cpuset/tasks | wc 158 158 572表示cpuset下的根cgroup默认管理所有的task。
可以在cpuset下通过mkdir创建目录,例如创建一个名字为test的目录:
root@Surface-Pro-3:/# mkdir /sys/fs/cgroup/cpuset/test root@Surface-Pro-3:/# ls /sys/fs/cgroup/cpuset/test/ cgroup.clone_children cpuset.memory_pressure cgroup.procs cpuset.memory_spread_page cpuset.cpu_exclusive cpuset.memory_spread_slab cpuset.cpus cpuset.mems cpuset.effective_cpus cpuset.sched_load_balance cpuset.effective_mems cpuset.sched_relax_domain_level cpuset.mem_exclusive notify_on_release cpuset.mem_hardwall tasks cpuset.memory_migrate在创建test目录后,test目录下会自动创建一系列的条目。
通过对这些条目的修改,可以指定该cgroup(即test目录)可以管理哪些cpu。
下面分别来看mount,mkdir时发生了什么。
先看mount:
static struct dentry *cgroup_mount(struct file_system_type *fs_type, int flags, const char *unused_dev_name, void *data) { //get user params parse_cgroupfs_options(data, &opts); for_each_root(root) { if (opts.subsys_mask != root->subsys_mask) if (!name_match) continue; ret = -EBUSY; goto out_unlock; } root = kzalloc(sizeof(*root), GFP_KERNEL); root->cgrp->root = root; cgroup_setup_root(root, opts.subsys_mask); }cgroup_mount先是根据mount的参数获得用户的参数,比如cpuset的参数是:
(rw,nosuid,nodev,noexec,relatime,cpuset,clone_children)
则parse_cgroupfs_options会以逗号为分割,获取各个用户参数。
这些参数中最重要的就是subsys_mask,也就是cpuset,表示将要生成的
新cgroup会启用cpuset这个subsystem。
接下来的for_each_root循环是完备性检查,主要就是看用户提供的subsys_mask(cpuset)
是否已经被别的cgroup目录挂载了,如果是的话就返回-EBUSY退出。否则,通过了检查,
就可以新生成一个cgroup根了,这个根比较特殊,是一个包含了cgroup的结构体,因为需要
保存整个树的其他信息,因此被封装成了cgroup_root结构体,接下来对这个cgroup根做进一步的
初始化,调用cgroup_setup_root(root, opts.subsys_mask)的意思是,把系统中对应opts.subsys_mask
的所有subsys系统,都attach到root指向的cgroup根上。
subsys系统,也就是cpuset,memory,devices等,在我们的例子里是cpuset,然后cpuset的初始化
要提前于cgroup的初始化,其实就是一个静态全局变量:
struct cgroup_subsys cpuset_cgrp_subsys = { .css_alloc = cpuset_css_alloc, .css_online = cpuset_css_online, .css_offline = cpuset_css_offline, .css_free = cpuset_css_free, .can_attach = cpuset_can_attach, .cancel_attach = cpuset_cancel_attach, .attach = cpuset_attach, .bind = cpuset_bind, .legacy_cftypes = files, .early_init = 1, };
注意,任意一个subsys,都只能和一个
cgroup 根/tree结合,所以如果subsys_mask对应的subsys已经被绑定到其他的cgroup_root了,那么需要先解除
绑定,再绑定到本次指定的root上,也就是cgroup_setup_root要完成的事:
static int cgroup_setup_root(struct cgroup_root *root, unsigned long ss_mask) { css_populate_dir(&root_cgrp->self, NULL); rebind_subsystems(root, ss_mask); }上面的两个函数,在cgroup里是很常用的API,其中,css_populate_dir的声明原型是:
css_populate_dir(struct cgroup_subsys_state *css, struct cgroup *cgrp_override)表示为指定的cgroup创建必要的sysfs子选项回调,比如tasks的read/write回调,以及cpus的read/write回调,
跟这个回调相关的函数集,来源是subsys的回调,对cpuset来说,这些回调来源于上面定义的cpuset_cgrp_subsys
的legacy_cftypes文件。
static int css_populate_dir(struct cgroup_subsys_state *css, struct cgroup *cgrp_override)可以看出,逻辑被分成两部分,如果css->ss没有赋值(在cgroup系统刚初始化时,css->ss没有赋值),
{
struct cgroup *cgrp = cgrp_override ?: css->cgroup;
if (!css->ss) {
if (cgroup_on_dfl(cgrp))
cfts = cgroup_dfl_base_files;
else
cfts = cgroup_legacy_base_files;
return cgroup_addrm_files(&cgrp->self, cgrp, cfts, true);
}
list_for_each_entry(cfts, &css->ss->cfts, node) {
ret = cgroup_addrm_files(css, cgrp, cfts, true);
if (ret < 0) {
failed_cfts = cfts;
goto err;
}
}
}
那么用的是默认的cgroup_dfl_base_files或者cgroup_legacy_base_files,然后添加到cgroup的对象;
如果css->ss已经赋值了,那么在此基础上还要添加跟子系统相关的css的回调,例如cpuset.开头的成员。
虽然我们整个流程说的是mount流程,为了说明cgroup_setup_root的使用,我们先提一下cgroup初始化函数,
因为这个函数里才会出现css->ss没有赋值的情况:
int __init cgroup_init(void) { cgroup_setup_root(&cgrp_dfl_root, 0); for_each_subsys(ss, ssid) { cgroup_init_subsys(ss, false); if (ss->dfl_cftypes == ss->legacy_cftypes) { cgroup_add_cftypes(ss, ss->dfl_cftypes); } else { cgroup_add_dfl_cftypes(ss, ss->dfl_cftypes); cgroup_add_legacy_cftypes(ss, ss->legacy_cftypes); } } }
我们看到cgroup_init一来就调用了cgroup_setup_root,而mask为0,说明只是想
初始化一下cgrp_dfl_root这个cgroup根的回调,并不想跟具体的子系统挂钩。
接下来cgroup_init_subsys(ss, false);会初始化所有的subsys,即为每个subsys分配css,
并将css->ss指回所属的subsys:
static void __init cgroup_init_subsys(struct cgroup_subsys *ss, bool early) { ss->root = &cgrp_dfl_root; css = ss->css_alloc(cgroup_css(&cgrp_dfl_root.cgrp, ss)); css->ss = ss; css->cgroup = &cgrp_dfl_root.cgrp; }
接下来是尝试把该subsys对应回调,添加到该subsys包含的css中去,
对cpuset来说,只有一个legacy_ctypes回调,因此将尝试把legacy_cftype添加
到subsys,即添加到ss的cfts链表:
list_add_tail(&cfts->node, &ss->cfts);到这里为止,子系统subsys的cfts文件成员列表就有值了,下一次再执行cgroup_setup_root
的时候,就会把subsys的cfts添加到cgroup的sysfs成员里了。
总结一下,但系统在cgroup初始化时,先调用cgroup_setup_root(&cgrp_dfl_root, 0);,
则生成默认的回调cgroup_dfl_base_files,包含cgroup.controllers,cgroup.events等子条目;
cgroup初始化完毕,用户mount子系统时,例如的cpuset,则子sysfs条目再加上加上cpuset自己的条目,
即cpuset.cpus,cpuset.mems等。
再回到mount流程,还是cgroup_setup_root函数,我们再把这个函数列出来:
static int cgroup_setup_root(struct cgroup_root *root, unsigned long ss_mask) { css_populate_dir(&root_cgrp->self, NULL); rebind_subsystems(root, ss_mask); list_add(&root->root_list, &cgroup_roots); }
在mount 流程中,css_populate_dir执行完毕后,cpuset目录已经生成了必要的sysfs条目,接下来,
是redind_subsystems。这个函数的作用是什么?是用新生成的这个cgroup根,接管系统里指定ss_mask
的子系统。注意,在cgroup_init_subsys中,是subsys(ss)和css(cgroup subsys state)关联起来了,但跟新生成的
cgroup还没有联系起来。具体和subsys子系统的绑定是靠rebind_subsystems:
static int rebind_subsystems(struct cgroup_root *dst_root, unsigned long ss_mask) { for_each_subsys_which(ss, ssid, &tmp_ss_mask) { struct cgroup *scgrp = &ss->root->cgrp; css_populate_dir(cgroup_css(scgrp, ss), dcgrp); } for_each_subsys_which(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); css_clear_dir(css, NULL); RCU_INIT_POINTER(scgrp->subsys[ssid], NULL); rcu_assign_pointer(dcgrp->subsys[ssid], css); ss->root = dst_root; css->cgroup = dcgrp; src_root->subsys_mask &= ~(1 << ssid); dst_root->subsys_mask |= 1 << ssid; if (dst_root == &cgrp_dfl_root) { static_branch_enable(cgroup_subsys_on_dfl_key[ssid]); } else { dcgrp->subtree_control |= 1 << ssid; cgroup_refresh_child_subsys_mask(dcgrp); static_branch_disable(cgroup_subsys_on_dfl_key[ssid]); } } }
首先我们强调了很多次,rebind函数的意思是把submask对应的子系统,全部重新绑定到指定根cgroup上,
假设有两个变量A和B,和一个公用变量C,初始C保存了A的信息,我们要把C的值改成B,并清理A的数据。
于是我们可以分三步,先把A的值拷贝给B,然后可以安全的销毁A,最后把C指向B,这就是rebind函数的思想。
遍历当前系统中的指定待接管subsys,将他们的参数(即各子系统css下面的sysfs回调)赋值给将要替换的cgroup的sysfs,
接着第二次遍历这些子系统,将他们的css下的sysfs条目清空,接着将css应该包含的条目指向新cgroup,最后
将原src_root 即cgroup根的相应子系统掩码去掉,进而由新cgroup根来接管(比如,本来dir A管辖cpuset和memory,然后
dir B要求接管cpuset后,dir A就必须把他子目录里cpuset开头的条目删除,让dir B来生成并管理)这就完成了一次
rebind过程。
还是最早的那个例子,我们曾看到mount的结果:
cgroup on /sys/fs/cgroup/systemd type cgroup (rw,nosuid,nodev,noexec,relatime,xattr,release_agent=/lib/systemd/systemd-cgroups-agent,name=systemd)
cgroup on /sys/fs/cgroup/cpuset type cgroup (rw,nosuid,nodev,noexec,relatime,cpuset,clone_children)其中systemd是没有子系统绑定的,我们到他目录看看:
root@Surface-Pro-3:/sys/fs/cgroup/systemd# ls
cgroup.clone_children cgroup.procs cgroup.sane_behavior notify_on_release release_agent system.slice tasks user.slic确实都是跟subsys子系统无关的条目,再来看看cpuset的目录:
root@Surface-Pro-3:/sys/fs/cgroup/cpuset# ls
cgroup.clone_children cpuset.cpus cpuset.mem_hardwall cpuset.memory_spread_page cpuset.sched_relax_domain_level tasks
cgroup.procs cpuset.effective_cpus cpuset.memory_migrate cpuset.memory_spread_slab notify_on_release user.slice
cgroup.sane_behavior cpuset.effective_mems cpuset.memory_pressure cpuset.mems release_agent
cpuset.cpu_exclusive cpuset.mem_exclusive cpuset.memory_pressure_enabled cpuset.sched_load_balance system.slice
可以看出多了一部分cpuset的条目。
相关文章推荐
- Java多线程学习(单一线程)
- 再看2015 --北漂程序员的成长史
- 再看2015 --北漂程序员的成长史
- shell如何取到文件中某一行某一列的内容
- Ubuntu 配置静态ip的方法
- tar.gz zip 解压缩 压缩命令
- 数理统计知识点归纳
- iOS 消息推送
- 用typedef自定义类型语法你真的会吗
- uva 488 - Triangle Wave
- .gitignore for android studio
- 安装WAMP
- PHP PSR-1 基本代码规范(中文版)
- mysql的主从配置
- Ubuntu安装星际词典
- 一件工作上的小事
- 年终总结
- 在2015-___-2016回望
- 2016
- 2015-2016跨年感想