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