第四个函数cgroup_init_early();的实现是在/kernel/cgroup/cgroup.c
int __init cgroup_init_early(void)
{
static struct cgroup_fs_context __initdata ctx;
struct cgroup_subsys *ss;
int i;
ctx.root = &cgrp_dfl_root;
init_cgroup_root(&ctx);
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;
}着主要实现如下三个主要目的:
初始化默认根层级,使用一个简略的文件系统完成
关联初始task,完成cgroup中task的指针指引
遍历并初始化子系统,并将之挂载到cgroup上
CGroup结构
CGroup是一个分层级的、基于子系统的进程资源管理框架,用于将系统中的进程与具体的资源控制策略动态关联起来,实现对进程组的资源限制、优先级控制、资源统计和隔离,同时也是现代容器技术Docker实现的基础。
具体来说,基于进程管理的框架是指:
task_struct-->css_set-->cgroup_subsys_state-->cgroup
通常意义上,cgroup结构指的是/include/linux/cgroup-defs.h文件中描述的(这是一个完备的结构):
struct cgroup {
struct cgroup_subsys_state self; // Cgroup本身被视为一个子系统
unsigned long flags; // 标志位集合,用于记录当前Cgroup状态,比如是否被销毁、是否根节点
int level; // 级别,根节点为0,子节点为1,依次类推
int max_depth; // 子树最大深度
int nr_descendants; // 当前存活的直接或间接Cgroup总数
int nr_dying_descendants; // 正在被销毁的Cgroup总数
int max_descendants; // 允许的最大子孙节点数量限制
int nr_populated_csets; // 关联到该Cgroup及其子孙的、包含实际进程任务的cset数量
int nr_populated_domain_children; // 拥有实际进程、类型为domain的直接子节点数量
int nr_populated_threaded_children; // 拥有实际进程、类型为threaded的直接子节点数量
int nr_threaded_children; // 所有类型为threaded的直接子节点总数
struct kernfs_node *kn; // Cgroup在VFS中对应的目录节点
struct cgroup_file procs_file; // 代表cgroup文件,用户读写这个文件时,内核通过这个结构体来处理
struct cgroup_file events_file; // 用于通知用户该空间Cgroup的状态变化
u16 subtree_control; // 挂载在该Cgroup上的位掩码,用于传播给子节点告知对应资源
u16 subtree_ss_mask; // 表示实际被使用的子系统掩码
u16 old_subtree_control; // 旧掩码
u16 old_subtree_ss_mask; // 旧子系统掩码
struct cgroup_subsys_state __rcu *subsys[CGROUP_SUBSYS_COUNT]; // 数组指针,元素指向特定子系统
struct cgroup_root *root; // 指向该Cgroup所属层级数的根节点
struct list_head cset_links; // 链表头,链接所有Cgroup关联的set,进而找到task
struct list_head e_csets[CGROUP_SUBSYS_COUNT]; // 每个子系统对应的有效css_set链表头数组
struct cgroup *dom_cgrp; // 指向该Cgroup所属的Domain节点,用于快速定位资源控制边界
struct cgroup *old_dom_cgrp; // 同样,用于旧的Domain根节点
struct cgroup_rstat_cpu __percpu *rstat_cpu; // 递归统计Cgroup资源使用量
struct list_head rstat_css_list; // 用于将脏数据连接到一个全局列表中
struct cgroup_base_stat last_bstat; // 上一次统计快照,用于计算数据增量
struct cgroup_base_stat bstat; // 当前累计的基础统计数据,如CPU内核态时间
struct prev_cputime prev_cputime; // 用于CPU时间统计,记录先前CPU时间戳
struct list_head pidlists; // 缓存PID列表头
struct mutex pidlist_mutex; // 保护pidlist的互斥锁
wait_queue_head_t offline_waitq; // 等待队列,当用户空间尝试删除一个非空的Cgroup时,进程等于此
struct work_struct release_agent_work; // 工作队列结构
struct psi_group psi; // 压力停滞信息,用于记录Cgroup在CPU内存、IO资源上因竞争而停止的时间
struct cgroup_bpf bpf; // 附加于这个Cgroup上的BPF程序钩子
atomic_t congestion_count; // 拥塞计数
struct cgroup_freezer_state freezer; // 冻结状态
u64 ancestor_ids[]; // 灵活数组成员
};这个完备大结构跟kernel初始化使用的早期结构略有不同。这个结构本身具备如下主要信息:
核心身份与层级关系,在这种新版的cgroup中,会使用struct kernfs_node *kn;这个用文件表示的节点来描述层级关系。其子节点将会在VFS中以子目录的形式展示。同时,有关子节点相关的属性也一并存在于结构体内。
资源权限情况以及子节点掩码,*subsys那个变量会保存一系列的子系统,在子系统中会具体表述当前Cgroup具备的权限,比如一组进程最多使用多少CPU时间、内存大小、磁盘IO带宽等。
资源使用统计,rstat_cpu变量会递归统计各种资源的使用情况
指向进程,Cgroup最终会由层级结构落地到各个进程之中,这些进程可以使用的权限、资源等问题被Cgroup描述,并最终在进行sched调度时,由调度器根据Cgroup内容完成分配。
上述的是主要功能,此外Cgroup还可以完成一些些定制化小功能,比如:
进程声明周期管理,可以杀死进程或冻结恢复进程
设备访问控制,籍由Devices子系统可以防止Cgroup控制下的进程存在的恶意威胁
压力停滞监控,可以由PSI进行CPU统计,并获取当前CPU使用情况
事件通知机制,允许用户空间监听内核程序,通过events向用户空间发送通知
BPF扩展,允许CPU驱动在Cgroup上展开
线程模式支持,允许将Cgroup标记为threaded并使内部的线程分布在不同的Cgroup之中
初始化时期的Cgroup
对于cgroup_init_early函数来说,很多正常cgroup可以关联到的东西此时都没有。
没有文件系统,所以root下挂的superblock和mount point都为NULL
没有SLAB这样的内存分配器,所以只能使用静态或者早期的分配工具分配
没有子系统,在初始化函数中,Cgroup也只会完成配合基本调度器的子系统
没有防崩溃机制
对于初始化函数本身来说,主要进行了如下内容:
初始化锁和链表
创建cgroup对象,并将其自身设置为父节点
将当前task放入当前cgroup中
如果存在子系统初始化函数,则会调用并初始化一个子系统
cgroup的文件上下文:
struct cgroup_fs_context {
struct kernfs_fs_context kfc;
struct cgroup_root *root;
struct cgroup_namespace *ns;
unsigned int flags; /* CGRP_ROOT_* flags */
/* cgroup1 bits */
bool cpuset_clone_children;
bool none; /* User explicitly requested empty subsystem */
bool all_ss; /* Seen 'all' option */
u16 subsys_mask; /* Selected subsystems */
char *name; /* Hierarchy name */
char *release_agent; /* Path for release notifications */
};这是cgroup文件系统挂载操作的临时工作台,它之所以被初始化,是因为内核需要一种标准、安全的方式来执行挂载请求,解析复杂的选项,并将其转化为内核内部的cgroup_root层级结构。
由于目前cgroup的层级结构实际上是依托文件系统实现的,所以需要首先构建一个文件系统,然后将文件系统中初始化的cgroup的root节点指向自己,加载当前task进入cgroup等操作。
除了文件系统,在初始化cgroup中还会根据/include/linux/cgroup-defs.h中的关键变量struct cgroup_subsys来进行一系列的权限设计。