现在的位置: 首页 > 综合 > 正文

Linux cgroup机制分析之框架分析

2013年08月04日 ⁄ 综合 ⁄ 共 29327字 ⁄ 字号 评论关闭
转载请注明出处:http://ericxiao.cublog.cn

一: 前言

Cgroup是近代linux kernel出现的.它为进程和其后续的子进程提供了一种性能控制机制.在这里不打算对cgroup的作用和使用做过多的描述.本文从linux kernel的源代码出发分析cgroup机制的相关实现.在本节中,主要分析cgroup的框架实现.在后续的部份再来详细分析kernel中的几个重要的subsystem.关于cgroup的使用和介绍可以查看linux-2.6.28-rc7/Documentation/cgroups/cgroup.txt.另外,本文的源代码分析基于linux
kernel 2.6.28版本.分析的源文件基本位于inux-2.6.28-rc7/kernel/cgroup.c和inux-2.6.28-rc7/kernel/debug_cgroup.c中.
二:cgroup中的概念
在深入到cgroup的代码分析之前.先来了解一下cgroup中涉及到的几个概念:
1:cgroup: 它的全称为control group.即一组进程的行为控制.比如,我们限制进程/bin/sh的CPU使用为20%.我们就可以建一个cpu占用为20%的cgroup.然后将/bin/sh进程添加到这个cgroup中.当然,一个cgroup可以有多个进程.
2:subsystem: 它类似于我们在netfilter中的过滤hook.比如上面的CPU占用率就是一个subsystem.简而言之.subsystem就是cgroup中可添加删除的模块.在cgroup架构的封装下为cgroup提供多种行为控制.subsystem在下文中简写成subsys.
3: hierarchy: 它是cgroup的集合.可以把它理解成cgroup的根.cgroup是hierarchy的结点.还是拿上面的例子: 整个cpu占用为100%.这就是根,也就是hierarchy.然后,cgroup A设置cpu占用20%,cgroup B点用50%,cgroup A和cgroup B就是它下面的子层cgroup.
三:cgroup中的重要数据结构
我们先来看cgroup的使用.有三面一个例子:
[root@localhost cgroups]# mount -t cgroup cgroup -o debug /dev/cgroup
[root@localhost cgroups]# mkdir /dev/cgroup/eric_test
如上所示,用debug subsystem做的一个测试. /dev/cgroup是debug subsys的挂载点.也就是我们在上面所分析的hierarchy.然后在hierarchy下又创建了一个名为eric_test的cgroup.
在kernel的源代码中.挂载目录,也就是cgroup的根目录用数据结构struct cgroupfs_root表示.而cgroup用struct cgroup表示.
分别来看一下这两个结构的含义,struct cgroupfs_root定义如下:
struct cgroupfs_root {
//cgroup文件系统的超级块
   struct super_block *sb;
 
    /*
     * The bitmask of subsystems intended to be attached to this
     * hierarchy
     */
     //hierarchy相关联的subsys 位图
    unsigned long subsys_bits;
 
    /* The bitmask of subsystems currently attached to this hierarchy */
    //当前hierarchy 中的subsys位图
    unsigned long actual_subsys_bits;
 
    /* A list running through the attached subsystems */
    //hierarchy中的subsys链表
    struct list_head subsys_list;
 
    /* The root cgroup for this hierarchy */
    //hierarchy中的顶层cgroup
    struct cgroup top_cgroup;
 
    /* Tracks how many cgroups are currently defined in hierarchy.*/
    //hierarchy中cgroup的数目
    int number_of_cgroups;
 
    /* A list running through the mounted hierarchies */
    //用来链入全局链表roots
    struct list_head root_list;
 
    /* Hierarchy-specific flags */
    //hierarchy的标志
    unsigned long flags;
 
    /* The path to use for release notifications. */
    char release_agent_path[PATH_MAX];
};
注意cgroupfs_root中有个struct cgroup结构的成员:top_cgroup.即在每个挂载点下面都会有一个总的cgroup.而通过mkdir创建的cgroup是它的子结点.
其中,release_agent_path[ ]的成员含义.我们在后面再来详细分析.
 
Struct cgroup的定义如下:
struct cgroup {
    //cgroup的标志
    unsigned long flags;        /* "unsigned long" so bitops work */
 
    /* count users of this cgroup. >0 means busy, but doesn't
     * necessarily indicate the number of tasks in the
     * cgroup */
     //引用计数
    atomic_t count;
 
    /*
     * We link our 'sibling' struct into our parent's 'children'.
     * Our children link their 'sibling' into our 'children'.
     */
     //用来链入父结点的children链表
    struct list_head sibling;   /* my parent's children */
    //子结点链表
    struct list_head children;  /* my children */
    //cgroup的父结点
    struct cgroup *parent;  /* my parent */
    //cgroup所处的目录
    struct dentry *dentry;      /* cgroup fs entry */
 
    /* Private pointers for each registered subsystem */
    struct cgroup_subsys_state *subsys[CGROUP_SUBSYS_COUNT];
    //cgroup所属的cgroupfs_root
    struct cgroupfs_root *root;
    //挂载目录下的最上层cgroup
    struct cgroup *top_cgroup;
    ……
……
}
上面并没有将cgroup的结构全部都列出来.其它的全部我们等遇到的时候再来进行分析.
其实,struct cgroupfs_root和struct cgroup就是表示了一种空间层次关系,它就对应着挂着点下面的文件示图.
 
在上面说过了,cgroup表示进程的行为控制.因为subsys必须要知道进程是位于哪一个cgroup.
所以.在struct task_struct和cgroup中存在一种映射.
Cgroup在struct task_struct中增加了两个成员,如下示:
struct task_struct {
    ……
    ……
#ifdef CONFIG_CGROUPS
    /* Control Group info protected by css_set_lock */
    struct css_set *cgroups;
    /* cg_list protected by css_set_lock and tsk->alloc_lock */
    struct list_head cg_list;
#endif
    ……
    ……
}
注意struct task_struct中并没有一个直接的成员指向cgroup,而是指向了css_set.css_set的结构如下:
struct css_set {
    //css_set引用计数
    atomic_t refcount;
//哈希指针.指向css_set_table[ ]
    struct hlist_node hlist;
//与css_set关联的task链表
    struct list_head tasks;
    //与css_set关联的cg_cgroup_link链表
    struct list_head cg_links;
//一组subsystem states.由subsys->create()创建而成
    struct cgroup_subsys_state *subsys[CGROUP_SUBSYS_COUNT];
}
那从css_set怎么转换到cgroup呢? 再来看一个辅助的数据结构.struct cg_cgroup_link.它的定义如下:
struct cg_cgroup_link {
    /*
     * List running through cg_cgroup_links associated with a
     * cgroup, anchored on cgroup->css_sets
     */
    struct list_head cgrp_link_list;
    /*
     * List running through cg_cgroup_links pointing at a
     * single css_set object, anchored on css_set->cg_links
     */
    struct list_head cg_link_list;
    struct css_set *cg;
};
如上所示.它的cgrp_link_list链入到了cgroup->css_sets. Cg_link_list链入到css_set->cg_links.
其中.cg就是批向cg_link_list所指向的css_set.
 
上面分析的几个数据结构关系十分复杂.联系也十分紧密.下面以图示的方式直观将各结构的联系表示如下:
 
注意上图中的css_set_table[ ].它是一个哈希数组.用来存放struct css_set.它的哈希函数为css_set_hash().所有的冲突项都链入数组对应项的hlist.
 
四:cgroup初始化
Cgroup的初始化包括两个部份.即cgroup_init_early()和cgroup_init().分别表示在系统初始时的初始化和系统初始化完成时的初始化.分为这两个部份是因为有些subsys是要在系统刚启动的时候就必须要初始化的.
 
4.1: cgroup_init_early()
先看cgroup_init_early()的代码:
int __init cgroup_init_early(void)
{
    int i;
    //初始化全局量init_css_set
    atomic_set(&init_css_set.refcount, 1);
    INIT_LIST_HEAD(&init_css_set.cg_links);
    INIT_LIST_HEAD(&init_css_set.tasks);
    INIT_HLIST_NODE(&init_css_set.hlist);
    //css_set_count:系统中struct css_set计数
    css_set_count = 1;
    //初始化全局变量rootnode
    init_cgroup_root(&rootnode);
    //将全局变量rootnode添加到roots链表
    list_add(&rootnode.root_list, &roots);
    root_count = 1;
    //使系统的初始化进程cgroup指向init_css_set
    init_task.cgroups = &init_css_set;
    //将init_css_set和rootnode.top_cgroup关联起来
    init_css_set_link.cg = &init_css_set;
    list_add(&init_css_set_link.cgrp_link_list,
         &rootnode.top_cgroup.css_sets);
    list_add(&init_css_set_link.cg_link_list,
         &init_css_set.cg_links);
    //初始化css_set_table[ ]
    for (i = 0; i < CSS_SET_TABLE_SIZE; i++)
        INIT_HLIST_HEAD(&css_set_table[i]);
    //对一些需要在系统启动时初始化的subsys进行初始化
    for (i = 0; i < CGROUP_SUBSYS_COUNT; i++) {
        struct cgroup_subsys *ss = subsys[i];
 
        BUG_ON(!ss->name);
        BUG_ON(strlen(ss->name) > MAX_CGROUP_TYPE_NAMELEN);
        BUG_ON(!ss->create);
        BUG_ON(!ss->destroy);
        if (ss->subsys_id != i) {
            printk(KERN_ERR "cgroup: Subsys %s id == %d\n",
                   ss->name, ss->subsys_id);
            BUG();
        }
 
        if (ss->early_init)
            cgroup_init_subsys(ss);
    }
    return 0;
}
这里主要是初始化init_task.cgroup结构.伴随着它的初始化.相继需要初始化rootnode和init_css_set.接着,又需要初始化init_css_set_link将rootnode.top_cgroup和init_css_set关联起来.
接着初始化了哈希数组css_set_table[]并且将一些需要在系统刚启动时候需要初始化的subsys进行初始化.
从上面的代码可以看到.系统中的cgroup subsystem都存放在subsys[].定义如下:
static struct cgroup_subsys *subsys[] = {
#include <linux/cgroup_subsys.h>
}
即所有的subsys都定义在linux/cgroup_subsys.h中.
 
对照之前分析的数据结构,应该不难理解这段代码.下面来分析一下里面所遇到的一些重要的子函数.
 
Init_cgroup_root()代码如下:
static void init_cgroup_root(struct cgroupfs_root *root)
{
    struct cgroup *cgrp = &root->top_cgroup;
    INIT_LIST_HEAD(&root->subsys_list);
    INIT_LIST_HEAD(&root->root_list);
    root->number_of_cgroups = 1;
    cgrp->root = root;
    cgrp->top_cgroup = cgrp;
    init_cgroup_housekeeping(cgrp);
}
它先初始化root中的几条链表.因为root中有一个top_cgroup.因此将root->number_of_cgroups置为1.然后,对root->top_cgroup进行初始化.使root->top_cgroup.root指向root. root->top_cgroup.top_cgroup指向它的本身.因为root->top_cgroup就是目录下的第一个cgroup.
最后在init_cgroup_housekeeping()初始化cgroup的链表和读写锁.
 
Cgroup_init_subsys()代码如下:
static void __init cgroup_init_subsys(struct cgroup_subsys *ss)
{
    struct cgroup_subsys_state *css;
 
    printk(KERN_INFO "Initializing cgroup subsys %s\n", ss->name);
 
    /* Create the top cgroup state for this subsystem */
    ss->root = &rootnode;
    css = ss->create(ss, dummytop);
    /* We don't handle early failures gracefully */
    BUG_ON(IS_ERR(css));
    init_cgroup_css(css, ss, dummytop);
 
    /* Update the init_css_set to contain a subsys
     * pointer to this state - since the subsystem is
     * newly registered, all tasks and hence the
     * init_css_set is in the subsystem's top cgroup. */
    init_css_set.subsys[ss->subsys_id] = dummytop->subsys[ss->subsys_id];
 
    need_forkexit_callback |= ss->fork || ss->exit;
    need_mm_owner_callback |= !!ss->mm_owner_changed;
 
    /* At system boot, before all subsystems have been
     * registered, no tasks have been forked, so we don't
     * need to invoke fork callbacks here. */
    BUG_ON(!list_empty(&init_task.tasks));
 
    ss->active = 1;
}
dummytop定义如下:
#define dummytop (&rootnode.top_cgroup)
在这个函数中:
1):将每个要注册的subsys->root都指向rootnode.
2):调用subsys->create()生成一个cgroup_subsys_state.
3):调用init_cgroup_css()将dummytop.subsys[i]设置成ss->create()生成的cgroup_subsys_state
4):更新init_css_set->subsys()对应项的值.
5):将ss->active设为1.表示它已经初始化了.
 
4.2: cgroup_init()
cgroup_init()是cgroup的第二阶段的初始化.代码如下:
int __init cgroup_init(void)
{
    int err;
    int i;
    struct hlist_head *hhead;
 
    err = bdi_init(&cgroup_backing_dev_info);
    if (err)
        return err;
    //将剩下的(不需要在系统启动时初始化的subsys)的subsys进行初始化
    for (i = 0; i < CGROUP_SUBSYS_COUNT; i++) {
        struct cgroup_subsys *ss = subsys[i];
        if (!ss->early_init)
            cgroup_init_subsys(ss);
    }
 
    /* Add init_css_set to the hash table */
    //将init_css_set添加到css_set_table[ ]
    hhead = css_set_hash(init_css_set.subsys);
    hlist_add_head(&init_css_set.hlist, hhead);
    //注册cgroup文件系统
    err = register_filesystem(&cgroup_fs_type);
    if (err < 0)
        goto out;
    //在proc文件系统的根目录下创建一个名为cgroups的文件
    proc_create("cgroups", 0, NULL, &proc_cgroupstats_operations);
 
out:
    if (err)
        bdi_destroy(&cgroup_backing_dev_info);
 
    return err;
}
这个函数比较简单.首先.它将剩余的subsys初始化.然后将init_css_set添加进哈希数组css_set_table[ ]中.在上面的代码中css_set_hash()是css_set_table的哈希函数.它是css_set->subsys为哈希键值,到css_set_table[ ]中找到对应项.然后调用hlist_add_head()将init_css_set添加到冲突项中.
然后,注册了cgroup文件系统.这个文件系统也是我们在用户空间使用cgroup时必须挂载的.
最后,在proc的根目录下创建了一个名为cgroups的文件.用来从用户空间观察cgroup的状态.
 
经过cgroup的两个阶段的初始化, init_css_set,rootnode,subsys已经都初始化完成.表面上看起来它们很复杂,其实,它们只是表示cgroup的初始化状态而已.例如,如果subsys->root等于rootnode,那表示subsys没有被其它的cgroup所使用.
五:父子进程之间的cgroup关联
在上面看到的代码中.将init_task.cgroup设置为了init_css_set.我们知道,init_task是系统的第一个进程.所有的过程都是由它创建的.init_task.cgroup到底会在它后面的子进程造成什么样的影响呢?接下来我们就来分析这个问题.
5.1:创建进程时的父子进程cgroup关联
在进程创建的时候,有:do_fork()àcopy_process(),有如下代码片段:
static struct task_struct *copy_process(unsigned long clone_flags,
                    unsigned long stack_start,
                    struct pt_regs *regs,
                    unsigned long stack_size,
                    int __user *child_tidptr,
                    struct pid *pid,
                    int trace)
{
    ……
    ……
    cgroup_fork(p);
    ……
    cgroup_fork_callbacks(p);
    ……
    cgroup_post_fork(p);
    ……
}
上面的代码片段是创建新进程的时候与cgroup关联的函数.挨个分析如下:
void cgroup_fork(struct task_struct *child)
{
    task_lock(current);
    child->cgroups = current->cgroups;
    get_css_set(child->cgroups);
    task_unlock(current);
    INIT_LIST_HEAD(&child->cg_list);
}
如上面代码所示,子进程和父进程指向同一个cgroups.并且由于增加了一次引用.所以要调用get_css_set()来增加它的引用计数.最后初始化child->cg_list链表.
如代码注释上说的,这里就有一个问题了:在dup_task_struct()为子进程创建struct task_struct的时候不是已经复制了父进程的cgroups么?为什么这里还要对它进行一次赋值呢?这里因为在dup_task_struct()中没有持有保护锁.而这里又是一个竞争操作.因为在cgroup_attach_task()中可能会更改进程的cgroups指向.因此通过cgroup_attach_task()所得到的cgroups可能是一个无效的指向.在递增其引用计数的时候就会因为它是一个无效的引用而发生错误.所以,这个函数在加锁的情况下进行操作.确保了父子进程之间的同步.
 
cgroup_fork_callbacks()代码如下,
void cgroup_fork_callbacks(struct task_struct *child)
{
    if (need_forkexit_callback) {
        int i;
        for (i = 0; i < CGROUP_SUBSYS_COUNT; i++) {
            struct cgroup_subsys *ss = subsys[i];
            if (ss->fork)
                ss->fork(ss, child);
        }
    }
}
它主要是在进程创建时调用subsys中的跟踪函数:subsys->fork().
首先来跟踪一下need_forkexita_callback这个变量.在如下代码片段中:
static void __init cgroup_init_subsys(struct cgroup_subsys *ss)
{
    ……
    need_forkexit_callback |= ss->fork || ss->exit;
    ……
}
从这段代码中我们可以看到,如果有subsys定义了fork和exit函数,就会调need_forkexit_callback设置为1.
回到cgroup_fork_callback()这个函数中.我们发现.进程会跟所有定义了fork的subsys进行这次操作.就算进程没有在这个subsys中,也会有这个操作.
 
Cgroup_pos_fork()如下所示:
void cgroup_post_fork(struct task_struct *child)
{
    if (use_task_css_set_links) {
        write_lock(&css_set_lock);
        if (list_empty(&child->cg_list))
            list_add(&child->cg_list, &child->cgroups->tasks);
        write_unlock(&css_set_lock);
    }
在use_task_css_set_link为1的情况下.就将子进程链入到它所指向的css_set->task链表.
那什么时候会将use_task_css_set_link设置为1呢?实际上,当你往cgroup中添加进程的时候就会将其置1了.
例如我们之前举的一个例子中:
echo $$ > /dev/cgroup/eric_task/tasks
这个过程就会将use_task_css_set_link置1了.这个过程我们之后再来详细分析.
 
5.2:子进程结束时的操作
子进程结束的时候,有:
Do_exit() à cgroup_exit().
Cgroup_exit()代码如下:
void cgroup_exit(struct task_struct *tsk, int run_callbacks)
{
    int i;
    struct css_set *cg;
 
    if (run_callbacks && need_forkexit_callback) {
        for (i = 0; i < CGROUP_SUBSYS_COUNT; i++) {
            struct cgroup_subsys *ss = subsys[i];
            if (ss->exit)
                ss->exit(ss, tsk);
        }
    }
 
    /*
     * Unlink from the css_set task list if necessary.
     * Optimistically check cg_list before taking
     * css_set_lock
     */
    if (!list_empty(&tsk->cg_list)) {
        write_lock(&css_set_lock);
        if (!list_empty(&tsk->cg_list))
            list_del(&tsk->cg_list);
        write_unlock(&css_set_lock);
    }
 
    /* Reassign the task to the init_css_set. */
    task_lock(tsk);
    cg = tsk->cgroups;
    tsk->cgroups = &init_css_set;
    task_unlock(tsk);
    if (cg)
        put_css_set_taskexit(cg);
}
这个函数的代码逻辑比较清晰.首先,如果以1为调用参数(run_callbacks为1),且有定义了exit操作的subsys.就调用这个subsys的exit操作.
然后断开task->cg_list链表.将其从所指向的css_set->task链上断开.
最后,断开当前的cgroup指向.将其指向init_css_set.也就是将其回复到初始状态.最后,减少旧指向css_set的引用计数.
 
在这个函数中,我们来跟踪分析put_css_set_taskexit(),代码如下:
static inline void put_css_set_taskexit(struct css_set *cg)
{
    __put_css_set(cg, 1);
}
 
跟踪到__put_css_set()中:
static void __put_css_set(struct css_set *cg, int taskexit)
{
    int i;
    /*
     * Ensure that the refcount doesn't hit zero while any readers
     * can see it. Similar to atomic_dec_and_lock(), but for an
     * rwlock
     */
    if (atomic_add_unless(&cg->refcount, -1, 1))
        return;
    write_lock(&css_set_lock);
    if (!atomic_dec_and_test(&cg->refcount)) {
        write_unlock(&css_set_lock);
        return;
    }
    unlink_css_set(cg);
    write_unlock(&css_set_lock);
 
    rcu_read_lock();
    for (i = 0; i < CGROUP_SUBSYS_COUNT; i++) {
        struct cgroup *cgrp = cg->subsys[i]->cgroup;
        if (atomic_dec_and_test(&cgrp->count) &&
            notify_on_release(cgrp)) {
            if (taskexit)
                set_bit(CGRP_RELEASABLE, &cgrp->flags);
            check_for_release(cgrp);
        }
    }
    rcu_read_unlock();
    kfree(cg);
}
atomic_add_unless(v,a,u)表示如果v的值不为u就加a.返回1.如果v的值等于u就返回0
因此,这个函数首先减小css_set的引用计数.如果css_set的引用计数为1.就会将css_set释放掉了. 要释放css_set.首先要释放css_set上挂载的链表.再释放css_set结构本身所占空间.
释放css_set上的挂载链表是在unlink_css_set()中完成的.代码如下:
static void unlink_css_set(struct css_set *cg)
{
    struct cg_cgroup_link *link;
    struct cg_cgroup_link *saved_link;
 
    hlist_del(&cg->hlist);
    css_set_count--;
 
    list_for_each_entry_safe(link, saved_link, &cg->cg_links,
                 cg_link_list) {
        list_del(&link->cg_link_list);
        list_del(&link->cgrp_link_list);
        kfree(link);
    }
}
它首先将cg->hlist断开,也就是将其从css_set_table[ ]中删除.然后减小css_set_count计数.最后遍历删除与css_set关联的cg_cgroup_link.
另外,在这个函数中还涉及到了notify_on_release的操作.在后面再来详细分析这一过程.这里先把它放一下.
六:cgroup文件系统的挂载
Cgroup文件系统定义如下:
static struct file_system_type cgroup_fs_type = {
    .name = "cgroup",
    .get_sb = cgroup_get_sb,
    .kill_sb = cgroup_kill_sb,
}
根据我们之前有关linux文件系统系列的文析.在挂载文件系统的时候,流程会流入file_system_type.get_sb().也就是cgroup_get_sb().由于该代码较长.分段分析如下:
static int cgroup_get_sb(struct file_system_type *fs_type,
             int flags, const char *unused_dev_name,
             void *data, struct vfsmount *mnt)
{
    struct cgroup_sb_opts opts;
    int ret = 0;
    struct super_block *sb;
    struct cgroupfs_root *root;
    struct list_head tmp_cg_links;
 
    /* First find the desired set of subsystems */
    //解析挂载参数
    ret = parse_cgroupfs_options(data, &opts);
    if (ret) {
        if (opts.release_agent)
            kfree(opts.release_agent);
        return ret;
    }
在这一部份,解析挂载的参数,并将解析的结果存放到opts.opts-> subsys_bits表示指定关联的subsys位图,opts->flags:挂载的标志: opts->release_agent表示指定的release_agent路径.
 
    //分配并初始化cgroufs_root
    root = kzalloc(sizeof(*root), GFP_KERNEL);
    if (!root) {
        if (opts.release_agent)
            kfree(opts.release_agent);
        return -ENOMEM;
    }
 
    init_cgroup_root(root);
    /*root->subsys_bits: 该hierarchy上关联的subsys*/
    root->subsys_bits = opts.subsys_bits;
    root->flags = opts.flags;
    /*如果带了release_agent参数,将其copy到root0<release_agent_path*/
    if (opts.release_agent) {
        strcpy(root->release_agent_path, opts.release_agent);
        kfree(opts.release_agent);
    }
 
    /*初始化一个super block*/
    sb = sget(fs_type, cgroup_test_super, cgroup_set_super, root);
 
    /*如果发生错误*/
    if (IS_ERR(sb)) {
        kfree(root);
        return PTR_ERR(sb);
    }
在这一部份,主要分配并初始化了一个cgroupfs_root结构.里面的子函数init_cgroup_root()我们在之前已经分析过,这里不再赘述.其实的初始化包括:设置与之关联的subsys位图,挂载标志和release_agent路径.然后再调用sget()生成一个super_block结构.调用cgroup_test_super来判断系统中是否有机同的cgroups_root.调用cgroup_set_super来对super_block进行初始化.
在cgroup_set_super()中,将sb->s_fs_info 指向了cgroutfs_root,cgroufs_root.sb指向生成的super_block.
类似的.如果找到的super_block相关联的cgroupfs_root所表示的subsys_bits和flags与当前cgroupfs_root相同的话,就表示是一个相同的super_block.因为它们的挂载参数是一样的.
举个例子来说明一下有重复super_block的情况:
[root@localhost ~]# mount -t cgroup cgroup -o debug /dev/cgroup/
[root@localhost ~]# mount -t cgroup cgroup -o debug /dev/eric_cgroup/
在上面的例子中,在挂载到/dev/eric_cgroup目录的时候,就会找到一个相同的super_block.这样实例上两者的操作是一样的.这两个不同挂载点所代码的vfsmount会找到同一个super_block.也就是说对其中一个目录的操作都会同表现在另一个目录中.
 
    /*重复挂载*/
    if (sb->s_fs_info != root) {
        /* Reusing an existing superblock */
        BUG_ON(sb->s_root == NULL);
        kfree(root);
        root = NULL;
    } else {
        /* New superblock */
        struct cgroup *cgrp = &root->top_cgroup;
        struct inode *inode;
        int i;
 
        BUG_ON(sb->s_root != NULL);
        /*初始化super_block对应的dentry和inode*/
        ret = cgroup_get_rootdir(sb);
        if (ret)
            goto drop_new_super;
        inode = sb->s_root->d_inode;
 
        mutex_lock(&inode->i_mutex);
        mutex_lock(&cgroup_mutex);
 
        /*
         * We're accessing css_set_count without locking
         * css_set_lock here, but that's OK - it can only be
         * increased by someone holding cgroup_lock, and
         * that's us. The worst that can happen is that we
         * have some link structures left over
         */
         /*分配css_set_count个cg_cgroup_link并将它们链入到tmp_cg_links*/
        ret = allocate_cg_links(css_set_count, &tmp_cg_links);
        if (ret) {
            mutex_unlock(&cgroup_mutex);
            mutex_unlock(&inode->i_mutex);
            goto drop_new_super;
        }
        /*bind subsys 到hierarchy*/
        ret = rebind_subsystems(root, root->subsys_bits);
        if (ret == -EBUSY) {
            mutex_unlock(&cgroup_mutex);
            mutex_unlock(&inode->i_mutex);
            goto drop_new_super;
        }
 
        /* EBUSY should be the only error here */
        BUG_ON(ret);
        /*将root添加到roots链入.增加root_count计数*/
        list_add(&root->root_list, &roots);
        root_count++;
 
        /*将挂载根目录dentry的私有结构d_fsdata反映向root->top_cgroup*/
        /*将root->top_cgroup.dentry指向挂载的根目录*/
        sb->s_root->d_fsdata = &root->top_cgroup;
        root->top_cgroup.dentry = sb->s_root;
 
        /* Link the top cgroup in this hierarchy into all
         * the css_set objects */
         /*将所有的css_set都和root->top_cgroup关联起来*/
        write_lock(&css_set_lock);
        for (i = 0; i < CSS_SET_TABLE_SIZE; i++) {
            struct hlist_head *hhead = &css_set_table[i];
            struct hlist_node *node;
            struct css_set *cg;
 
            hlist_for_each_entry(cg, node, hhead, hlist) {
                struct cg_cgroup_link *link;
 
                BUG_ON(list_empty(&tmp_cg_links));
                link = list_entry(tmp_cg_links.next,
                          struct cg_cgroup_link,
                          cgrp_link_list);
                list_del(&link->cgrp_link_list);
                link->cg = cg;
                list_add(&link->cgrp_link_list,
                     &root->top_cgroup.css_sets);
                list_add(&link->cg_link_list, &cg->cg_links);
            }
        }
        write_unlock(&css_set_lock);
        /*释放tmp_cg_links的多余项*/
        free_cg_links(&tmp_cg_links);
 
        BUG_ON(!list_empty(&cgrp->sibling));
        BUG_ON(!list_empty(&cgrp->children));
        BUG_ON(root->number_of_cgroups != 1);
        /*在root->top_cgroup下面创建一些文件,包括cgroup共有的和subsys私有的文件*/ 
        cgroup_populate_dir(cgrp);
        mutex_unlock(&inode->i_mutex);
        mutex_unlock(&cgroup_mutex);
    }
    /*将vfsmount和super_block关联起来*/
    return simple_set_mnt(mnt, sb);
 
 drop_new_super:
    up_write(&sb->s_umount);
    deactivate_super(sb);
    free_cg_links(&tmp_cg_links);
    return ret;
}
这一部份,首先判断找到的super_block是不是之前就存在的.如果是已经存在的,那就用不着再初始化一个cgroupfs_root结构了.将之前分配的结构释放掉.然后调用simple_set_mnt()将取得的super_block和vfsmount相关联后退出.
如果super_block是一个新建的.那么就必须要继续初始化cgroupfs_root了.
首先,调用cgroup_get_rootdir()初始化super_block对应的dentry和inode.
然后,调用rebind_subsystems()将需要关联到hierarchy的subsys和root->top_cgroup绑定起来.
最后,将所有的css_set都和root->top_cgroup关联起来.这样就可以从root->top_cgroup找到所有的进程了.再调用cgroup_populate_dir()在挂载目录下创建一些文件,然后,调用simple_set_mnt()将取得的super_block和vfsmount相关联后退出.
 
这个函数的流程还算简单.下面来分析一下里面涉及到的重要的子函数:
6.1: parse_cgroupfs_options()函数分析
这个函数主要是对挂载的参数进行解析.函数代码如下:
static int parse_cgroupfs_options(char *data,
                     struct cgroup_sb_opts *opts)
{
    /*如果挂载的时候没有带参数,将o设为"all".表示将所有
      *的subsys都与之关联
      */
    char *token, *o = data ?: "all";
 
    opts->subsys_bits = 0;
    opts->flags = 0;
    opts->release_agent = NULL;
 
    /*各参数是以","分隔的*/
    while ((token = strsep(&o, ",")) != NULL) {
        if (!*token)
            return -EINVAL;
        /*如果为all.表示关联所有的subsys*/
        if (!strcmp(token, "all")) {
            /* Add all non-disabled subsystems */
            int i;
            opts->subsys_bits = 0;
            for (i = 0; i < CGROUP_SUBSYS_COUNT; i++) {
                struct cgroup_subsys *ss = subsys[i];
                if (!ss->disabled)
                    opts->subsys_bits |= 1ul << i;
            }
        }
        /*如果指定参数noprefix.设定ROOT_NOPREFIX标志*/
        /*在指定noprefix的情况下.subsys创建的文件不会带subsys名称的前缀*/
        else if (!strcmp(token, "noprefix")) {
            set_bit(ROOT_NOPREFIX, &opts->flags);
        }
        /*如果指定了release_agent.分opt->release_agent分配内存,并将参数copy到里面*/
        else if (!strncmp(token, "release_agent=", 14)) {
            /* Specifying two release agents is forbidden */
            if (opts->release_agent)
                return -EINVAL;
            opts->release_agent = kzalloc(PATH_MAX, GFP_KERNEL);
            if (!opts->release_agent)
                return -ENOMEM;
            strncpy(opts->release_agent, token + 14, PATH_MAX - 1);
            opts->release_agent[PATH_MAX - 1] = 0;
        }
         /*其它情况下,将所带参数做为一个susys名处理.到sussys[]找到
           *对应的subsys.然后将opts->subsys_bits中的位置1
           */
        else {
            struct cgroup_subsys *ss;
            int i;
            for (i = 0; i < CGROUP_SUBSYS_COUNT; i++) {
                ss = subsys[i];
                if (!strcmp(token, ss->name)) {
                    if (!ss->disabled)
                        set_bit(i, &opts->subsys_bits);
                    break;
                }
            }
            if (i == CGROUP_SUBSYS_COUNT)
                return -ENOENT;
        }
    }
 
    /* We can't have an empty hierarchy */
    /*如果没有关联到subsys.错误*/
    if (!opts->subsys_bits)
        return -EINVAL;
 
    return 0;
}
对照代码中添加的注释应该很容易看懂.这里就不再做详细分析了.
 
6.2: rebind_subsystems()函数分析
rebind_subsystems()用来将cgroupfs_root和subsys绑定.代码如下:
static int rebind_subsystems(struct cgroupfs_root *root,
                  unsigned long final_bits)
{
    unsigned long added_bits, removed_bits;
    struct cgroup *cgrp = &root->top_cgroup;
    int i;
 
    /*root->actual_subsys_bits表示当进root中所关键的subsys位图*/
     /*如果在root->actual_subsys_bits中.但没有在final_bits中.表示这是
    *一次remonut的操作.需要将旧的subsys移除.如果在final_bits中
    *存在,但没有在root->actual_subsys_bits中,表示是需要添加的.
    */
    removed_bits = root->actual_subsys_bits & ~final_bits;
    added_bits = final_bits & ~root->actual_subsys_bits;
    /* Check that any added subsystems are currently free */
     /*如果要关联的subsys已经在其它的hierarchy中了.失败.
    *如果ss->root != &rootnode表示ss已经链入了其它的cgroupfs_root
    */
    for (i = 0; i < CGROUP_SUBSYS_COUNT; i++) {
        unsigned long bit = 1UL << i;
        struct cgroup_subsys *ss = subsys[i];
        if (!(bit & added_bits))
            continue;
        if (ss->root != &rootnode) {
            /* Subsystem isn't free */
            return -EBUSY;
        }
    }
 
    /* Currently we don't handle adding/removing subsystems when
     * any child cgroups exist. This is theoretically supportable
     * but involves complex error handling, so it's being left until
     * later */
     /*如果root->top_cgroup->children不为空.表示该hierarchy还要其它的cgroup
    *是不能被remount的.(新挂载的root->top_cgroup在初始化的时候将children置空了)
    */
    if (!list_empty(&cgrp->children))
        return -EBUSY;
 
    /* Process each subsystem */
    for (i = 0; i < CGROUP_SUBSYS_COUNT; i++) {
        struct cgroup_subsys *ss = subsys[i];
        unsigned long bit = 1UL << i;
        /*添加subsys的情况*/
        if (bit & added_bits) {
            /* We're binding this subsystem to this hierarchy */
            /* 添加情况下.将cgrp->subsys[i]指向dummytop->subsys[i]
              * 并更新dummytop->subsys[i]->root.将其指向要添加的root
              * 最后调用subsys->bind()操作
              */
            BUG_ON(cgrp->subsys[i]);
            BUG_ON(!dummytop->subsys[i]);
            BUG_ON(dummytop->subsys[i]->cgroup != dummytop);
            cgrp->subsys[i] = dummytop->subsys[i];
            cgrp->subsys[i]->cgroup = cgrp;
            list_add(&ss->sibling, &root->subsys_list);
            rcu_assign_pointer(ss->root, root);
            if (ss->bind)
                ss->bind(ss, cgrp);
 
        }
        /*移除subsys的情况*/
        else if (bit & removed_bits) {
            /* 移除操作,将对应的cgroup_subsys_state回归到原来的样子.并且也需要
              * 将与其subsys bind
              */
            /* We're removing this subsystem */
            BUG_ON(cgrp->subsys[i] != dummytop->subsys[i]);
            BUG_ON(cgrp->subsys[i]->cgroup != cgrp);
            if (ss->bind)
                ss->bind(ss, dummytop);
            dummytop->subsys[i]->cgroup = dummytop;
            cgrp->subsys[i] = NULL;
            rcu_assign_pointer(subsys[i]->root, &rootnode);
            list_del(&ss->sibling);
        } else if (bit & final_bits) {
            /* Subsystem state should already exist */
            BUG_ON(!cgrp->subsys[i]);
        } else {
            /* Subsystem state shouldn't exist */
            BUG_ON(cgrp->subsys[i]);
        }
    }
    /*更新root的位图*/
    root->subsys_bits = root->actual_subsys_bits = final_bits;
    synchronize_rcu();
 
    return 0;
}
从这个函数也可以看出来.rootnode就是起一个参照的作用.用来判断subsys是否处于初始化状态.
 
6.3: cgroup_populate_dir()函数分析
cgroup_populate_dir()用来在挂载目录下创建交互文件.代码如下:
static int cgroup_populate_dir(struct cgroup *cgrp)
{
    int err;
    struct cgroup_subsys *ss;
 
    /* First clear out any existing files */
    /*先将cgrp所在的目录清空*/
    cgroup_clear_directory(cgrp->dentry);
 
    /*创建files所代码的几个文件*/
    err = cgroup_add_files(cgrp, NULL, files, ARRAY_SIZE(files));
    if (err < 0)
        return err;
    /*如果是顶层top_cgroup.创建cft_release_agent所代码的文件*/
    if (cgrp == cgrp->top_cgroup) {
        if ((err = cgroup_add_file(cgrp, NULL, &cft_release_agent)) < 0)
            return err;
    }
 
    /*对所有与cgrp->root关联的subsys都调用populate()*/
    for_each_subsys(cgrp->root, ss) {
        if (ss->populate && (err = ss->populate(ss, cgrp)) < 0)
            return err;
    }
 
    return 0;
}
这个函数比较简单.跟踪cgroup_add_file().如下:
nt cgroup_add_file(struct cgroup *cgrp,
               struct cgroup_subsys *subsys,
               const struct cftype *cft)
{
    struct dentry *dir = cgrp->dentry;
    struct dentry *dentry;
    int error;
 
    char name[MAX_CGROUP_TYPE_NAMELEN + MAX_CFTYPE_NAME + 2] = { 0 };
    /*如果有指定subsys.且没有使用ROOT_NOPREFIX标志.需要在名称前加上
     *subsys的名称
     */
    if (subsys && !test_bit(ROOT_NOPREFIX, &cgrp->root->flags)) {
        strcpy(name, subsys->name);
        strcat(name, ".");
    }
    /*将cft->name链接到name代表的字串后面*/
    strcat(name, cft->name);
    BUG_ON(!mutex_is_locked(&dir->d_inode->i_mutex));
    /*到cgroup所在的目录下寻找name所表示的dentry,如果不存在,则新建之*/
    dentry = lookup_one_len(name, dir, strlen(name));
    if (!IS_ERR(dentry)) {
        /*创建文件inode*/
        error = cgroup_create_file(dentry, 0644 | S_IFREG,
                        cgrp->root->sb);
        /*使dentry->d_fsdata指向文件所代表的cftype*/
        if (!error)
            dentry->d_fsdata = (void *)cft;
        dput(dentry);
    } else
        error = PTR_ERR(dentry);
    return error;
}
 
cgroup_create_file()函数代码如下:
static int cgroup_create_file(struct dentry *dentry, int mode,
                struct super_block *sb)
{
    static struct dentry_operations cgroup_dops = {
        .d_iput = cgroup_diput,
    };
 
    struct inode *inode;
 
    if (!dentry)
        return -ENOENT;
    if (dentry->d_inode)
        return -EEXIST;
    /*分配一个inode*/
    inode = cgroup_new_inode(mode, sb);
    if (!inode)
        return -ENOMEM;
    /*如果新建的是目录*/
    if (S_ISDIR(mode)) {
        inode->i_op = &cgroup_dir_inode_operations;
        inode->i_fop = &simple_dir_operations;
 
        /* start off with i_nlink == 2 (for "." entry) */
        inc_nlink(inode);
 
        /* start with the directory inode held, so that we can
         * populate it without racing with another mkdir */
        mutex_lock_nested(&inode->i_mutex, I_MUTEX_CHILD);
    }
    /*新建一般文件*/
    else if (S_ISREG(mode)) {
        inode->i_size = 0;
        inode->i_fop = &cgroup_file_operations;
    }
    dentry->d_op = &cgroup_dops;
    /*将dentry和inode关联起来*/
    d_instantiate(dentry, inode);
    dget(dentry);   /* Extra count - pin the dentry in core */
    return 0;
}
从这个函数我们可以看到.如果是目录的话,对应的操作集为simple_dir_operations和cgroup_dir_inode_operations.它与cgroup_get_rootdir()中对根目录对应的inode所设置的操作集是一样的.如果是一般文件,它的操作集为cgroup_file_operations.
在这里,先将cgroup中的文件操作放到一边,我们在之后再来详细分析这个过程.
现在.我们已经将cgroup文件系统的挂载分析完成.接下来看它下面子层cgroup的创建.
 
七:创建子层cgroup
在目录下通过mkdir调用就可以创建一个子层cgroup.下面就分析这一过程:
经过上面的分析可以得知,cgroup中目录的操作集为:
cgroup_dir_inode_operations.结构如下:
static struct inode_operations cgroup_dir_inode_operations = {
    .lookup = simple_lookup,
    .mkdir = cgroup_mkdir,
    .rmdir = cgroup_rmdir,
    .rename = cgroup_rename,
};
从上面看到,对应mkdir的入口为cgroup_mkdir().代码如下:
static int cgroup_mkdir(struct inode *dir, struct dentry *dentry, int mode)
{
    /*找到它的上一级cgroup*/
    struct cgroup *c_parent = dentry->d_parent->d_fsdata;
 
    /* the vfs holds inode->i_mutex already */
    /*调用cgroup_create创建cgroup*/
    return cgroup_create(c_parent, dentry, mode | S_IFDIR);
}
跟踪cgroup_create().代码如下:
static long cgroup_create(struct cgroup *parent, struct dentry *dentry,
                 int mode)
{
    struct cgroup *cgrp;
    struct cgroupfs_root *root = parent->root;
    int err = 0;
    struct cgroup_subsys *ss;
    struct super_block *sb = root->sb;
    /*分配并初始化一个cgroup*/
    cgrp = kzalloc(sizeof(*cgrp), GFP_KERNEL);
    if (!cgrp)
        return -ENOMEM;
 
    /* Grab a reference on the superblock so the hierarchy doesn't
     * get deleted on unmount if there are child cgroups.  This
     * can be done outside cgroup_mutex, since the sb can't
     * disappear while someone has an open control file on the
     * fs */
    atomic_inc(&sb->s_active);
 
    mutex_lock(&cgroup_mutex);
 
    init_cgroup_housekeeping(cgrp);
 
    /*设置cgrp的层次关系*/
    cgrp->parent = parent;
    cgrp->root = parent->root;
    cgrp->top_cgroup = parent->top_cgroup;
 
    /*如果上一级cgroup设置了CGRP_NOTIFY_ON_RELEASE.那cgrp也设置这个标志*/
    if (notify_on_release(parent))
    

抱歉!评论已关闭.