前置:這里使用的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