Cgroup设计原理分析
CGroups的源代码是清楚的,并且可以从过程的角度分析CGroups相关数据结构之间的关系。 在Linux上,管理进程的数据结构为task_struct,与cgroups相关的代码如清单8所示。
清单8.task_struct代码
#ifdef CONFIG_CGROUPS
/* controlgroupinfoprotectedbycss _ set _ lock * /
struct css_set *cgroups;
/* CG _ listprotectedbycss _ set _ lockandtsk-alloc _ lock * /
struct list_head cg_list;
#endif
其中,cgroups指针指向css_set结构,css_set包含有关进程的cgroups信息。 cg_list是一个list_head结构,用于将连接到同一css_set的进程组织到一个链表中。 让我们看一下css_set的结构,如清单9所示。
清单9.css_set代码
struct css_set {
atomic_t refcount;
struct hlist_node hlist;
struct list_head tasks;
struct list_head cg_links;
struct cgroup _ subsys _ state * subsys [ cgroup _ subsys _ count ];
struct rcu_head rcu_head;
(;
其中refcount是对css_set的引用计数。 因为如果cgroups信息相同,则css_set可以由多个进程共同使用。 例如,所有创建的级别都位于同一个cgroup中的进程。 hlist是一个内置的hlist_node,用于将所有css_set组织到一个hash表中,以便内核可以快速查找特定的css_set。 tasks是一个链表,连接到此css_set的所有进程都连接在一起。 cg_links是指由struct_cg_cgroup_link连接的链表。
Subsys是包含一组指向cgroup_subsys_state的指针的指针数组。 cgroup_subsys_state是进程与特定子系统有关的信息。 通过该指针数组,过程可以获得适当的cgroups控制信息。
cgroup_subsys_state结构如列表10所示。
清单10.cgroup_subsys_state代码
struct cgroup_subsys_state {
struct cgroup *cgroup;
atomic_t refcnt;
无符号长标志;
struct css_id *id;
(;
cgroup指针指向cgroup结构,即进程所属的cgroup。 过程由子系统控制,实际上是通过加入特定的cgroup实现的。 因为cgroup处于特定级别,子系统调谐在其上。
通过以上三种结构,进程可以与cgroup连接。 这是task _ struct-CSS _ set-cgroup _ subsys _ state-cgroup。 cgroup结构如清单11所示:
清单11.cgroup代码
struct cgroup {
无符号长标志;
atomic_t count;
struct list_head sibling;
struct list_head children;
结构cgroup * parent;
struct dentry *dentry;
struct cgroup _ subsys _ state * subsys [ cgroup _ subsys _ count ];
struct cgroupfs_root *root;
struct cgroup *top_cgroup;
struct list_head css_sets;
struct list_head release_list;
struct list_head pidlists;
struct mutex pidlist_mutex;
struct rcu_head rcu_head;
struct list_head event_list;
spinlock_t event_list_lock;
(;
sibling、children和parent这三个嵌入的list_head负责将统一级别的cgroup连接到一个cgroup树。
subsys是指针数组
,存储一组指向cgroup_subsys_state的指针。这组指针指向了此cgroup跟各个子系统相关的信息,这个跟css_set中的道理是一样的。root指向了一个cgroupfs_root的结构,就是cgroup所在的层级对应的结构体。这样一来,之前谈到的几个cgroups概念就全部联系起来了。
top_cgroup指向了所在层级的根cgroup,也就是创建层级时自动创建的那个cgroup。
css_set指向一个由struct_cg_cgroup_link连成的链表,跟css_set中cg_links一样。
下面分析一个css_set和cgroup之间的关系,cg_cgroup_link的结构如清单12所示:
清单12.cg_cgroup_link代码
struct cg_cgroup_link {
struct list_head cgrp_link_list;
struct cgroup *cgrp;
struct list_head cg_link_list;
struct css_set *cg; };
cgrp_link_list连入到cgrouo->css_set指向的链表,cgrp则指向此cg_cgroup_link相关的cgroup。
cg_link_list则连入到css_set->cg_lonks指向的链表,cg则指向此cg_cgroup_link相关的css_set。
cgroup和css_set是一个多对多的关系,必须添加一个中间结构来将两者联系起来,这就是cg_cgroup_link的作用。cg_cgroup_link中的cgrp和cg就是此结构提的联合主键,而cgrp_link_list和cg_link_list分别连入到cgroup和css_set相应的链表,使得能从cgroup或css_set都可以进行遍历查询。
那为什么cgroup和css_set是多对多的关系呢?
一个进程对应一个css_set,一个css_set存储了一组进程(有可能被多个进程共享,所以是一组)跟各个子系统相关的信息,但是这些信息由可能不是从一个cgroup那里获得的,因为一个进程可以同时属于几个cgroup,只要这些cgroup不在同一个层级。举个例子:我们创建一个层级A,A上面附加了cpu和memory两个子系统,进程B属于A的根cgroup;然后我们再创建一个层级C,C上面附加了ns和blkio两个子系统,进程B同样属于C的根cgroup;那么进程B对应的cpu和memory的信息是从A的根cgroup获得的,ns和blkio信息则是从C的根cgroup获得的。因此,一个css_set存储的cgroup_subsys_state可以对应多个cgroup。另一方面,cgroup也存储了一组cgroup_subsys_state,这一组cgroup_subsys_state则是cgroup从所在的层级附加的子系统获得的。一个cgroup中可以有多个进程,而这些进程的css_set不一定都相同,因为有些进程可能还加入了其他cgroup。但是同一个cgroup中的进程与该cgroup关联的cgroup_subsys_state都受到该cgroup的管理(cgroups中进程控制是以cgroup为单位的)的,所以一个cgroup也可以对应多个css_set。
从前面的分析,我们可以看出从task到cgroup是很容易定位的,但是从cgroup获取此cgroup的所有的task就必须通过这个结构了。每个进程都回指向一个css_set,而与这个css_set关联的所有进程都会链入到css_set->tasks链表,而cgroup又通过一个中间结构cg_cgroup_link来寻找所有与之关联的所有css_set,从而可以得到与cgroup关联的所有进程。最后,我们看一下层级和子系统对应的结构体。层级对应的结构体是
cgroupfs_root如清单13所示:
清单13.cgroupfs_root代码
struct cgroupfs_root {
struct super_block *sb;
unsigned long subsys_bits;
int hierarchy_id;
unsigned long actual_subsys_bits;
struct list_head subsys_list;
struct cgroup top_cgroup;
int number_of_cgroups;
struct list_head root_list;
unsigned long flags;
char release_agent_path[PATH_MAX];
char name[MAX_CGROUP_ROOT_NAMELEN];
};
sb指向该层级关联的文件系统数据块。
subsys_bits和actual_subsys_bits分别指向将要附加到层级的子系统和现在实际附加到层级的子系统,在子系统附加到层级时使用。hierarchy_id是该层级唯一的id。top_cgroup指向该层级的根cgroup。number_of_cgroups记录该层级cgroup的个数。root_list是一个嵌入的list_head,用于将系统所有的层级连成链表。子系统对应的结构体是cgroup_subsys,代码如清单14所示。
清单14. cgroup_subsys代码
struct cgroup_subsys {
struct cgroup_subsys_state *(*create)(struct cgroup_subsys *ss, struct cgroup *cgrp);
int (*pre_destroy)(struct cgroup_subsys *ss, struct cgroup *cgrp);
void (*destroy)(struct cgroup_subsys *ss, struct cgroup *cgrp);
int (*can_attach)(struct cgroup_subsys *ss, struct cgroup *cgrp, struct task_struct *tsk, bool threadgroup);
void (*cancel_attach)(struct cgroup_subsys *ss, struct cgroup *cgrp, struct task_struct *tsk, bool threadgroup);
void (*attach)(struct cgroup_subsys *ss, struct cgroup *cgrp, struct cgroup *old_cgrp, struct task_struct *tsk, bool threadgroup);
void (*fork)(struct cgroup_subsys *ss, struct task_struct *task);
void (*exit)(struct cgroup_subsys *ss, struct task_struct *task);
int (*populate)(struct cgroup_subsys *ss, struct cgroup *cgrp);
void (*post_clone)(struct cgroup_subsys *ss, struct cgroup *cgrp);
void (*bind)(struct cgroup_subsys *ss, struct cgroup *root);
int subsys_id;
int active;
int disabled;
int early_init;
bool use_id;
#define MAX_CGROUP_TYPE_NAMELEN 32
const char *name;
struct mutex hierarchy_mutex;
struct lock_class_key subsys_key;
struct cgroupfs_root *root;
struct list_head sibling;
struct idr idr;
spinlock_t id_lock;
struct module *module;
};
cgroup_subsys定义了一组操作,让各个子系统根据各自的需要去实现。这个相当于C++中抽象基类,然后各个特定的子系统对应cgroup_subsys则是实现了相应操作的子类。类似的思想还被用在了cgroup_subsys_state中,cgroup_subsys_state并未定义控制信息,而只是定义了各个子系统都需要的共同信息,比如该cgroup_subsys_state从属的cgroup。然后各个子系统再根据各自的需要去定义自己的进程控制信息结构体,最后在各自的结构体中将cgroup_subsys_state包含进去,这样通过Linux内核的container_of等宏就可以通过cgroup_subsys_state来获取相应的结构体。
从基本层次顺序定义上来看, 由task_struct、css_set、cgroup_subsys_state、cgroup、cg_cgroup_link、cgroupfs_root、cgroup_subsys等结构体组成的CGroup可以基本从进程级别反应之间的响应关系。后续文章会针对文件系统、各子系统做进一步的分析。
结束语
就象大多数开源技术一样,CGroup不是全新创造的,它将进程管理从cpuset中剥离出来。通过物理限制的方式为进程间资源控制提供了简单的实现方式,为Linux Container技术、虚拟化技术的发展奠定了技术基础,本文的目标是让初学者可以通过自己动手的方式简单地理解技术,将起步门槛放低。
发个小广告!!!走过路过,不要错过
注:本公众号与当当店铺并无从属关系,仅为大家提供一个便捷购物地址。若有所冲突,纯属巧合,立删。
麦克叔叔每晚十点说