cgroups 數據結構設計
我們從進程出發來剖析cgroups相關數據結構之間的關系。
在Linux中,管理進程的數據結構是task_struct,其中與cgroups有關的:
#ifdef CONFIG_CGROUPS
/* Control Group info protected by css_set_lock */
struct css_set *cgroups;
/* cg_list protected by css_set_lock and tsk->alloc_lock */
struct list_head cg_list;
#endif
其中cgroups指針指向了一個css_set結構,而css_set存儲了與進程相關的cgroups信息。Cg_list是一個嵌入的list_head結構,用於將連到同一個css_set的進程組織成一個鏈表。下面我們來看css_set的結構:
struct css_set {
atomic_t refcount;
struct hlist_node hlist;
struct list_head tasks;
struct list_head cg_links;
struct cgroup_subsys_state *subsys[CGROUP_SUBSYS_COUNT];
struct rcu_head rcu_head;
};
其中refcount是該css_set的引用數,因為一個css_set可以被多個進程共用,只要這些進程的cgroups信息相同,比如:在所有已創建的層級里面都在同一個cgroup里的進程。
hlist是嵌入的hlist_node,用於把所有css_set組織成一個hash表,這樣內核可以快速查找特定的css_set。
tasks指向所有連到此css_set的進程連成的鏈表。
cg_links指向一個由struct cg_cgroup_link連成的鏈表。
Subsys是一個指針數組,存儲一組指向cgroup_subsys_state的指針。一個cgroup_subsys_state就是進程與一個特定子系統相關的信息。通過這個指針數組,進程就可以獲得相應的cgroups控制信息了。
下面我們就來看cgroup_subsys_state的結構:
struct cgroup_subsys_state {
struct cgroup *cgroup;
atomic_t refcnt;
unsigned long flags;
struct css_id *id;
};
cgroup指針指向了一個cgroup結構,也就是進程屬於的cgroup。進程受到子系統的控制,實際上是通過加入到特定的cgroup實現的,因為cgroup在特定的層級上,而子系統又是附加到曾經上的。通過以上三個結構,進程就可以和cgroup連接起來了:task_struct->css_set->cgroup_subsys_state->cgroup。
下面我們再來看cgroup的結構:
struct cgroup {
unsigned long flags;
atomic_t count;
struct list_head sibling;
struct list_head children;
struct cgroup *parent;
struct dentry *dentry;
struct cgroup_subsys_state *subsys[CGROUP_SUBSYS_COUNT];
struct cgroupfs_root *root;
struct cgroup *top_cgroup;
struct list_head css_sets;
struct list_head release_list;
struct list_head pidlists;
struct mutex pidlist_mutex;
struct rcu_head rcu_head;
struct list_head event_list;
spinlock_t event_list_lock;
};
sibling,children和parent三個嵌入的list_head負責將同一層級的cgroup連接成一顆cgroup樹。
subsys是一個指針數組,存儲一組指向cgroup_subsys_state的指針。這組指針指向了此cgroup跟各個子系統相關的信息,這個跟css_set中的道理是一樣的。
root指向了一個cgroupfs_root的結構,就是cgroup所在的層級對應的結構體。這樣以來,之前談到的幾個cgroups概念就全部聯系起來了。
top_cgroup指向了所在層級的根cgroup,也就是創建層級時自動創建的那個cgroup。
css_set指向一個由struct cg_cgroup_link連成的鏈表,跟css_set中cg_links一樣。
下面我們來分析一個css_set和cgroup之間的關系。我們先看一下 cg_cgroup_link的結構
struct cg_cgroup_link {
struct list_head cgrp_link_list;
struct cgroup *cgrp;
struct list_head cg_link_list;
struct css_set *cg;
};
cgrp_link_list連入到cgroup->css_set指向的鏈表,cgrp則指向此cg_cgroup_link相關的cgroup。
Cg_link_list則連入到css_set->cg_links指向的鏈表,cg則指向此cg_cgroup_link相關的css_set。
那為什么要這樣設計呢?
那是因為cgroup和css_set是一個多對多的關系,必須添加一個中間結構來將兩者聯系起來,這跟數據庫模式設計是一個道理。cg_cgroup_link中的cgrp和cg就是此結構體的聯合主鍵,而cgrp_link_list和cg_link_list分別連入到cgroup和css_set相應的鏈表,使得能從cgroup或css_set都可以進行遍歷查詢。
那為什么cgroup和css_set是多對多的關系呢?
一個進程對應css_set,一個css_set就存儲了一組進程(應該有可能被幾個進程共享,所以是一組)跟各個子系統相關的信息,但是這些信息有可能不是從一個cgroup那里獲得的,因為一個進程可以同時屬於幾個cgroup,只要這些cgroup不在同一個層級。舉個例子:我們創建一個層級A,A上面附加了cpu和memory兩個子系統,進程B屬於A的根cgroup;然后我們再創建一個層級C,C上面附加了ns和blkio兩個子系統,進程B同樣屬於C的根cgroup;那么進程B對應的cpu和memory的信息是從A的根cgroup獲得的,ns和blkio信息則是從C的根cgroup獲得的。因此,一個css_set存儲的cgroup_subsys_state可以對應多個cgroup。另一方面,cgroup也存儲了一組cgroup_subsys_state,這一組cgroup_subsys_state則是cgroup從所在的層級附加的子系統獲得的。一個cgroup中可以有多個進程,而這些進程的css_set不一定都相同,因為有些進程可能還加入了其他cgroup。但是同一個cgroup中的進程與該cgroup關聯的cgroup_subsys_state都受到該cgroup的管理(cgroups中進程控制是以cgroup為單位的)的,所以一個cgrouop也可以對應多個css_set。
那為什么要這樣一個結構呢?
從前面的分析,我們可以看出從task到cgroup是很容易定位的,但是從cgroup獲取此cgroup的所有的task就必須通過這個結構了。每個進程都會指向一個css_set,而與這個css_set關聯的所有進程都會鏈入到css_set->tasks鏈表.而cgroup又通過一個中間結構cg_cgroup_link來尋找所有與之關聯的所有css_set,從而可以得到與cgroup關聯的所有進程。
最后讓我們看一下層級和子系統對應的結構體。層級對應的結構體是cgroupfs_root:
struct cgroupfs_root {
struct super_block *sb;
unsigned long subsys_bits;
int hierarchy_id;
unsigned long actual_subsys_bits;
struct list_head subsys_list;
struct cgroup top_cgroup;
int number_of_cgroups;
struct list_head root_list;
unsigned long flags;
char release_agent_path[PATH_MAX];
char name[MAX_CGROUP_ROOT_NAMELEN];
};
sb指向該層級關聯的文件系統超級塊
subsys_bits和actual_subsys_bits分別指向將要附加到層級的子系統和現在實際附加到層級的子系統,在子系統附加到層級時使用
hierarchy_id是該層級唯一的id
top_cgroup指向該層級的根cgroup
number_of_cgroups記錄該層級cgroup的個數
root_list是一個嵌入的list_head,用於將系統所有的層級連成鏈表
子系統對應的結構體是cgroup_subsys:
struct cgroup_subsys {
struct cgroup_subsys_state *(*create)(struct cgroup_subsys *ss,
struct cgroup *cgrp);
int (*pre_destroy)(struct cgroup_subsys *ss, struct cgroup *cgrp);
void (*destroy)(struct cgroup_subsys *ss, struct cgroup *cgrp);
int (*can_attach)(struct cgroup_subsys *ss, struct cgroup *cgrp,
struct task_struct *tsk, bool threadgroup);
void (*cancel_attach)(struct cgroup_subsys *ss, struct cgroup *cgrp,
struct task_struct *tsk, bool threadgroup);
void (*attach)(struct cgroup_subsys *ss, struct cgroup *cgrp,
struct cgroup *old_cgrp, struct task_struct *tsk,
bool threadgroup);
void (*fork)(struct cgroup_subsys *ss, struct task_struct *task);
void (*exit)(struct cgroup_subsys *ss, struct task_struct *task);
int (*populate)(struct cgroup_subsys *ss,
struct cgroup *cgrp);
void (*post_clone)(struct cgroup_subsys *ss, struct cgroup *cgrp);
void (*bind)(struct cgroup_subsys *ss, struct cgroup *root);
int subsys_id;
int active;
int disabled;
int early_init;
bool use_id;
#define MAX_CGROUP_TYPE_NAMELEN 32
const char *name;
struct mutex hierarchy_mutex;
struct lock_class_key subsys_key;
struct cgroupfs_root *root;
struct list_head sibling;
struct idr idr;
spinlock_t id_lock;
struct module *module;
};
Cgroup_subsys定義了一組操作,讓各個子系統根據各自的需要去實現。這個相當於C++中抽象基類,然后各個特定的子系統對應cgroup_subsys則是實現了相應操作的子類。類似的思想還被用在了cgroup_subsys_state中,cgroup_subsys_state並未定義控制信息,而只是定義了各個子系統都需要的共同信息,比如該cgroup_subsys_state從屬的cgroup。然后各個子系統再根據各自的需要去定義自己的進程控制信息結構體,最后在各自的結構體中將cgroup_subsys_state包含進去,這樣通過Linux內核的container_of等宏就可以通過cgroup_subsys_state來獲取相應的結構體。
作者曰:從cgroups的數據結構設計,我們可以看出內核開發者的智慧,其中即包含了數據庫模式設計來解決數據冗余問題,又包含了OO思想來解決通用操作的問題。