当前位置 博文首页 > dongzhiyan_hjp:cgroup使用举例和linux内核源码详解

    dongzhiyan_hjp:cgroup使用举例和linux内核源码详解

    作者:[db:作者] 时间:2021-09-14 10:24

    ? ? ?cgroup的原理其实并不复杂,用法也比较简单。但是涉及的内核数据结构真的复杂,错综复杂的数据结构感觉才是cgroup真正的难点。本文结合个人学习cgroup源码的心得,尽可能以举例的形式,总结cgroup整体框架和核心源码实现,尽可能少贴源码。本次是在centos 7.6测试的cgroup,源码注释基于3.10.96。更详细的源码注释见https://github.com/dongzhiyan-stack/kernel-code-comment。

    这里先把cgroup涉及的各个数据结构的关系图发下,后边需要多次用到这幅图。

    1 cgroup的创建

    centos 7.6系统启动后,默认systemd就已经挂载好了cgroup文件系统

    [root@localhost ~]# mount | grep cgroup
    tmpfs on /sys/fs/cgroup type tmpfs (ro,nosuid,nodev,noexec,seclabel,mode=755)
    cgroup on /sys/fs/cgroup/systemd type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,xattr,release_agent=/usr/lib/systemd/systemd-cgroups-agent,name=systemd)
    cgroup on /sys/fs/cgroup/hugetlb type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,hugetlb)
    cgroup on /sys/fs/cgroup/cpu,cpuacct type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,cpuacct,cpu)
    cgroup on /sys/fs/cgroup/blkio type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,blkio)
    cgroup on /sys/fs/cgroup/perf_event type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,perf_event)
    cgroup on /sys/fs/cgroup/pids type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,pids)
    cgroup on /sys/fs/cgroup/cpuset type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,cpuset)
    cgroup on /sys/fs/cgroup/net_cls,net_prio type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,net_prio,net_cls)
    cgroup on /sys/fs/cgroup/memory type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,memory)
    cgroup on /sys/fs/cgroup/freezer type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,freezer)
    cgroup on /sys/fs/cgroup/devices type cgroup (rw,nosuid,nodev,noexec,relatime,seclabel,devices)

    进入cgroup挂载目录看一下cgroup各个子系统,如下:

    [root@localhost ~]# cd /sys/fs/cgroup/
    [root@localhost cgroup]# ls
    blkio? cpu? cpuacct? cpu,cpuacct? cpuset? devices? freezer? hugetlb? memory? net_cls? net_cls,net_prio? net_prio? perf_event? pids? systemd

    ? ? cpu、blkio、memory是比较常见的cgroup子系统,分别用来限制进程CPU使用率、IO传输吞吐量IOPS、内存分配。每个cgroup子系统都有特定的功能,这个比较好理解。这里需要提一下另一个概念:cgroup层级,如下是从https://tech.meituan.com/2015/03/31/cgroups.html 直接贴过来的演示cgroup层级的示意图:

    ? ? 个人认为一个cgroup层级更像是一个包含不同功能的cgroup子系统集合。比如示意图的cgroup层级A集成了cpu、cpuacct两个cgroup子系统,cgroup层级B集成了memory这个cgroup子系统。上边介绍centos 7.6的cgroup子系统时,ls? /sys/fs/cgroup时看到了很多个cgroup子系统,这种情况应该只有一个cgroup层级,当然你也可以再创建一个cgroup层级。网上关于cgroup层级的介绍有点抽象,当然我的理解也不一定到位。

    回到cgroup子系统,如果我们想限制进程1的CPU使用率不能超过50%,可以执行如下命令:

    [root@localhost cgroup]# cd /sys/fs/cgroup/cpu
    [root@localhost cpu]# mkdir test
    [root@localhost cpu]# cd test
    [root@localhost test]# ls
    cgroup.clone_children? cgroup.procs? cpuacct.usage???????? cpu.cfs_period_us? cpu.rt_period_us?? cpu.shares? notify_on_release
    cgroup.event_control?? cpuacct.stat? cpuacct.usage_percpu? cpu.cfs_quota_us?? cpu.rt_runtime_us? cpu.stat??? tasks
    [root@localhost test]# cat cpu.cfs_quota_us
    -1
    [root@localhost test]# cat cpu.cfs_period_us
    100000
    [root@localhost test]# echo 50000 > cpu.cfs_quota_us
    [root@localhost test]# echo 进程1PID > tasks

    之后top进程1的CPU使用率最大50%,即便进程1是陷入while(1);死循环。我们赋予cpu.cfs_quota_us的是50000,cpu.cfs_period_us是100000。cpu.cfs_period_us表示一个调度周期,cpu.cfs_quota_us表示一个调度周期进程可以使用的最大配额,显然是一半,即50%。关于cgroup的使用,这里不再介绍,我们重点介绍以上这些命令设置的cgroup内核源码实现。

    首先” mkdir test”命令,内核流程是sys_mkdir-> SyS_mkdirat-> vfs_mkdir-> cgroup_mkdir-> cgroup_create

    1. static long cgroup_create(struct cgroup *parent, struct dentry *dentry,//dentry是新创建的目录的dentry
    2. ???????????????? umode_t mode)
    3. {
    4. ??? struct cgroup *cgrp;
    5. ??? struct cgroupfs_root *root = parent->root;
    6. ?? ?//分配本次的cgroup结构体,一个目录对应一个struct cgroup
    7. ??? cgrp = kzalloc(sizeof(*cgrp), GFP_KERNEL);
    8. ??? //指向本cgroup对应目录的dentry
    9. ??? cgrp->dentry = dentry;
    10. ??? //cgroupparent
    11. ??? cgrp->parent = parent;
    12. ??? //指向根cgroupfs_root
    13. ??? cgrp->root = parent->root;
    14. ??? //遍历cgroupfs_root->subsys_list链表,找到该链表上的cgroup_subsys,即cgroup子系统,然后执行该cgroup子系统的css_alloc()函数
    15. ??? for_each_subsys(root, ss) {
    16. ??????? //分配cgroup子系统的控制结构(cpu的是task_group),返回task_group结构的第一个成员struct cgroup_subsys_state css的地址赋于css
    17. ??????? css = ss->css_alloc(cgrp);//cpu子系统是cpu_cgroup_css_alloc()
    18. ??????? //初始化struct cgroup_subsys_state css,并赋值cgrp->subsys[ss->subsys_id]=css
    19. ??????? init_cgroup_css(css, ss, cgrp);
    20. ??? }
    21. ??? //创建该目录的inode,建立dentryinode的关系
    22. ??? err = cgroup_create_file(dentry, S_IFDIR | mode, sb);
    23. ??? //生成该cgroup目录下相关子系统的控制文件,如"tasks""notify_on_release"
    24. ??? err = cgroup_populate_dir(cgrp, true, root->subsys_mask);
    25. }

    ?? 这个函数一下引出了很多数据结构,struct cgroup、struct cgroupfs_root、struct cgroup_subsys、struct cgroup_subsys_state。

    1? struct? cgroup:当我们mkdir? test创建一个cgroup目录时,首先会在vfs层创建一个该目录的dentry结构,然后执行cgroup文件系统的cgroup_mkdir()->cgroup_create()函数,该函数首先分配一个struct cgroup结构。每创建一个cgroup目录,都要分配一个struct cgroup结构与之对应。

    2?? struct cgroupfs_root和struct cgroup_subsys:当cgroup文件系统mount挂载时(比如cpu cgroup挂载时执行的mount -t cgroup -ocpu cpu /sys/fs/cgroup/cpu,cpuacct命令),内核里最后执行cgroup_mount(),分配struct cgroupfs_root和super_block结构,二者一一对应。然后执行cgroup_mount()->rebind_subsystems()中,按照该cgroup子系统编号从subsys[i]全局数组取出struct cgroup_subsys结构(cpu cgroup子系统的是struct cgroup_subsys ?cpuset_subsys,blkio的是struct cgroup_subsys blkio_subsys),然后把struct cgroup_subsys移动到struct cgroupfs_root的subsys_list链表。

    3? struct cgroup_subsys_state:以cpu 子系统为例,其他cgroup子系统类似。每次创建cgroup目录分配struct? cgroup后,都会执行cpu_cgroup_css_alloc()分配cgroup控制结构struct task_group,而struct task_group的第一个成员就是struct cgroup_subsys_state。再令struct? cgroup结构的成员struct cgroup_subsys_state *subsys[cpu子系统ID]指向刚才分配的struct cgroup_subsys_state结构。后续container_of(cgroup_subsys_state指针)就指向刚才分配的struct task_group。如此就可以通过struct? cgroup找到对应的struct? task_group,这是cgroup_subsys_state存在的意义,牵线搭桥。

    struct group代表的是cgroup目录,struct? task_group代表的是该cgroup目录对应的cpu cgroup子系统的控制结构,还有struct? task_group的第一个成员struct cgroup_subsys_state?? css,3者 一一对应。将来正是用struct? task_group限制进程的CPU使用率。

    为了便于理解,把这些数据结构的关系图单独截个大图,关系标的还算明确。

    ? ???cpu、memory、blkio等cgroup子系统用struct cgroup_subsys表示,struct cgroupfs_root是该cgroup子系统mount挂载时分配的,与super_block一一对应。所以cgroup子系统cgroup_subsys和它的cgroupfs_root一一对应,可以看些示意图。

    ? ? ?结合这个示意图,再啰嗦一下彼此的关系。struct? cgroup的成员struct? cgroup_subsys_state *subsys[]保存的是struct? cgroup_subsys_state指针。比如,当该cgroup是属于cpu cgroup子系统,subsys[cpuset_subsys_id]?? ( cpuset_subsys_idcpu cgroup子系统ID,这个数组只有一个成员指向的cgroup_subsys_state指针有效)指向的cgroup_subsys_state是struct? task_group结构的成员struct? cgroup_subsys_state css。这样知道了cgroup_subsys_state的地址,container_of(cgroup_subsys_state)就是struct? task_group的地址。

    如下是常见的cpu、memory、blkio这3个cgroup子系统的struct cgroup_subsys结构。

    1. struct cgroup_subsys cpu_cgroup_subsys = {
    2. ??? .name?????? = "cpu",
    3. ??? .css_alloc? = cpu_cgroup_css_alloc,//创建cpu cgroup具体的控制单元task_group
    4. ??? .css_free?? = cpu_cgroup_css_free,
    5. ??? .css_online = cpu_cgroup_css_online,
    6. ??? .css_offline??? = cpu_cgroup_css_offline,
    7. ??? .can_attach = cpu_cgroup_can_attach,
    8. ??? .attach???? = cpu_cgroup_attach,
    9. ??? .exit?????? = cpu_cgroup_exit,
    10. ??? .subsys_id? = cpu_cgroup_subsys_id,//cpu cgroup子系统的idcpu_cgroup_subsys_id
    11. ??? .base_cftypes?? = cpu_files, //cpu cgroup子系统独有的控制文件
    12. ??? .early_init = 1,
    13. }
    14. struct cgroup_subsys mem_cgroup_subsys = {
    15. ??? .name = "memory",
    16. ??? .subsys_id = mem_cgroup_subsys_id,
    17. ??? .css_alloc = mem_cgroup_css_alloc,
    18. ??? .css_online = mem_cgroup_css_online,
    19. ??? .css_offline = mem_cgroup_css_offline,
    20. ??? .css_free = mem_cgroup_css_free,
    21. ??? .can_attach = mem_cgroup_can_attach,
    22. ??? .cancel_attach = mem_cgroup_cancel_attach,
    23. ??? .attach = mem_cgroup_move_task,
    24. ??? .bind = mem_cgroup_bind,
    25. ??? .base_cftypes = mem_cgroup_files,//memory cgroup子系统独有的控制文件
    26. ??? .early_init = 0,