注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

Tsecer的回音岛

Tsecer的博客

 
 
 

日志

 
 

cgroup实现分析(1)--基本模式及相关数据结构分析  

2012-04-19 22:32:12|  分类: Linux内核 |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |
一、cgroup机制
这个是一个我一直想围观一下的机制,实现足够复杂,所以看起来比较有意思。因为有人说:“要想让当前的问题变得简单,那么最好尝试一下更复杂的问题”,比较文艺的说法就是“登东山而小鲁,登泰山而小天下”。但是以前一直没有决心详细看一下这个东西的实现,也是没有找到真正使用这个功能的切入点,但是最近想到了一个比较有实际意义的问题。
在之前我对SIGSTOP的一篇讨论文章中,说明了SIGSTOP不能只让一个特定线程挂起,它即使通过pthread_kill发送给特定线程,也会导致整个线程组挂起。所以假设我们要在让同一进程中的另一个线程使用SIGSTOP挂起,那是不可能的。通过SIGSTOP让一个特定线程挂起只有这个线程的调试线程才有这个能力。关于这一点我不知道在之前有没有讨论,所以这里就不再详细展开调试如何实现。
看到基于cgroup实现了一个freezer子系统,可以认为是一个大的冰柜,所以希望被冷冻的线程都可以方便的在这个机制下冷冻,效果非常好,挂起的线程呈现为深度TASK_UNINTERRUPTIBLE睡眠状态。这就好像我们对一个病人做手术一样,它麻醉的越深,手术效果越好。所以,如果我们想把一个线程单独挂起来的话,就可以把它流放到这个冰柜中,从而可以对他进行比较深入的手术操作。
二、部分用户操作体验cgroup的特征
这些操作演示并不是可有可无的,因为我之前看cgroup的时候并没有实际使用过这个功能(这个是比较新的版本中加的功能,一般比较老的内核默认并没有使能这个选项),所以看代码的时候很多地方不是很懂,这里如果有一个大致的操作印象,就会对底层代码的实现有一个大致的概念,同时也可以思考一下那些和我们预料行为不同的功能是如何实现的。
[root@Harry ~]# cd /home/tsecer/
[root@Harry tsecer]# cd cgrouptest/
[root@Harry cgrouptest]# ls
cpuacc_cpu  dupcpu  freezer  mix                                   这里预先建立4个目录,测试挂载行为,这个对于之后cgroup实现理解是一个基础
[root@Harry cgrouptest]# mount -t cgroup none -ocpu,cpuacct cpuacc_cpu   将cpuacct+cpu两个子系统挂载到同一个cpuacc_cpu文件夹上
You have new mail in /var/spool/mail/root
[root@Harry cgrouptest]# mount -t cgroup none -ofreezer freezer                    将freezer子系统挂载到freezer文件夹上
[root@Harry cgrouptest]# mount -t cgroup none -ocpu,cpuacct dupcpu           将fcpuacct+cpu两个子系统重复挂载到dupcup文件夹下,成功
[root@Harry cgrouptest]# mount -t cgroup none -ocpu,cpuacct,freezer mix     将cpuacct+cpu+freezer组合挂载到mix文件夹下,挂载失败
mount: none already mounted or mix busy
[root@Harry cgrouptest]# mkdir singlecpu                                                     
[root@Harry cgrouptest]# mount -t cgroup none -ocpu singlecpu/                   将cpu子系统单独挂载到singlecpu文件夹下,挂载失败
mount: none already mounted or singlecpu/ busy
[root@Harry cgrouptest]# mkdir freezer/freezersub
[root@Harry cgrouptest]# echo 1358 > freezer/freezersub/tasks                   将1358放入freezer控制系统
[root@Harry cgrouptest]# mkdir cpuacc_cpu/cpuacc_cpu_sub
[root@Harry cgrouptest]# echo 1358 > cpuacc_cpu/cpuacc_cpu_sub/tasks 1358再次放入cpuacct+cpu组合控制系统中
[root@Harry cgrouptest]# echo 1359 > freezer/freezersub/tasks                   1359单独放入freezer控制系统
[root@Harry cgrouptest]# echo 1360 > cpuacc_cpu/cpuacc_cpu_sub/tasks 1360单独放入cpuacct+cpu控制系统
[root@Harry cgrouptest]# cat /proc/1359/cgroup
64:freezer:/freezersub
12:cpuacct,cpu:/
[root@Harry cgrouptest]# cat /proc/1358/cgroup
64:freezer:/freezersub
12:cpuacct,cpu:/cpuacc_cpu_sub
[root@Harry cgrouptest]# cat /proc/1360/cgroup
64:freezer:/
12:cpuacct,cpu:/cpuacc_cpu_sub   这里的三个进程的最后有三种完全不同的组合(另一种组合就是//),这个将是之后内核中代码复杂的来源
[root@Harry cgrouptest]# ls freezer/
freezersub  notify_on_release  release_agent  tasks
[root@Harry cgrouptest]# ls freezer/freezersub/
freezer.state  notify_on_release  tasks                      每个文件夹中都有tasks文件,这个是我们最为关心和常用的文件
[root@Harry cgrouptest]# ls cpuacc_cpu/cpuacc_cpu_sub/
cpuacct.stat   cpuacct.usage_percpu  cpu.rt_runtime_us  notify_on_release
cpuacct.usage  cpu.rt_period_us      cpu.shares         tasks
[root@Harry cgrouptest]# ls dupcpu/cpuacc_cpu_sub/  
cpuacct.stat   cpuacct.usage_percpu  cpu.rt_runtime_us  notify_on_release
cpuacct.usage  cpu.rt_period_us      cpu.shares         tasks  两个dup的cpuacct+cpu文件夹内容完全相同
[root@Harry cgrouptest]# cat /proc/cgroups  
#subsys_name    hierarchy    num_cgroups    enabled
cpuset    0    1    1
ns    0    1    1
cpu    12    2    1
cpuacct    12    2    1
memory    0    1    1
devices    0    1    1
freezer    64    2    1
net_cls    0    1    1
[root@Harry cgrouptest]#
这里可以看到极为重要的一个特征,同一个控制子系统无论是组合还是单独出现,它都只能出现在一个挂载系统中(这个也就是cgroup中描述的hierarchy的概念),如果挂载到另一个文件夹下,则此处挂载必须和之前的某此挂载完全相同(例如上例中的cpuacct+cpu组合),这个相当于proc文件系统在多出挂载,不同挂载点使用的都是同一个super_block
三、基本数据结构
1、hierarchy
这个只是一个逻辑的概念,在内核中没有对应名称的数据结构,但是从实现来看,它是和cgroupfs_root相对应。当用户态每次执行一个
mount -t cgroup -oSubSys none dir
的时候,内核都会动态分配一个这样的结构(当然如果-o选项完全相同,内核会复用之前挂载生成的cgroupfs_root结构)。
①、cgroupfs_root实例分配
这个结构分配其实不同调试器就可以找到,但是为了增加可信度,我还是在这里放一下调用链,这个是单独挂载freezer时的一个调用链(基于2.6.37.1内核版本)
(gdb) bt
#0  kzalloc (size=4432, flags=208) at include/linux/slab.h:323
#1  0xc04e2bd2 in cgroup_root_from_opts (opts=0xceb51e64)
    at kernel/cgroup.c:1382
#2  0xc04e2e7d in cgroup_mount (fs_type=0xc0d43360, flags=32768,
    unused_dev_name=0xcea9ed88 "none", data=0xceb3d000) at kernel/cgroup.c:1484
#3  0xc05ae559 in vfs_kern_mount (type=0xc0d43360, flags=32768,
    name=0xcea9ed88 "none", data=0xceb3d000) at fs/super.c:985
#4  0xc05ae9b8 in do_kern_mount (fstype=0xcea9ed80 "cgroup", flags=32768,
    name=0xcea9ed88 "none", data=0xceb3d000) at fs/super.c:1153
#5  0xc05cc88f in do_new_mount (path=0xceb51f70, type=0xcea9ed80 "cgroup",
    flags=32768, mnt_flags=32, name=0xcea9ed88 "none", data=0xceb3d000)
    at fs/namespace.c:1746
#6  0xc05cd008 in do_mount (dev_name=0xcea9ed88 "none",
    dir_name=0xceb41000 "freezer/", type_page=0xcea9ed80 "cgroup",
    flags=32768, data_page=0xceb3d000) at fs/namespace.c:2066
#7  0xc05cd330 in sys_mount (dev_name=0xbfcb7f7d "none",
    dir_name=0xbfcb7f82 "freezer/", type=0xbfcb7f6c "cgroup", flags=32768,
    data=0x9ff7fb0) at fs/namespace.c:2210
#8  0xc041071f in ?? () at arch/x86/kernel/entry_32.S:425
#9  0xb7804424 in ?? ()
Backtrace stopped: previous frame inner to this frame (corrupt stack?)
(gdb)
其中1382行代码为
   1375 static struct cgroupfs_root *cgroup_root_from_opts(struct cgroup_sb_opts         *opts)
   1376 {
   1377         struct cgroupfs_root *root;
   1378
   1379         if (!opts->subsys_bits && !opts->none)
   1380                 return NULL;
   1381
   1382         root = kzalloc(sizeof(*root), GFP_KERNEL);
②、完全相同挂载判断
这个流程分支对应于前一节中说明的对cpuacct+cpu的第二次挂载。
cgroup_mount-->>>cgroup_root_from_opts中不管三七二十一就分配了一个新的cgroupfs_root实例,但是前面说过,完全相同的挂载是可以通过并且复用的,所以这个如果已经有完全相同挂载的话这个新分配的结构需要被释放掉。
这个判断的实现流程为
sb = sget(fs_type, cgroup_test_super, cgroup_set_super, &opts);
    root = sb->s_fs_info;
    BUG_ON(!root);
    if (root == opts.new_root) {对于完全相同的挂载将不满足这个条件,所以进入else分支,从而释放掉刚刚分配的cgroupfs_root实例。两个挂载是否相同,通过cgroup_test_super函数判断
……
} else {
        /*
         * We re-used an existing hierarchy - the new root (if
         * any) is not needed
         */
        cgroup_drop_root(opts.new_root);
        /* no subsys rebinding, so refcounts don't change */
        drop_parsed_module_refcounts(opts.subsys_bits);
    }
③、不同挂载何处失败
这个流程对应于前面例子中的对
[root@Harry cgrouptest]# mount -t cgroup none -ocpu singlecpu/                  
mount: none already mounted or singlecpu/ busy
此时挂载失败。这个失败的流程位于
cgroup_mount---->>rebind_subsystems
/* Check that any added subsystems are currently free */
    for (i = 0; i < CGROUP_SUBSYS_COUNT; i++) {
        unsigned long bit = 1UL << i;
        struct cgroup_subsys *ss = subsys[i];
        if (!(bit & added_bits))
            continue;
        /*
         * Nobody should tell us to do a subsys that doesn't exist:
         * parse_cgroupfs_options should catch that case and refcounts
         * ensure that subsystems won't disappear once selected.
         */
        BUG_ON(ss == NULL);
        if (ss->root != &rootnode) {
            /* Subsystem isn't free */
            return -EBUSY; 对于一个已经被挂载过的subsystem,它的root不等于rootnode
        }
    }
……
    for (i = 0; i < CGROUP_SUBSYS_COUNT; i++) {
            ss->root = root; 这里将会修改subsystem的root,对于已经挂载过的subsystem,这个操作也是导致前面判断失败的原因
}
2、cgroup_subsys
这个结构是内核提供的一些功能控制结构,它的数量和内容在系统创建之初就已经确定;如果需要添加一个新的subsystem,必须定义自己的实例,例如debug_subsys、freezer_subsys等系统中已经存在的系统,然后将它们添加到include\linux\cgroup_subsys.h文件中。
大致说来,这样的一个结构提供了对进程组某些行为的管理和控制。例如freezer_subsys可以冻结/解冻自己管理的进程组的运行状态,而cpuacct_subsys可以统计进程组中进程的运行时间,cpu_subsys可以控制进程组内进程在哪些核上运行等功能。它们和cgroup约定好接口,然后由cgroup提供对进程组的加入和退出管理维护,而不同的subsys则负责对进程组中的进程/线程进行操作。为了完成这些功能,它需要分分配一些数据结构用来保存对于特定进程组的管理信息,这个接口就是通过cgroup_subsys_state来中转,其实这个cgroup_subsys_state结构本身没有太大作用,只是cgroup系统和subsys系统之间一个互相查询的结构。这个结构实例一般包含在一个不同subsys识别的特有结构中,从而可以通过这个结构的地址来找到相邻的控制数据。例如cpuset_subsys中创建的控制结构
struct cpuset {
    struct cgroup_subsys_state css;

    unsigned long flags;        /* "unsigned long" so bitops work */
    cpumask_var_t cpus_allowed;    /* CPUs allowed to tasks in cpuset */
    nodemask_t mems_allowed;    /* Memory Nodes allowed to tasks */
……
}
知道了cgroup_subsys_state实例的对象,就可以通过container_of来找到整个cpuset结构的位置,例如下面是
/* Retrieve the cpuset for a task */
static inline struct cpuset *task_cs(struct task_struct *task)
{
    return container_of(task_subsys_state(task, cpuset_subsys_id),
                struct cpuset, css);
}
该结构部分重要接口:
①、create
该接口在一个cgroup挂载点内创建新的目录时执行,对于该挂载点选项中的每个subsys,都需要为这个新创建的文件夹创建自己的特有数据结构,这个结构根据不同的控制结构而各不相同,但是它返回给上层的是一个统一的cgroup_subsys_state结构指针,这样cgroup就可以将不同的subsys使用统一的cgroup_subsys_state指针管理起来,可以认为是C语言下实现的多态。
(gdb) bt
#0  cpuset_create (ss=0xc0d43c40, cont=0xceb2c400) at kernel/cpuset.c:1880
#1  0xc04e5d6a in cgroup_create (parent=0xceb5a018, dentry=0xcf243d48,
    mode=16877) at kernel/cgroup.c:3404
#2  0xc04e5f9d in cgroup_mkdir (dir=0xcf256158, dentry=0xcf243d48, mode=493)
    at kernel/cgroup.c:3469
#3  0xc05babff in vfs_mkdir (dir=0xcf256158, dentry=0xcf243d48, mode=493)
    at fs/namei.c:2066
#4  0xc05bacd5 in sys_mkdirat (dfd=-100, pathname=0xbfee5f85 "cpusetsub",
    mode=493) at fs/namei.c:2096
#5  0xc05bad3c in sys_mkdir (pathname=0xbfee5f85 "cpusetsub", mode=511)
    at fs/namei.c:2111
#6  0xc041071f in ?? () at arch/x86/kernel/entry_32.S:425
#7  0xb7801424 in ?? ()
②、populate
这个接口同样是在文件夹创建之后调用,通过这个接口,不同的subsys可以创建自己的文件,通过这些文件,用户可以调整这个文件夹(cgroup)下自己感兴趣的配置参数,例如cpuset下的cpus文件控制该文件夹下所有任务可以运行的cpu。
(gdb) bt
#0  cpuset_populate (ss=0xc0d43c40, cont=0xceb2c400) at kernel/cpuset.c:1820
#1  0xc04e5a6b in cgroup_populate_dir (cgrp=0xceb2c400) at kernel/cgroup.c:3298
#2  0xc04e5e8c in cgroup_create (parent=0xceb5a018, dentry=0xcf243d48,
    mode=16877) at kernel/cgroup.c:3433
#3  0xc04e5f9d in cgroup_mkdir (dir=0xcf256158, dentry=0xcf243d48, mode=493)
    at kernel/cgroup.c:3469
#4  0xc05babff in vfs_mkdir (dir=0xcf256158, dentry=0xcf243d48, mode=493)
    at fs/namei.c:2066
#5  0xc05bacd5 in sys_mkdirat (dfd=-100, pathname=0xbfee5f85 "cpusetsub",
    mode=493) at fs/namei.c:2096
#6  0xc05bad3c in sys_mkdir (pathname=0xbfee5f85 "cpusetsub", mode=511)
    at fs/namei.c:2111
#7  0xc041071f in ?? () at arch/x86/kernel/entry_32.S:425
#8  0xb7801424 in ?? ()
③、can_attach
当一个新的任务加入到这个控制组(cgroup)中之后,将会调用控制组中所有子系统的can_attach接口,如果这个接口判断为true,则调用该子系统的attach接口。这是一个任务新加入控制组的操作,所以子系统可能需要对新任务的某些特征进行调整。例如该组只允许进程在1和2号CPU上运行,那么新加的进程如果正在3号CPU上运行,那么attach中就需要将新加入任务迁移到1号或者2号CPU上。
(gdb) bt
#0  cpuset_can_attach (ss=0xc0d43c40, cont=0xceb2c400, tsk=0xceac25e0,
    threadgroup=false) at kernel/cpuset.c:1383
#1  0xc04e3526 in cgroup_attach_task (cgrp=0xceb2c400, tsk=0xceac25e0)
    at kernel/cgroup.c:1753
#2  0xc04e38d7 in attach_task_by_pid (cgrp=0xceb2c400, pid=38)
    at kernel/cgroup.c:1887
#3  0xc04e391c in cgroup_tasks_write (cgrp=0xceb2c400, cft=0xc0d433a0, pid=38)
    at kernel/cgroup.c:1897
#4  0xc04e3af6 in cgroup_write_X64 (cgrp=0xceb2c400, cft=0xc0d433a0,
    file=0xceb011e0,
    userbuf=0xb77ca000 "38\n \nho 38 >cpusetsub/tasks \033[J[J \033[J\033[JJetsub/cpuset.mems\ny_spread_slab\n", nbytes=3, unused_ppos=0xceb09f94)
    at kernel/cgroup.c:1968
#5  0xc04e3d35 in cgroup_file_write (file=0xceb011e0,
    buf=0xb77ca000 "38\n \nho 38 >cpusetsub/tasks \033[J[J \033[J\033[JJetsub/cpuset.mems\ny_spread_slab\n", nbytes=3, ppos=0xceb09f94) at kernel/cgroup.c:2026
#6  0xc05aa6d4 in vfs_write (file=0xceb011e0,
    buf=0xb77ca000 "38\n \nho 38 >cpusetsub/tasks \033[J[J \033[J\033[JJetsub/cpuset.mems\ny_spread_slab\n", count=3, pos=0xceb09f94) at fs/read_write.c:382
#7  0xc05aa82c in sys_write (fd=1,
    buf=0xb77ca000 "38\n \nho 38 >cpusetsub/tasks \033[J[J \033[J\033[JJetsub/cpuset.mems\ny_spread_slab\n", count=3) at fs/read_write.c:434
#8  0xc041071f in ?? () at arch/x86/kernel/entry_32.S:425
---Type <return> to continue, or q <return> to quit---
#9  0xb77cb424 in ?? ()
④、attach
参考对于can_attach接口的说明。
3、cgroup
到这里,感觉这个说明顺序是有些凌乱的,因为最为核心的cgroup并没有放在最开始,只是跟着感觉走,不知道看到的各位感觉这个顺序是否合理。
①、何时创建
这个结构是和一个文件夹对应,没创建一个新的文件夹就会常见一个新的cgroup实例,这个是毋庸置疑的。cgroup的设计就是使用一个文件夹来模拟一个控制组(早期的叫container“容器”,这个也挺合理),里面装入(聚合)文件夹下tasks文件描述的所有线程属性,然后一些额外的文件文件可以调整这个控制组中的参数。例如cpuset中的cpus文件控制这个组内的线程可以在哪些CPU上运行,而freezer文件夹下的freezer.state文件则可以控制这个进程组中文件是“冻结”还是“解冻”。
static long cgroup_create(struct cgroup *parent, struct dentry *dentry,
                 mode_t mode)
{
    struct cgroup *cgrp;
    struct cgroupfs_root *root = parent->root;
    int err = 0;
    struct cgroup_subsys *ss;
    struct super_block *sb = root->sb;

    cgrp = kzalloc(sizeof(*cgrp), GFP_KERNEL);这个创建时无条件的
②、文件夹内容
一个cgroup中一定会包含的是linux-2.6.37.1\kernel\cgroup.c文件中定义的
static struct cftype files[] = {
数组中的文件,其中最为重要的就是tasks文件,它可以用来控制进程的加入,通过
echo $pid >tasks
文件就可以将一个进程加入到该cgroup。
然后每个子系统还可以定义自己的文件,那就是通过各自的populate接口来完成对文件夹中文件的添加,这些添加的文件一般都是为了给用户提供对这个cgroup的控制接口。
③、cgroup_subsys_state创建
正如之前所说,这个结构本身是没有什么意义的,它只是一个锚点,或者说C语言的多态。cgroup只管理这种cgroup_subsys_state结构,而不同的子系统根据这个结构,通过container_of来还原出自己的真正控制结构,例如
struct cpuset {
    struct cgroup_subsys_state css;

    unsigned long flags;        /* "unsigned long" so bitops work */
……
}
struct freezer {
    struct cgroup_subsys_state css;
    enum freezer_state state;
    spinlock_t lock; /* protects _writes_ to state */
};
每个子系统的create接口在文件夹创建之后被调用,相当于要求每个子系统为这个控制组分配响应的控制结构。而一个控制组内子系统的配置是在mount的时候已经确定其实任务和cgroup_subsys都只关心 通过cgroup_subsys_state指针所确定的控制结构,cgroup只是为了建立建立任务到cgroup_subsys实例之间关系的一个实现方法
④、cgroup和cgroup_subsys_state关系的建立
cgroup_create函数中
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);
        if (ss->use_id) {
            err = alloc_css_id(ss, parent, cgrp);
            if (err)
                goto err_destroy;
        }
        /* At error, ->destroy() callback has to free assigned ID. */
        if (clone_children(parent) && ss->post_clone)
            ss->post_clone(ss, cgrp);
    }

static void init_cgroup_css(struct cgroup_subsys_state *css,
                   struct cgroup_subsys *ss,
                   struct cgroup *cgrp)
{
    css->cgroup = cgrp;
    atomic_set(&css->refcnt, 1);
    css->flags = 0;
    css->id = NULL;
    if (cgrp == dummytop)
        set_bit(CSS_ROOT, &css->flags);
    BUG_ON(cgrp->subsys[ss->subsys_id]);
    cgrp->subsys[ss->subsys_id] = css;
}
每个cgroup中包含了一个struct cgroup_subsys_state *subsys[CGROUP_SUBSYS_COUNT]成员域,所以每个可能的子系统(这个值可以编译时确定),cgroup都可以通过一个指针指向 每个子系统新创建的包含了cgroup_subsys_state的特有结构的地址,从而可以供各个特定子系统通过子系统id来找到自己为cgroup分 配的控制结构。
5、css_set
这个结构位于task_struct结构中,也就是在使能了cgroup的内核中,每个进程控制结构中会有这样的一个指针,它指向的是一个
struct css_set {
    atomic_t refcount;
    struct hlist_node hlist;
    struct list_head tasks;
    struct list_head cg_links;
    struct cgroup_subsys_state *subsys[CGROUP_SUBSYS_COUNT];
    struct rcu_head rcu_head;
};
其中最为关心的就是这个subsys数组,它和cgroup中其实相同,但是意义和初始化时机都不相同,我们想一下为什么会有这个结构。
①、结构的意义
在我们之前的例子中,可以看到有这样的操作
[root@Harry cgrouptest]# echo 1358 > freezer/freezersub/tasks                   将1358放入freezer控制系统
[root@Harry cgrouptest]# mkdir cpuacc_cpu/cpuacc_cpu_sub
[root@Harry cgrouptest]# echo 1358 > cpuacc_cpu/cpuacc_cpu_sub/tasks 1358再次放入cpuacct+cpu组合控制系统中
也就是1358进程被放入了两个不同的cgroup中;而
[root@Harry cgrouptest]# echo 1359 > freezer/freezersub/tasks                   1359单独放入freezer控制系统
[root@Harry cgrouptest]# echo 1360 > cpuacc_cpu/cpuacc_cpu_sub/tasks 1360单独放入cpuacct+cpu控制系统
1359和1360两个进程则分别只关联了一个不同的cgroup,所以不同的任务关联的cgroup可以是任意多的,但是对于同一个任务,它的某一个子系统只能关联到一个cgroup。
②、为什么独立为单独结构
独立出来的作用一般都是为了提供共享,这里也不例外。
a、进程的派生
当一个进程派生新进程的时候,它的子进程会集成父进程所在的子系统配置信息,这样,只要子进程的task_struct.cgroups指向父进程指向实体就可以了,事实上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);
}
b、不同进程之间共享
没有直接亲缘关系的进程,只要它们配置的cgroup组合完全相同(这也是task_struct中css_set命名为cgroups而不是cgroup的原因),它们同样可以通向一个css_set结构。例如
[root@Harry cgrouptest]# echo 2556 > freezer/freezersub/tasks
[root@Harry cgrouptest]# echo 2558 >freezer/freezersub/tasks
[root@Harry cgrouptest]# cat /proc/2556/cgroup
64:freezer:/freezersub
12:cpuacct,cpu:/
[root@Harry cgrouptest]# cat /proc/2558/cgroup
64:freezer:/freezersub
12:cpuacct,cpu:/
这个操作之后,2556和2558进程就有相同的cgroup组合,所以它们可以共享同一个css_set实例。
6、cg_cgroup_link
该结构主要是为了实现  task 和 cgroup 的多对多关系而用的一个结构。由于一个task可以指向任意多个的cgroup,反过来说,一个cgroup下也可以容纳任意多的任务,所以就需要一个中间连接件将它们粘合起来。
一个典型的多对多系统,我们通常都是直观的将它们分为左右两个部分,然后使用连线把它们连接起来,而这里的每个cg_cgroup_link都是这样的一条线的抽象。只是为了跟踪一个节点所有的连线关系,这里增加了两个额外链表成员cgrp_link_list和cg_lint_list
/* Link structure for associating css_set objects with cgroups */
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;
    struct cgroup *cgrp;
    /*
     * 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;
};
对于css_set,它的cg_links为一个队列头,它通过cg_cgroup_link链表中cg_link_list成员将一个任务引用的所有cgroup连接起来;反过来说,一个cgroup结构中的css_sets也是一个队列头,它将所有引用了自己(cgroup)的css_set串联起来。
①、分配
该结构分配是通过allocate_cg_links接口来分配的,所以对于它的分配时机还是比较容易跟踪一些,但是分配之后的操作并不是那么容易理解的。
典型的调用从文件夹下的tasks文件的写入接口开始
cgroup_tasks_write--->>>attach_task_by_pid---->>>cgroup_attach_task-->>find_css_set
if (allocate_cg_links(root_count, &tmp_cg_links) < 0)
这里是以已经创建的根节点为单位分配,比方说系统挂载了两个不同的cgroup(例如之前单独挂载的freezer和cpuacct+cpu),那么一次性分配2个cg_cgroup_link实例:
static int allocate_cg_links(int count, struct list_head *tmp)
{
    struct cg_cgroup_link *link;
    int i;
    INIT_LIST_HEAD(tmp);
    for (i = 0; i < count; i++) {
        link = kmalloc(sizeof(*link), GFP_KERNEL);
        if (!link) {
            free_cg_links(tmp);
            return -ENOMEM;
        }
        list_add(&link->cgrp_link_list, tmp);注意:这些新分配的结构通过cgrp_link_list串联在一起,这一点是后面理解的基础
    }
    return 0;
}
这里为什么要分配root_count个新节点呢?从代码上看,此时系统中已经创建了一个新的css_set结构(因为此前的find_existing_css_set已经失败),这个新的css_set是一个新的子系统组合,这个新的组合必将会对系统中每个已经挂载的cgroup中的某一个cgroup实例产生引用关系,所以要分配root_count个新的锚点来维护这种关系。
②、建立链表
find_css_set函数中通过下面循环来建立和维护链表
    list_for_each_entry(link, &oldcg->cg_links, cg_link_list) {对于进程原始css_set引用的所有cgroup实例进行遍历
        struct cgroup *c = link->cgrp;
        if (c->root == cgrp->root)
            c = cgrp;
        link_css_set(&tmp_cg_links, res, c);
    }
对于其中
        if (c->root == cgrp->root)
            c = cgrp;
的说明:
这个操作看起来比较简单,但是如果不明白底层机制,我想是无法理解的(我只是根据自己的情况来说,我看这个地方刚开始比较困惑),也是理解的一个难点。当满足if (c->root == cgrp->root)条件时,说明原始的css_set中的该cgroup和新atach的目的cgroup在同一个挂接点中,由于同一个css_set只可能关联同一个挂接点中的一个cgroup,所以新的css_set不再引用oldcg中的cgroup,而是引用了该挂接点中一个新的cgroup,所以要将新cgroup添加到css_set的cgrp_link_list中,表示该css_set引用的cgroup;反过来说,对于oldcfg中那些没有被修改的cgroup,由于新创建了一个css_set,这个css_set依然引用了oldcfg中配置的cgroup(因为此次只是在原始的css_set基础上调整了一个cgroup配置,其它cgroup引用依然不变)
static void link_css_set(struct list_head *tmp_cg_links,
             struct css_set *cg, struct cgroup *cgrp)
{
    struct cg_cgroup_link *link;

    BUG_ON(list_empty(tmp_cg_links));
    link = list_first_entry(tmp_cg_links, struct cg_cgroup_link,
                cgrp_link_list);
    link->cg = cg;
    link->cgrp = cgrp;
    atomic_inc(&cgrp->count);
    list_move(&link->cgrp_link_list, &cgrp->css_sets);前面已经说过,在 allocate_cg_links函数中就是通过cgrp_link_list连接在一起的,所以此时的list_move会将该函数中使用的cg_cgroup_link 从tmp_cg_links引导的链表中删除,进而在每次进入该函数都会使用一个新的cg_cgroup_link 节点
    /*
     * Always add links to the tail of the list so that the list
     * is sorted by order of hierarchy creation
     */
    list_add_tail(&link->cg_link_list, &cg->cg_links);
}
7、task_struct结构加入链表

这个也是一个重要操作,因为当一个文件夹下的配置改变之后,不同的subsys可能需要遍历cgroup中的所有任务来对这些任务进行处理,也就是控制组中进程遍历的问题。
int cgroup_attach_task(struct cgroup *cgrp, struct task_struct *tsk)

    rcu_assign_pointer(tsk->cgroups, newcg);
    task_unlock(tsk);

    /* Update the css_set linked lists if we're using them */
    write_lock(&css_set_lock);
    if (!list_empty(&tsk->cg_list)) {
        list_del(&tsk->cg_list);
        list_add(&tsk->cg_list, &newcg->tasks);系统中所有具有相同cgroup组合的进程通过该组合对应的css_set-->tasks链表链接在一起
    }
其中的list_empty在cgroup_post_fork
if (use_task_css_set_links) {该值在执行一次控制组进程遍历之后使能,也就是lazy思想的体现,到真正用到的时候初始化,并不影响功能
        if (list_empty(&child->cg_list))
            list_add(&child->cg_list, &child->cgroups->tasks);该操纵将会满足前面 cgroup_attach_task函数中!list_empty(&tsk->cg_list))判断。

四、TODO
这里只是分析了基本的数据结构,对于其它代码流程并没有分析,但是如果知道这些数据结构的意义和原理,我想分析其它流程应该问题不大。比如说可以以一个实例来分析一下当向tasks文件中添加一个新的任务时各个子系统如何响应;反过来,当更新了文件夹的一个控制文件时,控制组中的进程如何遍历处理。这两个是最为基本和核心的功能,分析完这两个功能,这个大致流程就走通了。由于时间关系,等之后再写。
  评论这张
 
阅读(3332)| 评论(0)
推荐 转载

历史上的今天

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2017