cgroup原理簡析:vfs文件系統

来源:http://www.cnblogs.com/acool/archive/2017/05/14/6852250.html
-Advertisement-
Play Games

要瞭解cgroup實現原理,必須先瞭解下vfs(虛擬文件系統).因為cgroup通過vfs向用戶層提供介面,用戶層通過掛載,創建目錄,讀寫文件的方式與cgroup交互. ...


要瞭解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;
            	   

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 一、 DFS:distributied file system 是一種允許文件通過網路在多台主機上風向的文件系統,可讓多機器上的多用戶分享文件和存儲空間 二、HDFS的shell **切記後面加的 / 符號 三、HDFS的dfsadmin命令 ...
  • 如圖: 無法安裝原因都是這幾個工具無法識別10.0這個版本,可以修改註冊表來先完成安裝,然後再改回去 PHPManager的修改方法如下: 打開註冊表工具(運行Regedt32),找到:HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\W3SV... ...
  • 定時任務兩實例 例1: 每分鐘列印一次自己的名字拼音全拼到“/server/log/自己的名字命名的文件”中。 錯誤示例: 解答知識小結: 1、定時任務要加註釋2、如果已經要定向到文件中,結尾不要有>/dev/null 2>&13、/server/log目錄必須要存在才能出結果,如沒有創建這個目錄。 ...
  • 在WindowsServer2016上安裝ExchangeServer2016的體驗與以往版本的不同,不再是直接運行SETUP然後一路NEXT就可以順利完成的了。本人安裝過程中就遇到兩個意料之外的情況還好最後成功解決了,我把經驗分享出來給同樣被困住的人參考參考,希望有所幫助。 ...
  • WannaCry ransomware used in widespread attacks all over the world Customer Guidance for WannaCrypt attacks(Microsoft Security Response Center) How to ...
  • a.場景: 平時會使用百度網盤下載電影,但是用ios版的百度雲實在是有點慢,而平時不用windows,因此只能在linux上尋找解決之道. b.背景(我正在使用中): linux:ubuntu 14.04 64bit 瀏覽器:firefox41.0.2 c.安裝詳情: 1.安裝火狐插件flashgo ...
  • 摘 要:本文闡述了MySQL DDL 的問題現狀、pt-online-schema-change的工作原理,並實際利用pt-online-schema-change工具線上修改生產環境下1.6億級數據表結構。 在一個軟體生命周期中,我們都知道,前期的表結構設計是非常重要的,因為當表數據量一上來後再進 ...
  • 一.檢查和安裝與Perl相關的模塊 PT工具是使用Perl語言編寫和執行的,所以需要系統中有Perl環境。 依賴包檢查命令為: rpm -qa perl-DBI perl-DBD-MySQL perl-Time-HiRes perl-IO-Socket-SSL 如果有依賴包確實,可以使用下麵的命令安 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...