進程_控制進程的資源使用

Cgroup用法解析

                                                                                                                ----小魔怪出品


      rhel6已經發布,6.1已經發布了。網上也出現了對其的介紹文章,但都是些表面上的,譬如:在安裝過程中出現的一些改動,以及默認的文件系統類型使用的是ext4,等等。安裝完rhel6,不知到你發現在根目錄下面默認會存在一個cgroup的文件夾麼,這個是個啥玩意嘛?這個就是rhel6的------資源管理。其實不算是啥新特性了。在內核2.6.26裏面就有了。

一、cgroup介紹:
      據官方文檔介紹,rhel6爲內核準備了一個新特性------資源控制,也即cgroup。此服務的軟件包是libcgroup。有了這個,你就可以分配資源,例如:CPU time,系統內存, 網絡帶寬等。這個是被cgconfig服務所控制的。如果此服務沒有啓動,在根目錄下的cgroup文件夾裏就不會存在內容。

[root@susir]#/etc/init.d/cgconfig status
[root@susir]#/etc/init.d/cgconfig stop
[root@susir]#ls /cgroup

啓動此服務之後,就會在/cgroup目錄下面多出一些內容,如圖所示:

[root@susir cgroup]# /etc/init.d/cgconfig restart
Stopping cgconfig service:                                 [  OK  ]
Starting cgconfig service:                                 [  OK  ]
[root@susir cgroup]# pwd
/cgroup
[root@susir cgroup]# ls
blkio  cpu  cpuacct  cpuset  devices  freezer  memory  net_cls

二、cgroup各個模塊的介紹:
      cgroup類似與進程,他們是分等級的,各個屬性都是從父進程哪裏繼承過來。cgroup包含了多個孤立的子系統,沒一個子系統代表一個單一的資源。rhel6中一共準備了9個子系統,以下是每個子系統的詳細說明:
   blkio 這個子系統設置限制每個塊設備的輸入輸出控制。例如:磁盤,光盤以及usb等等。
   cpu   這個子系統使用調度程序爲cgroup任務提供cpu的訪問。
   cpuacct 產生cgroup任務的cpu資源報告。
   cpuset  如果是多核心的cpu,這個子系統會爲cgroup任務分配單獨的cpu和內存。
   devices  允許或拒絕cgroup任務對設備的訪問。
   freezer   暫停和恢復cgroup任務。
   memory 設置每個cgroup的內存限制以及產生內存資源報告。
   net_cls   標記每個網絡包以供cgroup方便使用。
   ns  名稱空間子系統。
以上九個子系統之間也存在着一定的關係.詳情參閱官方文檔。

三、cgroup的使用:
1、cgroup的安裝:
在安裝系統的時候,默認已經安裝了libcgroup軟件包,如果沒有安裝可以使用以下命令進行安裝:
[root@susir /]# rpm -q libcgroup
libcgroup-0.36.1-6.el6.x86_64
如果沒有安裝請用YUM安裝。
2、cgroup服務的控制:
將其更改爲伴隨系統的啓動而啓動:
[root@susir /]# chkconfig --list cgconfig
cgconfig               0:off        1:off        2:off        3:off        4:off        5:off        6:off
[root@susir /]# chkconfig cgconfig on
[root@susir /]# chkconfig --list cgconfig
cgconfig               0:off        1:off        2:on        3:on        4:on        5:on        6:off
服務的停止和啓動
[root@susir cgroup]# /etc/init.d/cgconfig stop/start
3、cgroup的配置文件cgconfig.conf文件介紹:
在cgconfig.conf文件中,主要包含了兩個主要類型:mount和group。mount是指創建以及掛載哪些層次爲虛擬文件系統,並附上子系統的層次結構。cgconfig.conf文件的內容如下所示:
[root@susir /]# cat /etc/cgconfig.conf  | more
#
#  Copyright IBM Corporation. 2007
#
#  Authors:        Balbir Singh <[email protected]>
#  This program is free software; you can redistribute it and/or modify it
#  under the terms of version 2.1 of the GNU Lesser General Public License
#  as published by the Free Software Foundation.
#
#  This program is distributed in the hope that it would be useful, but
#  WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
#
# See man cgconfig.conf for further details.
#
# By default, mount all controllers to /cgroup/<controller>

mount {
        cpuset        = /cgroup/cpuset;
        cpu        = /cgroup/cpu;
        cpuacct        = /cgroup/cpuacct;
        memory        = /cgroup/memory;
        devices        = /cgroup/devices;
        freezer        = /cgroup/freezer
......省略。。。。。。。
例如cpuset=/cgroup/cputest,就可以理解爲以下幾條命令
[root@susir /]# mount -t cgroup -o cpuset cpuset /cgroup/cpuset
mount: cpuset already mounted or /cgroup/cpuset busy
[root@susir /]# umount /cgroup/cpu
cpu/     cpuacct/ cpuset/  
[root@susir /]# umount /cgroup/cpuset
[root@susir /]# mount -t cgroup -o cpuset cpuset /cgroup/cpuset
4、與cgroup相關的幾個命令:
lssubsys-----顯示已經存在的子系統。
[root@susir /]# lssubsys -am
cpu /cgroup/cpu
cpuacct /cgroup/cpuacct
memory /cgroup/memory
devices /cgroup/devices
freezer /cgroup/freezer
net_cls /cgroup/net_cls
blkio /cgroup/blkio
cpuset /cgroup/cpuset
ns
上所示,除ns子系統沒有在/etc/cgconfig.conf文件裏設置外,其他的都掛載在對應的目錄下面。
lscgroup-----顯示所有的cgroup。
[root@susir /]# lscgroup
cpu:/
cpuacct:/
memory:/
devices:/
freezer:/
net_cls:/
blkio:/
cpuset:/
5、掛載與卸載子系統層次:
如上所示,各個模塊均以掛載與對於的目錄下面。以下卸載了cpu子系統層次:
[root@susir /]# umount /cgroup/cpu
[root@susir /]# lssubsys -am  #(-a代表所有,-m代表顯示掛載點。)
cpuacct /cgroup/cpuacct
memory /cgroup/memory
devices /cgroup/devices
freezer /cgroup/freezer
net_cls /cgroup/net_cls
blkio /cgroup/blkio
cpuset /cgroup/cpuset
ns
cpu
其他的卸載與掛載都一樣。使用以下命令將其掛載回去:
[root@susir /]# mount -t cgroup -o cpu cpu /cgroup/cpu
[root@susir /]# lssubsys -am
cpuacct /cgroup/cpuacct
memory /cgroup/memory
devices /cgroup/devices
freezer /cgroup/freezer
net_cls /cgroup/net_cls
blkio /cgroup/blkio
cpuset /cgroup/cpuset
cpu /cgroup/cpu
ns
6、創建一個自定義的cgroup:
這裏要用到cgcreate命令,具體用法如下所示:先來看個示例:
[root@susir /]# cgcreate
Usage is cgcreate -t <tuid>:<tgid> -a <agid>:<auid> -g <list of controllers>:<relative path to cgroup>

[root@susir /]# cgcreate -g cpu,net_cls:/test-subgroup
[root@susir /]# lssubsys -am
cpuacct /cgroup/cpuacct
memory /cgroup/memory
devices /cgroup/devices
freezer /cgroup/freezer
net_cls /cgroup/net_cls
blkio /cgroup/blkio
cpuset /cgroup/cpuset
cpu /cgroup/cpu
ns
沒有出來。看到麼。其實掛載上去之後,使用lssubsys命令是看不到的。因爲啊。創建完了之後,系統還不能掛載,因爲掛載點不存在,等掛載點創建了之後,系統會自動掛載上去的。
7、刪除cgroup
[root@susir /]# cgdelete
Usage is cgdelete [-r] <list of controllers>:<relative path to cgroup> [...]

一: 前言
前段時間,一直在寫操作系統和研究Solaris kernel.從而對linux kernel關心甚少.不久前偶然收到富士通的面試,由於諸多原因推辭掉了這次機會.不過招聘要求給我留下了較深的印像.其中涉及到了cgroup機制.cgroup對我來說並不陌生,在LKML上看到過它的path.在2008 AKA大會上也有人對它做爲專題分析.不過一直都沒有深入代碼研究.這段時間打算將kernel中新加的功能整理一下,就先從cgroup開始吧.
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.

上面分析的幾個數據結構關係十分複雜.聯繫也十分緊密.下面以圖示的方式直觀將各結構的聯繫表示如下:

Click here to open new windowCTRL+Mouse wheel to zoom in/out
注意上圖中的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
        INIT_HLIST_HEAD(&css_set_table);
    //對一些需要在系統啓動時初始化的subsys進行初始化
    for (i = 0; i
        struct cgroup_subsys *ss = subsys;

        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
}
即所有的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設置成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
        struct cgroup_subsys *ss = subsys;
        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
        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
            struct cgroup_subsys *ss = subsys;
            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
            struct cgroup_subsys *ss = subsys;
            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
        struct cgroup *cgrp = cg->subsys->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
    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
            struct hlist_head *hhead = &css_set_table;
            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
                struct cgroup_subsys *ss = subsys;
                if (!ss->disabled)
                    opts->subsys_bits |= 1ul
            }
        }
        /*如果指定參數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
                ss = subsys;
                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
        unsigned long bit = 1UL
        struct cgroup_subsys *ss = subsys;
        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
        struct cgroup_subsys *ss = subsys;
        unsigned long bit = 1UL
        /*添加subsys的情況*/
        if (bit & added_bits) {
            /* We're binding this subsystem to this hierarchy */
            /* 添加情況下.將cgrp->subsys指向dummytop->subsys
              * 並更新dummytop->subsys->root.將其指向要添加的root
              * 最後調用subsys->bind()操作
              */
            BUG_ON(cgrp->subsys);
            BUG_ON(!dummytop->subsys);
            BUG_ON(dummytop->subsys->cgroup != dummytop);
            cgrp->subsys = dummytop->subsys;
            cgrp->subsys->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 != dummytop->subsys);
            BUG_ON(cgrp->subsys->cgroup != cgrp);
            if (ss->bind)
                ss->bind(ss, dummytop);
            dummytop->subsys->cgroup = dummytop;
            cgrp->subsys = NULL;
            rcu_assign_pointer(subsys->root, &rootnode);
            list_del(&ss->sibling);
        } else if (bit & final_bits) {
            /* Subsystem state should already exist */
            BUG_ON(!cgrp->subsys);
        } else {
            /* Subsystem state shouldn't exist */
            BUG_ON(cgrp->subsys);
        }
    }
    /*更新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
        return err;
    /*如果是頂層top_cgroup.創建cft_release_agent所代碼的文件*/
    if (cgrp == cgrp->top_cgroup) {
        if ((err = cgroup_add_file(cgrp, NULL, &cft_release_agent))
            return err;
    }

    /*對所有與cgrp->root關聯的subsys都調用populate()*/
    for_each_subsys(cgrp->root, ss) {
        if (ss->populate && (err = ss->populate(ss, cgrp))
            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))
        set_bit(CGRP_NOTIFY_ON_RELEASE, &cgrp->flags);

    /*調用subsys_create()生成cgroup_subsys_state.並與cgrp相關聯*/
    for_each_subsys(root, ss) {
        struct cgroup_subsys_state *css = ss->create(ss, cgrp);
        if (IS_ERR(css)) {
            err = PTR_ERR(css);
            goto err_destroy;
        }
        init_cgroup_css(css, ss, cgrp);
    }

    /*將cgrp添加到上一層cgroup的children鏈表*/
    list_add(&cgrp->sibling, &cgrp->parent->children);
    /*增加root的cgroups數目計數*/
    root->number_of_cgroups++;
    /*在當前目錄生成一個目錄*/
    err = cgroup_create_dir(cgrp, dentry, mode);
    if (err
        goto err_remove;

    /* The cgroup directory was pre-locked for us */
    BUG_ON(!mutex_is_locked(&cgrp->dentry->d_inode->i_mutex));
    /*在cgrp下創建幾個交互文件*/
    err = cgroup_populate_dir(cgrp);
    /* If err

    mutex_unlock(&cgroup_mutex);
    mutex_unlock(&cgrp->dentry->d_inode->i_mutex);

    return 0;

err_remove:

    list_del(&cgrp->sibling);
    root->number_of_cgroups--;

err_destroy:

    for_each_subsys(root, ss) {
        if (cgrp->subsys[ss->subsys_id])
            ss->destroy(ss, cgrp);
    }

    mutex_unlock(&cgroup_mutex);

    /* Release the reference count that we took on the superblock */
    deactivate_super(sb);

    kfree(cgrp);
    return err;
}
在這個函數中,主要分配並初始化了一個cgroup結構.並且將它和它的上一層目錄以及整個cgroupfs_root構成一個空間層次關係.然後,再調用subsys>create()操作函數.來讓subsys知道已經創建了一個cgroup結構.
爲了理順這一部份.將前面分析的cgroup文件系統掛載和cgroup的創建.以及接下來要分析的attach_task()操作總結成一個圖.如下示:
Click here to open new windowCTRL+Mouse wheel to zoom in/out
八:cgroup中文件的操作
接下來,就來看cgroup文件的操作.在上面曾分析到:文件對應的操作集爲cgroup_file_operations.如下所示:
static 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,
}

7.1:cgrou文件的open操作
對應的函數爲cgroup_file_open().代碼如下:
static int cgroup_file_open(struct inode *inode, struct file *file)
{
    int err;
    struct cftype *cft;

    err = generic_file_open(inode, file);
    if (err)
        return err;

    /*取得文件對應的struct cftype*/
    cft = __d_cft(file->f_dentry);
    if (!cft)
        return -ENODEV;
    /*如果定義了read_map或者是read_seq_string*/
    if (cft->read_map || cft->read_seq_string) {
        struct cgroup_seqfile_state *state =
            kzalloc(sizeof(*state), GFP_USER);
        if (!state)
            return -ENOMEM;
        state->cft = cft;
        state->cgroup = __d_cgrp(file->f_dentry->d_parent);
        file->f_op = &cgroup_seqfile_operations;
        err = single_open(file, cgroup_seqfile_show, state);
        if (err
            kfree(state);
    }
    /*否則調用cft->open()*/
    else if (cft->open)
        err = cft->open(inode, file);
    else
        err = 0;

    return err;
}
有兩種情況.一種是定義了read_map或者是read_seq_string的情況.這種情況下,它對應的操作集爲cgroup_seqfile_operations.如果是其它的情況.調用cftype的open()函數.第一種情況,我們等以後遇到了這樣的情況再來詳細分析.

7.2:cgroup文件的read操作
對應函數爲cgroup_file_read().代碼如下:
static ssize_t cgroup_file_read(struct file *file, char __user *buf,
                   size_t nbytes, loff_t *ppos)
{
    struct cftype *cft = __d_cft(file->f_dentry);
    struct cgroup *cgrp = __d_cgrp(file->f_dentry->d_parent);

    if (!cft || cgroup_is_removed(cgrp))
        return -ENODEV;

    if (cft->read)
        return cft->read(cgrp, cft, file, buf, nbytes, ppos);
    if (cft->read_u64)
        return cgroup_read_u64(cgrp, cft, file, buf, nbytes, ppos);
    if (cft->read_s64)
        return cgroup_read_s64(cgrp, cft, file, buf, nbytes, ppos);
    return -EINVAL;
}
如上代碼所示.read操作會轉入到cftype的read()或者read_u64或者read_s64的函數中.

7.3:cgroup文件的wirte操作
對應的操作函數是cgroup_file_write().如下示:
static ssize_t cgroup_file_write(struct file *file, const char __user *buf,
                        size_t nbytes, loff_t *ppos)
{
    struct cftype *cft = __d_cft(file->f_dentry);
    struct cgroup *cgrp = __d_cgrp(file->f_dentry->d_parent);

    if (!cft || cgroup_is_removed(cgrp))
        return -ENODEV;
    if (cft->write)
        return cft->write(cgrp, cft, file, buf, nbytes, ppos);
    if (cft->write_u64 || cft->write_s64)
        return cgroup_write_X64(cgrp, cft, file, buf, nbytes, ppos);
    if (cft->write_string)
        return cgroup_write_string(cgrp, cft, file, buf, nbytes, ppos);
    if (cft->trigger) {
        int ret = cft->trigger(cgrp, (unsigned int)cft->private);
        return ret ? ret : nbytes;
    }
    return -EINVAL;
}
從上面可以看到.最終的操作會轉入到cftype的write或者wirte_u64或者wirte_string或者trigger函數中.

7.4:debug subsytem分析
以debug subsystem爲例來說明cgroup中的文件操作
Debug subsys定義如下:
struct cgroup_subsys debug_subsys = {
    .name = "debug",
    .create = debug_create,
    .destroy = debug_destroy,
    .populate = debug_populate,
    .subsys_id = debug_subsys_id,
}
在cgroup_init_subsys()中,會以dummytop爲參數調用debug.create().對應函數爲debug_create().代碼如下:
static struct cgroup_subsys_state *debug_create(struct cgroup_subsys *ss,
                           struct cgroup *cont)
{
    struct cgroup_subsys_state *css = kzalloc(sizeof(*css), GFP_KERNEL);

    if (!css)
        return ERR_PTR(-ENOMEM);

    return css;
}
這裏沒啥好說的,就是分配了一個cgroup_subsys_state結構.

然後,將cgroup掛載.指令如下:
[root@localhost ~]# mount -t cgroup cgroup -o debug /dev/cgroup/
在rebind_subsystems()中,會調用subsys的bind函數.但在debug中無此接口.故不需要考慮.
然後在cgroup_populate_dir()中會調用populate接口.對應函數爲debug_populate().代碼如下:
static int debug_populate(struct cgroup_subsys *ss, struct cgroup *cont)
{
    return cgroup_add_files(cont, ss, files, ARRAY_SIZE(files));
}
Debug中的files定義如下:
static struct cftype files[] =  {
    {
        .name = "cgroup_refcount",
        .read_u64 = cgroup_refcount_read,
    },
    {
        .name = "taskcount",
        .read_u64 = taskcount_read,
    },

    {
        .name = "current_css_set",
        .read_u64 = current_css_set_read,
    },

    {
        .name = "current_css_set_refcount",
        .read_u64 = current_css_set_refcount_read,
    },

    {
        .name = "releasable",
        .read_u64 = releasable_read,
    },
}
來觀察一下 /dev/cgroup下的文件:
[root@localhost ~]# tree /dev/cgroup/
/dev/cgroup/
|-- debug.cgroup_refcount
|-- debug.current_css_set
|-- debug.current_css_set_refcount
|-- debug.releasable
|-- debug.taskcount
|-- notify_on_release
|-- release_agent
`-- tasks

0 directories, 8 files
上面帶debug字樣的文件是從debug subsys中創建的.其它的是cgroup.c的files中創建的.
我們先來分析每一個subsys共有的文件.即tasks,release_agent和notify_on_release.

7.5:task文件操作
Tasks文件對應的cftype結構如下:
static struct cftype files[] = {
    {
        .name = "tasks",
        .open = cgroup_tasks_open,
        .write_u64 = cgroup_tasks_write,
        .release = cgroup_tasks_release,
        .private = FILE_TASKLIST,
    }

7.5.1:task文件的open操作
當打開文件時,流程就會轉入cgroup_tasks_open().代碼如下:
static int cgroup_tasks_open(struct inode *unused, struct file *file)
{
    /*取得該文件所在層次的cgroup*/
    struct cgroup *cgrp = __d_cgrp(file->f_dentry->d_parent);
    pid_t *pidarray;
    int npids;
    int retval;

    /* Nothing to do for write-only files */
    /*如果是隻寫的文件系統*/
    if (!(file->f_mode & FMODE_READ))
        return 0;

    /*
     * If cgroup gets more users after we read count, we won't have
     * enough space - tough.  This race is indistinguishable to the
     * caller from the case that the additional cgroup users didn't
     * show up until sometime later on.
     */
     /*得到該層cgroup所關聯的進程個數*/
    npids = cgroup_task_count(cgrp);
    /*爲npids個進程的pid存放分配空間*/
    pidarray = kmalloc(npids * sizeof(pid_t), GFP_KERNEL);
    if (!pidarray)
        return -ENOMEM;
    /* 將與cgroup關聯進程的pid存放到pid_array_load數組.
      * 並且按照從小到大的順序排列
     */
    npids = pid_array_load(pidarray, npids, cgrp);
    sort(pidarray, npids, sizeof(pid_t), cmppid, NULL);

    /*
     * Store the array in the cgroup, freeing the old
     * array if necessary
     */
     /* 將npids,pidarray信息存放到cgroup中.如果cgroup之前
       * 就有task_pids.將其佔放的空間釋放
       */
    down_write(&cgrp->pids_mutex);
    kfree(cgrp->tasks_pids);
    cgrp->tasks_pids = pidarray;
    cgrp->pids_length = npids;
    cgrp->pids_use_count++;
    up_write(&cgrp->pids_mutex);

    /*將文件對應的操作集更改爲cgroup_task_operations*/
    file->f_op = &cgroup_tasks_operations;

    retval = seq_open(file, &cgroup_tasks_seq_operations);
    /*如果操作失敗,將cgroup中的pid信息釋放*/
    if (retval) {
        release_cgroup_pid_array(cgrp);
        return retval;
    }
    ((struct seq_file *)file->private_data)->private = cgrp;
    return 0;
}
首先,我們來思考一下這個問題:怎麼得到與cgroup關聯的進程呢?
回到在上面列出來的數據結構關係圖.每個進程都會指向一個css_set.而與這個css_set關聯的所有進程都會鏈入到css_set->tasks鏈表.而cgroup又可能通過一箇中間結構cg_cgroup_link來尋找所有與之關聯的所有css_set.從而可以得到與cgroup關聯的所有進程.
在上面的代碼中,通過調用cgroup_task_count()來得到與之關聯的進程數目,代碼如下:
int cgroup_task_count(const struct cgroup *cgrp)
{
    int count = 0;
    struct cg_cgroup_link *link;

    read_lock(&css_set_lock);
    list_for_each_entry(link, &cgrp->css_sets, cgrp_link_list) {
        count += atomic_read(&link->cg->refcount);
    }
    read_unlock(&css_set_lock);
    return count;
}
它就是遍歷cgro->css_sets.並調其轉換爲cg_cgroup_link.再從這個link得到css_set.這個css_set的引用計數就是與這個指向這個css_set的task數目.

在代碼中,是通過pid_array_load()來得到與cgroup關聯的task,並且將進程的pid寫入數組pidarray中.代碼如下:
static int pid_array_load(pid_t *pidarray, int npids, struct cgroup *cgrp)
{
    int n = 0;
    struct cgroup_iter it;
    struct task_struct *tsk;
    cgroup_iter_start(cgrp, &it);
    while ((tsk = cgroup_iter_next(cgrp, &it))) {
        if (unlikely(n == npids))
            break;
        pidarray[n++] = task_pid_vnr(tsk);
    }
    cgroup_iter_end(cgrp, &it);
    return n;
}
我們在這裏遇到了一個新的結構:struct cgroup_iter.它是cgroup的一個迭代器,通過它可以遍歷取得與cgroup關聯的task.它的使用方法爲:
1:調用cgroup_iter_start()來初始化這個迭代碼.
2:調用cgroup_iter_next()用來取得cgroup中的下一個task
3:使用完了,調用cgroup_iner_end().
下面來分析這三個過程:
Cgroup_iter_start()代碼如下:
void cgroup_iter_start(struct cgroup *cgrp, struct cgroup_iter *it)
{
    /*
     * The first time anyone tries to iterate across a cgroup,
     * we need to enable the list linking each css_set to its
     * tasks, and fix up all existing tasks.
     */
    if (!use_task_css_set_links)
        cgroup_enable_task_cg_lists();

    read_lock(&css_set_lock);
    it->cg_link = &cgrp->css_sets;
    cgroup_advance_iter(cgrp, it);
}
我們在這裏再次遇到了use_task_css_set_links變量.在之前分析cgroup_post_fork()中的時候,我們曾說過,只有在use_task_css_set_link設置爲1的時候,纔會調task->cg_list鏈入到css_set->tasks中.
所以,在這個地方,如果use_task_css_set_link爲0.那就必須要將之前所有的進程都鏈入到它所指向的css_set->tasks鏈表.這個過程是在cgroup_enable_task_cg_lists()完成的,這個函數相當簡單,就是一個task的遍歷,然後就是鏈表的鏈入,在這裏就不再詳細分析了.請自行閱讀它的代碼.*^_^*
然後,將it->cg_link指向cgrp->css_sets.我們在前面說過,可以通過cgrp->css_sets就可以得得所有的與cgroup關聯的css_set.
到這裏,這個迭代器裏面還是空的,接下來往裏面填充數據.這個過程是在cgroup_advance_iter()中完成,代碼如下示:
static void cgroup_advance_iter(struct cgroup *cgrp,
                  struct cgroup_iter *it)
{
struct list_head *l = it->cg_link;
struct cg_cgroup_link *link;
struct css_set *cg;

/* Advance to the next non-empty css_set */
do {
    l = l->next;
    if (l == &cgrp->css_sets) {
        it->cg_link = NULL;
        return;
    }
   link = list_entry(l, struct cg_cgroup_link, cgrp_link_list);
    cg = link->cg;
} while (list_empty(&cg->tasks));
it->cg_link = l;
it->task = cg->tasks.next;
}
通過前面的分析可得知,可通過it->cg_link找到與之關聯的css_set,然後再通過css_set找到與它關聯的task鏈表.因此每次往cgroup迭代器裏填充數據,就是找到一個tasks鏈表不爲空的css_set.取數據就從css_set->tasks中取.如果數據取完了,就找下一個tasks鏈表不爲空的css_set.
這樣,這個函數的代碼就很簡單了.它就是找到it->cg_link上tasks鏈表不爲空的css_set項.

cgroup_iter_next()的代碼如下:
struct task_struct *cgroup_iter_next(struct cgroup *cgrp,
                    struct cgroup_iter *it)
{
    struct task_struct *res;
    struct list_head *l = it->task;

    /* 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;
    if (l == &res->cgroups->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;
}
如果it->cg_link爲空表示it->cg_link已經遍歷完了,也就不存放在task了.否則,從it->task中取得task.如果已經是最後一個task就必須要調用cgroup_advance_iter()填充迭代器裏面的數據.最後將取得的task返回.

cgroup_iter_end()用來對迭代碼進行收尾的工作,代碼如下:
void cgroup_iter_end(struct cgroup *cgrp, struct cgroup_iter *it)
{
    read_unlock(&css_set_lock);
}
它就是釋放了在cgroup_iter_start()中持有的鎖.

回到cgroup_tasks_open()中.我們接下來會遇到kernel爲sequential file提供的一組接口.首先在代碼遇到的是seq_open().代碼如下:
int seq_open(struct file *file, const struct seq_operations *op)
{
    struct seq_file *p = file->private_data;

    if (!p) {
        p = kmalloc(sizeof(*p), GFP_KERNEL);
        if (!p)
            return -ENOMEM;
        file->private_data = p;
    }
    memset(p, 0, sizeof(*p));
    mutex_init(&p->lock);
    p->op = op;
    file->f_version = 0;
    /* SEQ files support lseek, but not pread/pwrite */
    file->f_mode &= ~(FMODE_PREAD | FMODE_PWRITE);
    return 0;
}
從代碼中可以看出,它就是初始化了一個struct seq_file結構.並且將其關聯到file->private_data.在這裏要注意將seq_file->op設置成了參數op.在我們分析的這個情景中,也就是cgroup_tasks_seq_operations.這個在我們分析文件的讀操作的時候會用到的.

7.5.2:task文件的read操作
從上面的代碼中可看到.在open的時候,更改了file->f_op.將其指向了cgroup_tasks_operations.該結構如下:
static struct file_operations cgroup_tasks_operations = {
    .read = seq_read,
    .llseek = seq_lseek,
    .write = cgroup_file_write,
    .release = cgroup_tasks_release,
}
相應的,read操作就會轉入到seq_read()中.由於該函數篇幅較大,這裏就不列出了.感興趣的可以自己跟蹤看一下,其它就是循環調用seq_file->op->start() à seq_file->op->show() à seq_file->op->next() à seq_file->op->stop()的過程.
我們在上面分析task文件的open操作的時候,曾經提配過,seq_file->op被指向了cgroup_tasks_seq_operations.定義如下:
static struct seq_operations cgroup_tasks_seq_operations = {
    .start = cgroup_tasks_start,
    .stop = cgroup_tasks_stop,
    .next = cgroup_tasks_next,
    .show = cgroup_tasks_show,
}
Cgroup_tasks_start()代碼如下:
static void *cgroup_tasks_start(struct seq_file *s, loff_t *pos)
{
    /*
     * Initially we receive a position value that corresponds to
     * one more than the last pid shown (or 0 on the first call or
     * after a seek to the start). Use a binary-search to find the
     * next pid to display, if any
     */
    struct cgroup *cgrp = s->private;
    int index = 0, pid = *pos;
    int *iter;

    down_read(&cgrp->pids_mutex);
    if (pid) {
        int end = cgrp->pids_length;

        while (index
            int mid = (index + end) / 2;
            if (cgrp->tasks_pids[mid] == pid) {
                index = mid;
                break;
            } else if (cgrp->tasks_pids[mid]
                index = mid + 1;
            else
                end = mid;
        }
    }
    /* If we're off the end of the array, we're done */
    if (index >= cgrp->pids_length)
        return NULL;
    /* Update the abstract position to be the actual pid that we found */
    iter = cgrp->tasks_pids + index;
    *pos = *iter;
    return iter;
}
它以二分法從cgrp->tasks_pids[ ]中去尋找第一個大於或者等於參數*pos值的項.如果找到了,返回該項.如果沒找到.返回NULL.

cgroup_tasks_show()代碼如下:
static int cgroup_tasks_show(struct seq_file *s, void *v)
{
    return seq_printf(s, "%d\n", *(int *)v);
}
它就是將pid轉換爲了字符串.

cgroup_tasks_next()就是找到數組中的下一項.代碼如下:
static void *cgroup_tasks_next(struct seq_file *s, void *v, loff_t *pos)
{
    struct cgroup *cgrp = s->private;
    int *p = v;
    int *end = cgrp->tasks_pids + cgrp->pids_length;

    /*
     * Advance to the next pid in the array. If this goes off the
     * end, we're done
     */
    p++;
    if (p >= end) {
        return NULL;
    } else {
        *pos = *p;
        return p;
    }
}

cgroup_tasks_stop()代碼如下:
static void cgroup_tasks_stop(struct seq_file *s, void *v)
{
    struct cgroup *cgrp = s->private;
    up_read(&cgrp->pids_mutex);
}
它只是釋放了在cgroup_tasks_start()中持有的讀寫鎖.

7.5.3:task文件的close操作
Task文件close時,調用的相應接口爲cgroup_tasks_release().代碼如下:
static int cgroup_tasks_release(struct inode *inode, struct file *file)
{
    struct cgroup *cgrp = __d_cgrp(file->f_dentry->d_parent);

    if (!(file->f_mode & FMODE_READ))
        return 0;

    release_cgroup_pid_array(cgrp);
    return seq_release(inode, file);
}
它就是將cgroup中的pid信息與seqfile信息釋放掉.

到這裏,我們已經分析完了task文件的open,read,close操作.我們現在就可以實現一下,看上面的分析是否正確.
在前面已經分析中cgroupfs_root.top_cgroup會將系統中的所有css_set與之關聯起來,那麼通過cgroupfs_root_top_cgroup找到的進程應該是系統當前的所有進程.那麼相應的,在掛載目錄的task文件的內容.應該是系統中所有進程的pid.
如下所示:
[root@localhost cgroup]# cat tasks
1
2
3
………
………
2578
其實,這樣做是cgroup子系統開發者特意設置的.它表示所有的進程都在hierarchy的控制之下.
反過來,當我們在掛載目錄mkdir一個目錄,它下面的task文件內容應該是空的.因爲在mkdir後,它對應的cgroup並沒有關聯任何task.
如下所示:
[root@localhost cgroup]# mkdir eric
[root@localhost cgroup]# cat eric/tasks
[root@localhost cgroup]#
下面我們來看一下task文件的寫操作,也就是怎樣將進程添加進cgroup.

7.5.4:task文件的write操作
根據上面的文件,可得知task文件的write操作對應的函數爲int cgroup_tasks_write().代碼如下:
static int cgroup_tasks_write(struct cgroup *cgrp, struct cftype *cft, u64 pid)
{
    int ret;
    /*如果cgroup已經被移除了,非法*/
    if (!cgroup_lock_live_group(cgrp))
        return -ENODEV;
    /*將PID爲pid的進程與cgroup關聯*/
    ret = attach_task_by_pid(cgrp, pid);
    cgroup_unlock();
    return ret;
}
Attach_task_by_pid()的代碼如下:
static int attach_task_by_pid(struct cgroup *cgrp, u64 pid)
{
    struct task_struct *tsk;
    int ret;

    /*如果pid不爲0.尋找PID爲pid的task.並增加其引用計數*/
    if (pid) {
        rcu_read_lock();
        tsk = find_task_by_vpid(pid);
        if (!tsk || tsk->flags & PF_EXITING) {
            rcu_read_unlock();
            return -ESRCH;
        }
        get_task_struct(tsk);
        rcu_read_unlock();

        if ((current->euid) && (current->euid != tsk->uid)
            && (current->euid != tsk->suid)) {
            put_task_struct(tsk);
            return -EACCES;
        }
    }
    /*如果pid爲0.表示是將當前進程添加進cgroup*/
    else {
        tsk = current;
        get_task_struct(tsk);
    }
    /*將cgroup與task相關聯*/
    ret = cgroup_attach_task(cgrp, tsk);
    /*操作完成,減少其引用計數*/
    put_task_struct(tsk);
    return ret;
}
如果寫入的是一個不這0的數,表示的是進程的PID值.如果是寫入0,表示是將當前進程.這個操作的核心操作是cgroup_attach_task().代碼如下:
int cgroup_attach_task(struct cgroup *cgrp, struct task_struct *tsk)
{
    int retval = 0;
    struct cgroup_subsys *ss;
    struct cgroup *oldcgrp;
    struct css_set *cg = tsk->cgroups;
    struct css_set *newcg;
    struct cgroupfs_root *root = cgrp->root;
    int subsys_id;

    /*得到與cgroup關聯的第一個subsys的序號*/
    get_first_subsys(cgrp, NULL, &subsys_id);

    /* Nothing to do if the task is already in that cgroup */
    /*找到這個進程之前所屬的cgroup*/
    oldcgrp = task_cgroup(tsk, subsys_id);
    /*如果已經在這個cgrp裏面了.*/
    if (cgrp == oldcgrp)
        return 0;
   
    /* 遍歷與hierarchy關聯的subsys
      * 如果subsys定義了can_attach函數,就調用它
      */
    for_each_subsys(root, ss) {
        if (ss->can_attach) {
            retval = ss->can_attach(ss, cgrp, tsk);
            if (retval)
                return retval;
        }
    }

    /*
     * Locate or allocate a new css_set for this task,
     * based on its final set of cgroups
     */
     /*找到這個task所關聯的css_set.如果不存在,則新建一個*/
    newcg = find_css_set(cg, cgrp);
    if (!newcg)
        return -ENOMEM;

    task_lock(tsk);
   
    /*如果task正在執行exit操作*/
    if (tsk->flags & PF_EXITING) {
        task_unlock(tsk);
        put_css_set(newcg);
        return -ESRCH;
    }
    /*將tak->cgroup指向這個css_set*/
    rcu_assign_pointer(tsk->cgroups, newcg);
    task_unlock(tsk);

    /* Update the css_set linked lists if we're using them */
    /*更改task->cg_list*/
    write_lock(&css_set_lock);
    if (!list_empty(&tsk->cg_list)) {
        list_del(&tsk->cg_list);
        list_add(&tsk->cg_list, &newcg->tasks);
    }
    write_unlock(&css_set_lock);

    /* 遍歷與hierarchy關聯的subsys
      * 如果subsys定義了attach 函數,就調用它
      */
    for_each_subsys(root, ss) {
        if (ss->attach)
            ss->attach(ss, cgrp, oldcgrp, tsk);
    }
    set_bit(CGRP_RELEASABLE, &oldcgrp->flags);
    synchronize_rcu();
    /*減小舊指向的引用計數*/
put_css_set(cg);
    return 0;
}
這個函數邏輯很清楚,它就是初始化task->cgroup.然後將它和subsys相關聯.可自行參照代碼中的註釋進行分析.這裏就不再贅述了.
在這裏,詳細分析一下find_css_set()函數,這個函數有點意思.代碼如下:
static struct css_set *find_css_set(
    struct css_set *oldcg, struct cgroup *cgrp)
{
    struct css_set *res;
    struct cgroup_subsys_state *template[CGROUP_SUBSYS_COUNT];
    int i;

    struct list_head tmp_cg_links;
    struct cg_cgroup_link *link;

    struct hlist_head *hhead;

    /* First see if we already have a cgroup group that matches
     * the desired set */
    read_lock(&css_set_lock);
    /*尋找從oldcg轉換爲cgrp的css_set.如果不存在,返回NULL */
    res = find_existing_css_set(oldcg, cgrp, template);
    /*如果css_set已經存在,增加其引用計數後退出*/
    if (res)
        get_css_set(res);
    read_unlock(&css_set_lock);

    if (res)
        return res;
這一部份,先從哈希數組中搜索從oldcg轉換cgrp的css_set.如果不存在,返回NULL.如果在哈希數組中存放,增加其引用計數返回即可.
Find_existing_css_set()的代碼如下:
static struct css_set *find_existing_css_set(
    struct css_set *oldcg,
    struct cgroup *cgrp,
    struct cgroup_subsys_state *template[])
{
    int i;
    struct cgroupfs_root *root = cgrp->root;
    struct hlist_head *hhead;
    struct hlist_node *node;
    struct css_set *cg;

    /* Built the set of subsystem state objects that we want to
     * see in the new css_set */
    for (i = 0; i
        if (root->subsys_bits & (1UL
            /* Subsystem is in this hierarchy. So we want
             * the subsystem state from the new
             * cgroup */
            template = cgrp->subsys;
        } else {
            /* Subsystem is not in this hierarchy, so we
             * don't want to change the subsystem state */
            template = oldcg->subsys;
        }
    }

    hhead = css_set_hash(template);
    hlist_for_each_entry(cg, node, hhead, hlist) {
        if (!memcmp(template, cg->subsys, sizeof(cg->subsys))) {
            /* All subsystems matched */
            return cg;
        }
    }

    /* No existing cgroup group matched */
    return NULL;
}
如果subsys與新的cgroup相關聯,那麼它指向新的cgroup->subsys[]中的對應項.否則指向舊的cgrop的對應項.這樣做主要是因爲,該進程可能還被關聯在其它的hierarchy中.所以要保持它在其它hierarchy中的信息.
最後,在css_set_table[ ]中尋找看是否有與template相等的項.有的話返回該項.如果沒有.返回NULL.

    /*分配一個css_set*/
    res = kmalloc(sizeof(*res), GFP_KERNEL);
    if (!res)
        return NULL;

    /* Allocate all the cg_cgroup_link objects that we'll need */
    /*分配root_count項cg_cgroup_link*/
    if (allocate_cg_links(root_count, &tmp_cg_links)
        kfree(res);
        return NULL;
    }

    /* 初始化剛分配的css_set */
    atomic_set(&res->refcount, 1);
    INIT_LIST_HEAD(&res->cg_links);
    INIT_LIST_HEAD(&res->tasks);
    INIT_HLIST_NODE(&res->hlist);

    /* Copy the set of subsystem state objects generated in
     * find_existing_css_set() */
     /*設置css_set->subsys*/
    memcpy(res->subsys, template, sizeof(res->subsys));
運行到這裏的話.表示沒有從css_set_table[ ]中找到相應項.因此需要分配並初始化一個css_set結構.並且設置css_set的subsys域.

    write_lock(&css_set_lock);
    /* Add reference counts and links from the new css_set. */
    /*遍歷所有的subsys以及css_set 中的subsys[ ].
     *建立task所在的cgroup到css_set的引用
     */
    for (i = 0; i
        struct cgroup *cgrp = res->subsys->cgroup;
        struct cgroup_subsys *ss = subsys;
        atomic_inc(&cgrp->count);
        /*
         * We want to add a link once per cgroup, so we
         * only do it for the first subsystem in each
         * hierarchy
         */
        if (ss->root->subsys_list.next == &ss->sibling) {
            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);
            list_add(&link->cgrp_link_list, &cgrp->css_sets);
            link->cg = res;
            list_add(&link->cg_link_list, &res->cg_links);
        }
    }
   
    /*似乎沒有地方會更改rootnode.subsys_list.?這裏的判斷大部份情況是滿足的*/
    if (list_empty(&rootnode.subsys_list)) {
        /*建立這個css_set到dumytop的引用*/
        /* 這樣做,是爲了讓新建的hierarchy能夠關聯到所有的進程*/
        link = list_entry(tmp_cg_links.next,
                  struct cg_cgroup_link,
                  cgrp_link_list);
        list_del(&link->cgrp_link_list);
        list_add(&link->cgrp_link_list, &dummytop->css_sets);
        link->cg = res;
        list_add(&link->cg_link_list, &res->cg_links);
    }
    BUG_ON(!list_empty(&tmp_cg_links));
這一部份的關鍵操作都在代碼中添加了相應的註釋.如果系統中存在多個hierarchy.那麼這個進程肯定也位於其它的hierarchy所對應的cgroup中.因此需要在新分配的css_set中保存這些信息,也就是建立從cgroup到css_set的引用.
另外,關於ist_empty(&rootnode.subsys_list)的操作.似乎沒看到有什麼地方會更改rootnode.subsys_list.不過,如果rootnode.subsys_list不爲空的話,也會在它前面的for循環中檢測出來.
總而言之.系統中有root_count個hierarchy.上述的引用保存過程就會進行root_count次.因此.到最後.tmp_cg_links肯定會空了.如果不爲空.說明某處發生了錯誤.

    /*增加css_set計數*/
    css_set_count++;

    /* Add this cgroup group to the hash table */
    /*將其添加到全局哈希數組: css_set_table[ ]*/
    hhead = css_set_hash(res->subsys);
    hlist_add_head(&res->hlist, hhead);

    write_unlock(&css_set_lock);

    return res;
}
最後,將生成的css_set添加到哈希數組css_set_table[ ]中.
到這裏,task文件的操作已經分析完了.

7.6: notify_on_release文件操作
notify_on_release文件對應的cftype結構如下:
{
        .name = "notify_on_release",
        .read_u64 = cgroup_read_notify_on_release,
        .write_u64 = cgroup_write_notify_on_release,
        .private = FILE_NOTIFY_ON_RELEASE,
}

從此得知.文件的讀操作接口爲cgroup_read_notify_on_release().代碼如下:
static u64 cgroup_read_notify_on_release(struct cgroup *cgrp,
                        struct cftype *cft)
{
    return notify_on_release(cgrp);
}
繼續跟進notify_on_release().如下示:
static int notify_on_release(const struct cgroup *cgrp)
{
    return test_bit(CGRP_NOTIFY_ON_RELEASE, &cgrp->flags);
}
從此可以看到,如果當前cgroup設置了CGRP_NOTIFY_ON_RELEASE標誌.就會返回1.否則.就是爲0.
從當前系統中測試一下,如下:
[root@localhost cgroup]# cat notify_on_release
0
[root@localhost cgroup]#
文件內容爲零.因爲top_cgroup上沒有設置CGRP_NOTIFY_ON_RELEASE的標誌.

notify_on_release文件讀操作接口爲cgroup_write_notify_on_release().代碼如下:
static int cgroup_write_notify_on_release(struct cgroup *cgrp,
                      struct cftype *cft,
                      u64 val)
{
    clear_bit(CGRP_RELEASABLE, &cgrp->flags);
    if (val)
        set_bit(CGRP_NOTIFY_ON_RELEASE, &cgrp->flags);
    else
        clear_bit(CGRP_NOTIFY_ON_RELEASE, &cgrp->flags);
    return 0;
}
從上面的代碼可以看到.如果我們寫入的是1.就會設置cgroup標誌的CGRP_NOTIFY_ON_RELEASE位.否則.清除CGRP_NOTIFY_ON_RELEASE位.測試如下:
[root@localhost cgroup]# echo 1 > notify_on_release
[root@localhost cgroup]# cat notify_on_release
1
[root@localhost cgroup]# echo 0 > notify_on_release
[root@localhost cgroup]# cat notify_on_release
0
[root@localhost cgroup]#

7.7: release_agent文件操作
release_agent只有在頂層目錄纔會有.它所代表的cftype結構如下:
static struct cftype cft_release_agent = {
    .name = "release_agent",
    .read_seq_string = cgroup_release_agent_show,
    .write_string = cgroup_release_agent_write,
    .max_write_len = PATH_MAX,
    .private = FILE_RELEASE_AGENT,
};

由此可以看到.讀文件的接口爲cgroup_release_agent_show.代碼如下:
static int cgroup_release_agent_show(struct cgroup *cgrp, struct cftype *cft,
                     struct seq_file *seq)
{
    if (!cgroup_lock_live_group(cgrp))
        return -ENODEV;
    seq_puts(seq, cgrp->root->release_agent_path);
    seq_putc(seq, '\n');
    cgroup_unlock();
    return 0;
}
從代碼中可以看到.就是打印出root的release_agent_path.

寫文件的接口爲cgroup_release_agent_write().如下示:
static int cgroup_release_agent_write(struct cgroup *cgrp, struct cftype *cft,
                      const char *buffer)
{
    BUILD_BUG_ON(sizeof(cgrp->root->release_agent_path)
    if (!cgroup_lock_live_group(cgrp))
        return -ENODEV;
    strcpy(cgrp->root->release_agent_path, buffer);
    cgroup_unlock();
    return 0;
}
由此得知.往這個文件中寫內容,就是設置root的release_agent_path.如下做個測試:
[root@localhost cgroup]# cat release_agent

[root@localhost cgroup]# echo /bin/ls > release_agent
[root@localhost cgroup]# cat release_agent
/bin/ls
[root@localhost cgroup]#

7.8:debug創建的文件分析
下面分析一下debug subsys中的文件.由於我們掛載的時候沒有帶noprefix.因爲.debug生成的文件都帶了一個”debug_”前綴.由debug創建的文件如下示:
debug.cgroup_refcount  debug.current_css_set_refcount  debug.taskcount debug.current_css_set  debug.releasable
挨個分析如下:
7.8.1: cgroup_refcount文件操作
Cgroup_refcount所代表的cftype結構如下示:
    {
        .name = "cgroup_refcount",
        .read_u64 = cgroup_refcount_read,
    },
可以看到,該文件不能寫,只能讀.讀操作接口爲cgroup_refcount_read().代碼如下:
static u64 cgroup_refcount_read(struct cgroup *cont, struct cftype *cft)
{
    return atomic_read(&cont->count);
}
它就是顯示出當前cgroup的引用計數.
測試如下:
[root@localhost cgroup]# cat debug.cgroup_refcount
0
[root@localhost cgroup]#
頂層的cgroup是位於cgroupfs_root.top_cgroup.它的引用計數爲0.
接下來,我們在下層創建一個子層cgroup.如下示:
[root@localhost cgroup]# mkdir /dev/cgroup/eric
[root@localhost cgroup]# cat /dev/cgroup/eric/debug.cgroup_refcount
0
[root@localhost cgroup]#
可見創建子層cgroup不會增加其引用計數.因爲它只是與它的上一層cgroup構成指針指向關係.
現在我們讓子層cgroup關聯一個進程
[root@localhost cgroup]# echo 1673 > /dev/cgroup/eric/tasks
[root@localhost cgroup]# cat /dev/cgroup/eric/debug.cgroup_refcount
1
[root@localhost cgroup]#
可以看到.它的計數比爲了1.這裏在關聯進程的css_set和所在的cgroup時增加的.

7.8.2: current_css_set文件操作
current_css_set對應的cftype結構如下示:
    {
        .name = "current_css_set",
        .read_u64 = current_css_set_read,
    },
可看出.它也是一個只讀的.讀接口爲current_css_set_read().代碼如下:
static u64 current_css_set_read(struct cgroup *cont, struct cftype *cft)
{
    return (u64)(long)current->cgroups;
}
它就是顯示了當前進程關聯的css_set的地址.
測試如下:
[root@localhost cgroup]# cat debug.current_css_set
18446744072645980768

7.8.3: current_css_set_refcount文件操作
current_css_set_refcount文件對應的ctype結構如下:
    {
        .name = "current_css_set_refcount",
        .read_u64 = current_css_set_refcount_read,
    },
照例.它也是隻讀的.接口如下:
static u64 current_css_set_refcount_read(struct cgroup *cont,
                       struct cftype *cft)
{
    u64 count;

    rcu_read_lock();
    count = atomic_read(&current->cgroups->refcount);
    rcu_read_unlock();
    return count;
}
它就是顯示出與當前進程關聯的css_set的引用計數.
測試如下:
[root@localhost cgroup]# cat debug.current_css_set_refcount
56
表示已經有56個進程關聯到這個css_set了.

7.8.3: taskcount文件操作
Taskcount文件對應cftype結構如下:
    {
        .name = "taskcount",
        .read_u64 = taskcount_read,
    },
只讀文件.接口如下:
static u64 taskcount_read(struct cgroup *cont, struct cftype *cft)
{
    u64 count;

    cgroup_lock();
    count = cgroup_task_count(cont);
    cgroup_unlock();
    return count;
}
其中,子函數cgroup_task_count()我們在之前已經分析過了.它就是計算與當前cgroup關聯的進程數目.這裏就不再分析了.測試如下:
[root@localhost cgroup]# cat debug.taskcount
56

7.8.4: releasable文件操作
Releasable文件對應的ctype結構如下示:
    {
        .name = "releasable",
        .read_u64 = releasable_read,
    },
只讀,讀接口代碼如下:
static u64 releasable_read(struct cgroup *cgrp, struct cftype *cft)
{
    return test_bit(CGRP_RELEASABLE, &cgrp->flags);
}
它用來查看當前cgroup是否有CGRP_RELEASABLE標誌.如果有.顯示爲1.否則顯示爲0.
測試如下:
[root@localhost cgroup]# cat debug.releasable
0
經過上面的分析.可以知道.如果往cgroup中刪除一個關聯進程,就會將其設置CGRP_RELEASABLE標誌.有下面測試:
[root@localhost cgroup]# mkdir eric
[root@localhost cgroup]# cat eric/debug.releasable
0
[root@localhost cgroup]# echo 1650 > eric/tasks
[root@localhost cgroup]# echo 1701 > eric/tasks     
[root@localhost cgroup]# cat eric/debug.releasable
0
[root@localhost cgroup]# echo 1650 >tasks
[root@localhost cgroup]# cat eric/debug.releasable
1

到這裏爲止,各subsys共有的文件和debug中的文件操作就已經分析完了.其它的subsys遠遠比debug要複雜.之後再給出專題分析.詳情請關注本站更新.*^_^*

九: notify_on_release操作
下面我們來分析在之前一直在忽略的一個問題.也就是涉及到CGRP_NOTIFY_ON_RELEASE標誌和root-> release_agent_path[]部份.
它的重用,就是在cgroup中最後的一個進程離開(包括進程退出.進程關聯到其它同類型的cgroup),或者是在最後一個子層cgroup被移除的時候.就會調用用戶空間的一個程序.這個程序的路徑是在root-> release_agent_path[]中指定的.
下面我們從代碼的角度來跟蹤一下.

9.1:進程退出
我們在之前在分析父子進程之間的cgroup關係的時候.忽略掉了__put_css_set函數中的一個部份.現在是時候來剝開它了.
次__put_css_set()被忽略的代碼片段列出,如下:
static void __put_css_set(struct css_set *cg, int taskexit)
{
    ......
    ......
    for (i = 0; i
        struct cgroup *cgrp = cg->subsys->cgroup;
        if (atomic_dec_and_test(&cgrp->count) &&
            notify_on_release(cgrp)) {
            if (taskexit)
                set_bit(CGRP_RELEASABLE, &cgrp->flags);
            check_for_release(cgrp);
        }
    }
    ......
    ......
}
首先,進程退出時,調用__put_css_set時.taskexit參數是爲1的,因此在這裏,它會將cgroup的flag的CGRP_RELEASABLE位置1.
atomic_dec_and_test(&cgrp->count)返回爲真的話,說明進程所屬的cgroup中已經沒有其它的進程了.因此即將要退出的子進程就是cgroup中的最後一個進程.
notify_on_release(cgrp)代碼如下:
static int notify_on_release(const struct cgroup *cgrp)
{
    return test_bit(CGRP_NOTIFY_ON_RELEASE, &cgrp->flags);
}
它用來判斷cgroup有沒有設定CGRP_NOTIFY_ON_RELEASE標誌
綜合上面的分析.如果cgroup中最後一個進程退出.且cgroup設定了CGRP_NOTIFY_ON_RELEASE標誌.流程就會轉到check_for_release()中.該函數代碼如下:
static void check_for_release(struct cgroup *cgrp)
{
    /* All of these checks rely on RCU to keep the cgroup
     * structure alive */
    if (cgroup_is_releasable(cgrp) && !atomic_read(&cgrp->count)
        && list_empty(&cgrp->children) && !cgroup_has_css_refs(cgrp)) {
        /* Control Group is currently removeable. If it's not
         * already queued for a userspace notification, queue
         * it now */
        int need_schedule_work = 0;
        spin_lock(&release_list_lock);
        if (!cgroup_is_removed(cgrp) &&
            list_empty(&cgrp->release_list)) {
            list_add(&cgrp->release_list, &release_list);
            need_schedule_work = 1;
        }
        spin_unlock(&release_list_lock);
        if (need_schedule_work)
            schedule_work(&release_agent_work);
    }
}
首先,在這裏必須要滿足以下四個條件才能繼續下去:
1:cgroup_is_releasable()返回1.
代碼如下:
static int cgroup_is_releasable(const struct cgroup *cgrp)
{
    const int bits =
        (1
        (1
    return (cgrp->flags & bits) == bits;
}
它表示當前cgroup是含含有CGRP_RELEASABLE和CGRP_NOTIFY_ON_RELEASE標誌.結合我們在上面分析的. CGRP_RELEASABLE標誌是進程在退出是就會設置的.

2:cgroup的引用計數爲0
3:cgroup沒有子層cgroup
4: cgroup_has_css_refs()返回0.代碼如下:
static int cgroup_has_css_refs(struct cgroup *cgrp)
{
    int i;
    for (i = 0; i
        struct cgroup_subsys *ss = subsys;
        struct cgroup_subsys_state *css;
        /* Skip subsystems not in this hierarchy */
        if (ss->root != cgrp->root)
            continue;
        css = cgrp->subsys[ss->subsys_id];
        if (css && atomic_read(&css->refcnt))
            return 1;
    }
    return 0;
}
也就是說,cgroup關聯的css_set引用計數必須要爲0

滿足上面幾個條件之後.就說明該cgroup是可以釋放的.因此將cgroup鏈接到了release_list.接着調度了工作隊列.在工作隊列中會完成餘下的工作.
下面跟蹤看看這個工作隊列是怎麼處理餘下任務的.
release_agent_work定義如下:
static DECLARE_WORK(release_agent_work, cgroup_release_agent);
該工作隊列對應的處理函數爲cgroup_release_agent().代碼如下:
static void cgroup_release_agent(struct work_struct *work)
{
    BUG_ON(work != &release_agent_work);
    mutex_lock(&cgroup_mutex);
    spin_lock(&release_list_lock);
    /*遍歷鏈表,直到其爲空*/
    while (!list_empty(&release_list)) {
        char *argv[3], *envp[3];
        int i;
        char *pathbuf = NULL, *agentbuf = NULL;
        /*取得鏈表項對應的cgroup*/
        struct cgroup *cgrp = list_entry(release_list.next,
                            struct cgroup,
                            release_list);
        /*將cgroup從release_list中斷開*/
        list_del_init(&cgrp->release_list);
        spin_unlock(&release_list_lock);
        /*將cgroup的路徑存放到pathbuf中*/
        pathbuf = kmalloc(PAGE_SIZE, GFP_KERNEL);
        if (!pathbuf)
            goto continue_free;
        if (cgroup_path(cgrp, pathbuf, PAGE_SIZE)
            goto continue_free;
        /*agentbuf存放release_agent_path的內容*/
        agentbuf = kstrdup(cgrp->root->release_agent_path, GFP_KERNEL);
        if (!agentbuf)
            goto continue_free;
        /*初始化運行參數和環境變量*/
        i = 0;
        argv[i++] = agentbuf;
        argv[i++] = pathbuf;
        argv = NULL;

        i = 0;
        /* minimal command environment */
        envp[i++] = "HOME=/";
        envp[i++] = "PATH=/sbin:/bin:/usr/sbin:/usr/bin";
        envp = NULL;

        /* Drop the lock while we invoke the usermode helper,
         * since the exec could involve hitting disk and hence
         * be a slow process */
         /*調用用戶空間的進程*/
        mutex_unlock(&cgroup_mutex);
        call_usermodehelper(argv[0], argv, envp, UMH_WAIT_EXEC);
        mutex_lock(&cgroup_mutex);
continue_free:
        kfree(pathbuf);
        kfree(agentbuf);
        spin_lock(&release_list_lock);
    }
    spin_unlock(&release_list_lock);
    mutex_unlock(&cgroup_mutex);
}
該函數遍歷release_list中的cgroup.然後以其路徑做爲參數.調用root->release_agent_path對應的程序.
我們來做如下的實驗:
爲了配合這次實驗.必須要寫兩個測試的程序.代碼如下:
Test.c

#include
#include

main()
{
        int i = 30;
        while(i){
                i--;
                sleep(1);
        }
}

這個進程睡眠30s之後退出.編譯成test

另外一個程序代碼如下:
Main.c
#include
#include

int main(int argc,char *argv[])
{
        char buf[125] = "";
        int i = 0;

        sprintf(buf,"rm -f /var/eric_test");
        system(buf);

        while(i
                sprintf(buf,"echo %s >> /var/eric_test",argv);
                system(buf);
                i++;
        }

}
它就是將調用參數輸出到/var/eric_test下面.
下面就可以開始我們的測試了.掛載目錄下已經有一個子層cgroup.如下示:
.
|-- debug.cgroup_refcount
|-- debug.current_css_set
|-- debug.current_css_set_refcount
|-- debug.releasable
|-- debug.taskcount
|-- eric
|   |-- debug.cgroup_refcount
|   |-- debug.current_css_set
|   |-- debug.current_css_set_refcount
|   |-- debug.releasable
|   |-- debug.taskcount
|   |-- notify_on_release
|   `-- tasks
|-- notify_on_release
|-- release_agent
`-- tasks

接下來設置realesse_agent_path和CGRP_NOTIFY_ON_RELEASE標誌,指令如下:
[root@localhost cgroup]# echo /root/main > release_agent
[root@localhost cgroup]# echo 1 > eric/notify_on_release
下面往子層cgroup中添加一個進程.指令如下:
[root@localhost cgroup]# /root/test &
[1] 4350
[root@localhost cgroup]# echo 4350 > eric/tasks
[root@localhost cgroup]#
[1]+  Done                    /root/test
等/root/test運行完之後.就會進行notify_on_release的操作了.印證一下:
[root@localhost cgroup]# cat /var/eric_test
/root/main
/eric
一切都如我們上面分析的一樣

9.2:取消進程與cgroup的關聯
當cgroup中的最後一個進程取消關聯的時候,也會有notify_on_release過程.見下面的代碼片段:
int cgroup_attach_task(struct cgroup *cgrp, struct task_struct *tsk)
{
    int retval = 0;
    struct cgroup_subsys *ss;
    struct cgroup *oldcgrp;
    struct css_set *cg = tsk->cgroups;
    ......
    ......
    set_bit(CGRP_RELEASABLE, &oldcgrp->flags);
    synchronize_rcu();
    put_css_set(cg);
}
這個函數我們在之前分析過,不過也把notify_on_release的過程去掉了.現在也把它加上.
代碼中的cg是指向進程原本所引用的css_set
Oldcgrp是過程之前所在的cgroup
在代碼中,會將oldcgrp標誌設爲CGRP_RELEASABLE.之後也會調用put_css_set().put_css_set()就是我們在上面分析的過程了.如果cgroup爲空的話,就會產生notify_on_release的操作.
同樣做個測試:
接着上面的測試環境.我們先來看下環境下的相關文件內容:
[root@localhost cgroup]# cat release_agent
/root/main
[root@localhost cgroup]# cat eric/tasks
[root@localhost cgroup]# cat eric/notify_on_release
1
[root@localhost cgroup]# pwd
/dev/cgroup
好了,測試開始了:
[root@localhost cgroup]# rm -rf /var/eric_test
[root@localhost cgroup]# echo 1701 > eric/tasks
[root@localhost cgroup]# echo 1701 >tasks
[root@localhost cgroup]# cat /var/eric_test
/root/main
/eric
在上面的測試過程中.爲了避免影響測試效果.先將/var/eric_test文件刪了.然後將進程1701關聯到eric所表示的cgroup.然後再把1701再加最上層cgroup.這樣就會造成eric下關聯進程爲空.相應的會發生notify_on_release過程.上面的測試也印證了這一說話.

9.3:移除cgroup
當移除cgroup下的最後一個子層cgroup時.也會發生notify_on_release.
看一下移除cgroup時的代碼片段:
static int cgroup_rmdir(struct inode *unused_dir, struct dentry *dentry)
{
    ......
    ......
    set_bit(CGRP_RELEASABLE, &parent->flags);
    check_for_release(parent);
    ......
}
代碼中,parent表示cgroup的上一層.在移除cgroup時,會設置上一層的cgroup標誌的CGRP_RELEASABLE位.然後流程同樣會轉入到check_for_release().這樣,如果上一層cgroup是空的話.就會生notify_on_release操作了.
測試如下:
還是用上層的測試環境.先來看一下初始環境:
[root@localhost cgroup]# pwd
/dev/cgroup
[root@localhost cgroup]# cat release_agent
/root/main
[root@localhost cgroup]# cat eric/notify_on_release
1
在eric下面再加一層cgroup.
[root@localhost cgroup]# mkdir eric/test
[root@localhost cgroup]# tree
.
|-- debug.cgroup_refcount
|-- debug.current_css_set
|-- debug.current_css_set_refcount
|-- debug.releasable
|-- debug.taskcount
|-- eric
|   |-- debug.cgroup_refcount
|   |-- debug.current_css_set
|   |-- debug.current_css_set_refcount
|   |-- debug.releasable
|   |-- debug.taskcount
|   |-- notify_on_release
|   |-- tasks
|   `-- test
|       |-- debug.cgroup_refcount
|       |-- debug.current_css_set
|       |-- debug.current_css_set_refcount
|       |-- debug.releasable
|       |-- debug.taskcount
|       |-- notify_on_release
|       `-- tasks
|-- notify_on_release
|-- release_agent
`-- tasks

2 directories, 22 files
接着運行如下指令:
[root@localhost cgroup]# rm -rf /var/eric_test
[root@localhost cgroup]# rmdir eric/test/
[root@localhost cgroup]# cat /var/eric_test
/root/main
/eric
如上所示.把eric下的唯一一個cgroup移除的時候.就發生了notity_on_release過程.

十:cgroup的proc節點
10.1:/proce/cgroups
在前面分析cgroup初始化的時候.在cgroup_init()中有下面代碼片段:
int __init cgroup_init(void)
{
    ......
    ......
    proc_create("cgroups", 0, NULL, &proc_cgroupstats_operations)
    ......
    ......
}
也就是說.會在proc根目錄下創建一個名爲cgroups的文件.如下示:
[root@localhost cgroup]# ls /proc/cgroups  
/proc/cgroups
接下來就來分析這個文件的操作.
該文件對應的操作集爲
proc_cgroupstats_operations.定義如下:
static struct file_operations proc_cgroupstats_operations = {
    .open = cgroupstats_open,
    .read = seq_read,
    .llseek = seq_lseek,
    .release = single_release,
}
從上面看到,這個文件是隻讀的.
先來看open時的操作,對應接口爲cgroupstats_open.代碼如下:
static int cgroupstats_open(struct inode *inode, struct file *file)
{
    return single_open(file, proc_cgroupstats_show, NULL);
}
Single_open()函數十分簡單.它也是sequences file中提供的一個接口.有關sequences file部份我們在上面已經分析過了. 這裏就不再詳細分析了.它將seq_file的show操作指向了proc_cgroupstats_show.
我們在上面的proc_cgroupstats_operations結構中可看到,它提供的read操作爲seq_read().它就是調用seq_file中的相關操作.在open的時候,已經將seq_file的show接口指向了proc_cgroupstats_show().代碼如下:
static int proc_cgroupstats_show(struct seq_file *m, void *v)
{
    int i;

    seq_puts(m, "#subsys_name\thierarchy\tnum_cgroups\tenabled\n");
    mutex_lock(&cgroup_mutex);
    for (i = 0; i
        struct cgroup_subsys *ss = subsys;
        seq_printf(m, "%s\t%lu\t%d\t%d\n",
               ss->name, ss->root->subsys_bits,
               ss->root->number_of_cgroups, !ss->disabled);
    }
    mutex_unlock(&cgroup_mutex);
    return 0;
}
從代碼中看到,它就是將系統中每subsys名稱.所在hierarchy的位碼. Hierarchy下面的cgroup數目和subsys的啓用狀態.
測試如下:
[root@localhost cgroup]# cat /proc/cgroups
#subsys_name    hierarchy       num_cgroups     enabled
cpuset  0       1       1
debug   2       2       1
ns      0       1       1
cpuacct 0       1       1
memory  0       1       1
devices 0       1       1
freezer 0       1       1
從這裏可以看到所有的subsys和hierarchy的情況.在上面顯示的debug和其它的subsys不同.是因爲用的是之前測試notify_on_release的環境.如下示:
[root@localhost cgroup]# tree ../cgroup/
../cgroup/
|-- debug.cgroup_refcount
|-- debug.current_css_set
|-- debug.current_css_set_refcount
|-- debug.releasable
|-- debug.taskcount
|-- eric
|   |-- debug.cgroup_refcount
|   |-- debug.current_css_set
|   |-- debug.current_css_set_refcount
|   |-- debug.releasable
|   |-- debug.taskcount
|   |-- notify_on_release
|   `-- tasks
|-- notify_on_release
|-- release_agent
`-- tasks

1 directory, 15 files

10.2:proc下進程鏡像中的cgroup
除了在proc頂層目錄創建cgroup外.另外在每個進程鏡像下都有一個cgroup的文件.如下示:
[root@localhost cgroup]# ls /proc/648/cgroup
/proc/648/cgroup

來看一下這個文件對應的操作,如下示:
static const struct pid_entry tid_base_stuff[] = {
    ......
    ......
#ifdef CONFIG_CGROUPS
    REG("cgroup",  S_IRUGO, cgroup),
#endif
......
}

#define REG(NAME, MODE, OTYPE)              \
    NOD(NAME, (S_IFREG|(MODE)), NULL,       \
        &proc_##OTYPE##_operations, {})
從上面可以看到.Cgroup對應的操作爲&proc_cgroup_operations
定義如下:
struct file_operations proc_cgroup_operations = {
    .open       = cgroup_open,
    .read       = seq_read,
    .llseek     = seq_lseek,
    .release    = single_release,
};
Open對應的操作爲cgroup_open.定義如下:
static int cgroup_open(struct inode *inode, struct file *file)
{
    struct pid *pid = PROC_I(inode)->pid;
    return single_open(file, proc_cgroup_show, pid);
}
又見到single_open()了.如上面的分析一樣,read操作的時候會轉入到proc_cgroup_show().代碼如下:
static int proc_cgroup_show(struct seq_file *m, void *v)
{
    struct pid *pid;
    struct task_struct *tsk;
    char *buf;
    int retval;
    struct cgroupfs_root *root;

    retval = -ENOMEM;
    buf = kmalloc(PAGE_SIZE, GFP_KERNEL);
    if (!buf)
        goto out;

    retval = -ESRCH;
    pid = m->private;
    tsk = get_pid_task(pid, PIDTYPE_PID);
    if (!tsk)
        goto out_free;

    retval = 0;

    mutex_lock(&cgroup_mutex);

    /*遍歷所有的cgroupfs_root*/
    for_each_root(root) {
        struct cgroup_subsys *ss;
        struct cgroup *cgrp;
        int subsys_id;
        int count = 0;

        /* Skip this hierarchy if it has no active subsystems */
        /*如果hierarchy中沒有subsys.就繼續下一個rootnode就是這樣的情況*/
        if (!root->actual_subsys_bits)
            continue;
        /*打印hierarchy中的subsys位圖*/
        seq_printf(m, "%lu:", root->subsys_bits);
        /*打印hierarchy中的subsys名稱*/
        for_each_subsys(root, ss)
            seq_printf(m, "%s%s", count++ ? "," : "", ss->name);
        seq_putc(m, ':');
        /*進程所在cgroup的path*/
        get_first_subsys(&root->top_cgroup, NULL, &subsys_id);
        cgrp = task_cgroup(tsk, subsys_id);
        retval = cgroup_path(cgrp, buf, PAGE_SIZE);
        if (retval
            goto out_unlock;
        seq_puts(m, buf);
        seq_putc(m, '\n');
    }

out_unlock:
    mutex_unlock(&cgroup_mutex);
    put_task_struct(tsk);
out_free:
    kfree(buf);
out:
    return retval;
}
它的核心操作在這個for循環中,它的操作在註釋中已經詳細的說明了.在這裏不做詳細分析.
我將虛擬機重啓了 *^_^*,所以現在的環境不是我們之前的測試環境了
測試一下:
[root@localhost ~]# cat /proc/646/cgroup
[root@localhost ~]#
說明當前系統中還沒有hierarchy.
接下來掛載上一個:
[root@localhost ~]# mkdir /dev/cgroup
[root@localhost ~]# mount -t cgroup cgroup -o debug /dev/cgroup/
[root@localhost ~]# cat /proc/6
6/   609/ 646/
[root@localhost ~]# cat /proc/646/cgroup
2:debug:/
[root@localhost ~]#
從上面可以看到.系統已經有一個hierarchy.且綁定的是debug subsys.當前進程是位於它的頂層.
繼續測試:
[root@localhost ~]# mkdir /dev/cgroup/eric
[root@localhost ~]# echo 646 > /dev/cgroup/eric/tasks
[root@localhost ~]# cat /proc/646/cgroup                        
2:debug:/eric
[root@localhost ~]#
可以看到,當前進程是位於eric這個cgroup中.

十一:小結
在這一節裏,用大篇幅詳細的描述了整個cgroup的框架.cgroup框架並不複雜,只是其中的數據結構和大量的全局變量弄的頭昏眼花.因此理順這些數據結構和變量是閱讀cgroup代碼的關鍵.另外在cgroup中對於RCU和rw_mutex的使用也有值得推敲的地方.不過由於篇幅關係,就不再分析這一部份.在接下來專題裏.以cgroup框架爲基礎來分析幾個重要的subsys.
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章