linux源码分析(四)-start_kernel-cgroup

合集下载
  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

linux源码分析(四)-start_kernel-cgroup
前置:这⾥使⽤的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:///patch/7368941/
struct list_head task_iters;
// 这个css_set是否已经⽆效了
bool dead;
// rcu锁所需要的callback等信息
struct rcu_head rcu_head;
};
这⾥的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结构。

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[];
};
还有⼀个结构是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结构,⾥⾯的设置就可以基本看懂了。

参考。

相关文档
最新文档