要打听cgroup已毕原理,必须先了然下vfs(虚拟文件系统).因为cgroup通过vfs向用户层提供接口,用户层通过挂载,成立目录,读写文件的办法与cgroup交互.
因为是介绍cgroup的篇章,由此只演讲cgroup文件系统是如何集成进vfs的,过多的vfs完结可参看其余资料.

要询问cgroup完成原理,必须先理解下vfs(虚拟文件系统).因为cgroup通过vfs向用户层提供接口,用户层通过挂载,创立目录,读写文件的主意与cgroup交互.
因为是介绍cgroup的文章,由此只演说cgroup文件系统是哪些集成进vfs的,过多的vfs完成可参考其余资料.

cgroup原理简析:vfs文件系统,cgroup原理简析vfs

要驾驭cgroup已毕原理,必须先了然下vfs(虚拟文件系统).因为cgroup通过vfs向用户层提供接口,用户层通过挂载,创造目录,读写文件的格局与cgroup交互.
因为是介绍cgroup的篇章,因而只讲演cgroup文件系统是什么集成进vfs的,过多的vfs完成可参看其余资料.

1.[[email protected]_109_95_centos /cgroup]#mount -t cgroup -ocpu cpu /cgroup/cpu/
2.[[email protected]_109_95_centos /cgroup]#cd cpu/  &&  mkdir cpu_c1
3.[[email protected]_109_95_centos /cgroup/cpu]#cd cpu_c1/  && echo 2048 >> cpu.shares
4.[[email protected]_109_95_centos /cgroup/cpu/cpu_c1]#echo 7860 >> tasks

大家以地点4行命令为主线展开辨析,从八个cgroup使用者的角度来看:
指令1
创办了叁个新的cgroup层级(挂载了3个新cgroup文件系统).并且绑定了cpu子系统(subsys),同时创建了该层级的根cgroup.命名为cpu,路径为/cgroup/cpu/.

指令2 在cpu层级(姑且这么叫)通过mkdir新创制三个cgroup节点,命名为cpu_c1.

命令3
将cpu_c1索引下的cpu.shares文件值设为2048,那样在系统出现cpu争抢时,属于cpu_c1那些cgroup的经过占用的cpu财富是别的进度占用cpu能源的2倍.(暗中同意成立的根cgroup该值为1024).

指令4
将pid为7860的那一个历程加到cpu_c1以此cgroup.就是说在系统出现cpu争抢时,pid为7860的这几个进度占用的cpu能源是其它进度占用cpu能源的2倍.

子系统的贯彻

1.[root@VM_109_95_centos /cgroup]#mount -t cgroup -ocpu cpu /cgroup/cpu/
2.[root@VM_109_95_centos /cgroup]#cd cpu/  &&  mkdir cpu_c1
3.[root@VM_109_95_centos /cgroup/cpu]#cd cpu_c1/  && echo 2048 >> cpu.shares
4.[root@VM_109_95_centos /cgroup/cpu/cpu_c1]#echo 7860 >> tasks
1.[root@VM_109_95_centos /cgroup]#mount -t cgroup -ocpu cpu /cgroup/cpu/
2.[root@VM_109_95_centos /cgroup]#cd cpu/  &&  mkdir cpu_c1
3.[root@VM_109_95_centos /cgroup/cpu]#cd cpu_c1/  && echo 2048 >> cpu.shares
4.[root@VM_109_95_centos /cgroup/cpu/cpu_c1]#echo 7860 >> tasks

那么系统在暗自做了那么些工作呢?上边逐一分析(内核版本3.10).

1.mount -t cgroup -ocpu cpu /cgroup/cpu/

static struct file_system_type cgroup_fs_type = {
    .name = "cgroup",
    .mount = cgroup_mount,
    .kill_sb = cgroup_kill_sb,
    // 其他属性未初始化
};

cgroup模块以cgroup_fs_type实例向基础注册cgroup文件系统,用户层通过mount()系统调用层层调用,最后来到cgroup_mount()函数:

static struct dentry *cgroup_mount(struct file_system_type *fs_type,int flags, const char *unused_dev_name,void *data) {

    ret = parse_cgroupfs_options(data, &opts);      // 解析mount时的参数

    new_root = cgroup_root_from_opts(&opts);        // 根据选项创建一个层级(struct cgroupfs_root)

    sb = sget(fs_type, cgroup_test_super, cgroup_set_super, 0, &opts);     // 创建一个新的超级快(struct super_block)

    ret = rebind_subsystems(root, root->subsys_mask);       // 给层级绑定subsys

    cgroup_populate_dir(root_cgrp, true, root->subsys_mask);    // 创建根cgroup下的各种文件
}

第三分析mount时上层传下的参数,那里就解析到该层级需要绑定cpu
subsys统.然后依据参数创造3个层级.跟进到cgroup_root_from_opts()函数:

static struct cgroupfs_root *cgroup_root_from_opts(struct cgroup_sb_opts *opts)
{
    struct cgroupfs_root *root;

    if (!opts->subsys_mask && !opts->none)  // 未指定层级,并且用户曾未明确指定需要空层级return NULL
        return NULL;

    root = kzalloc(sizeof(*root), GFP_KERNEL);  // 申请内存
    if (!root)
        return ERR_PTR(-ENOMEM);

    if (!init_root_id(root)) {          // 初始化层级unique id
        kfree(root);
        return ERR_PTR(-ENOMEM);
    }
    init_cgroup_root(root);         // 创建根cgroup

    root->subsys_mask = opts->subsys_mask;
    root->flags = opts->flags;
    ida_init(&root->cgroup_ida);    // 初始化idr
    if (opts->release_agent)        // 拷贝清理脚本的路径,见后面struct cgroupfs_root说明.
        strcpy(root->release_agent_path, opts->release_agent);
    if (opts->name)                 // 设置name
        strcpy(root->name, opts->name);
    if (opts->cpuset_clone_children)    // 该选项打开,表示当创建子cpuset cgroup时,继承父cpuset cgroup的配置
        set_bit(CGRP_CPUSET_CLONE_CHILDREN, &root->top_cgroup.flags);
    return root;
}

层级结构体:

struct cgroupfs_root {
    struct super_block *sb;     // 超级块指针,最终指向该cgroup文件系统的超级块
    unsigned long subsys_mask;  // 该层级准备绑定的subsys统掩码
    int hierarchy_id;   // 全局唯一的层级ID
    unsigned long actual_subsys_mask;   // 该层级已经绑定的subsys统掩码(估计和上层remount有关吧?暂不深究)
    struct list_head subsys_list;   // subsys统链表,将该层级绑定的所有subsys统连起来.
    struct cgroup top_cgroup;   // 该层级的根cgroup
    int number_of_cgroups;      //该层级下cgroup的数目(层级可以理解为cgroup组成的树)
    struct list_head root_list;     // 层级链表,将系统上所有的层级连起来
    struct list_head allcg_list;    // cgroup链表,将该层级上所有的cgroup连起来???
    unsigned long flags;        // 一些标志().
    struct ida cgroup_ida;      // idr机制,方便查找(暂不深究)
    char release_agent_path[PATH_MAX];  // 清理脚本的路径,对应应用层的根cgroup目录下的release_agent文件
    char name[MAX_CGROUP_ROOT_NAMELEN];     //层级名称
};

接下去创设一级块,在vfs中中国足球球社团一级联赛级块用来表示3个已安装文件系统的有关新闻.跟进到cgroup_root_from_opts()函数:

struct super_block *sget(struct file_system_type *type, int (*test)(struct super_block *,void *), int (*set)(struct super_block *,void *), int flags, void *data)
{
    struct super_block *s = NULL;
    struct super_block *old;
    int err;

retry:
    spin_lock(&sb_lock);
    if (test) {             // 尝试找到一个已存在的sb
        hlist_for_each_entry(old, &type->fs_supers, s_instances) {
            if (!test(old, data))
                continue;
            if (!grab_super(old))
                goto retry;
            if (s) {
                up_write(&s->s_umount);
                destroy_super(s);
                s = NULL;
            }
            return old;
        }
    }
    if (!s) {
        spin_unlock(&sb_lock);
        s = alloc_super(type, flags);  //分配一个新的sb
        if (!s)
            return ERR_PTR(-ENOMEM);
        goto retry;
    }

    err = set(s, data);     // 初始化sb属性
    if (err) {
        spin_unlock(&sb_lock);
        up_write(&s->s_umount);
        destroy_super(s);
        return ERR_PTR(err);
    }
    s->s_type = type;       //该sb所属文件系统类型为cgroup_fs_type
    strlcpy(s->s_id, type->name, sizeof(s->s_id));  // s->s_id = "cgroup"
    list_add_tail(&s->s_list, &super_blocks);    // 加进super_block全局链表
    hlist_add_head(&s->s_instances, &type->fs_supers);  //同一文件系统可挂载多个实例,全部挂到cgroup_fs_type->fs_supers指向的链表中
    spin_unlock(&sb_lock);
    get_filesystem(type);
    register_shrinker(&s->s_shrink);
    return s;
}

超级块结构体类型(属性太多,只列cgroup差别化的,更加多内容请参考vfs相关资料):

struct super_block {
    struct list_head    s_list;     // 全局sb链表 
    ...
    struct file_system_type *s_type;    // 所属文件系统类型
    const struct super_operations   *s_op;      // 超级块相关操作
    struct hlist_node   s_instances;        // 同一文件系统的sb链表
    char s_id[32];              //  文本格式的name
    void  *s_fs_info;       //文件系统私有数据,cgroup用其指向层级
};

sget函数里先在已存的链表里摸索是不是有适合的,没有的话再分配新的sb.err =
set(s, data)
set是个函数指针,依据地点的代码可以通晓最终调用的是cgroup_set_super函数,首借使给新分配的sb赋值.那段代码相比较主要,展开看下:

static int cgroup_set_super(struct super_block *sb, void *data)
{
    int ret;
    struct cgroup_sb_opts *opts = data;

    /* If we don't have a new root, we can't set up a new sb */
    if (!opts->new_root)
        return -EINVAL;

    BUG_ON(!opts->subsys_mask && !opts->none);

    ret = set_anon_super(sb, NULL);
    if (ret)
        return ret;

    sb->s_fs_info = opts->new_root;     // super_block的s_fs_info字段指向对应的cgroupfs_root
    opts->new_root->sb = sb;            //cgroupfs_root的sb字段指向super_block

    sb->s_blocksize = PAGE_CACHE_SIZE;
    sb->s_blocksize_bits = PAGE_CACHE_SHIFT;
    sb->s_magic = CGROUP_SUPER_MAGIC;
    sb->s_op = &cgroup_ops;             //super_block的s_op字段指向cgroup_ops,这句比较关键.

    return 0;
}

如此那般一流块(super_block)和层级(cgroupfs_root)那多少个概念就相继对应起来了,并且能够并行索引到.super_block.s_op指向一组函数,那组函数就是该文件系统向上层提供的拥有操作.看下cgroup_ops:

static const struct super_operations cgroup_ops = {
    .statfs = simple_statfs,
    .drop_inode = generic_delete_inode,
    .show_options = cgroup_show_options,
    .remount_fs = cgroup_remount,
};

依然只提供三个操作….常见的文件系统(ext2)都会提供诸如alloc_inode 
read_inode等函数供上层操作文件.不过cgroup文件系统不要求这么些操作,
很好领会,cgroup是依据memory的文本系统.用不到那2个操作.
到这边好多struct已经复出水面,眼花缭乱.画个图理理.

图1
澳门金沙国际 1

继续.创制完一流块后ret = rebind_subsystems(root,
root->subsys_mask);依据上层的参数给该层级绑定subsys统(subsys和根cgroup联系起来),看下cgroup_subsys_state,cgroup和cgroup_subsys(子系统)的结构.

struct cgroup_subsys_state {
    struct cgroup *cgroup;  
    atomic_t refcnt;
    unsigned long flags;
    struct css_id __rcu *id;
    struct work_struct dput_work;
};

先看下cgroup_subsys_state.可以认为cgroup_subsys_state是subsys结构体的三个最小化的架空
逐条子系统各有温馨的相关社团,cgroup_subsys_state保存各样subsys之间联合的音讯,种种subsys的struct内嵌cgroup_vfs文件系统,cgroup原理简析。subsys_state为第二个因素,通过container_of机制使得cgroup各样具体(cpu
mem net io)subsys新闻连接起来.

(例如进度调度连串的task_group)见图2

struct cgroup {

    unsigned long flags;        
    struct list_head sibling;   // 兄弟链表
    struct list_head children;  // 孩子链表
    struct list_head files;     // 该cgroup下的文件链表(tasks cpu.shares ....)
    struct cgroup *parent;      // 父cgroup
    struct dentry *dentry;
    struct cgroup_name __rcu *name;
    struct cgroup_subsys_state *subsys[CGROUP_SUBSYS_COUNT]; //指针数组,每个非空元素指向挂载的subsys
    struct cgroupfs_root *root; //根cgroup
    struct list_head css_sets;
    struct list_head pidlists;  // 加到该cgroup下的taskid链表
};

subsys是一个cgroup_subsys_state*
类型的数组,逐个成分指向3个有血有肉subsys的cgroup_subsys_state,通过container_of(cgroup_subsys_state)就拿到了实际subsys的决定音信.

struct cgroup_subsys { // 删减版
    struct cgroup_subsys_state *(*css_alloc)(struct cgroup *cgrp);
    int (*css_online)(struct cgroup *cgrp);         // 一堆函数指针,由各个subsys实现.函数名意思比较鲜明
    void (*css_offline)(struct cgroup *cgrp);
    void (*css_free)(struct cgroup *cgrp);
    int (*can_attach)(struct cgroup *cgrp, struct cgroup_taskset *tset);
    void (*cancel_attach)(struct cgroup *cgrp, struct cgroup_taskset *tset);
    void (*attach)(struct cgroup *cgrp, struct cgroup_taskset *tset);
    void (*fork)(struct task_struct *task);
    void (*exit)(struct cgroup *cgrp, struct cgroup *old_cgrp,
             struct task_struct *task);
    void (*bind)(struct cgroup *root);
    int subsys_id;      // subsys id
    int disabled;
    ...
    struct list_head cftsets;       // cftype结构体(参数文件管理结构)链表
    struct cftype *base_cftypes;    // 指向一个cftype数组
    struct cftype_set base_cftset;  //
    struct module *module;
};

cgroup_subsys也是各样subsys的三个华而不实,真正的贯彻由逐一subsys已毕.可以和cgroup_subsys_state对比下,cgroup_subsys更偏向与叙述各样subsys的操作钩子,cgroup_subsys_state则与种种子系统的任务结构关联.
cgroup_subsys是与层级关系的,cgroup_subsys_state是与cgroup关联的。

struct cftype { // 删减版
    char name[MAX_CFTYPE_NAME];
    int private;
    umode_t mode;
    size_t max_write_len;
    unsigned int flags;
    s64 (*read_s64)(struct cgroup *cgrp, struct cftype *cft);
    int (*write_s64)(struct cgroup *cgrp, struct cftype *cft, s64 val);
    ...  
};

cftsets base_cftypes
base_cftset这一个多个性格保存的是均等份该subsys下对应控制文件的操作方法.只是访问形式不一样.
以cpu subsys为例,该subsys下有cpu.shares cpu.cfs_quota_us
cpu.cpu_cfs_period_read_u64这一个决定文件,逐个访问格局都不相同.
从而各类文件对应1个struct cftype结构,保存其对应文件名和读写函数.

图2

澳门金沙国际 2
比如说用户曾执行echo 1024 >> cpu.shares
最后经过inode.file_operations.cgroup_file_read->cftype.write_s64.
同理,创立子group除了正规的mkdir操作之外,inode.inode_operations.cgroup_mkdir函数内部额外调用上边已经初叶化好的钩子,成立新的cgroup.

末段一步,cgroup_populate_dir(root_cgrp, true,
root->subsys_mask);就是依据地点已经实例化好的cftype,创造cgroup下各个subsys的全部控制文件

static int cgroup_populate_dir(struct cgroup *cgrp, bool base_files, unsigned long subsys_mask)
{
    int err;
    struct cgroup_subsys *ss;

    if (base_files) {           //基本控制文件
        err = cgroup_addrm_files(cgrp, NULL, files, true);
        if (err < 0)
            return err;
    }

    /* process cftsets of each subsystem */
    for_each_subsys(cgrp->root, ss) {       //每个subsys
        struct cftype_set *set;
        if (!test_bit(ss->subsys_id, &subsys_mask))
            continue;

        list_for_each_entry(set, &ss->cftsets, node)  //每个subsys的每个控制文件
            cgroup_addrm_files(cgrp, ss, set->cfts, true);
    }
    ...
    return 0;
}

威名赫赫,先先河化了着力的文本,进而初叶化逐个subsys的各类控制文件.什么是主导文件?

static struct cftype files[] = {
    {
        .name = "tasks",
        .open = cgroup_tasks_open,
        .write_u64 = cgroup_tasks_write,
        .release = cgroup_pidlist_release,
        .mode = S_IRUGO | S_IWUSR,
    },
    {
        .name = CGROUP_FILE_GENERIC_PREFIX "procs",
        .open = cgroup_procs_open,
        .write_u64 = cgroup_procs_write,
        .release = cgroup_pidlist_release,
        .mode = S_IRUGO | S_IWUSR,
    },
    {
        .name = "notify_on_release",
        .read_u64 = cgroup_read_notify_on_release,
        .write_u64 = cgroup_write_notify_on_release,
    },
    {
        .name = CGROUP_FILE_GENERIC_PREFIX "event_control",
        .write_string = cgroup_write_event_control,
        .mode = S_IWUGO,
    },
    {
        .name = "cgroup.clone_children",
        .flags = CFTYPE_INSANE,
        .read_u64 = cgroup_clone_children_read,
        .write_u64 = cgroup_clone_children_write,
    },
    {
        .name = "cgroup.sane_behavior",
        .flags = CFTYPE_ONLY_ON_ROOT,
        .read_seq_string = cgroup_sane_behavior_show,
    },
    {
        .name = "release_agent",
        .flags = CFTYPE_ONLY_ON_ROOT,
        .read_seq_string = cgroup_release_agent_show,
        .write_string = cgroup_release_agent_write,
        .max_write_len = PATH_MAX,
    },
    { } /* terminate */
};

那么些文件在用户层应该见过.进到cgroup_create_file()函数看下:

static int cgroup_create_file(struct dentry *dentry, umode_t mode, struct super_block *sb)
{
    struct inode *inode;

    if (!dentry)
        return -ENOENT;
    if (dentry->d_inode)
        return -EEXIST;

    inode = cgroup_new_inode(mode, sb);     // 申请inode
    if (!inode)
        return -ENOMEM;

    if (S_ISDIR(mode)) {        //目录
        inode->i_op = &cgroup_dir_inode_operations; 
        inode->i_fop = &simple_dir_operations;
        ...
    } else if (S_ISREG(mode)) { //文件
        inode->i_size = 0;
        inode->i_fop = &cgroup_file_operations;
        inode->i_op = &cgroup_file_inode_operations;
    }
    d_instantiate(dentry, inode);
    dget(dentry);   /* Extra count - pin the dentry in core */
    return 0;
}

const struct file_operations simple_dir_operations = {
    .open       = dcache_dir_open,
    .release    = dcache_dir_close,
    .llseek     = dcache_dir_lseek,
    .read       = generic_read_dir,
    .readdir    = dcache_readdir,
    .fsync      = noop_fsync,
};

static const struct inode_operations cgroup_dir_inode_operations = {
    .lookup = cgroup_lookup,
    .mkdir = cgroup_mkdir,
    .rmdir = cgroup_rmdir,
    .rename = cgroup_rename,
    .setxattr = cgroup_setxattr,
    .getxattr = cgroup_getxattr,
    .listxattr = cgroup_listxattr,
    .removexattr = cgroup_removexattr,
};

static const 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,
};

static const struct inode_operations cgroup_file_inode_operations = {
    .setxattr = cgroup_setxattr,
    .getxattr = cgroup_getxattr,
    .listxattr = cgroup_listxattr,
    .removexattr = cgroup_removexattr,
};

这一个回调函数,上边以file_operations.cgroup_file_read 
cgroup_dir_inode_operations.cgroup_mkdir举例已经表达.
而外健康vfs的操作,还要执行cgroup机制相关操作.

cpu子系统

   cpu子系统用于控制cgroup中拥有进度可以采纳的cpu时间片。附加了cpu子系统的hierarchy下边建立的cgroup的目录下都有贰个cpu.shares的文本,对其写入整数值可以控制该cgroup拿到的时间片。例如:在七个 cgroup 中都将 cpu.shares 设定为 1 的天职将有平等的 CPU 时间,但在 cgroup 上校 cpu.shares 设定为 2 的天职可采纳的 CPU 时间是在 cgroup 元帅 cpu.shares 设定为 1 的职务可应用的 CPU 时间的两倍。

cpu子系统是经过Linux CFS调度器完成的。所以在介绍cpu子系统之前,先简单说一下CFS调度器。依据小编Ingo Molnar的说法:”CFS十分八的干活得以用一句话总结:CFS在真正的硬件上效仿了截然可以的多任务处理器”。在“完全可以的多职责处理器”下,每一种过程都能而且拿到CPU的施行时间。当系统中有八个经过时,CPU的一个钱打二十七个结时间被分成两份,每一种进程取得4/8。但是在实际的硬件上,当一个经过占用CPU时,别的进程就亟须等待。所以CFS将处以当前历程,使其余进度能够在下次调度时尽量取代当前进度。最后兑现所有进程的公平调度。  

CFS调度器将有着情形为RUNABLE的进程都被插入红黑树。在每一个调度点,CFS调度器都会挑选红黑树的最左侧的纸牌节点作为下1个将获取cpu的长河。 那红黑树的键值是怎么总括的吧?红黑树的键值是经过所谓的虚拟运营时刻。二个进度的杜撰运营时刻是进程时间运作的时光按任何红黑树中兼有的长河数量normalized的结果。

历次tick中断,CFS调度器都要翻新进程的杜撰运营时刻,然后调整当前经过在红黑树中的地点,调整形成后只要发现脚下进程不再是最左边的纸牌,就标志need_resched 标志,中断重返时就会调用scheduler()已毕进度切换。

末尾再说一下,进度的优先级和经过虚拟运维时刻的关联。前面提到了,每一次tick中断,CFS调度器都要翻新进度的杜撰运行时刻。那这几个时辰是怎么总结的吗?CFS首先计算出进程的时间运作时刻delta_exec,然后总括normalized后的delta_exec_weighted,最终再将delta_exec_weighted加到进度的虚构运营时刻上。跟进度优先级有关的就是delta_exec_weighted,delta_exec_weighted=delta_exec_weighted*NICE_0_LOAD/se->load,其中NICE_0_LOAD是个常量,而se->load跟进度的nice值成反比,由此进度优先级越高(nice值越小)则se->load越大,则总计出来的delta_exec_weighted越小,那样经过优先级高的进程就足以收获越多的cpu时间。

介绍完CFS调度器,大家开始介绍cpu子系统是怎样通过CFS调度器完毕的。CFS调度器不仅协理基于进度的调度,还帮衬基于进度组的组调度。CFS中定义了七个task_group的数据结构来管理组调度。

struct task_group {

struct cgroup_subsys_state css;

 

#ifdef CONFIG_FAIR_GROUP_SCHED

/* schedulable entities of this group on each cpu */

struct sched_entity **se;

/* runqueue “owned” by this group on each cpu */

struct cfs_rq **cfs_rq;

unsigned long shares;

#endif

 

#ifdef CONFIG_RT_GROUP_SCHED

struct sched_rt_entity **rt_se;

struct rt_rq **rt_rq;

 

struct rt_bandwidth rt_bandwidth;

#endif

 

struct rcu_head rcu;

struct list_head list;

 

struct task_group *parent;

struct list_head siblings;

struct list_head children;

};

task_group中内嵌了3个cgroup_subsys_state,相当于说进度可以经过cgroup_subsys_state来拿到它所在的task_group,同样地cgroup也足以经过cgroup_subsys_state来收获它对应的task_group,由此进程和cgroup都留存了一组cgroup_subsys_state指针。

struct sched_entity **se是2个指南针数组,存了一组指向该task_group在种种cpu的调度实体(即一个struct sched_entity)。

struct cfs_rq **cfs_rq也是3个指南针数组,存在一组指向该task_group在各种cpu上所怀有的一个可调度的长河队列。

Parent、siblings和children七个指针负责将task_group 连成一颗树,这些跟cgroup树类似。

有了这几个数据结构,我们来CFS在调度的时候是怎么处理进程组的。大家依然从CFS对tick中断的拍卖发轫。

CFS对tick中断的处理在task_tick_fair中进行,在task_tick_fair中有:

for_each_sched_entity(se) {

cfs_rq = cfs_rq_of(se);

entity_tick(cfs_rq, se, queued);

}

咱俩先是来看一下在组调度的事态下,for_each_sched_entity是怎么定义的:

#define for_each_sched_entity(se) \

for (; se; se = se->parent)

即从当前进程的se起先,沿着task_group树从下到上对se调用entity_tick,即更新种种se的虚构运行时刻。

在非组调度情状下,#define for_each_sched_entity(se) \

for (; se; se = NULL)

即只会对脚下se做处理。

CFS处理完tick中断后,即便有必不可少就会举办调度,CFS调度是通过pick_next_task_fair函数选拔下贰个周转的进度的。在pick_next_task_fair中有:

do {

se = pick_next_entity(cfs_rq);

set_next_entity(cfs_rq, se);

cfs_rq = group_cfs_rq(se);

} while (cfs_rq);

在那么些轮回中,首先从当下的行列选一个se,这么些跟非组调度一样的(红黑树最左边的节点),再将se设置成下二个运作的se,再从该se获取该se对应的task_group拥有的cfs_rq(固然该se对应1个经过而非叁个task_group的话,cfs_rq会变成NULL),继续这几个进程直到cfs_rq为空,即当se对应的是3个历程。

   简单的说,同一层的task_group跟进程被当成同样的调度实体来挑选,当被选到的是task_group时,则对task_group的男女节点重复那几个进程,直到选到3个运维的长河。由此当设置一个cgroup的shares值时,该cgroup当作3个整机和剩余的进度或其他cgroup分享cpu时间。比如,作者在根cgroup下建立cgroup A,将其shares值设1024,再建立cgroup B,将其shares设为2048,再将某些经过分别进入到那多个cgroup中,则长时间调度的结果应当是A:B:C=1:2:1(即cpu占用时间,其中C是系统中为参预到A或B的长河)。

   引起CFS调度的除外tick中断外,还有就是有新的进程进入可运营队列那种场馆。CFS处理那么些情景的函数是enqueue_task_fair,在enqueue_task_fair中有:

for_each_sched_entity(se) {

if (se->on_rq)

break;

cfs_rq = cfs_rq_of(se);

enqueue_entity(cfs_rq, se, flags);

flags = ENQUEUE_WAKEUP;

}

大家前边早已看过for_each_sched_entity在组调度下的概念了,那里是将近年来se和se的亲情祖先节点都插足到红黑树(enqueue_entity),而在非组调度意况下,只要求将眼下se本身参加即可。造成那种反差的案由,在于在pick_next_task_fair中选拔se时,是从上往下的,倘若二个se的先世节点不在红黑树中,它永远都不会被入选。而在非组调度的境况下,se之间并没有父子关系,全体se皆以同等独立,在pick_next_task_fair,第二次入选的放任自流就是进程,不须要向下迭代。

就好像的处理还发生在将1个se出列(dequeue_task_fair)和put_prev_task_fair中。

如上是cpu系统经过CFS调度器达成以cgroup为单位的cpu时间片分享,下面大家来看一下cpu子系统本人。cpu子系统通过一个cgroup_subsys结构体来管理:

struct cgroup_subsys cpu_cgroup_subsys = {

.name = “cpu”,

.create = cpu_cgroup_create,

.destroy = cpu_cgroup_destroy,

.can_attach = cpu_cgroup_can_attach,

.attach = cpu_cgroup_attach,

.populate = cpu_cgroup_populate,

.subsys_id = cpu_cgroup_subsys_id,

.early_init = 1,

};

Cpu_cgroup_subsys其实是对抽象的cgroup_subsys的落实,其中的函数指针指向了特定于cpu子系统的兑现。那里再说一下,Cgroups的完好统筹。当用户使用cgroup文件系统,创设cgroup的时候,会调用cgroup目录操作的mkdir指针指向的函数,该函数调用了cgroup_create,而cgroup_create会依据该cgroup关联的子系统,分别调用对应的子系统达成的create指针指向的函数。即做了五次转换,3次从系统通用命令到cgroup文件系统,另三次从cgroup文件系统再特定的子系统完成。

Cgroups中除了通用的主宰文件外,各种子系统还有温馨的控制文件,子系统也是通过cftype来保管那些决定文件。Cpu子系统很首要的二个文本就是cpu.shares文件,因为就是因此那些文件的数值来调节cgroup所占用的cpu时间。Shares文件对应的cftype结构为:

#ifdef CONFIG_FAIR_GROUP_SCHED

{

.name = “shares”,

.read_u64 = cpu_shares_read_u64,

.write_u64 = cpu_shares_write_u64,

},

#endif

当对cgroup目录下的文本举办操作时,该结构体中定义的函数指针指向的函数就会被调用.下边大家就在探望这几个八个函数的落成呢,从而发现shares文件的值是怎么起成效的。

static u64 cpu_shares_read_u64(struct cgroup *cgrp, struct cftype *cft)

{

struct task_group *tg = cgroup_tg(cgrp);

 

return (u64) tg->shares;

}

相比较简单,简单的读取task_group中蕴藏的shares就行了。

static int cpu_shares_write_u64(struct cgroup *cgrp, struct cftype *cftype,

u64 shareval)

{

return sched_group_set_shares(cgroup_tg(cgrp), shareval);

}

则是设定cgroup对应的task_group的shares值。

那那一个shares值是怎么起效果的吗?在sched_group_set_shares中有:

tg->shares = shares;

for_each_possible_cpu(i) {

/*

 * force a rebalance

 */

cfs_rq_set_shares(tg->cfs_rq[i], 0);

set_se_shares(tg->se[i], shares);

}

cfs_rq_set_shares强制做1遍cpu SMP负载均衡。

真的起效果的是在set_se_shares中,它调用了__set_se_shares,在__set_se_shares中有:

se->load.weight = shares;

se->load.inv_weight = 0;

基于以前大家解析的CFS的调度原理可以知晓,load.weight的值越大,算出来的虚构运维时刻越小,进度能应用的cpu时间越来越多。那样以来,shares值最终就是由此调度实体的load值来起效果的。

PS:从那篇日记发轫,将依次介绍cgroups各样子系统的完毕。

小编们以地点4行命令为主线展开解析,从多少个cgroup使用者的角度来看:
命令1
创造了四个新的cgroup层级(挂载了2个新cgroup文件系统).并且绑定了cpu子系统(subsys),同时创制了该层级的根cgroup.命名为cpu,路径为/cgroup/cpu/.

我们以地方4行命令为主线展开剖析,从二个cgroup使用者的角度来看:
一声令下1
创制了多少个新的cgroup层级(挂载了一个新cgroup文件系统).并且绑定了cpu子系统(subsys),同时成立了该层级的根cgroup.命名为cpu,路径为/cgroup/cpu/.

有点懵,辛亏说的基本上了.后边会轻松点,或者结合前边看目前,也会轻松些.

2.mkdir cpu_c1
其一大概来说就是分成多个部分,平常vfs创制目录的逻辑,在该目录下开立异的cgroup,集成父cgroup的subsys.
一声令下贴全[[email protected]_109_95_centos
/cgroup]#cd cpu/  &&  mkdir cpu_c1
大家是在/cgroup/目录下挂载的新文件系统,对于该cgroup文件系统,/cgroup/就是其根目录(用croot代替吧).
那么在croot目录下mkdir
cpu_c1.对于vfs来说,当然是调用croot目录对应inode.i_op.mkdir.

static int cgroup_get_rootdir(struct super_block *sb)
{
    struct inode *inode =
        cgroup_new_inode(S_IFDIR | S_IRUGO | S_IXUGO | S_IWUSR, sb);
    inode->i_fop = &simple_dir_operations;
    inode->i_op = &cgroup_dir_inode_operations;
    return 0;
}

可以看出croot目录项的inode.i_op也被设置为&cgroup_dir_inode_operations,那么mkdir就会调用cgroup_mkdir函数
cgroup_mkdir只是简单的包装,实际工作的函数是cgroup_create()函数.
看下cgroup_create函数(删减版)

static long cgroup_create(struct cgroup *parent, struct dentry *dentry,umode_t mode)
{
    struct cgroup *cgrp;
    struct cgroup_name *name;
    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

    name = cgroup_alloc_name(dentry);
    rcu_assign_pointer(cgrp->name, name);   // 设置名称

    init_cgroup_housekeeping(cgrp);     //cgroup一些成员的初始化

    dentry->d_fsdata = cgrp;        //目录项(dentry)与cgroup关联起来
    cgrp->dentry = dentry;
    cgrp->parent = parent;      // 设置cgroup层级关系
    cgrp->root = parent->root;

    if (notify_on_release(parent))  // 继承父cgroup的CGRP_NOTIFY_ON_RELEASE属性
        set_bit(CGRP_NOTIFY_ON_RELEASE, &cgrp->flags);

    if (test_bit(CGRP_CPUSET_CLONE_CHILDREN, &parent->flags))  // 继承父cgroup的CGRP_CPUSET_CLONE_CHILDREN属性
        set_bit(CGRP_CPUSET_CLONE_CHILDREN, &cgrp->flags);

    for_each_subsys(root, ss) {
        struct cgroup_subsys_state *css;

        css = ss->css_alloc(cgrp);  // mount时各个subsys的钩子函数已经注册,这里直接使用来创建各个subsys的结构(task_group)

        init_cgroup_css(css, ss, cgrp); //初始化cgroup_subsys_state类型的值
        if (ss->use_id) {
            err = alloc_css_id(ss, parent, cgrp);
        }
    }

    err = cgroup_create_file(dentry, S_IFDIR | mode, sb);   //创建该目录项对应的inode,并初始化后与dentry关联上.

    list_add_tail(&cgrp->allcg_node, &root->allcg_list);    // 该cgroup挂到层级的cgroup链表上
    list_add_tail_rcu(&cgrp->sibling, &cgrp->parent->children); // 该cgroup挂到福cgroup的子cgroup链表上.
    ....
    for_each_subsys(root, ss) {         // 将各个subsys的控制结构(task_group)建立父子关系.
        err = online_css(ss, cgrp);
    }

    err = cgroup_populate_dir(cgrp, true, root->subsys_mask);   // 生成该cgroup目录下相关子系统的控制文件
    ...
}

cgroup_create里面做的事体,上边大致都看过了.不再解释.
css = ss->css_alloc(cgrp);
err = online_css(ss, cgrp);
那两行简单表达下:大家用cgroup来限制机器的cpu mem IO
net,不过cgroup自个儿是从未界定效能的.cgroup更像是内核几大宗旨子系统为上层提供的入口..
以那么些事例来说,大家创造了三个绑定了cpu
subsys的cgroup.当大家把某部进度id加到该cgroup的tasks文件中时,
实质上是改变了该进度在进程调度系统中的相关参数,从而影响完全公平调度算法和实时调度算法达到限制的目标.
由此在那些事例中,ss->css_alloc尽管回到的是cgroup_subsys_state指针,但骨子里它创制了task_group.
该社团第①个变量为cgroup_subsys_state.

struct task_group {  //删减版
    struct cgroup_subsys_state css;
    struct sched_entity **se;
    struct cfs_rq **cfs_rq;
    unsigned long shares;
    atomic_t load_weight;
    atomic64_t load_avg;
    atomic_t runnable_avg;
    struct rcu_head rcu;
    struct list_head list;
    struct task_group *parent;
    struct list_head siblings;
    struct list_head children;
};

struct sched_entity {
    struct load_weight  load;       /* for load-balancing */
    struct rb_node      run_node;
    struct list_head    group_node;
    unsigned int        on_rq;
    u64         exec_start;
    u64         sum_exec_runtime;
    u64         vruntime;
    u64         prev_sum_exec_runtime;
    u64         nr_migrations;
};

cpu子系统是透过安装task_group来限制进度的,相应的mem
IO子系统也有个别的结构.
不过它们的共性就是首先个变量是cgroup_subsys_state,那样cgroup和子系统控制结构就透过cgroup_subsys_state连接起来.

一声令下2
在cpu层级(姑且这么叫)通过mkdir新创立2个cgroup节点,命名为cpu_c1.

命令2
在cpu层级(姑且这么叫)通过mkdir新成立一个cgroup节点,命名为cpu_c1.

mount时根cgroup也是要成立那么些子系统控制结构的,被本身略掉了.

3.echo 2048 >> cpu.shares
上边已经看见了cpu.shares这些文件的inode_i_fop =
&cgroup_file_operations,写文件调用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->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;
}

mount时早已驾驭各种subsys的各类控制文件的操作函数都是不均等的(通过cftype落成的).大家直接看下cpu.shares文件的操作函数.

static struct cftype cpu_files[] = {
    {
        .name = "shares",
        .read_u64 = cpu_shares_read_u64,
        .write_u64 = cpu_shares_write_u64,
    },
    ...
}

写cpu.shares最终调用cpu_shares_write_u64,
中间几层细节略过.最后实施update_load_set:

static inline void update_load_set(struct load_weight *lw, unsigned long w)
{
    lw->weight = w;
    lw->inv_weight = 0;
}

命令3
将cpu_c1索引下的cpu.shares文件值设为2048,那样在系统出现cpu争抢时,属于cpu_c1以此cgroup的经过占用的cpu能源是其他进度占用cpu能源的2倍.(暗中认可成立的根cgroup该值为1024).

命令3
将cpu_c1目录下的cpu.shares文件值设为2048,那样在系统出现cpu争抢时,属于cpu_c1以此cgroup的进度占用的cpu能源是别的进度占用cpu能源的2倍.(默许创设的根cgroup该值为1024).

其中load_weight=task_group.se.load,改变了load_weight.weight,起到了限定该task_group对cpu的使用.

4.echo 7860 >> tasks
进程是近似的,不过tasks文件最终调用的是cgroup_tasks_write那几个函数.

static struct cftype files[] = {
    {
        .name = "tasks",
        .open = cgroup_tasks_open,
        .write_u64 = cgroup_tasks_write,
        .release = cgroup_pidlist_release,
        .mode = S_IRUGO | S_IWUSR,
    },
}

cgroup_tasks_write最后调用attach_task_by_pid

static int attach_task_by_pid(struct cgroup *cgrp, u64 pid, bool threadgroup)
{
    struct task_struct *tsk;
    const struct cred *cred = current_cred(), *tcred;
    int ret;
    if (pid) {              //根据pid找到该进程的task_struct
        tsk = find_task_by_vpid(pid);
        if (!tsk) {
            rcu_read_unlock();
            ret= -ESRCH;
            goto out_unlock_cgroup;
        }
    }
    .....
    .....
    ret = cgroup_attach_task(cgrp, tsk, threadgroup);    //将进程关联到cgroup
    return ret;
}

说到底通过cgroup_attach_task函数,将经过挂载到响应cgroup.先看多少个新的布局体.

struct css_set {
    atomic_t refcount;         //引用计数
    struct hlist_node hlist;   //css_set链表,将系统中所有css_set连接起来.
    struct list_head tasks;    //task链表,链接所有属于这个set的进程
    struct list_head cg_links; // 指向一个cg_cgroup_link链表
    struct cgroup_subsys_state *subsys[CGROUP_SUBSYS_COUNT];  // 关联到subsys
    struct rcu_head rcu_head;
};

struct cg_cgroup_link {
    struct list_head cgrp_link_list;   //内嵌到cgroup->css_set链表
    struct cgroup *cgrp;   // 指向对应的cgroup
    struct list_head cg_link_list;     //内嵌到css_set->cg_links链表
    struct css_set *cg;    // 指向对应的css_set
};

struct task_struct {
    struct css_set __rcu *cgroups;  // 指向所属的css_set
    struct list_head cg_list;       // 将同属于一个css_set的task_struct连接起来.
}

css_set感觉像是进度和cgroup机制间的3个桥梁.cg_cgroup_link又将css_set和cgroup多对多的映照起来.
task_struct中并从未向来与cgroup关联,struct css_set __rcu
*cgroups指向友好所属的css_set.
如此这般task和cgroup subsys cgroup都得以互相索引到了.

图3

澳门金沙国际 3

 

进到cgroup_attach_task看看:

struct task_and_cgroup {
    struct task_struct  *task;
    struct cgroup       *cgrp;
    struct css_set      *cg;
};

struct cgroup_taskset {
    struct task_and_cgroup  single;
    struct flex_array   *tc_array;
    int         tc_array_len;
    int         idx;
    struct cgroup       *cur_cgrp;
};

static int cgroup_attach_task(struct cgroup *cgrp, struct task_struct *tsk,
                  bool threadgroup)
{
    int retval, i, group_size;
    struct cgroup_subsys *ss, *failed_ss = NULL;
    struct cgroupfs_root *root = cgrp->root;
    /* threadgroup list cursor and array */
    struct task_struct *leader = tsk;
    struct task_and_cgroup *tc;
    struct flex_array *group;
    struct cgroup_taskset tset = { };

    group = flex_array_alloc(sizeof(*tc), group_size, GFP_KERNEL);
    retval = flex_array_prealloc(group, 0, group_size, GFP_KERNEL);     //预分配内存,考虑到了多线程的进程

    i = 0;
    rcu_read_lock();
    do {                // 兼顾多线程进程,将所有线程的相关信息放在tset里
        struct task_and_cgroup ent;
        ent.task = tsk;
        ent.cgrp = task_cgroup_from_root(tsk, root);
        retval = flex_array_put(group, i, &ent, GFP_ATOMIC);
        BUG_ON(retval != 0);
        i++;
    next:
        if (!threadgroup)
            break;
    } while_each_thread(leader, tsk);
    rcu_read_unlock();
    group_size = i;
    tset.tc_array = group;
    tset.tc_array_len = group_size;

    for_each_subsys(root, ss) {         //调用每个subsys的方法,判断是否可绑定.
        if (ss->can_attach) {
            retval = ss->can_attach(cgrp, &tset);
            if (retval) {
                failed_ss = ss;
                goto out_cancel_attach;
            }
        }
    }

    for (i = 0; i < group_size; i++) {      // 为每个task准备(已有或分配)css_set,css_set是多个进程共享.
        tc = flex_array_get(group, i);
        tc->cg = find_css_set(tc->task->cgroups, cgrp);
        if (!tc->cg) {
            retval = -ENOMEM;
            goto out_put_css_set_refs;
        }
    }

    for (i = 0; i < group_size; i++) {      // 将所有task从old css_set迁移到new css_set.
        tc = flex_array_get(group, i);
        cgroup_task_migrate(tc->cgrp, tc->task, tc->cg);
    }

    for_each_subsys(root, ss) {         // 调用subsys的attach方法,执行绑定.
        if (ss->attach)
            ss->attach(cgrp, &tset);
    }
    retval = 0
    return retval;
}

这里的can_attach和attach由种种subsys已毕,那里先不说了.
因为成立层级时会把系统上享有的经过加到根cgroup的tasks中,所以用户层将task加进有些cgroup等同于将task从二个cgroup移到另四个cgriup.
cgroup_task_migrate就是将task与新的cgroup对应的css_set重新照射起来.

假若不对请指出。

参考资料:

  linux-3.10源码

  <linux
cgroup详解><[email protected]>连接找不到了

要打听cgroup完成原理,必须先精通下vfs(虚拟文件系统).因为cgroup通过vfs向用户层提供接口,用户层…

命令4
将pid为7860的这几个进程加到cpu_c1那么些cgroup.就是说在系统出现cpu争抢时,pid为7860的那么些历程占用的cpu财富是其他进度占用cpu能源的2倍.

一声令下4
将pid为7860的那一个进程加到cpu_c1这一个cgroup.就是说在系统出现cpu争抢时,pid为7860的那些进度占用的cpu能源是其余进度占用cpu资源的2倍.

那就是说系统在专断做了那么些工作啊?下边逐一分析(内核版本3.10).

1.mount -t cgroup -ocpu cpu
/cgroup/cpu/

static struct file_system_type cgroup_fs_type = {
    .name = "cgroup",
    .mount = cgroup_mount,
    .kill_sb = cgroup_kill_sb,
    // 其他属性未初始化
};

cgroup模块以cgroup_fs_type实例向基础注册cgroup文件系统,用户层通过mount()系统调用层层调用,最后来到cgroup_mount()函数:

static struct dentry *cgroup_mount(struct file_system_type *fs_type,int flags, const char *unused_dev_name,void *data) {

    ret = parse_cgroupfs_options(data, &opts);      // 解析mount时的参数

    new_root = cgroup_root_from_opts(&opts);        // 根据选项创建一个层级(struct cgroupfs_root)

    sb = sget(fs_type, cgroup_test_super, cgroup_set_super, 0, &opts);     // 创建一个新的超级快(struct super_block)

    ret = rebind_subsystems(root, root->subsys_mask);       // 给层级绑定subsys

    cgroup_populate_dir(root_cgrp, true, root->subsys_mask);    // 创建根cgroup下的各种文件
}

先是分析mount时上层传下的参数,这里就解析到该层级必要绑定cpu
subsys统.然后基于参数创造3个层级.跟进到cgroup_root_from_opts()函数:

static struct cgroupfs_root *cgroup_root_from_opts(struct cgroup_sb_opts *opts)
{
    struct cgroupfs_root *root;

    if (!opts->subsys_mask && !opts->none)  // 未指定层级,并且用户曾未明确指定需要空层级return NULL
        return NULL;

    root = kzalloc(sizeof(*root), GFP_KERNEL);  // 申请内存
    if (!root)
        return ERR_PTR(-ENOMEM);

    if (!init_root_id(root)) {          // 初始化层级unique id
        kfree(root);
        return ERR_PTR(-ENOMEM);
    }
    init_cgroup_root(root);         // 创建根cgroup

    root->subsys_mask = opts->subsys_mask;
    root->flags = opts->flags;
    ida_init(&root->cgroup_ida);    // 初始化idr
    if (opts->release_agent)        // 拷贝清理脚本的路径,见后面struct cgroupfs_root说明.
        strcpy(root->release_agent_path, opts->release_agent);
    if (opts->name)                 // 设置name
        strcpy(root->name, opts->name);
    if (opts->cpuset_clone_children)    // 该选项打开,表示当创建子cpuset cgroup时,继承父cpuset cgroup的配置
        set_bit(CGRP_CPUSET_CLONE_CHILDREN, &root->top_cgroup.flags);
    return root;
}

层级结构体:

struct cgroupfs_root {
    struct super_block *sb;     // 超级块指针,最终指向该cgroup文件系统的超级块
    unsigned long subsys_mask;  // 该层级准备绑定的subsys统掩码
    int hierarchy_id;   // 全局唯一的层级ID
    unsigned long actual_subsys_mask;   // 该层级已经绑定的subsys统掩码(估计和上层remount有关吧?暂不深究)
    struct list_head subsys_list;   // subsys统链表,将该层级绑定的所有subsys统连起来.
    struct cgroup top_cgroup;   // 该层级的根cgroup
    int number_of_cgroups;      //该层级下cgroup的数目(层级可以理解为cgroup组成的树)
    struct list_head root_list;     // 层级链表,将系统上所有的层级连起来
    struct list_head allcg_list;    // cgroup链表,将该层级上所有的cgroup连起来???
    unsigned long flags;        // 一些标志().
    struct ida cgroup_ida;      // idr机制,方便查找(暂不深究)
    char release_agent_path[PATH_MAX];  // 清理脚本的路径,对应应用层的根cgroup目录下的release_agent文件
    char name[MAX_CGROUP_ROOT_NAMELEN];     //层级名称
};

接下去成立超级块,在vfs中一级块用来表示贰个已安装文件系统的连带新闻.跟进到cgroup_root_from_opts()函数:

struct super_block *sget(struct file_system_type *type, int (*test)(struct super_block *,void *), int (*set)(struct super_block *,void *), int flags, void *data)
{
    struct super_block *s = NULL;
    struct super_block *old;
    int err;

retry:
    spin_lock(&sb_lock);
    if (test) {             // 尝试找到一个已存在的sb
        hlist_for_each_entry(old, &type->fs_supers, s_instances) {
            if (!test(old, data))
                continue;
            if (!grab_super(old))
                goto retry;
            if (s) {
                up_write(&s->s_umount);
                destroy_super(s);
                s = NULL;
            }
            return old;
        }
    }
    if (!s) {
        spin_unlock(&sb_lock);
        s = alloc_super(type, flags);  //分配一个新的sb
        if (!s)
            return ERR_PTR(-ENOMEM);
        goto retry;
    }

    err = set(s, data);     // 初始化sb属性
    if (err) {
        spin_unlock(&sb_lock);
        up_write(&s->s_umount);
        destroy_super(s);
        return ERR_PTR(err);
    }
    s->s_type = type;       //该sb所属文件系统类型为cgroup_fs_type
    strlcpy(s->s_id, type->name, sizeof(s->s_id));  // s->s_id = "cgroup"
    list_add_tail(&s->s_list, &super_blocks);    // 加进super_block全局链表
    hlist_add_head(&s->s_instances, &type->fs_supers);  //同一文件系统可挂载多个实例,全部挂到cgroup_fs_type->fs_supers指向的链表中
    spin_unlock(&sb_lock);
    get_filesystem(type);
    register_shrinker(&s->s_shrink);
    return s;
}

一级块结构体类型(属性太多,只列cgroup差距化的,更加多内容请参见vfs相关资料):

struct super_block {
    struct list_head    s_list;     // 全局sb链表 
    ...
    struct file_system_type *s_type;    // 所属文件系统类型
    const struct super_operations   *s_op;      // 超级块相关操作
    struct hlist_node   s_instances;        // 同一文件系统的sb链表
    char s_id[32];              //  文本格式的name
    void  *s_fs_info;       //文件系统私有数据,cgroup用其指向层级
};

sget函数里先在已存的链表里摸索是还是不是有适量的,没有的话再分配新的sb.err
= set(s, data)
set是个函数指针,依据上边的代码可以领会最终调用的是cgroup_set_super函数,首借使给新分配的sb赋值.那段代码比较根本,展开看下:

static int cgroup_set_super(struct super_block *sb, void *data)
{
    int ret;
    struct cgroup_sb_opts *opts = data;

    /* If we don't have a new root, we can't set up a new sb */
    if (!opts->new_root)
        return -EINVAL;

    BUG_ON(!opts->subsys_mask && !opts->none);

    ret = set_anon_super(sb, NULL);
    if (ret)
        return ret;

    sb->s_fs_info = opts->new_root;     // super_block的s_fs_info字段指向对应的cgroupfs_root
    opts->new_root->sb = sb;            //cgroupfs_root的sb字段指向super_block

    sb->s_blocksize = PAGE_CACHE_SIZE;
    sb->s_blocksize_bits = PAGE_CACHE_SHIFT;
    sb->s_magic = CGROUP_SUPER_MAGIC;
    sb->s_op = &cgroup_ops;             //super_block的s_op字段指向cgroup_ops,这句比较关键.

    return 0;
}

这么一流块(super_block)和层级(cgroupfs_root)那七个概念就相继对应起来了,并且能够相互索引到.super_block.s_op指向一组函数,那组函数就是该文件系统向上层提供的全体操作.看下cgroup_ops:

static const struct super_operations cgroup_ops = {
    .statfs = simple_statfs,
    .drop_inode = generic_delete_inode,
    .show_options = cgroup_show_options,
    .remount_fs = cgroup_remount,
};

竟然只提供一个操作….常见的文件系统(ext2)都会提供诸如alloc_inode 
read_inode等函数供上层操作文件.不过cgroup文件系统不要求这一个操作,
很好通晓,cgroup是基于memory的公文系统.用不到那多少个操作.
到那边好多struct已经复出水面,眼花缭乱.画个图理理.

图1
澳门金沙国际 4

继续.创制完一流块后ret =
rebind_subsystems(root,
root->subsys_mask);依据上层的参数给该层级绑定subsys统(subsys和根cgroup联系起来),看下cgroup_subsys_state,cgroup和cgroup_subsys(子系统)的结构.

struct cgroup_subsys_state {
    struct cgroup *cgroup;  
    atomic_t refcnt;
    unsigned long flags;
    struct css_id __rcu *id;
    struct work_struct dput_work;
};

先看下cgroup_subsys_state.能够认为cgroup_subsys_state是subsys结构体的多少个最小化的抽象
逐一子系统各有协调的相干协会,cgroup_subsys_state保存各类subsys之间联合的音信,各种subsys的struct内嵌cgroup_subsys_state为第一个要素,通过container_of机制使得cgroup各类具体(cpu
mem net io)subsys新闻连接起来.

(例如进度调度种类的task_group)见图2

struct cgroup {

    unsigned long flags;        
    struct list_head sibling;   // 兄弟链表
    struct list_head children;  // 孩子链表
    struct list_head files;     // 该cgroup下的文件链表(tasks cpu.shares ....)
    struct cgroup *parent;      // 父cgroup
    struct dentry *dentry;
    struct cgroup_name __rcu *name;
    struct cgroup_subsys_state *subsys[CGROUP_SUBSYS_COUNT]; //指针数组,每个非空元素指向挂载的subsys
    struct cgroupfs_root *root; //根cgroup
    struct list_head css_sets;
    struct list_head pidlists;  // 加到该cgroup下的taskid链表
};

subsys是一个cgroup_subsys_state*
类型的数组,每一个成分指向三个具体subsys的cgroup_subsys_state,通过container_of(cgroup_subsys_state)就拿到了现实subsys的主宰消息.

struct cgroup_subsys { // 删减版
    struct cgroup_subsys_state *(*css_alloc)(struct cgroup *cgrp);
    int (*css_online)(struct cgroup *cgrp);         // 一堆函数指针,由各个subsys实现.函数名意思比较鲜明
    void (*css_offline)(struct cgroup *cgrp);
    void (*css_free)(struct cgroup *cgrp);
    int (*can_attach)(struct cgroup *cgrp, struct cgroup_taskset *tset);
    void (*cancel_attach)(struct cgroup *cgrp, struct cgroup_taskset *tset);
    void (*attach)(struct cgroup *cgrp, struct cgroup_taskset *tset);
    void (*fork)(struct task_struct *task);
    void (*exit)(struct cgroup *cgrp, struct cgroup *old_cgrp,
             struct task_struct *task);
    void (*bind)(struct cgroup *root);
    int subsys_id;      // subsys id
    int disabled;
    ...
    struct list_head cftsets;       // cftype结构体(参数文件管理结构)链表
    struct cftype *base_cftypes;    // 指向一个cftype数组
    struct cftype_set base_cftset;  //
    struct module *module;
};

cgroup_subsys也是逐一subsys的3个虚幻,真正的兑现由各样subsys完毕.可以和cgroup_subsys_state对比下,cgroup_subsys更偏向与讲述各种subsys的操作钩子,cgroup_subsys_state则与各样子系统的任务结构关联.
cgroup_subsys是与层级关系的,cgroup_subsys_state是与cgroup关联的。

struct cftype { // 删减版
    char name[MAX_CFTYPE_NAME];
    int private;
    umode_t mode;
    size_t max_write_len;
    unsigned int flags;
    s64 (*read_s64)(struct cgroup *cgrp, struct cftype *cft);
    int (*write_s64)(struct cgroup *cgrp, struct cftype *cft, s64 val);
    ...  
};

cftsets base_cftypes
base_cftset那一个多个属性保存的是平等份该subsys下对应控制文件的操作方法.只是造访方式不相同.
以cpu subsys为例,该subsys下有cpu.shares
cpu.cfs_quota_us
cpu.cpu_cfs_period_read_u64那一个决定文件,各种访问格局都不相同.
因此各种文件对应三个struct
cftype结构,保存其对应文件名和读写函数.

图2

澳门金沙国际 5
譬如说用户曾执行echo 1024 >>
cpu.shares
最后通过inode.file_operations.cgroup_file_read->cftype.write_s64.
同理,创制子group除了不荒谬的mkdir操作之外,inode.inode_operations.cgroup_mkdir函数内部额外调用上边已经初步化好的钩,创制新的cgroup.

末段一步,cgroup_populate_dir(root_cgrp,
true,
root->subsys_mask);就是根据上边已经实例化好的cftype,创立cgroup下各个subsys的具备控制文件

static int cgroup_populate_dir(struct cgroup *cgrp, bool base_files, unsigned long subsys_mask)
{
    int err;
    struct cgroup_subsys *ss;

    if (base_files) {           //基本控制文件
        err = cgroup_addrm_files(cgrp, NULL, files, true);
        if (err < 0)
            return err;
    }

    /* process cftsets of each subsystem */
    for_each_subsys(cgrp->root, ss) {       //每个subsys
        struct cftype_set *set;
        if (!test_bit(ss->subsys_id, &subsys_mask))
            continue;

        list_for_each_entry(set, &ss->cftsets, node)  //每个subsys的每个控制文件
            cgroup_addrm_files(cgrp, ss, set->cfts, true);
    }
    ...
    return 0;
}

明朗,先初叶化了骨干的文书,进而初阶化每种subsys的各个控制文件.什么是基本文件?

static struct cftype files[] = {
    {
        .name = "tasks",
        .open = cgroup_tasks_open,
        .write_u64 = cgroup_tasks_write,
        .release = cgroup_pidlist_release,
        .mode = S_IRUGO | S_IWUSR,
    },
    {
        .name = CGROUP_FILE_GENERIC_PREFIX "procs",
        .open = cgroup_procs_open,
        .write_u64 = cgroup_procs_write,
        .release = cgroup_pidlist_release,
        .mode = S_IRUGO | S_IWUSR,
    },
    {
        .name = "notify_on_release",
        .read_u64 = cgroup_read_notify_on_release,
        .write_u64 = cgroup_write_notify_on_release,
    },
    {
        .name = CGROUP_FILE_GENERIC_PREFIX "event_control",
        .write_string = cgroup_write_event_control,
        .mode = S_IWUGO,
    },
    {
        .name = "cgroup.clone_children",
        .flags = CFTYPE_INSANE,
        .read_u64 = cgroup_clone_children_read,
        .write_u64 = cgroup_clone_children_write,
    },
    {
        .name = "cgroup.sane_behavior",
        .flags = CFTYPE_ONLY_ON_ROOT,
        .read_seq_string = cgroup_sane_behavior_show,
    },
    {
        .name = "release_agent",
        .flags = CFTYPE_ONLY_ON_ROOT,
        .read_seq_string = cgroup_release_agent_show,
        .write_string = cgroup_release_agent_write,
        .max_write_len = PATH_MAX,
    },
    { } /* terminate */
};

这几个文件在用户层应该见过.进到cgroup_create_file()函数看下:

static int cgroup_create_file(struct dentry *dentry, umode_t mode, struct super_block *sb)
{
    struct inode *inode;

    if (!dentry)
        return -ENOENT;
    if (dentry->d_inode)
        return -EEXIST;

    inode = cgroup_new_inode(mode, sb);     // 申请inode
    if (!inode)
        return -ENOMEM;

    if (S_ISDIR(mode)) {        //目录
        inode->i_op = &cgroup_dir_inode_operations; 
        inode->i_fop = &simple_dir_operations;
        ...
    } else if (S_ISREG(mode)) { //文件
        inode->i_size = 0;
        inode->i_fop = &cgroup_file_operations;
        inode->i_op = &cgroup_file_inode_operations;
    }
    d_instantiate(dentry, inode);
    dget(dentry);   /* Extra count - pin the dentry in core */
    return 0;
}

const struct file_operations simple_dir_operations = {
    .open       = dcache_dir_open,
    .release    = dcache_dir_close,
    .llseek     = dcache_dir_lseek,
    .read       = generic_read_dir,
    .readdir    = dcache_readdir,
    .fsync      = noop_fsync,
};

static const struct inode_operations cgroup_dir_inode_operations = {
    .lookup = cgroup_lookup,
    .mkdir = cgroup_mkdir,
    .rmdir = cgroup_rmdir,
    .rename = cgroup_rename,
    .setxattr = cgroup_setxattr,
    .getxattr = cgroup_getxattr,
    .listxattr = cgroup_listxattr,
    .removexattr = cgroup_removexattr,
};

static const 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,
};

static const struct inode_operations cgroup_file_inode_operations = {
    .setxattr = cgroup_setxattr,
    .getxattr = cgroup_getxattr,
    .listxattr = cgroup_listxattr,
    .removexattr = cgroup_removexattr,
};

这个回调函数,上面以file_operations.cgroup_file_read 
cgroup_dir_inode_operations.cgroup_mkdir举例已经说明.
除开不荒谬vfs的操作,还要举办cgroup机制相关操作.

那么系统在专断做了这些工作啊?上边逐一分析(内核版本3.10).

1.mount -t cgroup -ocpu cpu
/cgroup/cpu/

static struct file_system_type cgroup_fs_type = {
    .name = "cgroup",
    .mount = cgroup_mount,
    .kill_sb = cgroup_kill_sb,
    // 其他属性未初始化
};

cgroup模块以cgroup_fs_type实例向基础注册cgroup文件系统,用户层通过mount()系统调用层层调用,最后来到cgroup_mount()函数:

static struct dentry *cgroup_mount(struct file_system_type *fs_type,int flags, const char *unused_dev_name,void *data) {

    ret = parse_cgroupfs_options(data, &opts);      // 解析mount时的参数

    new_root = cgroup_root_from_opts(&opts);        // 根据选项创建一个层级(struct cgroupfs_root)

    sb = sget(fs_type, cgroup_test_super, cgroup_set_super, 0, &opts);     // 创建一个新的超级快(struct super_block)

    ret = rebind_subsystems(root, root->subsys_mask);       // 给层级绑定subsys

    cgroup_populate_dir(root_cgrp, true, root->subsys_mask);    // 创建根cgroup下的各种文件
}

先是分析mount时上层传下的参数,那里就解析到该层级要求绑定cpu
subsys统.然后根据参数成立3个层级.跟进到cgroup_root_from_opts()函数:

static struct cgroupfs_root *cgroup_root_from_opts(struct cgroup_sb_opts *opts)
{
    struct cgroupfs_root *root;

    if (!opts->subsys_mask && !opts->none)  // 未指定层级,并且用户曾未明确指定需要空层级return NULL
        return NULL;

    root = kzalloc(sizeof(*root), GFP_KERNEL);  // 申请内存
    if (!root)
        return ERR_PTR(-ENOMEM);

    if (!init_root_id(root)) {          // 初始化层级unique id
        kfree(root);
        return ERR_PTR(-ENOMEM);
    }
    init_cgroup_root(root);         // 创建根cgroup

    root->subsys_mask = opts->subsys_mask;
    root->flags = opts->flags;
    ida_init(&root->cgroup_ida);    // 初始化idr
    if (opts->release_agent)        // 拷贝清理脚本的路径,见后面struct cgroupfs_root说明.
        strcpy(root->release_agent_path, opts->release_agent);
    if (opts->name)                 // 设置name
        strcpy(root->name, opts->name);
    if (opts->cpuset_clone_children)    // 该选项打开,表示当创建子cpuset cgroup时,继承父cpuset cgroup的配置
        set_bit(CGRP_CPUSET_CLONE_CHILDREN, &root->top_cgroup.flags);
    return root;
}

层级结构体:

struct cgroupfs_root {
    struct super_block *sb;     // 超级块指针,最终指向该cgroup文件系统的超级块
    unsigned long subsys_mask;  // 该层级准备绑定的subsys统掩码
    int hierarchy_id;   // 全局唯一的层级ID
    unsigned long actual_subsys_mask;   // 该层级已经绑定的subsys统掩码(估计和上层remount有关吧?暂不深究)
    struct list_head subsys_list;   // subsys统链表,将该层级绑定的所有subsys统连起来.
    struct cgroup top_cgroup;   // 该层级的根cgroup
    int number_of_cgroups;      //该层级下cgroup的数目(层级可以理解为cgroup组成的树)
    struct list_head root_list;     // 层级链表,将系统上所有的层级连起来
    struct list_head allcg_list;    // cgroup链表,将该层级上所有的cgroup连起来???
    unsigned long flags;        // 一些标志().
    struct ida cgroup_ida;      // idr机制,方便查找(暂不深究)
    char release_agent_path[PATH_MAX];  // 清理脚本的路径,对应应用层的根cgroup目录下的release_agent文件
    char name[MAX_CGROUP_ROOT_NAMELEN];     //层级名称
};

接下去创设一流块,在vfs中国足球协会一流联赛级块用来代表二个已安装文件系统的相关音讯.跟进到cgroup_root_from_opts()函数:

struct super_block *sget(struct file_system_type *type, int (*test)(struct super_block *,void *), int (*set)(struct super_block *,void *), int flags, void *data)
{
    struct super_block *s = NULL;
    struct super_block *old;
    int err;

retry:
    spin_lock(&sb_lock);
    if (test) {             // 尝试找到一个已存在的sb
        hlist_for_each_entry(old, &type->fs_supers, s_instances) {
            if (!test(old, data))
                continue;
            if (!grab_super(old))
                goto retry;
            if (s) {
                up_write(&s->s_umount);
                destroy_super(s);
                s = NULL;
            }
            return old;
        }
    }
    if (!s) {
        spin_unlock(&sb_lock);
        s = alloc_super(type, flags);  //分配一个新的sb
        if (!s)
            return ERR_PTR(-ENOMEM);
        goto retry;
    }

    err = set(s, data);     // 初始化sb属性
    if (err) {
        spin_unlock(&sb_lock);
        up_write(&s->s_umount);
        destroy_super(s);
        return ERR_PTR(err);
    }
    s->s_type = type;       //该sb所属文件系统类型为cgroup_fs_type
    strlcpy(s->s_id, type->name, sizeof(s->s_id));  // s->s_id = "cgroup"
    list_add_tail(&s->s_list, &super_blocks);    // 加进super_block全局链表
    hlist_add_head(&s->s_instances, &type->fs_supers);  //同一文件系统可挂载多个实例,全部挂到cgroup_fs_type->fs_supers指向的链表中
    spin_unlock(&sb_lock);
    get_filesystem(type);
    register_shrinker(&s->s_shrink);
    return s;
}

一级块结构体类型(属性太多,只列cgroup差别化的,更加多内容请参见vfs相关资料):

struct super_block {
    struct list_head    s_list;     // 全局sb链表 
    ...
    struct file_system_type *s_type;    // 所属文件系统类型
    const struct super_operations   *s_op;      // 超级块相关操作
    struct hlist_node   s_instances;        // 同一文件系统的sb链表
    char s_id[32];              //  文本格式的name
    void  *s_fs_info;       //文件系统私有数据,cgroup用其指向层级
};

sget函数里先在已存的链表里搜寻是不是有确切的,没有的话再分配新的sb.err
= set(s, data)
set是个函数指针,按照下边的代码可以清楚最终调用的是cgroup_set_super函数,重如若给新分配的sb赋值.那段代码相比紧要,展开看下:

static int cgroup_set_super(struct super_block *sb, void *data)
{
    int ret;
    struct cgroup_sb_opts *opts = data;

    /* If we don't have a new root, we can't set up a new sb */
    if (!opts->new_root)
        return -EINVAL;

    BUG_ON(!opts->subsys_mask && !opts->none);

    ret = set_anon_super(sb, NULL);
    if (ret)
        return ret;

    sb->s_fs_info = opts->new_root;     // super_block的s_fs_info字段指向对应的cgroupfs_root
    opts->new_root->sb = sb;            //cgroupfs_root的sb字段指向super_block

    sb->s_blocksize = PAGE_CACHE_SIZE;
    sb->s_blocksize_bits = PAGE_CACHE_SHIFT;
    sb->s_magic = CGROUP_SUPER_MAGIC;
    sb->s_op = &cgroup_ops;             //super_block的s_op字段指向cgroup_ops,这句比较关键.

    return 0;
}

这么顶级块(super_block)和层级(cgroupfs_root)那三个概念就相继对应起来了,并且能够相互索引到.super_block.s_op指向一组函数,那组函数就是该文件系统向上层提供的全体操作.看下cgroup_ops:

static const struct super_operations cgroup_ops = {
    .statfs = simple_statfs,
    .drop_inode = generic_delete_inode,
    .show_options = cgroup_show_options,
    .remount_fs = cgroup_remount,
};

竟然只提供一个操作….常见的文件系统(ext2)都会提供诸如alloc_inode 
read_inode等函数供上层操作文件.不过cgroup文件系统不需求那几个操作,
很好驾驭,cgroup是基于memory的公文系统.用不到那多少个操作.
到那边好多struct已经复出水面,眼花缭乱.画个图理理.

图1
澳门金沙国际 6

继续.创制完一级块后ret =
rebind_subsystems(root,
root->subsys_mask);按照上层的参数给该层级绑定subsys统(subsys和根cgroup联系起来),看下cgroup_subsys_state,cgroup和cgroup_subsys(子系统)的结构.

struct cgroup_subsys_state {
    struct cgroup *cgroup;  
    atomic_t refcnt;
    unsigned long flags;
    struct css_id __rcu *id;
    struct work_struct dput_work;
};

先看下cgroup_subsys_state.可以认为cgroup_subsys_state是subsys结构体的1个最小化的虚幻
逐一子系统各有谈得来的连带协会,cgroup_subsys_state保存各样subsys之间联合的音讯,各样subsys的struct内嵌cgroup_subsys_state为第1个成分,通过container_of机制使得cgroup各样具体(cpu
mem net io)subsys新闻连接起来.

(例如进程调度系列的task_group)见图2

struct cgroup {

    unsigned long flags;        
    struct list_head sibling;   // 兄弟链表
    struct list_head children;  // 孩子链表
    struct list_head files;     // 该cgroup下的文件链表(tasks cpu.shares ....)
    struct cgroup *parent;      // 父cgroup
    struct dentry *dentry;
    struct cgroup_name __rcu *name;
    struct cgroup_subsys_state *subsys[CGROUP_SUBSYS_COUNT]; //指针数组,每个非空元素指向挂载的subsys
    struct cgroupfs_root *root; //根cgroup
    struct list_head css_sets;
    struct list_head pidlists;  // 加到该cgroup下的taskid链表
};

subsys是一个cgroup_subsys_state*
类型的数组,每一种成分指向贰个有血有肉subsys的cgroup_subsys_state,通过container_of(cgroup_subsys_state)就得到了切实可行subsys的操纵音讯.

struct cgroup_subsys { // 删减版
    struct cgroup_subsys_state *(*css_alloc)(struct cgroup *cgrp);
    int (*css_online)(struct cgroup *cgrp);         // 一堆函数指针,由各个subsys实现.函数名意思比较鲜明
    void (*css_offline)(struct cgroup *cgrp);
    void (*css_free)(struct cgroup *cgrp);
    int (*can_attach)(struct cgroup *cgrp, struct cgroup_taskset *tset);
    void (*cancel_attach)(struct cgroup *cgrp, struct cgroup_taskset *tset);
    void (*attach)(struct cgroup *cgrp, struct cgroup_taskset *tset);
    void (*fork)(struct task_struct *task);
    void (*exit)(struct cgroup *cgrp, struct cgroup *old_cgrp,
             struct task_struct *task);
    void (*bind)(struct cgroup *root);
    int subsys_id;      // subsys id
    int disabled;
    ...
    struct list_head cftsets;       // cftype结构体(参数文件管理结构)链表
    struct cftype *base_cftypes;    // 指向一个cftype数组
    struct cftype_set base_cftset;  //
    struct module *module;
};

cgroup_subsys也是逐一subsys的二个虚幻,真正的兑现由各种subsys完毕.可以和cgroup_subsys_state对比下,cgroup_subsys更偏向与叙述各种subsys的操作钩子,cgroup_subsys_state则与各种子系统的职务结构关联.
cgroup_subsys是与层级关系的,cgroup_subsys_state是与cgroup关联的。

struct cftype { // 删减版
    char name[MAX_CFTYPE_NAME];
    int private;
    umode_t mode;
    size_t max_write_len;
    unsigned int flags;
    s64 (*read_s64)(struct cgroup *cgrp, struct cftype *cft);
    int (*write_s64)(struct cgroup *cgrp, struct cftype *cft, s64 val);
    ...  
};

cftsets base_cftypes
base_cftset那么些三个属性保存的是一模一样份该subsys下对应控制文件的操作方法.只是造访方式差异.
以cpu subsys为例,该subsys下有cpu.shares
cpu.cfs_quota_us
cpu.cpu_cfs_period_read_u64这几个决定文件,每一种访问格局都不相同.
因而种种文件对应二个struct
cftype结构,保存其对应文件名和读写函数.

图2

澳门金沙国际 7
例如用户曾执行echo 1024 >>
cpu.shares
最后经过inode.file_operations.cgroup_file_read->cftype.write_s64.
同理,创设子group除了健康的mkdir操作之外,inode.inode_operations.cgroup_mkdir函数内部额外调用下面已经初叶化好的钩,成立新的cgroup.

说到底一步,cgroup_populate_dir(root_cgrp,
true,
root->subsys_mask);就是基于地点已经实例化好的cftype,创设cgroup下各种subsys的具备控制文件

static int cgroup_populate_dir(struct cgroup *cgrp, bool base_files, unsigned long subsys_mask)
{
    int err;
    struct cgroup_subsys *ss;

    if (base_files) {           //基本控制文件
        err = cgroup_addrm_files(cgrp, NULL, files, true);
        if (err < 0)
            return err;
    }

    /* process cftsets of each subsystem */
    for_each_subsys(cgrp->root, ss) {       //每个subsys
        struct cftype_set *set;
        if (!test_bit(ss->subsys_id, &subsys_mask))
            continue;

        list_for_each_entry(set, &ss->cftsets, node)  //每个subsys的每个控制文件
            cgroup_addrm_files(cgrp, ss, set->cfts, true);
    }
    ...
    return 0;
}

光天化日,先初步化了骨干的文书,进而初叶化每种subsys的各样控制文件.什么是基本文件?

static struct cftype files[] = {
    {
        .name = "tasks",
        .open = cgroup_tasks_open,
        .write_u64 = cgroup_tasks_write,
        .release = cgroup_pidlist_release,
        .mode = S_IRUGO | S_IWUSR,
    },
    {
        .name = CGROUP_FILE_GENERIC_PREFIX "procs",
        .open = cgroup_procs_open,
        .write_u64 = cgroup_procs_write,
        .release = cgroup_pidlist_release,
        .mode = S_IRUGO | S_IWUSR,
    },
    {
        .name = "notify_on_release",
        .read_u64 = cgroup_read_notify_on_release,
        .write_u64 = cgroup_write_notify_on_release,
    },
    {
        .name = CGROUP_FILE_GENERIC_PREFIX "event_control",
        .write_string = cgroup_write_event_control,
        .mode = S_IWUGO,
    },
    {
        .name = "cgroup.clone_children",
        .flags = CFTYPE_INSANE,
        .read_u64 = cgroup_clone_children_read,
        .write_u64 = cgroup_clone_children_write,
    },
    {
        .name = "cgroup.sane_behavior",
        .flags = CFTYPE_ONLY_ON_ROOT,
        .read_seq_string = cgroup_sane_behavior_show,
    },
    {
        .name = "release_agent",
        .flags = CFTYPE_ONLY_ON_ROOT,
        .read_seq_string = cgroup_release_agent_show,
        .write_string = cgroup_release_agent_write,
        .max_write_len = PATH_MAX,
    },
    { } /* terminate */
};

那一个文件在用户层应该见过.进到cgroup_create_file()函数看下:

static int cgroup_create_file(struct dentry *dentry, umode_t mode, struct super_block *sb)
{
    struct inode *inode;

    if (!dentry)
        return -ENOENT;
    if (dentry->d_inode)
        return -EEXIST;

    inode = cgroup_new_inode(mode, sb);     // 申请inode
    if (!inode)
        return -ENOMEM;

    if (S_ISDIR(mode)) {        //目录
        inode->i_op = &cgroup_dir_inode_operations; 
        inode->i_fop = &simple_dir_operations;
        ...
    } else if (S_ISREG(mode)) { //文件
        inode->i_size = 0;
        inode->i_fop = &cgroup_file_operations;
        inode->i_op = &cgroup_file_inode_operations;
    }
    d_instantiate(dentry, inode);
    dget(dentry);   /* Extra count - pin the dentry in core */
    return 0;
}

const struct file_operations simple_dir_operations = {
    .open       = dcache_dir_open,
    .release    = dcache_dir_close,
    .llseek     = dcache_dir_lseek,
    .read       = generic_read_dir,
    .readdir    = dcache_readdir,
    .fsync      = noop_fsync,
};

static const struct inode_operations cgroup_dir_inode_operations = {
    .lookup = cgroup_lookup,
    .mkdir = cgroup_mkdir,
    .rmdir = cgroup_rmdir,
    .rename = cgroup_rename,
    .setxattr = cgroup_setxattr,
    .getxattr = cgroup_getxattr,
    .listxattr = cgroup_listxattr,
    .removexattr = cgroup_removexattr,
};

static const 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,
};

static const struct inode_operations cgroup_file_inode_operations = {
    .setxattr = cgroup_setxattr,
    .getxattr = cgroup_getxattr,
    .listxattr = cgroup_listxattr,
    .removexattr = cgroup_removexattr,
};

那一个回调函数,上面以file_operations.cgroup_file_read 
cgroup_dir_inode_operations.cgroup_mkdir举例已经表达.
除开寻常vfs的操作,还要进行cgroup机制相关操作.

有点懵,幸而说的几近了.前面会轻松点,或许结合后边看前边,也会轻松些.

2.mkdir cpu_c1
本条简单的话就是分成七个部分,不奇怪vfs创立目录的逻辑,在该目录下开立异的cgroup,集成父cgroup的subsys.
一声令下贴全[root@VM_109_95_centos
/cgroup]#cd cpu/  &&  mkdir cpu_c1
我们是在/cgroup/目录下挂载的新文件系统,对于该cgroup文件系统,/cgroup/就是其根目录(用croot代替吧).
那么在croot目录下mkdir
cpu_c1.对于vfs来说,当然是调用croot目录对应inode.i_op.mkdir.

static int cgroup_get_rootdir(struct super_block *sb)
{
    struct inode *inode =
        cgroup_new_inode(S_IFDIR | S_IRUGO | S_IXUGO | S_IWUSR, sb);
    inode->i_fop = &simple_dir_operations;
    inode->i_op = &cgroup_dir_inode_operations;
    return 0;
}

可以看出croot目录项的inode.i_op也被装置为&cgroup_dir_inode_operations,那么mkdir就会调用cgroup_mkdir函数
cgroup_mkdir只是简短的卷入,实际工作的函数是cgroup_create()函数.
看下cgroup_create函数(删减版)

static long cgroup_create(struct cgroup *parent, struct dentry *dentry,umode_t mode)
{
    struct cgroup *cgrp;
    struct cgroup_name *name;
    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

    name = cgroup_alloc_name(dentry);
    rcu_assign_pointer(cgrp->name, name);   // 设置名称

    init_cgroup_housekeeping(cgrp);     //cgroup一些成员的初始化

    dentry->d_fsdata = cgrp;        //目录项(dentry)与cgroup关联起来
    cgrp->dentry = dentry;
    cgrp->parent = parent;      // 设置cgroup层级关系
    cgrp->root = parent->root;

    if (notify_on_release(parent))  // 继承父cgroup的CGRP_NOTIFY_ON_RELEASE属性
        set_bit(CGRP_NOTIFY_ON_RELEASE, &cgrp->flags);

    if (test_bit(CGRP_CPUSET_CLONE_CHILDREN, &parent->flags))  // 继承父cgroup的CGRP_CPUSET_CLONE_CHILDREN属性
        set_bit(CGRP_CPUSET_CLONE_CHILDREN, &cgrp->flags);

    for_each_subsys(root, ss) {
        struct cgroup_subsys_state *css;

        css = ss->css_alloc(cgrp);  // mount时各个subsys的钩子函数已经注册,这里直接使用来创建各个subsys的结构(task_group)

        init_cgroup_css(css, ss, cgrp); //初始化cgroup_subsys_state类型的值
        if (ss->use_id) {
            err = alloc_css_id(ss, parent, cgrp);
        }
    }

    err = cgroup_create_file(dentry, S_IFDIR | mode, sb);   //创建该目录项对应的inode,并初始化后与dentry关联上.

    list_add_tail(&cgrp->allcg_node, &root->allcg_list);    // 该cgroup挂到层级的cgroup链表上
    list_add_tail_rcu(&cgrp->sibling, &cgrp->parent->children); // 该cgroup挂到福cgroup的子cgroup链表上.
    ....
    for_each_subsys(root, ss) {         // 将各个subsys的控制结构(task_group)建立父子关系.
        err = online_css(ss, cgrp);
    }

    err = cgroup_populate_dir(cgrp, true, root->subsys_mask);   // 生成该cgroup目录下相关子系统的控制文件
    ...
}

cgroup_create里面做的事情,下面大致都看过了.不再解释.
css = ss->css_alloc(cgrp);
err = online_css(ss, cgrp);
那两行简单表达下:我们用cgroup来限制机器的cpu mem
IO
net,可是cgroup自身是从未界定效用的.cgroup更像是内核几大宗旨子系统为上层提供的入口..
以那一个事例来说,我们创造了一个绑定了cpu
subsys的cgroup.当大家把某部进度id加到该cgroup的tasks文件中时,
实质上是改变了该进度在进度调度系统中的相关参数,从而影响完全公平调度算法和实时调度算法达到限制的目的.
从而在那一个例子中,ss->css_alloc就算回到的是cgroup_subsys_state指针,但事实上它创设了task_group.
该协会第三个变量为cgroup_subsys_state.

struct task_group {  //删减版
    struct cgroup_subsys_state css;
    struct sched_entity **se;
    struct cfs_rq **cfs_rq;
    unsigned long shares;
    atomic_t load_weight;
    atomic64_t load_avg;
    atomic_t runnable_avg;
    struct rcu_head rcu;
    struct list_head list;
    struct task_group *parent;
    struct list_head siblings;
    struct list_head children;
};

struct sched_entity {
    struct load_weight  load;       /* for load-balancing */
    struct rb_node      run_node;
    struct list_head    group_node;
    unsigned int        on_rq;
    u64         exec_start;
    u64         sum_exec_runtime;
    u64         vruntime;
    u64         prev_sum_exec_runtime;
    u64         nr_migrations;
};

cpu子系统是透过安装task_group来限制进度的,相应的mem
IO子系统也有独家的结构.
可是它们的共性就是第2个变量是cgroup_subsys_state,那样cgroup和子系统控制结构就通过cgroup_subsys_state连接起来.

有点懵,万幸说的大半了.前边会轻松点,或者结合前边看日前,也会轻松些.

2.mkdir cpu_c1
本条大致来说就是分成七个部分,符合规律vfs成立目录的逻辑,在该目录下创办新的cgroup,集成父cgroup的subsys.
一声令下贴全[root@VM_109_95_centos
/cgroup]#cd cpu/  &&  mkdir cpu_c1
咱俩是在/cgroup/目录下挂载的新文件系统,对于该cgroup文件系统,/cgroup/就是其根目录(用croot代替吧).
那么在croot目录下mkdir
cpu_c1.对于vfs来说,当然是调用croot目录对应inode.i_op.mkdir.

static int cgroup_get_rootdir(struct super_block *sb)
{
    struct inode *inode =
        cgroup_new_inode(S_IFDIR | S_IRUGO | S_IXUGO | S_IWUSR, sb);
    inode->i_fop = &simple_dir_operations;
    inode->i_op = &cgroup_dir_inode_operations;
    return 0;
}

可以看来croot目录项的inode.i_op也被安装为&cgroup_dir_inode_operations,那么mkdir就会调用cgroup_mkdir函数
cgroup_mkdir只是简短的包装,实际工作的函数是cgroup_create()函数.
看下cgroup_create函数(删减版)

static long cgroup_create(struct cgroup *parent, struct dentry *dentry,umode_t mode)
{
    struct cgroup *cgrp;
    struct cgroup_name *name;
    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

    name = cgroup_alloc_name(dentry);
    rcu_assign_pointer(cgrp->name, name);   // 设置名称

    init_cgroup_housekeeping(cgrp);     //cgroup一些成员的初始化

    dentry->d_fsdata = cgrp;        //目录项(dentry)与cgroup关联起来
    cgrp->dentry = dentry;
    cgrp->parent = parent;      // 设置cgroup层级关系
    cgrp->root = parent->root;

    if (notify_on_release(parent))  // 继承父cgroup的CGRP_NOTIFY_ON_RELEASE属性
        set_bit(CGRP_NOTIFY_ON_RELEASE, &cgrp->flags);

    if (test_bit(CGRP_CPUSET_CLONE_CHILDREN, &parent->flags))  // 继承父cgroup的CGRP_CPUSET_CLONE_CHILDREN属性
        set_bit(CGRP_CPUSET_CLONE_CHILDREN, &cgrp->flags);

    for_each_subsys(root, ss) {
        struct cgroup_subsys_state *css;

        css = ss->css_alloc(cgrp);  // mount时各个subsys的钩子函数已经注册,这里直接使用来创建各个subsys的结构(task_group)

        init_cgroup_css(css, ss, cgrp); //初始化cgroup_subsys_state类型的值
        if (ss->use_id) {
            err = alloc_css_id(ss, parent, cgrp);
        }
    }

    err = cgroup_create_file(dentry, S_IFDIR | mode, sb);   //创建该目录项对应的inode,并初始化后与dentry关联上.

    list_add_tail(&cgrp->allcg_node, &root->allcg_list);    // 该cgroup挂到层级的cgroup链表上
    list_add_tail_rcu(&cgrp->sibling, &cgrp->parent->children); // 该cgroup挂到福cgroup的子cgroup链表上.
    ....
    for_each_subsys(root, ss) {         // 将各个subsys的控制结构(task_group)建立父子关系.
        err = online_css(ss, cgrp);
    }

    err = cgroup_populate_dir(cgrp, true, root->subsys_mask);   // 生成该cgroup目录下相关子系统的控制文件
    ...
}

cgroup_create里面做的政工,上边大概都看过了.不再解释.
css = ss->css_alloc(cgrp);
err = online_css(ss, cgrp);
那两行简单说明下:大家用cgroup来限制机器的cpu mem
IO
net,不过cgroup本人是从未有过限制作用的.cgroup更像是内核几大核心子系统为上层提供的入口..
以这么些事例来说,我们创立了四个绑定了cpu
subsys的cgroup.当大家把某部进度id加到该cgroup的tasks文件中时,
实际上是改变了该进程在经过调度种类中的相关参数,从而影响完全公平调度算法和实时调度算法达到限制的目标.
据此在这些事例中,ss->css_alloc即使回到的是cgroup_subsys_state指针,但实质上它成立了task_group.
该社团第四个变量为cgroup_subsys_state.

struct task_group {  //删减版
    struct cgroup_subsys_state css;
    struct sched_entity **se;
    struct cfs_rq **cfs_rq;
    unsigned long shares;
    atomic_t load_weight;
    atomic64_t load_avg;
    atomic_t runnable_avg;
    struct rcu_head rcu;
    struct list_head list;
    struct task_group *parent;
    struct list_head siblings;
    struct list_head children;
};

struct sched_entity {
    struct load_weight  load;       /* for load-balancing */
    struct rb_node      run_node;
    struct list_head    group_node;
    unsigned int        on_rq;
    u64         exec_start;
    u64         sum_exec_runtime;
    u64         vruntime;
    u64         prev_sum_exec_runtime;
    u64         nr_migrations;
};

cpu子系统是经过安装task_group来限制进度的,相应的mem
IO子系统也有些的结构.
只是它们的共性就是首先个变量是cgroup_subsys_state,那样cgroup和子系统控制结构就经过cgroup_subsys_state连接起来.

mount时根cgroup也是要开创这一个子系统控制结构的,被自个儿略掉了.

3.echo 2048 >> cpu.shares
上边已经看见了cpu.shares那一个文件的inode_i_fop
= &cgroup_file_operations,写文件调用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->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;
}

mount时曾经知晓各样subsys的种种控制文件的操作函数都是不均等的(通过cftype达成的).我们直接看下cpu.shares文件的操作函数.

static struct cftype cpu_files[] = {
    {
        .name = "shares",
        .read_u64 = cpu_shares_read_u64,
        .write_u64 = cpu_shares_write_u64,
    },
    ...
}

写cpu.shares最后调用cpu_shares_write_u64,
中间几层细节略过.最终实施update_load_set:

static inline void update_load_set(struct load_weight *lw, unsigned long w)
{
    lw->weight = w;
    lw->inv_weight = 0;
}

mount时根cgroup也是要开创这么些子系统控制结构的,被本身略掉了.

3.echo 2048 >> cpu.shares
下面已经看见了cpu.shares那几个文件的inode_i_fop
= &cgroup_file_operations,写文件调用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->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;
}

mount时一度知晓各样subsys的种种控制文件的操作函数都以不雷同的(通过cftype达成的).我们平素看下cpu.shares文件的操作函数.

static struct cftype cpu_files[] = {
    {
        .name = "shares",
        .read_u64 = cpu_shares_read_u64,
        .write_u64 = cpu_shares_write_u64,
    },
    ...
}

写cpu.shares最后调用cpu_shares_write_u64,
中间几层细节略过.最后实施update_load_set:

static inline void update_load_set(struct load_weight *lw, unsigned long w)
{
    lw->weight = w;
    lw->inv_weight = 0;
}

其中load_weight=task_group.se.load,改变了load_weight.weight,起到了限定该task_group对cpu的使用.

4.echo 7860 >> tasks
进度是相仿的,不过tasks文件最终调用的是cgroup_tasks_write这么些函数.

static struct cftype files[] = {
    {
        .name = "tasks",
        .open = cgroup_tasks_open,
        .write_u64 = cgroup_tasks_write,
        .release = cgroup_pidlist_release,
        .mode = S_IRUGO | S_IWUSR,
    },
}

cgroup_tasks_write最后调用attach_task_by_pid

static int attach_task_by_pid(struct cgroup *cgrp, u64 pid, bool threadgroup)
{
    struct task_struct *tsk;
    const struct cred *cred = current_cred(), *tcred;
    int ret;
    if (pid) {              //根据pid找到该进程的task_struct
        tsk = find_task_by_vpid(pid);
        if (!tsk) {
            rcu_read_unlock();
            ret= -ESRCH;
            goto out_unlock_cgroup;
        }
    }
    .....
    .....
    ret = cgroup_attach_task(cgrp, tsk, threadgroup);    //将进程关联到cgroup
    return ret;
}

最终通过cgroup_attach_task函数,将经过挂载到响应cgroup.先看多少个新的布局体.

struct css_set {
    atomic_t refcount;         //引用计数
    struct hlist_node hlist;   //css_set链表,将系统中所有css_set连接起来.
    struct list_head tasks;    //task链表,链接所有属于这个set的进程
    struct list_head cg_links; // 指向一个cg_cgroup_link链表
    struct cgroup_subsys_state *subsys[CGROUP_SUBSYS_COUNT];  // 关联到subsys
    struct rcu_head rcu_head;
};

struct cg_cgroup_link {
    struct list_head cgrp_link_list;   //内嵌到cgroup->css_set链表
    struct cgroup *cgrp;   // 指向对应的cgroup
    struct list_head cg_link_list;     //内嵌到css_set->cg_links链表
    struct css_set *cg;    // 指向对应的css_set
};

struct task_struct {
    struct css_set __rcu *cgroups;  // 指向所属的css_set
    struct list_head cg_list;       // 将同属于一个css_set的task_struct连接起来.
}

css_set感觉像是进度和cgroup机制间的一个桥梁.cg_cgroup_link又将css_set和cgroup多对多的炫耀起来.
task_struct中并不曾平素与cgroup关联,struct
css_set __rcu *cgroups指向和睦所属的css_set.
那般task和cgroup subsys
cgroup都可以相互索引到了.

图3

澳门金沙国际 8

 

进到cgroup_attach_task看看:

struct task_and_cgroup {
    struct task_struct  *task;
    struct cgroup       *cgrp;
    struct css_set      *cg;
};

struct cgroup_taskset {
    struct task_and_cgroup  single;
    struct flex_array   *tc_array;
    int         tc_array_len;
    int         idx;
    struct cgroup       *cur_cgrp;
};

static int cgroup_attach_task(struct cgroup *cgrp, struct task_struct *tsk,
                  bool threadgroup)
{
    int retval, i, group_size;
    struct cgroup_subsys *ss, *failed_ss = NULL;
    struct cgroupfs_root *root = cgrp->root;
    /* threadgroup list cursor and array */
    struct task_struct *leader = tsk;
    struct task_and_cgroup *tc;
    struct flex_array *group;
    struct cgroup_taskset tset = { };

    group = flex_array_alloc(sizeof(*tc), group_size, GFP_KERNEL);
    retval = flex_array_prealloc(group, 0, group_size, GFP_KERNEL);     //预分配内存,考虑到了多线程的进程

    i = 0;
    rcu_read_lock();
    do {                // 兼顾多线程进程,将所有线程的相关信息放在tset里
        struct task_and_cgroup ent;
        ent.task = tsk;
        ent.cgrp = task_cgroup_from_root(tsk, root);
        retval = flex_array_put(group, i, &ent, GFP_ATOMIC);
        BUG_ON(retval != 0);
        i++;
    next:
        if (!threadgroup)
            break;
    } while_each_thread(leader, tsk);
    rcu_read_unlock();
    group_size = i;
    tset.tc_array = group;
    tset.tc_array_len = group_size;

    for_each_subsys(root, ss) {         //调用每个subsys的方法,判断是否可绑定.
        if (ss->can_attach) {
            retval = ss->can_attach(cgrp, &tset);
            if (retval) {
                failed_ss = ss;
                goto out_cancel_attach;
            }
        }
    }

    for (i = 0; i < group_size; i++) {      // 为每个task准备(已有或分配)css_set,css_set是多个进程共享.
        tc = flex_array_get(group, i);
        tc->cg = find_css_set(tc->task->cgroups, cgrp);
        if (!tc->cg) {
            retval = -ENOMEM;
            goto out_put_css_set_refs;
        }
    }

    for (i = 0; i < group_size; i++) {      // 将所有task从old css_set迁移到new css_set.
        tc = flex_array_get(group, i);
        cgroup_task_migrate(tc->cgrp, tc->task, tc->cg);
    }

    for_each_subsys(root, ss) {         // 调用subsys的attach方法,执行绑定.
        if (ss->attach)
            ss->attach(cgrp, &tset);
    }
    retval = 0
    return retval;
}

这里的can_attach和attach由各类subsys已毕,那里先不说了.
因为创设层级时会把系统上拥有的历程加到根cgroup的tasks中,所以用户层将task加进有些cgroup等同于将task从2个cgroup移到另四个cgriup.
cgroup_task_migrate就是将task与新的cgroup对应的css_set重新照射起来.

倘若不对请提议。

参考资料:

  linux-3.10源码

  <linux
cgroup详解><zhefwang@gmail.com>连接找不到了

其中load_weight=task_group.se.load,改变了load_weight.weight,起到了限制该task_group对cpu的使用.

4.echo 7860 >> tasks
进度是相近的,可是tasks文件最终调用的是cgroup_tasks_write这些函数.

static struct cftype files[] = {
    {
        .name = "tasks",
        .open = cgroup_tasks_open,
        .write_u64 = cgroup_tasks_write,
        .release = cgroup_pidlist_release,
        .mode = S_IRUGO | S_IWUSR,
    },
}

cgroup_tasks_write最后调用attach_task_by_pid

static int attach_task_by_pid(struct cgroup *cgrp, u64 pid, bool threadgroup)
{
    struct task_struct *tsk;
    const struct cred *cred = current_cred(), *tcred;
    int ret;
    if (pid) {              //根据pid找到该进程的task_struct
        tsk = find_task_by_vpid(pid);
        if (!tsk) {
            rcu_read_unlock();
            ret= -ESRCH;
            goto out_unlock_cgroup;
        }
    }
    .....
    .....
    ret = cgroup_attach_task(cgrp, tsk, threadgroup);    //将进程关联到cgroup
    return ret;
}

末段经过cgroup_attach_task函数,将经过挂载到响应cgroup.先看几个新的社团体.

struct css_set {
    atomic_t refcount;         //引用计数
    struct hlist_node hlist;   //css_set链表,将系统中所有css_set连接起来.
    struct list_head tasks;    //task链表,链接所有属于这个set的进程
    struct list_head cg_links; // 指向一个cg_cgroup_link链表
    struct cgroup_subsys_state *subsys[CGROUP_SUBSYS_COUNT];  // 关联到subsys
    struct rcu_head rcu_head;
};

struct cg_cgroup_link {
    struct list_head cgrp_link_list;   //内嵌到cgroup->css_set链表
    struct cgroup *cgrp;   // 指向对应的cgroup
    struct list_head cg_link_list;     //内嵌到css_set->cg_links链表
    struct css_set *cg;    // 指向对应的css_set
};

struct task_struct {
    struct css_set __rcu *cgroups;  // 指向所属的css_set
    struct list_head cg_list;       // 将同属于一个css_set的task_struct连接起来.
}

css_set感觉像是进度和cgroup机制间的三个桥梁.cg_cgroup_link又将css_set和cgroup多对多的映射起来.
task_struct中并不曾直接与cgroup关联,struct
css_set __rcu *cgroups指向和睦所属的css_set.
如此task和cgroup subsys
cgroup都可以相互索引到了.

图3

澳门金沙国际 9

 

进到cgroup_attach_task看看:

struct task_and_cgroup {
    struct task_struct  *task;
    struct cgroup       *cgrp;
    struct css_set      *cg;
};

struct cgroup_taskset {
    struct task_and_cgroup  single;
    struct flex_array   *tc_array;
    int         tc_array_len;
    int         idx;
    struct cgroup       *cur_cgrp;
};

static int cgroup_attach_task(struct cgroup *cgrp, struct task_struct *tsk,
                  bool threadgroup)
{
    int retval, i, group_size;
    struct cgroup_subsys *ss, *failed_ss = NULL;
    struct cgroupfs_root *root = cgrp->root;
    /* threadgroup list cursor and array */
    struct task_struct *leader = tsk;
    struct task_and_cgroup *tc;
    struct flex_array *group;
    struct cgroup_taskset tset = { };

    group = flex_array_alloc(sizeof(*tc), group_size, GFP_KERNEL);
    retval = flex_array_prealloc(group, 0, group_size, GFP_KERNEL);     //预分配内存,考虑到了多线程的进程

    i = 0;
    rcu_read_lock();
    do {                // 兼顾多线程进程,将所有线程的相关信息放在tset里
        struct task_and_cgroup ent;
        ent.task = tsk;
        ent.cgrp = task_cgroup_from_root(tsk, root);
        retval = flex_array_put(group, i, &ent, GFP_ATOMIC);
        BUG_ON(retval != 0);
        i++;
    next:
        if (!threadgroup)
            break;
    } while_each_thread(leader, tsk);
    rcu_read_unlock();
    group_size = i;
    tset.tc_array = group;
    tset.tc_array_len = group_size;

    for_each_subsys(root, ss) {         //调用每个subsys的方法,判断是否可绑定.
        if (ss->can_attach) {
            retval = ss->can_attach(cgrp, &tset);
            if (retval) {
                failed_ss = ss;
                goto out_cancel_attach;
            }
        }
    }

    for (i = 0; i < group_size; i++) {      // 为每个task准备(已有或分配)css_set,css_set是多个进程共享.
        tc = flex_array_get(group, i);
        tc->cg = find_css_set(tc->task->cgroups, cgrp);
        if (!tc->cg) {
            retval = -ENOMEM;
            goto out_put_css_set_refs;
        }
    }

    for (i = 0; i < group_size; i++) {      // 将所有task从old css_set迁移到new css_set.
        tc = flex_array_get(group, i);
        cgroup_task_migrate(tc->cgrp, tc->task, tc->cg);
    }

    for_each_subsys(root, ss) {         // 调用subsys的attach方法,执行绑定.
        if (ss->attach)
            ss->attach(cgrp, &tset);
    }
    retval = 0
    return retval;
}

这里的can_attach和attach由逐个subsys完结,那里先不说了.
因为创制层级时会把系统上具有的经过加到根cgroup的tasks中,所以用户层将task加进有些cgroup等同于将task从二个cgroup移到另1个cgriup.
cgroup_task_migrate就是将task与新的cgroup对应的css_set重新照射起来.

要是不对请指出。

参考资料:

  linux-3.10源码

  <linux
cgroup详解><zhefwang@gmail.com>连接找不到了

相关文章