cgroup原理簡析:vfs文件系統


要了解cgroup實現原理,必須先了解下vfs(虛擬文件系統).因為cgroup通過vfs向用戶層提供接口,用戶層通過掛載,創建目錄,讀寫文件的方式與cgroup交互.
因為是介紹cgroup的文章,因此只闡述cgroup文件系統是如何集成進vfs的,過多的vfs實現可參考其他資料.

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

我們以上面4行命令為主線進行分析,從一個cgroup使用者的角度來看:
命令1 創建了一個新的cgroup層級(掛載了一個新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倍.

那么系統在背后做了那些工作呢?下面逐一分析(內核版本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統.然后根據參數創建一個層級.跟進到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, };

竟然只提供3個操作....常見的文件系統(ext2)都會提供諸如alloc_inode  read_inode等函數供上層操作文件.但是cgroup文件系統不需要這些操作,
很好理解,cgroup是基於memory的文件系統.用不到那些操作.
到這里好多struct已經復出水面,眼花繚亂.畫個圖理理.

圖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_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的一個抽象,真正的實現由各個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


例如用戶曾執行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子系統也有各自的結構.
不過它們的共性就是第一個變量是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; }

其中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

 

進到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詳解><zhefwang@gmail.com>連接找不到了


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM