Linux内核的cgroup实现(一):Overview

| 分类 linux  | 标签 linux  docker 

我们知道,docker背后实现的技术主要是linux namespace和linux cgroup(control group),本文就从内核的角度来分析一下linux cgroup究竟是如何实现的。本文研读的代码是linux kernel 2.6.32。

1.数据结构:

1.1 task_struct

每个进程的数据结构,通过css_set可以描述当前的进程和哪些cgroup关联,以及关联的子系统有哪些。对每个进程而言,每种类型的子系统最多可以关联一个cgroup。

1.2 css_set

css_set和一个或几个task相关联,描述一个任务属于哪些cgroup。通过该group内的task fork出来的child task也必然在该css_set内。如果某个task被放到/sys/cgroup/cpuset /sys/cgroup/mememry两个cgroup中,就使用一个css_set将这两个cgroup和css_set关联。 从kernel角度出发,一个task一定有一个css_set与之相关联。css_set是task和cgroup之间的桥梁。css_set可以有一个或多个子系统注册。

1.3 cg_cgroup_link

用来将css_set和cgroup做关联。

1.4 cgroup:

一个cgroup可以有一个或多个子系统注册,例如一个子系统cpu注册的cgroup: /cgroup/cpu/docker/{uuid}/
同时cpu, mememory注册的cgroup
/cgroup/cpu_and_mem/{uuid}/

每个cgroup对应一个mount到的cgroup文件系统,一个cgroup可以有多个子系统注册,多个子系统注册只需要在指定mount -t的时候带不同的子系统配置。

/cgroup/cpu (cpu)
/cgroup2/cpu (cpu)
/cgroup2/cpu_and_memory (cpu,memory)
/cgroup/cpu/docker (cpu)

在cgroup下通过mkdir新建的child group 如/cgroup/cpu/docker/{uid}也会创建新的cgroup。

1.5 cgroup_subsys_state

每个子系统的每次mount 都有一个cgroup_subsys_state与之对应,例如cpu和memory的cgroup_subsys_state

/cgroup/cpu (cpu state)
/cgroup/memory (memory state)
/cgroup2/cpu (cpu state)
/cgroup2/cpu_and_memery (cpu state)
/cgroup2/cpu_and_memery (memory state)

1.6 cgroup_subsys

内核可以支持的子系统。每个子系统必须附属于一个cgroupfs_root对象,一个唯一的subsys_id可以标示子系统。子系统提供相应的函数实现,必要的时候需要在内核中做适当的处理(例如cpuset需要在调度算法里按指定的cpu进行调度)

1.7 cgroupfs_root

cgroup的根对象,该对象在系统中mount完产生,其数目是有限的。比如 /cgroup/cpu, /cgroup/memory等2个cgroupfs_root。当然如果/cgroup/cpu_and_memery只有一个cgroupfs_root。通过cgroupfs_root可以关联到cgroup_subsys子系统,例如/cgroup/cpu关联到cpu的cgroup_subsys,而/cgroup/cpu_and_memery关联到cpu和memory两个cgroup_subsys。

struct cpuset {
    struct cgroup_subsys_state css;

    unsigned long flags;        /* "unsigned long" so bitops work */
    cpumask_var_t cpus_allowed;    /* CPUs allowed to tasks in cpuset */
    nodemask_t mems_allowed;    /* Memory Nodes allowed to tasks */

    struct cpuset *parent;        /* my parent */

    struct fmeter fmeter;        /* memory_pressure filter */

    /* partition number for rebuild_sched_domains() */
    int pn;

    /* for custom sched domain */
    int relax_domain_level;

    /* used for walking a cpuset heirarchy */
    struct list_head stack_list;
};

2.用户case

2.1初始化

向系统注册 cgroup类型的文件系统

//该函数初始化cgroup的树状结构,主要是init_css_set, rootnode, dummpytop等。
int __init cgroup_init_early(void)
{
    int i;
    atomic_set(&init_css_set.refcount, 1);
    INIT_LIST_HEAD(&init_css_set.cg_links);
    INIT_LIST_HEAD(&init_css_set.tasks);
    INIT_HLIST_NODE(&init_css_set.hlist);
    css_set_count = 1;
    init_cgroup_root(&rootnode);
    root_count = 1;
    init_task.cgroups = &init_css_set;

    init_css_set_link.cg = &init_css_set;
    init_css_set_link.cgrp = dummytop;
    list_add(&init_css_set_link.cgrp_link_list,
         &rootnode.top_cgroup.css_sets);
    list_add(&init_css_set_link.cg_link_list,
         &init_css_set.cg_links);

    for (i = 0; i < CSS_SET_TABLE_SIZE; i++)
        INIT_HLIST_HEAD(&css_set_table[i]);

    for (i = 0; i < CGROUP_SUBSYS_COUNT; i++) {
        struct cgroup_subsys *ss = subsys[i];

        BUG_ON(!ss->name);
        BUG_ON(strlen(ss->name) > MAX_CGROUP_TYPE_NAMELEN);
        BUG_ON(!ss->create);
        BUG_ON(!ss->destroy);
        if (ss->subsys_id != i) {
            printk(KERN_ERR "cgroup: Subsys %s id == %d\n",
                   ss->name, ss->subsys_id);
            BUG();
        }

        if (ss->early_init)
            cgroup_init_subsys(ss);
    }
    return 0;
}
```


```C
/**
 * cgroup_init - cgroup initialization
 *
 * Register cgroup filesystem and /proc file, and initialize
 * any subsystems that didn't request early init.
 */
int __init cgroup_init(void)
{
    int err;
    int i;
    struct hlist_head *hhead;

    err = bdi_init(&cgroup_backing_dev_info);
    if (err)
        return err;

    for (i = 0; i < CGROUP_SUBSYS_COUNT; i++) {
        struct cgroup_subsys *ss = subsys[i];
        if (!ss->early_init)
            cgroup_init_subsys(ss);
        if (ss->use_id)
            cgroup_subsys_init_idr(ss);
    }

    /* Add init_css_set to the hash table */
    hhead = css_set_hash(init_css_set.subsys);
    hlist_add_head(&init_css_set.hlist, hhead);
    BUG_ON(!init_root_id(&rootnode));
    err = register_filesystem(&cgroup_fs_type);
    if (err < 0)
        goto out;

    proc_create("cgroups", 0, NULL, &proc_cgroupstats_operations);

out:
    if (err)
        bdi_destroy(&cgroup_backing_dev_info);

    return err;
}

2.2 mount cgroup

如果在用户空间执行 mount -t cgroup -o cpu,cpuset,memory /sysgroup/cpu_and_mem 那么下面的函数将会被调用

static int cgroup_get_sb(struct file_system_type *fs_type,
             int flags, const char *unused_dev_name,
             void *data, struct vfsmount *mnt)

该函数首先初始化一个cgroupfs_root对象,cgroupfs_root结构里面包括了一个cgroup对象,初始化该cgroup对象使其成为一个top cgroup。所谓top cgroup就是最顶层的cgroup对象,该cgroup的top_cgroup指向自己。

static void init_cgroup_root(struct cgroupfs_root *root)
{
    struct cgroup *cgrp = &root->top_cgroup;
    INIT_LIST_HEAD(&root->subsys_list);
    INIT_LIST_HEAD(&root->root_list);
    root->number_of_cgroups = 1;
    cgrp->root = root;
    cgrp->top_cgroup = cgrp;
    init_cgroup_housekeeping(cgrp);
}

接下来根据指定的option对每个需要加载的subsys进行处理,将top cgroup里面的subsys指针初始化,并将对应的cgroup_subsys链接到cgroupfs_root中

            cgrp->subsys[i] = dummytop->subsys[i];
            cgrp->subsys[i]->cgroup = cgrp;
            list_move(&ss->sibling, &root->subsys_list);
            ss->root = root;

之后,将该top group 加入到root列表中; 紧接着,因为每个任务对应一个css_set,将每个任务对应的css_set和当前的root_cgroup做关联,也就算将每个任务默认的加入到root cgroup中 最后,在root dir下对每个cgroup_subsys按照该subsystem的规则生成控制文件

在2.6 kernel中cpu, cpuset, memory等subsystem仅可以加入一个group,不允许某个subsystem加入到多个group中。 例如下面试图将cpu加到两个cgroup就会失败

mount -t cgroup -o cpu,memory cpu_and_mem /tmp/cpu_and_mem
mount -t cgroup -o cpu cgroup /cgroup/cpu

2.3.mkdir group1 under mounted dentry

对应的是在root cgroup下创建子cgroup,函数

static int cgroup_mkdir(struct inode *dir, struct dentry *dentry, int mode)

负责处理该请求。

该函数首先对该cgroup下注册的的每个subsys创建cgroup_subsys_state对象,用来跟踪状态。cgroup_subsys_state对象是通过调用cgroup_subsys提供的函数create生成, 例如

struct cgroup_subsys_state *css = ss->create(ss, cgrp);

接着会创建新的cgroup对象,并建立新的cgroup和parent cgroup, cgroupfs_root等直接的层级关系。 最后调用new indoor在当前目录下创建cgroup.event_control cgroup.procs notify_on_release tasks release_agent等控制文件,同时调用cgroup_subsys的populate方法创建子系统额外的控制文件

2.4. echo $PID > tasks

这个操作会往当前cgroup中加入对应的task,具体的做法是通过调用函数

int cgroup_attach_task(struct cgroup *, struct task_struct *);

该函数首先对每个注册在当前cgroup下的cgroup_subsys,依次调用其提供的函数:

ss->can_attach
ss->can_attach_task(cgrp)
ss->pre_attach(cgrp);
ss->attach_task(cgrp,
ss->attach(ss, cgrp, oldcgrp, tsk, false);

来完成cgroup_subsys的加入,完成这一步之后标志着对该task的cgroup控制在内核开始生效。 接着对task_struct中的css_set数据进行处理,将该task_struct和对应的cgroup和cgroup_subsys_state做关联。 注意默认该task在top cgroup(cgroup_root)中,所以必定需要做一次cgroup migration, 将该task从一个css_set移到新的css_set,并和当前新的cgroup关联。

2.5. fork process from cgroup

和2.4类似,也是两个主要目的,一个是完成task_struct的关联工作;另一个是完成cgroup_subsys的回调将该forked task加入到cgroup中完成控制。

这几个目的是在fork系统调用中通过调用下面的三个函数来完成的。 先看第一步,将该task加入到对应的css_set中。task_struct的css_set指针指向父进程的css_set并增加引用计数。

void cgroup_fork(struct task_struct *child)
{
    task_lock(current);
    child->cgroups = current->cgroups;
    get_css_set(child->cgroups);
    task_unlock(current);
    INIT_LIST_HEAD(&child->cg_list);
}

第二步对每个cgroup_subsys调用fork操作,完成cgroup的控制。

void cgroup_fork_callbacks(struct task_struct *child)
{
    if (need_forkexit_callback) {
        int i;
        for (i = 0; i < CGROUP_SUBSYS_COUNT; i++) {
            struct cgroup_subsys *ss = subsys[i];
            if (ss->fork)
                ss->fork(ss, child);
        }
    }
}

第三步将task_struct加入到css_set的链表中,以便从css_set可以遍历所管理的tasks。

void cgroup_post_fork(struct task_struct *child)
{
    if (use_task_css_set_links) {
        write_lock(&css_set_lock);
        task_lock(child);
        if (list_empty(&child->cg_list))
            list_add(&child->cg_list, &child->cgroups->tasks);
        task_unlock(child);
        write_unlock(&css_set_lock);
    }
}

2.6. 具体实现

各个子系统在内核根据具体的代码逻辑有不同的实现,比如cpu和cpuset主要是在schedule过程中做相应的调度。这部分内容会在后续的系列文章中展开。

  • cpu: 按share调度cpu
  • cpuset: 限制某些cpu node可以使用
  • cpuacct: cpu使用统计
  • memory: 内存使用限制到bytes
  • blkio: 限制block device的读写速度。

2.7. umount /sys/cgroup/cpu_and_mem

static void cgroup_kill_sb(struct super_block *sb) {


上一篇     下一篇