博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
cgroup代码浅析(1)
阅读量:5746 次
发布时间:2019-06-18

本文共 8196 字,大约阅读时间需要 27 分钟。

前置:这里使用的linux版本是4.8,x86体系。

cgroup_init_early();

聊这个函数就需要先了解cgroup。

cgroup概念

这个函数就是初始化cgroup所需要的参数的。cgroup最初是在2006年由google的一名工程师提出的,目的是把一些共同目标的进程放在一个组里面,而这个组里面的进程能共享指定数额的资源。而后就有了cgroup这个概念了。

我们把每种资源叫做子系统,比如CPU子系统,内存子系统。为什么叫做子系统呢,因为它是从整个操作系统的资源衍生出来的。然后我们创建一种虚拟的节点,叫做cgroup,然后这个虚拟节点可以扩展,以树形的结构,有root节点,和子节点。这个父节点和各个子节点就形成了层级(hierarchiy)。每个层级都可以附带继承一个或者多个子系统,就意味着,我们把资源按照分割到多个层级系统中,层级系统中的每个节点对这个资源的占比各有不同。

下面我们想法子把进程分组,进程分组的逻辑叫做css_set。这里的css是cgroup_subsys_state的缩写。所以css_set和进程的关系是一对多的关系。另外,在cgroup眼中,进程请不要叫做进程,叫做task。这个可能是为了和内核中进程的名词区分开吧。

进程分组css_set,不同层级中的节点cgroup也都有了。那么,就要把节点cgroup和层级进行关联,和数据库中关系表一样。这个事一个多对多的关系。为什么呢?首先,一个节点可以隶属于多个css_set,这就代表这这批css_set中的进程都拥有这个cgroup所代表的资源。其次,一个css_set需要多个cgroup。因为一个层级的cgroup只代表一种或者几种资源,而一般进程是需要多种资源的集合体。

美团的这个图片描写的非常清晰,一看就了解了:

task_struct

首先先看进程的结构,里面和cgroup有关的是

#ifdef CONFIG_CGROUPS     // 设置这个进程属于哪个css_set    struct css_set __rcu *cgroups;     // cg_list是用于将所有同属于一个css_set的task连成一起    struct list_head cg_list;#endif

我们会在代码中经常见到list_head。它其实就是表示,这个在链表中存在。

struct list_head {    struct list_head *next, *prev;};

它的结构很简单,就能把某种相同性质的结构连成一个链表,根据这个链表我能前后找全整个链表或者头部节点等。

css_set

结构体在include/linux/cgroup-defs.h中。

struct css_set {    // 引用计数,gc使用,如果子系统有引用到这个css_set,则计数+1    atomic_t refcount;     // TODO: 列出有相同hash值的cgroup(还不清楚为什么)    struct hlist_node hlist;     // 将所有的task连起来。mg_tasks代表迁移的任务    struct list_head tasks;    struct list_head mg_tasks;     // 将这个css_set对应的cgroup连起来    struct list_head cgrp_links;    // 默认连接的cgroup    struct cgroup *dfl_cgrp;     // 包含一系列的css(cgroup_subsys_state),css就是子系统,这个就代表了css_set和子系统的多对多的其中一面    struct cgroup_subsys_state *subsys[CGROUP_SUBSYS_COUNT];    // 内存迁移的时候产生的系列数据    struct list_head mg_preload_node;    struct list_head mg_node;    struct cgroup *mg_src_cgrp;    struct cgroup *mg_dst_cgrp;    struct css_set *mg_dst_cset;    // 把->subsys[ssid]->cgroup->e_csets[ssid]结构展平放在这里,提高迭代效率    struct list_head e_cset_node[CGROUP_SUBSYS_COUNT];     // 所有迭代任务的列表,这个补丁参考:https://patchwork.kernel.org/patch/7368941/    struct list_head task_iters;    // 这个css_set是否已经无效了    bool dead;    // rcu锁所需要的callback等信息    struct rcu_head rcu_head;};

这里说一下rcu锁,这个锁是linux2.6引入的。它是非常高效的,适合读多写少的情况。全称是(Read-Copy Update)读-拷贝修改。原理就是读操作的时候,不需要任何锁,直接进行读取,写操作的时候,先拷贝一个副本,然后对副本进行修改,最后使用回调(callback)在适当的时候把指向原来数据的指针指向新的被修改的数据。

这里的rcu_head就存储了对这个结构上rcu锁所需要的回调信息。

struct callback_head {    struct callback_head *next;    void (*func)(struct callback_head *head);} __attribute__((aligned(sizeof(void *))));#define rcu_head callback_head

回到css_set,其实最重要的就是cgroup_subsys_state subsys[]数组这个结构。

cgroup_subsys_state 和 cgroup_subsys

这个结构最重要的就是存储的进程与特定子系统相关的信息。通过它,可以将task_struct和cgroup连接起来了:task_struct->css_set->cgroup_subsys_state->cgroup

struct cgroup_subsys_state {    // 对应的cgroup    struct cgroup *cgroup;    // 子系统    struct cgroup_subsys *ss;    // 带cpu信息的引用计数(不大理解)    struct percpu_ref refcnt;    // 父css    struct cgroup_subsys_state *parent;    // 兄弟和孩子链表串    struct list_head sibling;    struct list_head children;    // css的唯一id    int id;     // 可设置的flag有:CSS_NO_REF/CSS_ONLINE/CSS_RELEASED/CSS_VISIBLE    unsigned int flags;    // 为了保证遍历的顺序性,设置遍历按照这个字段的升序走    u64 serial_nr;    // 计数,计算本身css和子css的活跃数,当这个数大于1,说明还有有效子css    atomic_t online_cnt;    // TODO: 带cpu信息的引用计数使用的rcu锁(不大理解)    struct rcu_head rcu_head;    struct work_struct destroy_work;};

cgroup_subsys结构体在include/linux/cgroup-defs.h里面

struct cgroup_subsys {     // 下面的是函数指针,定义了子系统对css_set结构的系列操作    struct cgroup_subsys_state *(*css_alloc)(struct cgroup_subsys_state *parent_css);    int (*css_online)(struct cgroup_subsys_state *css);    void (*css_offline)(struct cgroup_subsys_state *css);    void (*css_released)(struct cgroup_subsys_state *css);    void (*css_free)(struct cgroup_subsys_state *css);    void (*css_reset)(struct cgroup_subsys_state *css);     // 这些函数指针表示了对子系统对进程task的一系列操作    int (*can_attach)(struct cgroup_taskset *tset);    void (*cancel_attach)(struct cgroup_taskset *tset);    void (*attach)(struct cgroup_taskset *tset);    void (*post_attach)(void);    int (*can_fork)(struct task_struct *task);    void (*cancel_fork)(struct task_struct *task);    void (*fork)(struct task_struct *task);    void (*exit)(struct task_struct *task);    void (*free)(struct task_struct *task);    void (*bind)(struct cgroup_subsys_state *root_css);     // 是否在前期初始化了    bool early_init:1;     // 如果设置了true,那么在cgroup.controllers和cgroup.subtree_control就不会显示, TODO:    bool implicit_on_dfl:1;     // 如果设置为false,则子cgroup会继承父cgroup的子系统资源,否则不继承或者只继承一半     // 但是现在,我们规定,不允许一个cgroup有不可继承子系统仍然可以衍生出cgroup。如果做类似操作,我们会根据     // warned_broken_hierarch出现错误提示。    bool broken_hierarchy:1;    bool warned_broken_hierarchy:1;    int id;    const char *name;     // 如果子cgroup的结构继承子系统的时候没有设置name,就会沿用父系统的子系统名字,所以这里存的就是父cgroup的子系统名字    const char *legacy_name;    struct cgroup_root *root;  // 这个就是子系统指向的层级中的root的cgroup    struct idr css_idr; // 对应的css的idr     // 对应的文件系统相关信息    struct list_head cfts;    struct cftype *dfl_cftypes;    /* 默认的文件系统 */    struct cftype *legacy_cftypes;    /* 继承的文件系统 */     // 有的子系统是依赖其他子系统的,这里是一个掩码来表示这个子系统依赖哪些子系统    unsigned int depends_on;};

这里特别说一下cftype。它是cgroup_filesystem_type的缩写。这个要从我们的linux虚拟文件系统说起(VFS)。VFS封装了标准文件的所有系统调用。那么我们使用cgroup,也抽象出了一个文件系统,自然也需要实现这个VFS。实现这个VFS就是使用这个cftype结构。

这里说一下idr。这个是linux的整数id管理机制。你可以把它看成一个map,这个map是把id和制定指针关联在一起的机制。它的原理是使用基数树。一个结构存储了一个idr,就能很方便根据id找出这个id对应的结构的地址了。

cgroup

cgroup结构也在相同文件,但是cgroup_root和子节点cgroup是使用两个不同结构表示的。

struct cgroup {    // cgroup所在css    struct cgroup_subsys_state self;    unsigned long flags;    int id;    // 这个cgroup所在层级中,当前cgroup的深度    int level;    // 每当有个非空的css_set和这个cgroup关联的时候,就增加计数1    int populated_cnt;    struct kernfs_node *kn;        /* cgroup kernfs entry */    struct cgroup_file procs_file;    /* handle for "cgroup.procs" */    struct cgroup_file events_file;    /* handle for "cgroup.events" */    // TODO: 不理解    u16 subtree_control;    u16 subtree_ss_mask;    u16 old_subtree_control;    u16 old_subtree_ss_mask;    // 一个cgroup属于多个css,这里就是保存了cgroup和css直接多对多关系的另一半    struct cgroup_subsys_state __rcu *subsys[CGROUP_SUBSYS_COUNT];     // 根cgroup    struct cgroup_root *root;    // 相同css_set的cgroup链表    struct list_head cset_links;    // 这个cgroup使用的所有子系统的每个链表    struct list_head e_csets[CGROUP_SUBSYS_COUNT];    // TODO: 不理解    struct list_head pidlists;    struct mutex pidlist_mutex;    // 用来保存下线task    wait_queue_head_t offline_waitq;    // TODO: 用来保存释放任务?(不理解)    struct work_struct release_agent_work;    // 保存每个level的祖先    int ancestor_ids[];};

这里看到一个新的结构,wait_queue_head_t,这个结构是用来将一个资源挂在等待队列中,具体参考:

还有一个结构是cgroup_root

struct cgroup_root {     // TODO: 不清楚    struct kernfs_root *kf_root;    // 子系统掩码    unsigned int subsys_mask;    // 层级的id    int hierarchy_id;    // 根部的cgroup,这里面就有下级cgroup    struct cgroup cgrp;    // 相等于cgrp->ancester_ids[0]    int cgrp_ancestor_id_storage;    // 这个root层级下的cgroup数,初始化的时候为1    atomic_t nr_cgrps;    // 串起所有的cgroup_root    struct list_head root_list;    unsigned int flags;    // TODO: 不清楚    struct idr cgroup_idr;    // TODO: 不清楚    char release_agent_path[PATH_MAX];    // 这个层级的名称,有可能为空    char name[MAX_CGROUP_ROOT_NAMELEN];};

cgroup_init_early

回到这个函数

int __init cgroup_init_early(void){    // 初始化cgroup_root,就是一个cgroup_root的结构    init_cgroup_root(&cgrp_dfl_root, &opts);    cgrp_dfl_root.cgrp.self.flags |= CSS_NO_REF;    RCU_INIT_POINTER(init_task.cgroups, &init_css_set);    for_each_subsys(ss, i) {        WARN(!ss->css_alloc || !ss->css_free || ss->name || ss->id,             "invalid cgroup_subsys %d:%s css_alloc=%p css_free=%p id:name=%d:%s\n",             i, cgroup_subsys_name[i], ss->css_alloc, ss->css_free,             ss->id, ss->name);        WARN(strlen(cgroup_subsys_name[i]) > MAX_CGROUP_TYPE_NAMELEN,             "cgroup_subsys_name %s too long\n", cgroup_subsys_name[i]);        ss->id = i;        ss->name = cgroup_subsys_name[i];        if (!ss->legacy_name)            ss->legacy_name = cgroup_subsys_name[i];        if (ss->early_init)            cgroup_init_subsys(ss, true);    }    return 0;}

这个函数初始化的cgroup_root是一个全局的变量。定义在kernel/cgroup.c中。

struct cgroup_root cgrp_dfl_root;EXPORT_SYMBOL_GPL(cgrp_dfl_root);

理解了cgroup结构,里面的设置就可以基本看懂了。

参考

转载于:https://www.cnblogs.com/muahao/p/10280998.html

你可能感兴趣的文章
Linux中rc的含义
查看>>
安卓中数据库的搭建与使用
查看>>
AT3908 Two Integers
查看>>
.NET 设计规范--.NET约定、惯用法与模式-2.框架设计基础
查看>>
win7 64位+Oracle 11g 64位下使用 PL/SQL Developer 的解决办法
查看>>
HTML5新手入门指南
查看>>
sql 内联,左联,右联,全联
查看>>
C++关于字符串的处理
查看>>
Breaking parallel loops in .NET C# using the Stop method z
查看>>
[轉]redis;mongodb;memcache三者的性能比較
查看>>
让你的WPF程序在Win7下呈现Win8风格主题
查看>>
802.11 学习笔记
查看>>
Leetcode-Database-176-Second Highest Salary-Easy(转)
查看>>
构建Docker Compose服务堆栈
查看>>
Hadoop生态圈-Kafka常用命令总结
查看>>
浮点数内存如何存储的
查看>>
EventSystem
查看>>
用WINSOCK API实现同步非阻塞方式的网络通讯
查看>>
玩一玩博客,嘿嘿
查看>>
JsonCpp 的使用
查看>>