[知其然不知其所以然-15] cgroup概述

来源:互联网 发布:复杂网络中的幂律分布 编辑:程序博客网 时间:2024/05/01 04:04

cgroup提出的背景是对进程分组,然后以组为单位占用资源并调度。

学习一个内核功能的捷径是通过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.cpus0-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/testroot@Surface-Pro-3:/# ls /sys/fs/cgroup/cpuset/test/cgroup.clone_children  cpuset.memory_pressurecgroup.procs           cpuset.memory_spread_pagecpuset.cpu_exclusive   cpuset.memory_spread_slabcpuset.cpus            cpuset.memscpuset.effective_cpus  cpuset.sched_load_balancecpuset.effective_mems  cpuset.sched_relax_domain_levelcpuset.mem_exclusive   notify_on_releasecpuset.mem_hardwall    taskscpuset.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 paramsparse_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){struct cgroup *cgrp = cgrp_override ?: css->cgroup;if (!css->ss) {if (cgroup_on_dfl(cgrp))cfts = cgroup_dfl_base_files;elsecfts = 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;}}}
可以看出,逻辑被分成两部分,如果css->ss没有赋值(在cgroup系统刚初始化时,css->ss没有赋值),

那么用的是默认的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# lscgroup.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# lscgroup.clone_children  cpuset.cpus            cpuset.mem_hardwall             cpuset.memory_spread_page  cpuset.sched_relax_domain_level  taskscgroup.procs           cpuset.effective_cpus  cpuset.memory_migrate           cpuset.memory_spread_slab  notify_on_release                user.slicecgroup.sane_behavior   cpuset.effective_mems  cpuset.memory_pressure          cpuset.mems                release_agentcpuset.cpu_exclusive   cpuset.mem_exclusive   cpuset.memory_pressure_enabled  cpuset.sched_load_balance  system.slice
可以看出多了一部分cpuset的条目。






0 0
原创粉丝点击