要了解cgroup实现原理,必须先了解下vfs(虚拟文件系统).因为cgroup通过vfs向用户层提供接口,用户层通过挂载,创建目录,读写文件的方式与cgroup交互.
因为是介绍cgroup的文章,因此只阐述cgroup文件系统是如何集成进vfs的,过多的vfs实现可参考其他资料.

  1. 1.[root@VM_109_95_centos /cgroup]#mount -t cgroup -ocpu cpu /cgroup/cpu/
  2. 2.[root@VM_109_95_centos /cgroup]#cd cpu/ && mkdir cpu_c1
  3. 3.[root@VM_109_95_centos /cgroup/cpu]#cd cpu_c1/ && echo 2048 >> cpu.shares
  4. 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/

  1. static struct file_system_type cgroup_fs_type = {
  2. .name = "cgroup",
  3. .mount = cgroup_mount,
  4. .kill_sb = cgroup_kill_sb,
  5. // 其他属性未初始化
  6. };

cgroup模块以cgroup_fs_type实例向内核注册cgroup文件系统,用户层通过mount()系统调用层层调用,最终来到cgroup_mount()函数:

  1. static struct dentry *cgroup_mount(struct file_system_type *fs_type,int flags, const char *unused_dev_name,void *data) {
  2.  
  3. ret = parse_cgroupfs_options(data, &opts); // 解析mount时的参数
  4.  
  5. new_root = cgroup_root_from_opts(&opts); // 根据选项创建一个层级(struct cgroupfs_root)
  6.  
  7. sb = sget(fs_type, cgroup_test_super, cgroup_set_super, , &opts); // 创建一个新的超级快(struct super_block)
  8.  
  9. ret = rebind_subsystems(root, root->subsys_mask); // 给层级绑定subsys
  10.  
  11. cgroup_populate_dir(root_cgrp, true, root->subsys_mask); // 创建根cgroup下的各种文件
  12. }

首先解析mount时上层传下的参数,这里就解析到该层级需要绑定cpu subsys统.然后根据参数创建一个层级.跟进到cgroup_root_from_opts()函数:

  1. static struct cgroupfs_root *cgroup_root_from_opts(struct cgroup_sb_opts *opts)
  2. {
  3. struct cgroupfs_root *root;
  4.  
  5. if (!opts->subsys_mask && !opts->none) // 未指定层级,并且用户曾未明确指定需要空层级return NULL
  6. return NULL;
  7.  
  8. root = kzalloc(sizeof(*root), GFP_KERNEL); // 申请内存
  9. if (!root)
  10. return ERR_PTR(-ENOMEM);
  11.  
  12. if (!init_root_id(root)) { // 初始化层级unique id
  13. kfree(root);
  14. return ERR_PTR(-ENOMEM);
  15. }
  16. init_cgroup_root(root); // 创建根cgroup
  17.  
  18. root->subsys_mask = opts->subsys_mask;
  19. root->flags = opts->flags;
  20. ida_init(&root->cgroup_ida); // 初始化idr
  21. if (opts->release_agent) // 拷贝清理脚本的路径,见后面struct cgroupfs_root说明.
  22. strcpy(root->release_agent_path, opts->release_agent);
  23. if (opts->name) // 设置name
  24. strcpy(root->name, opts->name);
  25. if (opts->cpuset_clone_children) // 该选项打开,表示当创建子cpuset cgroup时,继承父cpuset cgroup的配置
  26. set_bit(CGRP_CPUSET_CLONE_CHILDREN, &root->top_cgroup.flags);
  27. return root;
  28. }

层级结构体:

  1. struct cgroupfs_root {
  2. struct super_block *sb; // 超级块指针,最终指向该cgroup文件系统的超级块
  3. unsigned long subsys_mask; // 该层级准备绑定的subsys统掩码
  4. int hierarchy_id; // 全局唯一的层级ID
  5. unsigned long actual_subsys_mask; // 该层级已经绑定的subsys统掩码(估计和上层remount有关吧?暂不深究)
  6. struct list_head subsys_list; // subsys统链表,将该层级绑定的所有subsys统连起来.
  7. struct cgroup top_cgroup; // 该层级的根cgroup
  8. int number_of_cgroups; //该层级下cgroup的数目(层级可以理解为cgroup组成的树)
  9. struct list_head root_list; // 层级链表,将系统上所有的层级连起来
  10. struct list_head allcg_list; // cgroup链表,将该层级上所有的cgroup连起来???
  11. unsigned long flags; // 一些标志().
  12. struct ida cgroup_ida; // idr机制,方便查找(暂不深究)
  13. char release_agent_path[PATH_MAX]; // 清理脚本的路径,对应应用层的根cgroup目录下的release_agent文件
  14. char name[MAX_CGROUP_ROOT_NAMELEN]; //层级名称
  15. };

接下来创建超级块,在vfs中超级块用来表示一个已安装文件系统的相关信息.跟进到cgroup_root_from_opts()函数:

  1. 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)
  2. {
  3. struct super_block *s = NULL;
  4. struct super_block *old;
  5. int err;
  6.  
  7. retry:
  8. spin_lock(&sb_lock);
  9. if (test) { // 尝试找到一个已存在的sb
  10. hlist_for_each_entry(old, &type->fs_supers, s_instances) {
  11. if (!test(old, data))
  12. continue;
  13. if (!grab_super(old))
  14. goto retry;
  15. if (s) {
  16. up_write(&s->s_umount);
  17. destroy_super(s);
  18. s = NULL;
  19. }
  20. return old;
  21. }
  22. }
  23. if (!s) {
  24. spin_unlock(&sb_lock);
  25. s = alloc_super(type, flags); //分配一个新的sb
  26. if (!s)
  27. return ERR_PTR(-ENOMEM);
  28. goto retry;
  29. }
  30.  
  31. err = set(s, data); // 初始化sb属性
  32. if (err) {
  33. spin_unlock(&sb_lock);
  34. up_write(&s->s_umount);
  35. destroy_super(s);
  36. return ERR_PTR(err);
  37. }
  38. s->s_type = type; //该sb所属文件系统类型为cgroup_fs_type
  39. strlcpy(s->s_id, type->name, sizeof(s->s_id)); // s->s_id = "cgroup"
  40. list_add_tail(&s->s_list, &super_blocks); // 加进super_block全局链表
  41. hlist_add_head(&s->s_instances, &type->fs_supers); //同一文件系统可挂载多个实例,全部挂到cgroup_fs_type->fs_supers指向的链表中
  42. spin_unlock(&sb_lock);
  43. get_filesystem(type);
  44. register_shrinker(&s->s_shrink);
  45. return s;
  46. }

超级块结构体类型(属性太多,只列cgroup差异化的,更多内容请参考vfs相关资料):

  1. struct super_block {
  2. struct list_head s_list; // 全局sb链表
  3. ...
  4. struct file_system_type *s_type; // 所属文件系统类型
  5. const struct super_operations *s_op; // 超级块相关操作
  6. struct hlist_node s_instances; // 同一文件系统的sb链表
  7. char s_id[]; // 文本格式的name
  8. void *s_fs_info; //文件系统私有数据,cgroup用其指向层级
  9. };

sget函数里先在已存的链表里查找是否有合适的,没有的话再分配新的sb.err = set(s, data) set是个函数指针,根据上面的代码可以知道最终调用的是cgroup_set_super函数,主要是给新分配的sb赋值.这段代码比较重要,展开看下:

  1. static int cgroup_set_super(struct super_block *sb, void *data)
  2. {
  3. int ret;
  4. struct cgroup_sb_opts *opts = data;
  5.  
  6. /* If we don't have a new root, we can't set up a new sb */
  7. if (!opts->new_root)
  8. return -EINVAL;
  9.  
  10. BUG_ON(!opts->subsys_mask && !opts->none);
  11.  
  12. ret = set_anon_super(sb, NULL);
  13. if (ret)
  14. return ret;
  15.  
  16. sb->s_fs_info = opts->new_root; // super_block的s_fs_info字段指向对应的cgroupfs_root
  17. opts->new_root->sb = sb; //cgroupfs_root的sb字段指向super_block
  18.  
  19. sb->s_blocksize = PAGE_CACHE_SIZE;
  20. sb->s_blocksize_bits = PAGE_CACHE_SHIFT;
  21. sb->s_magic = CGROUP_SUPER_MAGIC;
  22. sb->s_op = &cgroup_ops; //super_block的s_op字段指向cgroup_ops,这句比较关键.
  23.  
  24. return ;
  25. }

这样超级块(super_block)和层级(cgroupfs_root)这两个概念就一一对应起来了,并且可以相互索引到.super_block.s_op指向一组函数,这组函数就是该文件系统向上层提供的所有操作.看下cgroup_ops:

  1. static const struct super_operations cgroup_ops = {
  2. .statfs = simple_statfs,
  3. .drop_inode = generic_delete_inode,
  4. .show_options = cgroup_show_options,
  5. .remount_fs = cgroup_remount,
  6. };

竟然只提供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(子系统)的结构.

  1. struct cgroup_subsys_state {
  2. struct cgroup *cgroup;
  3. atomic_t refcnt;
  4. unsigned long flags;
  5. struct css_id __rcu *id;
  6. struct work_struct dput_work;
  7. };

先看下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

  1. struct cgroup {
  2.  
  3. unsigned long flags;
  4. struct list_head sibling; // 兄弟链表
  5. struct list_head children; // 孩子链表
  6. struct list_head files; // 该cgroup下的文件链表(tasks cpu.shares ....)
  7. struct cgroup *parent; // 父cgroup
  8. struct dentry *dentry;
  9. struct cgroup_name __rcu *name;
  10. struct cgroup_subsys_state *subsys[CGROUP_SUBSYS_COUNT]; //指针数组,每个非空元素指向挂载的subsys
  11. struct cgroupfs_root *root; //根cgroup
  12. struct list_head css_sets;
  13. struct list_head pidlists; // 加到该cgroup下的taskid链表
  14. };

subsys是一个cgroup_subsys_state* 类型的数组,每个元素指向一个具体subsys的cgroup_subsys_state,通过container_of(cgroup_subsys_state)就拿到了具体subsys的控制信息.

  1. struct cgroup_subsys { // 删减版
  2. struct cgroup_subsys_state *(*css_alloc)(struct cgroup *cgrp);
  3. int (*css_online)(struct cgroup *cgrp); // 一堆函数指针,由各个subsys实现.函数名意思比较鲜明
  4. void (*css_offline)(struct cgroup *cgrp);
  5. void (*css_free)(struct cgroup *cgrp);
  6. int (*can_attach)(struct cgroup *cgrp, struct cgroup_taskset *tset);
  7. void (*cancel_attach)(struct cgroup *cgrp, struct cgroup_taskset *tset);
  8. void (*attach)(struct cgroup *cgrp, struct cgroup_taskset *tset);
  9. void (*fork)(struct task_struct *task);
  10. void (*exit)(struct cgroup *cgrp, struct cgroup *old_cgrp,
  11. struct task_struct *task);
  12. void (*bind)(struct cgroup *root);
  13. int subsys_id; // subsys id
  14. int disabled;
  15. ...
  16. struct list_head cftsets; // cftype结构体(参数文件管理结构)链表
  17. struct cftype *base_cftypes; // 指向一个cftype数组
  18. struct cftype_set base_cftset; //
  19. struct module *module;
  20. };

cgroup_subsys也是各个subsys的一个抽象,真正的实现由各个subsys实现.可以和cgroup_subsys_state对比下,cgroup_subsys更偏向与描述各个subsys的操作钩子,cgroup_subsys_state则与各个子系统的任务结构关联.
cgroup_subsys是与层级关联的,cgroup_subsys_state是与cgroup关联的。

  1. struct cftype { // 删减版
  2. char name[MAX_CFTYPE_NAME];
  3. int private;
  4. umode_t mode;
  5. size_t max_write_len;
  6. unsigned int flags;
  7. s64 (*read_s64)(struct cgroup *cgrp, struct cftype *cft);
  8. int (*write_s64)(struct cgroup *cgrp, struct cftype *cft, s64 val);
  9. ...
  10. };

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的所有控制文件

  1. static int cgroup_populate_dir(struct cgroup *cgrp, bool base_files, unsigned long subsys_mask)
  2. {
  3. int err;
  4. struct cgroup_subsys *ss;
  5.  
  6. if (base_files) { //基本控制文件
  7. err = cgroup_addrm_files(cgrp, NULL, files, true);
  8. if (err < )
  9. return err;
  10. }
  11.  
  12. /* process cftsets of each subsystem */
  13. for_each_subsys(cgrp->root, ss) { //每个subsys
  14. struct cftype_set *set;
  15. if (!test_bit(ss->subsys_id, &subsys_mask))
  16. continue;
  17.  
  18. list_for_each_entry(set, &ss->cftsets, node) //每个subsys的每个控制文件
  19. cgroup_addrm_files(cgrp, ss, set->cfts, true);
  20. }
  21. ...
  22. return ;
  23. }

显而易见,先初始化了基本的文件,进而初始化每个subsys的每个控制文件.什么是基本文件?

  1. static struct cftype files[] = {
  2. {
  3. .name = "tasks",
  4. .open = cgroup_tasks_open,
  5. .write_u64 = cgroup_tasks_write,
  6. .release = cgroup_pidlist_release,
  7. .mode = S_IRUGO | S_IWUSR,
  8. },
  9. {
  10. .name = CGROUP_FILE_GENERIC_PREFIX "procs",
  11. .open = cgroup_procs_open,
  12. .write_u64 = cgroup_procs_write,
  13. .release = cgroup_pidlist_release,
  14. .mode = S_IRUGO | S_IWUSR,
  15. },
  16. {
  17. .name = "notify_on_release",
  18. .read_u64 = cgroup_read_notify_on_release,
  19. .write_u64 = cgroup_write_notify_on_release,
  20. },
  21. {
  22. .name = CGROUP_FILE_GENERIC_PREFIX "event_control",
  23. .write_string = cgroup_write_event_control,
  24. .mode = S_IWUGO,
  25. },
  26. {
  27. .name = "cgroup.clone_children",
  28. .flags = CFTYPE_INSANE,
  29. .read_u64 = cgroup_clone_children_read,
  30. .write_u64 = cgroup_clone_children_write,
  31. },
  32. {
  33. .name = "cgroup.sane_behavior",
  34. .flags = CFTYPE_ONLY_ON_ROOT,
  35. .read_seq_string = cgroup_sane_behavior_show,
  36. },
  37. {
  38. .name = "release_agent",
  39. .flags = CFTYPE_ONLY_ON_ROOT,
  40. .read_seq_string = cgroup_release_agent_show,
  41. .write_string = cgroup_release_agent_write,
  42. .max_write_len = PATH_MAX,
  43. },
  44. { } /* terminate */
  45. };

这些文件在用户层应该见过.进到cgroup_create_file()函数看下:

  1. static int cgroup_create_file(struct dentry *dentry, umode_t mode, struct super_block *sb)
  2. {
  3. struct inode *inode;
  4.  
  5. if (!dentry)
  6. return -ENOENT;
  7. if (dentry->d_inode)
  8. return -EEXIST;
  9.  
  10. inode = cgroup_new_inode(mode, sb); // 申请inode
  11. if (!inode)
  12. return -ENOMEM;
  13.  
  14. if (S_ISDIR(mode)) { //目录
  15. inode->i_op = &cgroup_dir_inode_operations;
  16. inode->i_fop = &simple_dir_operations;
  17. ...
  18. } else if (S_ISREG(mode)) { //文件
  19. inode->i_size = ;
  20. inode->i_fop = &cgroup_file_operations;
  21. inode->i_op = &cgroup_file_inode_operations;
  22. }
  23. d_instantiate(dentry, inode);
  24. dget(dentry); /* Extra count - pin the dentry in core */
  25. return ;
  26. }
  1. const struct file_operations simple_dir_operations = {
  2. .open = dcache_dir_open,
  3. .release = dcache_dir_close,
  4. .llseek = dcache_dir_lseek,
  5. .read = generic_read_dir,
  6. .readdir = dcache_readdir,
  7. .fsync = noop_fsync,
  8. };
  9.  
  10. static const struct inode_operations cgroup_dir_inode_operations = {
  11. .lookup = cgroup_lookup,
  12. .mkdir = cgroup_mkdir,
  13. .rmdir = cgroup_rmdir,
  14. .rename = cgroup_rename,
  15. .setxattr = cgroup_setxattr,
  16. .getxattr = cgroup_getxattr,
  17. .listxattr = cgroup_listxattr,
  18. .removexattr = cgroup_removexattr,
  19. };
  20.  
  21. static const struct file_operations cgroup_file_operations = {
  22. .read = cgroup_file_read,
  23. .write = cgroup_file_write,
  24. .llseek = generic_file_llseek,
  25. .open = cgroup_file_open,
  26. .release = cgroup_file_release,
  27. };
  28.  
  29. static const struct inode_operations cgroup_file_inode_operations = {
  30. .setxattr = cgroup_setxattr,
  31. .getxattr = cgroup_getxattr,
  32. .listxattr = cgroup_listxattr,
  33. .removexattr = cgroup_removexattr,
  34. };

这些回调函数,上面以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.

  1. static int cgroup_get_rootdir(struct super_block *sb)
  2. {
  3. struct inode *inode =
  4. cgroup_new_inode(S_IFDIR | S_IRUGO | S_IXUGO | S_IWUSR, sb);
  5. inode->i_fop = &simple_dir_operations;
  6. inode->i_op = &cgroup_dir_inode_operations;
  7. return ;
  8. }

可以看到croot目录项的inode.i_op也被设置为&cgroup_dir_inode_operations,那么mkdir就会调用cgroup_mkdir函数
cgroup_mkdir只是简单的包装,实际工作的函数是cgroup_create()函数.
看下cgroup_create函数(删减版)

  1. static long cgroup_create(struct cgroup *parent, struct dentry *dentry,umode_t mode)
  2. {
  3. struct cgroup *cgrp;
  4. struct cgroup_name *name;
  5. struct cgroupfs_root *root = parent->root;
  6. int err = ;
  7. struct cgroup_subsys *ss;
  8. struct super_block *sb = root->sb;
  9.  
  10. cgrp = kzalloc(sizeof(*cgrp), GFP_KERNEL); //分配cgroup
  11.  
  12. name = cgroup_alloc_name(dentry);
  13. rcu_assign_pointer(cgrp->name, name); // 设置名称
  14.  
  15. init_cgroup_housekeeping(cgrp); //cgroup一些成员的初始化
  16.  
  17. dentry->d_fsdata = cgrp; //目录项(dentry)与cgroup关联起来
  18. cgrp->dentry = dentry;
  19. cgrp->parent = parent; // 设置cgroup层级关系
  20. cgrp->root = parent->root;
  21.  
  22. if (notify_on_release(parent)) // 继承父cgroup的CGRP_NOTIFY_ON_RELEASE属性
  23. set_bit(CGRP_NOTIFY_ON_RELEASE, &cgrp->flags);
  24.  
  25. if (test_bit(CGRP_CPUSET_CLONE_CHILDREN, &parent->flags)) // 继承父cgroup的CGRP_CPUSET_CLONE_CHILDREN属性
  26. set_bit(CGRP_CPUSET_CLONE_CHILDREN, &cgrp->flags);
  27.  
  28. for_each_subsys(root, ss) {
  29. struct cgroup_subsys_state *css;
  30.  
  31. css = ss->css_alloc(cgrp); // mount时各个subsys的钩子函数已经注册,这里直接使用来创建各个subsys的结构(task_group)
  32.  
  33. init_cgroup_css(css, ss, cgrp); //初始化cgroup_subsys_state类型的值
  34. if (ss->use_id) {
  35. err = alloc_css_id(ss, parent, cgrp);
  36. }
  37. }
  38.  
  39. err = cgroup_create_file(dentry, S_IFDIR | mode, sb); //创建该目录项对应的inode,并初始化后与dentry关联上.
  40.  
  41. list_add_tail(&cgrp->allcg_node, &root->allcg_list); // 该cgroup挂到层级的cgroup链表上
  42. list_add_tail_rcu(&cgrp->sibling, &cgrp->parent->children); // 该cgroup挂到福cgroup的子cgroup链表上.
  43. ....
  44. for_each_subsys(root, ss) { // 将各个subsys的控制结构(task_group)建立父子关系.
  45. err = online_css(ss, cgrp);
  46. }
  47.  
  48. err = cgroup_populate_dir(cgrp, true, root->subsys_mask); // 生成该cgroup目录下相关子系统的控制文件
  49. ...
  50. }

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.

  1. struct task_group { //删减版
  2. struct cgroup_subsys_state css;
  3. struct sched_entity **se;
  4. struct cfs_rq **cfs_rq;
  5. unsigned long shares;
  6. atomic_t load_weight;
  7. atomic64_t load_avg;
  8. atomic_t runnable_avg;
  9. struct rcu_head rcu;
  10. struct list_head list;
  11. struct task_group *parent;
  12. struct list_head siblings;
  13. struct list_head children;
  14. };
  15.  
  16. struct sched_entity {
  17. struct load_weight load; /* for load-balancing */
  18. struct rb_node run_node;
  19. struct list_head group_node;
  20. unsigned int on_rq;
  21. u64 exec_start;
  22. u64 sum_exec_runtime;
  23. u64 vruntime;
  24. u64 prev_sum_exec_runtime;
  25. u64 nr_migrations;
  26. };

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:

  1. static ssize_t cgroup_file_read(struct file *file, char __user *buf, size_t nbytes, loff_t *ppos)
  2. {
  3. struct cftype *cft = __d_cft(file->f_dentry);
  4. struct cgroup *cgrp = __d_cgrp(file->f_dentry->d_parent);
  5.  
  6. if (cft->read)
  7. return cft->read(cgrp, cft, file, buf, nbytes, ppos);
  8. if (cft->read_u64)
  9. return cgroup_read_u64(cgrp, cft, file, buf, nbytes, ppos);
  10. if (cft->read_s64)
  11. return cgroup_read_s64(cgrp, cft, file, buf, nbytes, ppos);
  12. return -EINVAL;
  13. }

mount时已经知道每个subsys的每个控制文件的操作函数都是不一样的(通过cftype实现的).我们直接看下cpu.shares文件的操作函数.

  1. static struct cftype cpu_files[] = {
  2. {
  3. .name = "shares",
  4. .read_u64 = cpu_shares_read_u64,
  5. .write_u64 = cpu_shares_write_u64,
  6. },
  7. ...
  8. }

写cpu.shares最终调用cpu_shares_write_u64, 中间几层细节略过.最终执行update_load_set:

  1. static inline void update_load_set(struct load_weight *lw, unsigned long w)
  2. {
  3. lw->weight = w;
  4. lw->inv_weight = ;
  5. }

其中load_weight=task_group.se.load,改变了load_weight.weight,起到了限制该task_group对cpu的使用.
--------------------------------------------------------
4.echo 7860 >> tasks
过程是类似的,不过tasks文件最终调用的是cgroup_tasks_write这个函数.

  1. static struct cftype files[] = {
  2. {
  3. .name = "tasks",
  4. .open = cgroup_tasks_open,
  5. .write_u64 = cgroup_tasks_write,
  6. .release = cgroup_pidlist_release,
  7. .mode = S_IRUGO | S_IWUSR,
  8. },

cgroup_tasks_write最终调用attach_task_by_pid

  1. static int attach_task_by_pid(struct cgroup *cgrp, u64 pid, bool threadgroup)
  2. {
  3. struct task_struct *tsk;
  4. const struct cred *cred = current_cred(), *tcred;
  5. int ret;
  6. if (pid) { //根据pid找到该进程的task_struct
  7. tsk = find_task_by_vpid(pid);
  8. if (!tsk) {
  9. rcu_read_unlock();
  10. ret= -ESRCH;
  11. goto out_unlock_cgroup;
  12. }
  13. }
  14. .....
  15. .....
  16. ret = cgroup_attach_task(cgrp, tsk, threadgroup); //将进程关联到cgroup
  17. return ret;
  18. }

最终通过cgroup_attach_task函数,将进程挂载到响应cgroup.先看几个新的结构体.

  1. struct css_set {
  2. atomic_t refcount; //引用计数
  3. struct hlist_node hlist; //css_set链表,将系统中所有css_set连接起来.
  4. struct list_head tasks; //task链表,链接所有属于这个set的进程
  5. struct list_head cg_links; // 指向一个cg_cgroup_link链表
  6. struct cgroup_subsys_state *subsys[CGROUP_SUBSYS_COUNT]; // 关联到subsys
  7. struct rcu_head rcu_head;
  8. };
  9.  
  10. struct cg_cgroup_link {
  11. struct list_head cgrp_link_list; //内嵌到cgroup->css_set链表
  12. struct cgroup *cgrp; // 指向对应的cgroup
  13. struct list_head cg_link_list; //内嵌到css_set->cg_links链表
  14. struct css_set *cg; // 指向对应的css_set
  15. };
  16.  
  17. struct task_struct {
  18. struct css_set __rcu *cgroups; // 指向所属的css_set
  19. struct list_head cg_list; // 将同属于一个css_set的task_struct连接起来.
  20. }

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看看:

  1. struct task_and_cgroup {
  2. struct task_struct *task;
  3. struct cgroup *cgrp;
  4. struct css_set *cg;
  5. };
  6.  
  7. struct cgroup_taskset {
  8. struct task_and_cgroup single;
  9. struct flex_array *tc_array;
  10. int tc_array_len;
  11. int idx;
  12. struct cgroup *cur_cgrp;
  13. };
  14.  
  15. static int cgroup_attach_task(struct cgroup *cgrp, struct task_struct *tsk,
  16. bool threadgroup)
  17. {
  18. int retval, i, group_size;
  19. struct cgroup_subsys *ss, *failed_ss = NULL;
  20. struct cgroupfs_root *root = cgrp->root;
  21. /* threadgroup list cursor and array */
  22. struct task_struct *leader = tsk;
  23. struct task_and_cgroup *tc;
  24. struct flex_array *group;
  25. struct cgroup_taskset tset = { };
  26.  
  27. group = flex_array_alloc(sizeof(*tc), group_size, GFP_KERNEL);
  28. retval = flex_array_prealloc(group, , group_size, GFP_KERNEL); //预分配内存,考虑到了多线程的进程
  29.  
  30. i = ;
  31. rcu_read_lock();
  32. do { // 兼顾多线程进程,将所有线程的相关信息放在tset里
  33. struct task_and_cgroup ent;
  34. ent.task = tsk;
  35. ent.cgrp = task_cgroup_from_root(tsk, root);
  36. retval = flex_array_put(group, i, &ent, GFP_ATOMIC);
  37. BUG_ON(retval != );
  38. i++;
  39. next:
  40. if (!threadgroup)
  41. break;
  42. } while_each_thread(leader, tsk);
  43. rcu_read_unlock();
  44. group_size = i;
  45. tset.tc_array = group;
  46. tset.tc_array_len = group_size;
  47.  
  48. for_each_subsys(root, ss) { //调用每个subsys的方法,判断是否可绑定.
  49. if (ss->can_attach) {
  50. retval = ss->can_attach(cgrp, &tset);
  51. if (retval) {
  52. failed_ss = ss;
  53. goto out_cancel_attach;
  54. }
  55. }
  56. }
  57.  
  58. for (i = ; i < group_size; i++) { // 为每个task准备(已有或分配)css_set,css_set是多个进程共享.
  59. tc = flex_array_get(group, i);
  60. tc->cg = find_css_set(tc->task->cgroups, cgrp);
  61. if (!tc->cg) {
  62. retval = -ENOMEM;
  63. goto out_put_css_set_refs;
  64. }
  65. }
  66.  
  67. for (i = ; i < group_size; i++) { // 将所有task从old css_set迁移到new css_set.
  68. tc = flex_array_get(group, i);
  69. cgroup_task_migrate(tc->cgrp, tc->task, tc->cg);
  70. }
  71.  
  72. for_each_subsys(root, ss) { // 调用subsys的attach方法,执行绑定.
  73. if (ss->attach)
  74. ss->attach(cgrp, &tset);
  75. }
  76. retval =
  77. return retval;
  78. }

这里的can_attach和attach由每个subsys实现,这里先不说了.
因为创建层级时会把系统上所有的进程加到根cgroup的tasks中,所以用户层将task加进某个cgroup等同于将task从一个cgroup移到另一个cgriup.
cgroup_task_migrate就是将task与新的cgroup对应的css_set重新映射起来.

如若不对请指出。

参考资料:

  linux-3.10源码

  <linux cgroup详解><zhefwang@gmail.com>连接找不到了

cgroup原理简析:vfs文件系统的更多相关文章

  1. Java Android 注解(Annotation) 及几个常用开源项目注解原理简析

    不少开源库(ButterKnife.Retrofit.ActiveAndroid等等)都用到了注解的方式来简化代码提高开发效率. 本文简单介绍下 Annotation 示例.概念及作用.分类.自定义. ...

  2. PHP的错误报错级别设置原理简析

    原理简析 摘录php.ini文件的默认配置(php5.4): ; Common Values: ; E_ALL (Show all errors, warnings and notices inclu ...

  3. Java Annotation 及几个常用开源项目注解原理简析

    PDF 版: Java Annotation.pdf, PPT 版:Java Annotation.pptx, Keynote 版:Java Annotation.key 一.Annotation 示 ...

  4. [转载] Thrift原理简析(JAVA)

    转载自http://shift-alt-ctrl.iteye.com/blog/1987416 Apache Thrift是一个跨语言的服务框架,本质上为RPC,同时具有序列化.发序列化机制:当我们开 ...

  5. Spring系列.@EnableRedisHttpSession原理简析

    在集群系统中,经常会需要将Session进行共享.不然会出现这样一个问题:用户在系统A上登陆以后,假如后续的一些操作被负载均衡到系统B上面,系统B发现本机上没有这个用户的Session,会强制让用户重 ...

  6. SIFT特征原理简析(HELU版)

    SIFT(Scale-Invariant Feature Transform)是一种具有尺度不变性和光照不变性的特征描述子,也同时是一套特征提取的理论,首次由D. G. Lowe于2004年以< ...

  7. 基于IdentityServer4的OIDC实现单点登录(SSO)原理简析

    写着前面 IdentityServer4的学习断断续续,兜兜转转,走了不少弯路,也花了不少时间.可能是因为没有阅读源码,也没有特别系统的学习资料,相关文章很多园子里的大佬都有涉及,有系列文章,比如: ...

  8. ARP攻击原理简析及防御措施

    0x1  简介 网络欺骗攻击作为一种非常专业化的攻击手段,给网络安全管理者,带来严峻的考验.网络安全的战场已经从互联网蔓延到用户内部的网络, 特别是局域网.目前利用ARP欺骗的木马病毒在局域网中广泛传 ...

  9. MapReduce本地运行模式wordcount实例(附:MapReduce原理简析)

    1.      环境配置 a)        配置系统环境变量HADOOP_HOME b)        把hadoop.dll文件放到c:/windows/System32目录下 c)        ...

随机推荐

  1. MVC学习笔记2 - Razor语法

    Razor 同时支持 C# (C sharp) 和 VB (Visual Basic). C# 的主要 Razor 语法规则 Razor 代码封装于 @{ ... } 中 行内表达式(变量和函数)以 ...

  2. Diary of Codeforces Round #402 (Div. 2)

    这一场的表现可以用"全程智障"4个字,生动传神地描述出来. About 写题: A. 写了一堆if比较大小, 这很勤勉.(绝对值君对自己の存在感为0表示很难过.) B. 题,直接读 ...

  3. JQ实战一之烟花

    本次的效果大概为当用户点击网页时,网页下方弹出一个类似烟花的长条条,然后在桌面上散开以达成类似烟花的特效.话不多说先上图. 首先布局,布局很简单 <style> body { backgr ...

  4. 所谓“脚本(Script)”——个人见解浅谈

    编程初学者,在学习的时候总会听人说到或者看到“脚本”这个词汇,我初学的时候也不清楚脚本是什么,所以每每看到有人说你会写“脚本”的时候,总以为是一些高深深奥的编程技术.然而事实正好相反,脚本语言是一种比 ...

  5. [SinGuLaRiTy] ZKW线段树

    [SinGuLaRiTy-1007] Copyrights (c) SinGuLaRiTy 2017. All Rights Reserved. 关于ZKW线段树 Zkw线段树是清华大学张昆玮发明非递 ...

  6. WPF之路四:窗体自适应

    下面我来举个例子说明如何用Grid或DockPanel来实现自适应窗体. 让我们新建一个WPF工程,完成后我们打开对应的XAML文件,可以看到VS已经自动添加了<Grid></Gri ...

  7. c中的可重入和不可重入函数

    可重入和不可重入 的基本概念 ---简介--- 可重入函数主要用于多任务环境中,一个可重入的函数简单来说就是可以被中断的函数,也就是说,可以在这个函数执行的任何时刻中断它,转入OS调度下去执行另外一段 ...

  8. 从零开始用 Flask 搭建一个网站(二)

    从零开始用 Flask 搭建一个网站(一) 介绍了如何搭建 Python 环境,以及 Flask 应用基本项目结构.我们要搭建的网站是管理第三方集成的控制台,类似于 Slack. 本篇主要讲解数据如何 ...

  9. Doctype 文档类型,标准模式,混杂模式

    HTML4.01和XHTML1.0 基于 SGML,支持DTD声明,HTML5不是,但是需要 doctype 来规范浏览器的行为. 标准模式是指,DTD声明定义了标准文档的类型后,浏览器按W3C标准解 ...

  10. jQuery扩展函数设置所有对象只读

    jQuery(function ($) {             $.fn.disable = function () {                 return this.each(func ...