linux源碼分析(四)-start_kernel-cgroup


前置:這里使用的linux版本是4.8,x86體系。

cgroup_init_early();

聊這個函數就需要先了解cgroup。

cgroup概念

這個函數就是初始化cgroup所需要的參數的。cgroup最初是在2006年由google的一名工程師提出的,目的是把一些共同目標的進程放在一個組里面,而這個組里面的進程能共享指定數額的資源。而后就有了cgroup這個概念了。

我們把每種資源叫做子系統,比如CPU子系統,內存子系統。為什么叫做子系統呢,因為它是從整個操作系統的資源衍生出來的。然后我們創建一種虛擬的節點,叫做cgroup,然后這個虛擬節點可以擴展,以樹形的結構,有root節點,和子節點。這個父節點和各個子節點就形成了層級(hierarchiy)。每個層級都可以附帶繼承一個或者多個子系統,就意味着,我們把資源按照分割到多個層級系統中,層級系統中的每個節點對這個資源的占比各有不同。

下面我們想法子把進程分組,進程分組的邏輯叫做css_set。這里的css是cgroup_subsys_state的縮寫。所以css_set和進程的關系是一對多的關系。另外,在cgroup眼中,進程請不要叫做進程,叫做task。這個可能是為了和內核中進程的名詞區分開吧。

進程分組css_set,不同層級中的節點cgroup也都有了。那么,就要把節點cgroup和層級進行關聯,和數據庫中關系表一樣。這個事一個多對多的關系。為什么呢?首先,一個節點可以隸屬於多個css_set,這就代表這這批css_set中的進程都擁有這個cgroup所代表的資源。其次,一個css_set需要多個cgroup。因為一個層級的cgroup只代表一種或者幾種資源,而一般進程是需要多種資源的集合體。

美團的這個圖片描寫的非常清晰,一看就了解了:

task_struct

首先先看進程的結構,里面和cgroup有關的是

#ifdef CONFIG_CGROUPS
     // 設置這個進程屬於哪個css_set
    struct css_set __rcu *cgroups;
     // cg_list是用於將所有同屬於一個css_set的task連成一起
    struct list_head cg_list;
#endif

我們會在代碼中經常見到list_head。它其實就是表示,這個在鏈表中存在。

struct list_head {
    struct list_head *next, *prev;
};

它的結構很簡單,就能把某種相同性質的結構連成一個鏈表,根據這個鏈表我能前后找全整個鏈表或者頭部節點等。

css_set

結構體在include/linux/cgroup-defs.h中。

struct css_set {
    // 引用計數,gc使用,如果子系統有引用到這個css_set,則計數+1
    atomic_t refcount;

     // TODO: 列出有相同hash值的cgroup(還不清楚為什么)
    struct hlist_node hlist;

     // 將所有的task連起來。mg_tasks代表遷移的任務
    struct list_head tasks;
    struct list_head mg_tasks;
     // 將這個css_set對應的cgroup連起來
    struct list_head cgrp_links;

    // 默認連接的cgroup
    struct cgroup *dfl_cgrp;

     // 包含一系列的css(cgroup_subsys_state),css就是子系統,這個就代表了css_set和子系統的多對多的其中一面
    struct cgroup_subsys_state *subsys[CGROUP_SUBSYS_COUNT];

    // 內存遷移的時候產生的系列數據
    struct list_head mg_preload_node;
    struct list_head mg_node;
    struct cgroup *mg_src_cgrp;
    struct cgroup *mg_dst_cgrp;
    struct css_set *mg_dst_cset;

    // 把->subsys[ssid]->cgroup->e_csets[ssid]結構展平放在這里,提高迭代效率
    struct list_head e_cset_node[CGROUP_SUBSYS_COUNT];

     // 所有迭代任務的列表,這個補丁參考:https://patchwork.kernel.org/patch/7368941/
    struct list_head task_iters;

    // 這個css_set是否已經無效了
    bool dead;

    // rcu鎖所需要的callback等信息
    struct rcu_head rcu_head;
};

這里說一下rcu鎖,這個鎖是linux2.6引入的。它是非常高效的,適合讀多寫少的情況。全稱是(Read-Copy Update)讀-拷貝修改。原理就是讀操作的時候,不需要任何鎖,直接進行讀取,寫操作的時候,先拷貝一個副本,然后對副本進行修改,最后使用回調(callback)在適當的時候把指向原來數據的指針指向新的被修改的數據。https://www.ibm.com/developerworks/cn/linux/l-rcu/

這里的rcu_head就存儲了對這個結構上rcu鎖所需要的回調信息。

struct callback_head {
    struct callback_head *next;
    void (*func)(struct callback_head *head);
} __attribute__((aligned(sizeof(void *))));
#define rcu_head callback_head

回到css_set,其實最重要的就是cgroup_subsys_state subsys[]數組這個結構。

cgroup_subsys_state 和 cgroup_subsys

這個結構最重要的就是存儲的進程與特定子系統相關的信息。通過它,可以將task_struct和cgroup連接起來了:task_struct->css_set->cgroup_subsys_state->cgroup

struct cgroup_subsys_state {
    // 對應的cgroup
    struct cgroup *cgroup;

    // 子系統
    struct cgroup_subsys *ss;

    // 帶cpu信息的引用計數(不大理解)
    struct percpu_ref refcnt;

    // 父css
    struct cgroup_subsys_state *parent;

    // 兄弟和孩子鏈表串
    struct list_head sibling;
    struct list_head children;

    // css的唯一id
    int id;

     // 可設置的flag有:CSS_NO_REF/CSS_ONLINE/CSS_RELEASED/CSS_VISIBLE
    unsigned int flags;

    // 為了保證遍歷的順序性,設置遍歷按照這個字段的升序走
    u64 serial_nr;

    // 計數,計算本身css和子css的活躍數,當這個數大於1,說明還有有效子css
    atomic_t online_cnt;

    // TODO: 帶cpu信息的引用計數使用的rcu鎖(不大理解)
    struct rcu_head rcu_head;
    struct work_struct destroy_work;
};

cgroup_subsys結構體在include/linux/cgroup-defs.h里面

struct cgroup_subsys {
     // 下面的是函數指針,定義了子系統對css_set結構的系列操作
    struct cgroup_subsys_state *(*css_alloc)(struct cgroup_subsys_state *parent_css);
    int (*css_online)(struct cgroup_subsys_state *css);
    void (*css_offline)(struct cgroup_subsys_state *css);
    void (*css_released)(struct cgroup_subsys_state *css);
    void (*css_free)(struct cgroup_subsys_state *css);
    void (*css_reset)(struct cgroup_subsys_state *css);

     // 這些函數指針表示了對子系統對進程task的一系列操作
    int (*can_attach)(struct cgroup_taskset *tset);
    void (*cancel_attach)(struct cgroup_taskset *tset);
    void (*attach)(struct cgroup_taskset *tset);
    void (*post_attach)(void);
    int (*can_fork)(struct task_struct *task);
    void (*cancel_fork)(struct task_struct *task);
    void (*fork)(struct task_struct *task);
    void (*exit)(struct task_struct *task);
    void (*free)(struct task_struct *task);

    void (*bind)(struct cgroup_subsys_state *root_css);

     // 是否在前期初始化了
    bool early_init:1;

     // 如果設置了true,那么在cgroup.controllers和cgroup.subtree_control就不會顯示, TODO:
    bool implicit_on_dfl:1;

     // 如果設置為false,則子cgroup會繼承父cgroup的子系統資源,否則不繼承或者只繼承一半
     // 但是現在,我們規定,不允許一個cgroup有不可繼承子系統仍然可以衍生出cgroup。如果做類似操作,我們會根據
     // warned_broken_hierarch出現錯誤提示。
    bool broken_hierarchy:1;
    bool warned_broken_hierarchy:1;

    int id;
    const char *name;

     // 如果子cgroup的結構繼承子系統的時候沒有設置name,就會沿用父系統的子系統名字,所以這里存的就是父cgroup的子系統名字
    const char *legacy_name;

    struct cgroup_root *root;  // 這個就是子系統指向的層級中的root的cgroup

    struct idr css_idr; // 對應的css的idr

     // 對應的文件系統相關信息
    struct list_head cfts;
    struct cftype *dfl_cftypes;    /* 默認的文件系統 */
    struct cftype *legacy_cftypes;    /* 繼承的文件系統 */

     // 有的子系統是依賴其他子系統的,這里是一個掩碼來表示這個子系統依賴哪些子系統
    unsigned int depends_on;
};

這里特別說一下cftype。它是cgroup_filesystem_type的縮寫。這個要從我們的linux虛擬文件系統說起(VFS)。VFS封裝了標准文件的所有系統調用。那么我們使用cgroup,也抽象出了一個文件系統,自然也需要實現這個VFS。實現這個VFS就是使用這個cftype結構。

這里說一下idr。這個是linux的整數id管理機制。你可以把它看成一個map,這個map是把id和制定指針關聯在一起的機制。它的原理是使用基數樹。一個結構存儲了一個idr,就能很方便根據id找出這個id對應的結構的地址了。http://blog.csdn.net/dlutbrucezhang/article/details/10103371

cgroup

cgroup結構也在相同文件,但是cgroup_root和子節點cgroup是使用兩個不同結構表示的。

struct cgroup {
    // cgroup所在css
    struct cgroup_subsys_state self;

    unsigned long flags;

    int id;

    // 這個cgroup所在層級中,當前cgroup的深度
    int level;

    // 每當有個非空的css_set和這個cgroup關聯的時候,就增加計數1
    int populated_cnt;

    struct kernfs_node *kn;        /* cgroup kernfs entry */
    struct cgroup_file procs_file;    /* handle for "cgroup.procs" */
    struct cgroup_file events_file;    /* handle for "cgroup.events" */

    // TODO: 不理解
    u16 subtree_control;
    u16 subtree_ss_mask;
    u16 old_subtree_control;
    u16 old_subtree_ss_mask;

    // 一個cgroup屬於多個css,這里就是保存了cgroup和css直接多對多關系的另一半
    struct cgroup_subsys_state __rcu *subsys[CGROUP_SUBSYS_COUNT];

     // 根cgroup
    struct cgroup_root *root;

    // 相同css_set的cgroup鏈表
    struct list_head cset_links;

    // 這個cgroup使用的所有子系統的每個鏈表
    struct list_head e_csets[CGROUP_SUBSYS_COUNT];

    // TODO: 不理解
    struct list_head pidlists;
    struct mutex pidlist_mutex;

    // 用來保存下線task
    wait_queue_head_t offline_waitq;

    // TODO: 用來保存釋放任務?(不理解)
    struct work_struct release_agent_work;

    // 保存每個level的祖先
    int ancestor_ids[];
};

這里看到一個新的結構,wait_queue_head_t,這個結構是用來將一個資源掛在等待隊列中,具體參考:http://www.cnblogs.com/lubiao/p/4858086.html

還有一個結構是cgroup_root

struct cgroup_root {
     // TODO: 不清楚
    struct kernfs_root *kf_root;

    // 子系統掩碼
    unsigned int subsys_mask;

    // 層級的id
    int hierarchy_id;

    // 根部的cgroup,這里面就有下級cgroup
    struct cgroup cgrp;

    // 相等於cgrp->ancester_ids[0]
    int cgrp_ancestor_id_storage;

    // 這個root層級下的cgroup數,初始化的時候為1
    atomic_t nr_cgrps;

    // 串起所有的cgroup_root
    struct list_head root_list;

    unsigned int flags;

    // TODO: 不清楚
    struct idr cgroup_idr;

    // TODO: 不清楚
    char release_agent_path[PATH_MAX];

    // 這個層級的名稱,有可能為空
    char name[MAX_CGROUP_ROOT_NAMELEN];
};

cgroup_init_early

回到這個函數

int __init cgroup_init_early(void)
{
    // 初始化cgroup_root,就是一個cgroup_root的結構
    init_cgroup_root(&cgrp_dfl_root, &opts);
    cgrp_dfl_root.cgrp.self.flags |= CSS_NO_REF;

    RCU_INIT_POINTER(init_task.cgroups, &init_css_set);

    for_each_subsys(ss, i) {
        WARN(!ss->css_alloc || !ss->css_free || ss->name || ss->id,
             "invalid cgroup_subsys %d:%s css_alloc=%p css_free=%p id:name=%d:%s\n",
             i, cgroup_subsys_name[i], ss->css_alloc, ss->css_free,
             ss->id, ss->name);
        WARN(strlen(cgroup_subsys_name[i]) > MAX_CGROUP_TYPE_NAMELEN,
             "cgroup_subsys_name %s too long\n", cgroup_subsys_name[i]);

        ss->id = i;
        ss->name = cgroup_subsys_name[i];
        if (!ss->legacy_name)
            ss->legacy_name = cgroup_subsys_name[i];

        if (ss->early_init)
            cgroup_init_subsys(ss, true);
    }
    return 0;
}

這個函數初始化的cgroup_root是一個全局的變量。定義在kernel/cgroup.c中。

struct cgroup_root cgrp_dfl_root;
EXPORT_SYMBOL_GPL(cgrp_dfl_root);

理解了cgroup結構,里面的設置就可以基本看懂了。

參考

http://files.cnblogs.com/files/lisperl/cgroups介紹.pdf
http://tech.meituan.com/cgroups.html
http://coolshell.cn/articles/17049.html


免責聲明!

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



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