您现在的位置是:首页 >技术杂谈 >Linux CGroup 原理网站首页技术杂谈

Linux CGroup 原理

Mr..Deer 2024-08-07 18:01:03
简介Linux CGroup 原理

Linux CGroup 原理

1、CGroup简介

cgroups是Linux下控制一个(或一组)进程的资源限制机制,全称是control groups,可以对cpu、内存等资源做精细化控制。

开发者可以直接基于cgroups来进行进程资源控制,比如8核的机器上部署了一个web服务和一个计算服务,可以让web服务仅可使用其中6个核,把剩下的两个核留给计算服务。cgroups cpu限制除了可以限制使用多少/哪几个核心之外,还可以设置cpu占用比(注意占用比是各自都跑满情况下的使用比例,如果一个cgroup空闲而另一个繁忙,那么繁忙的cgroup是有可能占满整个cpu核心的)。

2、CGroup子系统

从实现角度来看,cgroups实现了一个通用的进程分组框架,不同资源的具体管理工作由各cgroup子系统(subsystem)来实现,一个子系统就是一个资源控制器。当需要多个限制策略,比如同时针对cpu和内存进行限制,则同时关联多个cgroup子系统即可。

cgroups子系统

cgroups为每种资源定义了一个子系统,在/sys/fs/cgroup/这个目录下可以看到cgroup子系统,典型的子系统如下:

  • cpu 子系统,主要限制进程的 cpu 使用率。
  • cpuacct 子系统,可以统计 cgroups 中的进程的 cpu 使用报告。
  • cpuset 子系统,可以为 cgroups 中的进程分配单独的 cpu 节点或者内存节点。
  • memory 子系统,可以限制进程的 memory 使用量。
  • blkio 子系统,可以限制进程的块设备 io。
  • devices 子系统,可以控制进程能够访问某些设备。
  • net_cls 子系统,可以标记 cgroups 中进程的网络数据包,然后可以使用 tc 模块(traffic control)对数据包进行控制。
  • freezer 子系统,可以挂起或者恢复 cgroups 中的进程。
  • ns 子系统,可以使不同 cgroups 下面的进程使用不同的 namespace。

每个子系统都是定义了一套限制策略,它们需要与内核的其他模块配合来完成资源限制功能,比如对 cpu 资源的限制是通过进程调度模块根据 cpu 子系统的配置来完成的;对内存资源的限制则是内存模块根据 memory 子系统的配置来完成的,而对网络数据包的控制则需要 Traffic Control 子系统来配合完成。

hierarchy:控制族群可以组织成 hierarchical 的形式,既一颗控制族群树。控制族群树上的子节点控制族群是父节点控制族群的孩子,继承父控制族群的特定的属性;

3、CGroup的层级结构

在cgrups中一个进程可以加入到某个cgroup,也从一个进程组迁移到另一个cgroup。一个进程组的进程可以使用 cgroups 以控制族群为单位分配的资源,同时受到 cgroups 以控制族群为单位设定的限制。

多个cgroup形成一个层级结构(树形结构),cgroup树上的子节点cgroup是父节点cgroup的孩子,继承父cgroup的特定的属性。注意:cgroups层级只会关联某个子系统之后才能进行对应的资源控制,一个子系统附加到某个层级以后,这个层级上的所有cgroup都受到这个子系统的控制。

他们之间的互相关系是:

  • 每次在系统中创建新层级时,该系统中的所有任务都是那个层级的默认 cgroup(我们称之为 root cgroup,此 cgroup 在创建层级时自动创建,后面在该层级中创建的 cgroup 都是此 cgroup 的后代)的初始成员;

  • 一个子系统最多只能附加到一个层级;

  • 一个层级可以附加多个子系统;

  • 一个任务可以是多个 cgroup 的成员,但是这些 cgroup 必须在不同的层级;

  • 系统中的进程(任务)创建子进程(任务)时,该子任务自动成为其父进程所在 cgroup 的成员。然后可根据需要将该子任务移动到不同的 cgroup 中,但开始时它总是继承其父任务的 cgroup。

4、CGroup文件子系统

Linux 使用了多种数据结构在内核中实现了 cgroups 的配置,关联了进程和 cgroups 节点(见第5节),并使用VFS将cgroups功能暴露用户态的进程,VFS给用户态进程提供一个统一的文件系统 API 接口,cgroups 与 VFS 之间的衔接部分称之为 cgroups 文件系统。通过cgroups适配VFS,用户可以使用VFS接口来操作cgroup功能。

以freezer子系统为例,这个子系统可以对一组线程批量冻结,使用下面命令将打开freezer子系统:

mount cgroup none /dev/freezer freezer

该命令将子系统挂载于/dev/freezer目录,接下来可以在/dev/freezer目录下创建若干个目录,例如目录top、background,每个目录代表一组线程的资源分配行为,以cgroup实例描述,那么多级的目录以cgroup实例的形式组成了一个树形结构。接下来,可以给top组或者background组配置组内进程,

echo 1199 > /dev/freezer/top/cgroup.procs

当前cgroup版本已经不支持同一进程组内不同线程分属于子系统内不同cgroup。配置完top组内的线程后,可以通过操作freezer.state节点配置组内所有线程冻结:

echo FROZEN > /dev/freezer/top/freezer.state

解冻组内所有线程:

echo THAW > /dev/freezer/top/freezer.state

5、cgroups原理

关于cgroups原理,可以从进程角度来剖析相关数据结构之间关系,Linux 下管理进程的数据结构是 task_struct,其中与cgrups相关属性如下:

// task_struct代码
#ifdef CONFIG_CGROUPS 
/* Control Group info protected by css_set_lock */ 
struct css_set *cgroups; 
/* cg_list protected by css_set_lock and tsk->alloc_lock */ 
struct list_head cg_list; 
#endif

每个进程对应一个css_set结构,css_set存储了与进程相关的cgropus信息。cg_list是一个嵌入的 list_head 结构,用于将连到同一个 css_set 的进程组织成一个链表。进程和css_set的关系是多对一关系,tasks表示关联的多个进程。

struct css_set { 
	atomic_t refcount;
	struct hlist_node hlist; 
	struct list_head tasks; 
	struct list_head cg_links; 
	struct cgroup_subsys_state *subsys[CGROUP_SUBSYS_COUNT]; 
	struct rcu_head rcu_head; 
};

subsys 是一个指针数组,存储一组指向 cgroup_subsys_state 的指针,通过这个指针进程可以获取到对应的cgroups信息,一个 cgroup_subsys_state 就是进程与一个特定子系统相关的信息,cgroup_subsys_state结构体如下:

struct cgroup_subsys_state {
	struct cgroup *cgroup;
	atomic_t refcnt;
	unsigned long flags;
	struct css_id *id;
};

cgroup 指针指向了一个 cgroup 结构,也就是进程属于的 cgroup,进程受到子系统控制就是加入到特定的cgroup来实现的,就是对应这里的cgroup,由此看出进程和cgroup的关系是多对多关系。

struct cgroup {
	unsigned long flags; 
	atomic_t count; 
	struct list_head sibling; 
	struct list_head children; 
	struct cgroup *parent; 
	struct dentry *dentry; 
	struct cgroup_subsys_state *subsys[CGROUP_SUBSYS_COUNT]; 
	struct cgroupfs_root *root;
	struct cgroup *top_cgroup; 
	struct list_head css_sets; 
	struct list_head release_list; 
	struct list_head pidlists;
	struct mutex pidlist_mutex; 
	struct rcu_head rcu_head; 
	struct list_head event_list; 
	spinlock_t event_list_lock; 
};

sibling、children 和 parent 三个嵌入的 list_head 负责将统一层级的 cgroup 连接成一棵 cgroup 树。subsys 是一个指针数组,存储一组指向 cgroup_subsys_state 的指针。这组指针指向了此 cgroup 跟各个子系统相关的信息,也就是说一个cgroup可以关联多个子系统,二者关系是多对多关系。

Linux下的cgroups的数据结构图示如下:

[外链图片转存失败,源站可能有防盗]!链机制,建(htps://luoxn28.github.io/2020/01/04/cgroup-yuan-li-jie-xi/cgroup%E5%8E%9F%E7%90%86%E8%A7%A3%E6%9E%90/image-20200104135253154.png)]

6、以冷冻子系统为例梳理CGroup的核心逻辑

当某个子系统被挂载使能后,系统中所有线程默认处于子系统根目录所代表的cgroup实例中。

1. 配置task到目标cgroup

用户或者应用程序通过往cgroup.procs节点写入pid,cgroup.procs节点的write函数对应到cgroup_procs_write。

Image

cgroup_kn_lock_live函数根据当前节点目录解析到该目录所对应的cgroup实体,通过写权限检查以后,进入到主逻辑cgroup_attach_task函数里。

Image

cgroup_migrate_prepare_dst函数利用保存的当前css_set,查找是否存在满足条件的目标css_set,如果不存在满足条件的目标css_set,则创建一个新的css_set,插入多对多二维关系链表中。接下来cgroup_migrate函数将task从源css_set迁移到目标css_set中。

2. 操控cgroup属性

以freezer子系统为例,通过freezer.state节点控制cgroup组的冻结与解冻,这个节点对应到freezer_write函数,实际的逻辑在freezer_change_state函数中。(freezer.c)

Image

freezer_change_state函数第一个参数属于freezer结构体,它通过内嵌的cgroup_subsys_state结构与cgroup关联,也就是说每个freezer结构体直接对应到freezer子系统的一个目录。接下来看函数逻辑,css_for_each_descendant_pre循环体里对当前目录以及每个子孙目录所代表cgroup实体执行freezer_apply_state函数。freezer_apply_state函数通过调用freeze_cgroup和unfreeze_cgroup函数实际操作cgroup内每个task的冻结与解冻。

参考文章:

深入浅出cgroup_cgroup.procs_码出钞能力的博客-CSDN博客

Cgroup原理及使用 - zhrx - 博客园 (cnblogs.com)

https://luoxn28.github.io/2020/01/04/cgroup-yuan-li-jie-xi/

https://gaozhipeng.me/posts/freezer/

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。