cgroup代码浅析(1)
前置:这里使用的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)在适当的时候把指向原来数据的指针指向新的被修改的数据。https://www.ibm.com/developerworks/cn/linux/l-rcu/
这里的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对应的结构的地址了。http://blog.csdn.net/dlutbrucezhang/article/details/10103371
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,这个结构是用来将一个资源挂在等待队列中,具体参考:http://www.cnblogs.com/lubiao/p/4858086.html
还有一个结构是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结构,里面的设置就可以基本看懂了。
参考
http://files.cnblogs.com/files/lisperl/cgroups介绍.pdf
http://tech.meituan.com/cgroups.html
http://coolshell.cn/articles/17049.html
cgroup代码浅析(1)的更多相关文章
- cgroup代码浅析(2)
info include/linux/memcontrol.h memcg相关的函数 数据结构 mem_cgroup在每个node下,都有一个lruvec, 这个lruvec保存在mem_cgroup ...
- Mosquitto pub/sub服务实现代码浅析-主体框架
Mosquitto 是一个IBM 开源pub/sub订阅发布协议 MQTT 的一个单机版实现(目前也只有单机版),MQTT主打轻便,比较适用于移动设备等上面,花费流量少,解析代价低.相对于XMPP等来 ...
- VC 函数调用的 汇编代码 浅析
摘要:主要谈谈vc里面函数调用汇编成汇编代码的情形,首先针对之前的一个小程序,说说vc编译器的优化. 例子程序: #include <iostream>using namespace st ...
- 游戏音频技术备忘 (五)Wwise Unreal Engine 集成代码浅析 二
AkAmbientSound类的实现 Unreal Engine提供了一个基本对象的构造器ObjectInitializer,一般来说用户创建的类总是拥有很多变量,因此 AkAmbientSound ...
- 游戏音频技术备忘 (四) Wwise Unreal Engine 集成代码浅析 (一)
在Engine\Plugins\Wwise\Source下为主要Wwise相关代码,AkAudio文件夹下为运行时相关代码,AudiokineticTools下为编辑器工具相关代码,Audiokine ...
- TensorFlow教程——Bi-LSTM+CRF进行序列标注(代码浅析)
https://blog.csdn.net/guolindonggld/article/details/79044574 Bi-LSTM 使用TensorFlow构建Bi-LSTM时经常是下面的代码: ...
- jQuery on()方法绑定动态元素的点击事件实例代码浅析
之前就一直受这个问题的困扰,在jQuery1.7版本之后添加了on方法,之前就了解过,其优越性高于live(),bind(),delegate()等方法,在此之前项目中想用这个来测试结果发现,居然动态 ...
- Linux profile1,bashrc,.bash_profile,.bash_login,.profile,.bashrc,.bash_logout浅析 Part1
profile,bashrc,.bash_profile,.bash_login,.profile,.bashrc,.bash_logout浅析 Part 1 by:授客 QQ:103355312 ...
- cgroup 分析之CPU和内存部分
https://ggaaooppeenngg.github.io/zh-CN/2017/05/07/cgroups-%E5%88%86%E6%9E%90%E4%B9%8B%E5%86%85%E5%AD ...
随机推荐
- web 开发之js---js 中的数组操作
js数组元素的添加和删除一直比较迷惑,今天终于找到详细说明的资料了,先给个我测试的代码^-^var arr = new Array();arr[0] = "aaa";arr[1] ...
- 利用runtime检測这个对象是否存在某属性?
假定有实例对象-instance,我们怎样知道该实例对象是否存在属性-propertyName? 利用runtime,我们能够获取到它的属性列表 1)属性列表 unsigned int outCoun ...
- Estimating duration from bitrate, this may be inaccurate
通过 ffmpeg 获取 媒体 播放时长 ./ffmpeg-linux64-v3.3.1 -i /tmp/mp30001.mp3 ffmpeg version N-86111-ga441aa90e8 ...
- https://security.stackexchange.com/questions/68405/what-is-tmunblock-cgi-and-can-it-be-exploited-by-shellshock-linux-apache-w
hndUnblock.cgi Line #1124 : 187.38.233.45 - - [15/Jan/2018:21:36:45 +0800] "GET /hndUnblock.c ...
- 利用JFreeChart生成区域图 (5) (转自 JSP开发技术大全)
利用JFreeChart生成区域图 (5) (转自 JSP开发技术大全) 14.5 利用JFreeChart生成区域图 通过JFreeChart插件只能生成普通效果的区域图,利用工厂类ChartFac ...
- CentOS 6.5下安装MySQL 5.6.21
Linux中使用最广泛的数据库就是MySQL,使用在线yum的方式安装的版本落后MySQL网站好几个小版本,本节亲自测试安装新版的MySQL. 测试机器环境: VMware Workstation 1 ...
- [ZJOI 2007] 矩阵游戏
[题目链接] https://www.lydsy.com/JudgeOnline/problem.php?id=1059 [算法] 二分图最大匹配 时间复杂度 : O(N^3) [代码] #inclu ...
- 32.ExtJS简单的动画效果
转自:http://blog.sina.com.cn/s/blog_74684ec501015lhq.html 说明:这篇文章的大部分内容来源于网上,经过自己实现其效果后,整理如下: 在进行 Java ...
- 《Effective C++》笔记:III(转载)
转自:http://www.cnblogs.com/destino74/p/3960802.html 条款5:Know what functions C++ silently writes and c ...
- 堆和栈的区别【以java为例潜入分析】
Java的堆是一个运行时数据区,类的对象从中分配空间,这些对象通过new等指令建立. 堆是由垃圾回收来负责的,堆的优势是可以动态地分配内存大小,生存期也不必事先告诉编译器,Java的垃圾收集器会自动 ...