菜单

kaoqi
kaoqi
发布于 2026-09-21 / 9 阅读
0
0

kernel第4个函数展开

第四个函数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来进行一系列的权限设计。


评论