Control Groups提供了一种机制,可以把task以及他们的子task聚集或者分组成带有特定行为的hierarchical groups。
定义:
cgroup:一个cgroup关联了带有特定参数的一个或者多个子系统的一组task。
subsystem:subsystem是一个模块,利用cgroup提供的task分组机制,对分组后的task进行特定的处理。一个subsystem通常是一个resource controller。这个资源控制器可以用来限制每组task的资源上限等。但是subsystem也可以在这组task上施加其他的行为,例如虚拟化子系统。
hierarchy:hierarchy是一组以树形结构组织的cgroup,系统中每个task都确定的归属于这个hierarchy下的一个cgroup中。每个subsystem有系统特定的状态关联到hierarchy下的cgroup上。每个hierarchy都关联一个cgroup vfs实例。
可以同时有多个激活的hierarchy,每个hierarchy都包含了系统所有的task并对task进行了分组。
用户级的代码可以创建或者销毁vfs中的cgroup,指定或者查询task的cgroup归属,可以列出一个cgroup下所有task的pid。
cgroup本身只是实现任务的跟踪划分。其他的subsystem可以通过cgroup接口给cgroup下的task提供新的特性。例如统计或者限制一个cgroup下所有task的资源使用。
内核中有多处需要对进程进行汇聚分组,例如cpusets, CKRM/ResGroups, UserBeanCounters, and virtual server namespaces。这些场景下除了需要对现有的进程进行分组外,还需新创建的进程与父进程同一分组。cgroup则在内核中实现这种对task进行高效分组的机制。cgroup对系统关键路径仅有很小的影响,同时为子系统提供了特定的接口以实现特定的功能。
对于不同的subsystem,task分组的划分可能时不同的,因此内核提供了多hierarchy的支持。一种极端场景是每个subsystem使用一个独立的hierarchy,另外一种极端场景时所有的子系统使用相同的hierarchy。
cgroup按照如下方式扩展内核:
- 系统中每个task有一个指向css_set的、带有引用计数的指针。
- 一个css_set包含一组指向cgroup_subsys_state带有引用计数的指针,一个cgroup_subsys_state对应一个在系统中注册的cgroup subsystem。task和cgroup之间并没有直接的关联,这是因为访问子系统的状态相比访问task归属的cgroup更为频繁。从task到cgroup的路径为:task_struct->css_set->cgroup_subsys_state->cgroup。
- 一个cgroup hierarchy filesystem可以在用户空间挂载后进行浏览或者操作。
- 可以列出和任意一个cgroup关联的所有task的pid。
cgroup的实现需要在kernel非关键路径上插入一些钩子。
- 在 init/main.c中,系统启动时需要初始化root cgroups和initial css_set。
- 在fork和exit的时候,需要在这个task的css_set中attach或者detach这个task。
另外,一个新的cgroup文件系统可以被挂载,以让用户浏览或者修改cgroup的相关配置。当挂载一个cgroup hierarchy的时候,可以指定一个逗号分割的subsystem列表来作为文件系统挂载选项。默认情况下,挂载一个cgroup文件系统hau尝试挂载一个包所有subsystem的hierarchy。
如果一个带有相同subsystem设置的hierarchy已经存在,在新的挂载中它将会被重用。如果没有现存的hierarchy匹配,并且请求的选项中任意一个subsystem正在其他hierarchy中使用,挂载将会失败并返回-EBUSY。否则一个新的hierarchy被激活并关联请求的subsystem。
当前还不支持把一个子系统绑定到一个被激活的hierarchy上,也不支持从一个激活的hierarchy上解绑一个subsystem。将来可能会支持,但是担心会充满纠错代码。
当一个cgroup文件系统被卸载的时候,如果在这个top-level cgroup下存在child cgroup,对应的hierarchy仍然会保留在激活状态。如果没有child cgroup,这个hierarchy将会被去激活。
对cgroup没有增加新的系统调用,对于cgroup查询和修改的支持都是通过cgroup文件系统实现的。
/proc下的每个task都新增了一个cgroup文件用来展示每个激活的hierarchy下的subsystem名字、cgroup名字(即相对于根cgroup文件系统的路径)。
每个cgroup都被呈现为cgroup文件系统下的一个目录,并且包含下面的文件来描述这个cgroup:
- tasks:关联到这个cgroup的task的pid列表。这个列表不保证有序,向这个文件写入一个线程id的时候将会移动对应的线程到这个cgroup。
- cgroup.procs:这个cgroup下的thread group ID列表,这个列表不保证有序和tgid的不重复,用户空间如果有需要应该排序或者去重这个list。写一个tgid到这个文件会移动这个tgid下的所有线程到这个cgroup下。
- notify_on_release flag:在退出时运行这个release代理?
- release agent:release notification的路径。(这个文件仅存在于顶层cgroup下)。
其他的子系统例如cpusets可能会在对应的cgroup目录下添加额外的文件。
新的cgroup使用mkdir系统调用或者shell命令创建。可以通过修改这个cgroup下的文件来修改这个cgroup的属性。
The named hierarchical structure of nested cgroups allows partitioning
a large system into nested, dynamically changeable, "soft-partitions".
一个task通过fork创建的子task自动继承父task的cgroup。如果具有相关的权限,一个task可以重新被绑定到其他的cgroup上。
当一个task从一个cgroup移动到另外一个cgroup的时候,它会获得一个新的css_set指针。如果已经存在一个带有相同cgroup集合的css_set(通过hash table进行查找),这个css_set将会被重用,否则一个新的css_set将会被创建。
为了通过cgroup访问关联的css_set以及task,一组cg_cgroup_link对象关联了css_set和cgroup的多对多关系。因为一个cgroup关联的task可以首先迭代关联的css_set,在每个css_set下迭代关联的task。
The use of a Linux virtual file system (vfs) to represent the
cgroup hierarchy provides for a familiar permission and name space
for cgroups, with a minimum of additional kernel code.
如果notify_on_release这个标志位使能(设置为1),当cgroup中最后一个task离开的时候(task退出或者绑定到其他cgroup上)并且最后的child cgroup被移除,内核将会运行顶层cgroup下的release_agent文件中指定的命令,并提供这个cgroup相对于挂载点的路径。这允许自动移除被废弃的cgroup。在系统启动的时候,root cgroupd的notify_on_release默认值为0。其他cgroup的值为在创建时继承在父cgroup的notify_on_release设置。一个cgroup hierarchy的release_agent默认值为空。
1) mount -t tmpfs cgroup_root /sys/fs/cgroup
2) mkdir /sys/fs/cgroup/cpuset
3) mount -t cgroup -ocpuset cpuset /sys/fs/cgroup/cpuset
4) Create the new cgroup by doing mkdir's and write's (or echo's) in
the /sys/fs/cgroup/cpuset virtual file system.
5) Start a task that will be the "founding father" of the new job.
6) Attach that task to the new cgroup by writing its PID to the
/sys/fs/cgroup/cpuset tasks file for that cgroup.
7) fork, exec or clone the job tasks from this founding father task.
可以通过cgroup VFS创建、修改和使用cgroup。
可以使用下面命令挂载一个带有有所subsystem的cgroup hierarchy:
# mount -t cgroup xxx /sys/fs/cgroup
xxx并没有被cgroup系统解释,但是会出现在/proc/mounts中作为一个标识符。xxx可以是任意字符串。
注意:一些subsystem只有在用户首先输入有效配置后才能工作,例如cpusets子系统只有在用户填充每个cgroup的cpus和mems配置文件后才能使能工作。
应该为资源分组或者为每个独立的资源创建独立的hierarchy,因此应该首先在/sys/fs/cgroup下挂载tmpfs,并为每个cgroup资源或者资源组创建目录。
# mount -t tmpfs cgroup_root /sys/fs/cgroup
# mkdir /sys/fs/cgroup/rg1
挂载一个仅带有cpuset和memory子系统的cgroup hierarchy,按照如下命令执行:
# mount -t cgroup -o cpuset,memory hier1 /sys/fs/cgroup/rg1
While remounting cgroups is currently supported, it is not recommend
to use it. Remounting allows changing bound subsystems and
release_agent. Rebinding is hardly useful as it only works when the
hierarchy is empty and release_agent itself should be replaced with
conventional fsnotify. The support for remounting will be removed in
the future.
可以通过如下方式指定一个hierarchy的release_agent:
# mount -t cgroup -o cpuset,release_agent="/sbin/cpuset_release_agent" xxx /sys/fs/cgroup/rg1
多次指定release_agent将会返回失败。
当前只有在hierarchy仅包含一个root cgroup的时候,才支持改变hierarchy对应的子系统集。预计将来会实现任意绑定/解绑现存的hierarchy下的子系统的能力。
也可以通过如下命令改变release_agent的设置:
# echo "/sbin/new_release_agent" > /sys/fs/cgroup/rg1/release_agent
release_agent同样可以通过remount来改变。
如果想在 /sys/fs/cgroup/rg1下创建新的cgroup:
# cd /sys/fs/cgroup/rg1
# mkdir my_cgroup
如果想操作这个cgroup,进入后可以看到一些文件(如cgroup.procs notify_on_release tasks):
# cd my_cgroup
把当前shell添加到对应的cgroup下:
# /bin/echo $$ > tasks
同样可以通过mkdir在自己的cgroup下创建新的cgroup:
# mkdir my_sub_cs
使用rmdir可以移除一个cgroup:
# rmdir my_sub_cs
This will fail if the cgroup is in use (has cgroups inside, or
has processes attached, or is held alive by other subsystem-specific
reference).
# /bin/echo PID > tasks
注意一次只能添加一个task。如果有多个task需要attach,需要多次执行上面的命令。
可以通过echo 0把当前shell attach到对应的cgroup上:
# echo 0 > tasks
可以使用cgroup.procs文件代替tasks文件,从而一次把一个线程组添加到cgroup中。向cgroup.procs添加任意一个线程的pid都会把整个线程组下所有的task attach到这个group下。向cgroup.procs写入0的时候将会把写入线程对应的线程组添加进去。
注意:在一个挂载的hierarchy下,每一个task一定归属于某个cgroup。因此如果要从当前cgroup移除某个task,你必须要通过写入新cgroup的tasks文件来把这个task移动到一个新的cgroup下(可能是root cgroup)。
注意:因为一些cgroup subsystem的限制,移动一个进程到其他的cgroup可能会失败。
当挂载一个cgroup hierarchy的时候,传递name=<x>选项将会把对应的name关联到hierarchy上。当挂载一个已经存在的hierarchy的时候,可以通过名字来关联对应的hierarchy,每个hierarchy如果有关联名字,则名字必须唯一。名字必须符合[\w.-]+。
当向一个新的hierarchy传递name=<x>选项的时候,需要手动指定subsytem选项。并不支持通过显示指定none来挂载所有的子系统的行为。子系统的名字将会作为hierarchy的描述出现在/proc/mounts和/proc/<pid>/cgroups中。
每个想要hook到通用cgroup系统的内核subsystem都需要创建一个cgroup_subsys对象。这个对象包含了很对可以让cgroup系统回调的方法,还会包含一个cgroup系统分配的子系统ID。
cgroup_subsys中还包含一些其他字段:
subsys_id:一个唯一的子系统数组索引,指示这个子系统对应cgroup->subsys[] 中的entry。
name:应该被初始化为一个唯一的子系统名字,长度不应该超过MAX_CGROUP_TYPE_NAMELEN。
early_init:指示这个子系统是否需要在系统启动时候进行早期初始化。
系统创建的每个cgroup对象都包含一个由子系统ID索引的指针数组。这个指针由子系统进行管理,通用cgroup代码并不会touch这个指针。
cgroup系统会使用到一个全局的cgroup_mutex。任何想要修改cgroup的代码都需要首先持有这个mutex。当想要阻止cgroup被修改的时候,也可以持有这个mutex,但是这个时候使用更细化的锁更合适。
子系统可以通过cgroup_loack()/cgroup_unlock来持有/释放cgroup_mutex。
Accessing a task's cgroup pointer may be done in the following ways:
- while holding cgroup_mutex
- while holding the task's alloc_lock (via task_lock())
- inside an rcu_read_lock() section via rcu_dereference()
每个子系统应该:
- 在linux/cgroup_sussys.h下添加一个entry。
- 定义一个名为<name>_subsys的cgroup_subsys对象
如果一个子系统可以被编译为一个模块,它应该在模块的initcall中调用cgroup_load_subsys(),并且在模块的exitcall中调用cgroup_unload_subsys()。并且应该在.c文件中设置its_subsys.module =THIS_MODULE
子系统应该导出下面的方法,必须要要导出的方法是css_alloc/free。当其他的函数指针为null的时候,则会被假设为成功的no-ops调用。
struct cgroup_subsys_state *css_alloc(struct cgroup *cgrp)
(cgroup_mutex held by caller)
用来为cgroup分配一个subsystem state对象。这个subsystem应该为传进来的cgroup分配它的子系统状态对象,并在分配成功时返回一个指向新对象的指针,失败时候返回ERR_PTR()值。在成功的时候子系统指针应该指向一个cgroup_subsys_state状态对象。这个对象将会被cgroup系统初始化。这个函数将会在创建这个subsystem的root subsystem state的时候调用。这可以通过传进来的cgroup对象指针来识别(传进来的cgroup对象将会有一个NULL parent)。这里是一个合适的地发来进行初始化。
int css_online(struct cgroup *cgrp)
(cgroup_mutex held by caller)
当cgroup完成所有的分配并且对于cgroup_for_each_child/descendant_*() 迭代器可见时将会调用这个函数。子系统可以选择返回-errno来指示创建失败。这个回调函数可以用来在hierarchy上实现可靠的状态共享和传播。详细内容可以查看cgroup_for_each_descendant_pre()。
void css_offline(struct cgroup *cgrp);
(cgroup_mutex held by caller)
是与css_online相对应的回调函数,并且在css_online成功的时候才会调用。This signifies the beginning of the end of @cgrp。cgroup正在被移除,子系统应该丢弃它在cgroup上所有的引用。当所有的引用都被移除后,cgroup将会进行到下一步 - css_free()。在这个回调函数后,cgroup应该对这个子系统来说应该处于dead状态了。
void css_free(struct cgroup *cgrp)
(cgroup_mutex held by caller)
cgroup系统即将被释放掉,子系统应该释放它的子系统状态对象。当这个函数被调用后,cgroup完全无用了。@cgrp->parent仍然是有效的。(注意:当子系统的creat()方法调用后,如果后续发生错误同样可以在新创建的cgroup上调用这个函数。)
int can_attach(struct cgroup *cgrp, struct cgroup_taskset *tset)
(cgroup_mutex held by caller)
在移动一个或者多个task到一个cgroup之前调用这个函数。如果子系统返回一个error,将会终止attach操作。@tset包含将要进行attach操作的task,并且保证里面至少有一个task。
如果在taskset中有多个task:
- 外层确保所有的task来自同一个线程组
- @tset包含这个线程组的所有task,不管这些task是否需要切换cgroup(@tset contains all tasks from the thread group whether or not they're switching cgroups)
- 里面第一个task是线程组leader
每个@tset同样包含task原来的cgroup,同一个线程组下没有进行cgroup切换的task可以通过cgroup_taskset_for_each()迭代器进行处理。注意这个回调函数并不会在fork的时候调用。如果这个函数返回0(success),这表示如果调用方持有cgroup_mutex,这个状态将会一直有效,并且调用方将会调用attch()或者cancel_attach()。
void css_reset(struct cgroup_subsys_state *css)
(cgroup_mutex held by caller)
一个可以把@css恢复为初始状态的可选操作。This is currently only used on the unified hierarchy when a subsystem is disabled on a cgroup through "cgroup.subtree_control" but should remain enabled because other subsystems depend on it。cgroup通过移除关联的接口文件并调用这个接口函数来让子系统恢复到初始状态来隐藏css。这阻止了hiden cdd不可预期的资源控制并确保当它后续visible的时候,配置处于初始状态。
void cancel_attach(struct cgroup *cgrp, struct cgroup_taskset *tset)
(cgroup_mutex held by caller)
在can_attach()操作成功并且task attach操作失败的时候调用这个函数。有些子系统的can_attach()调用有side-effect,因此需要这个函数进行回滚。
void attach(struct cgroup *cgrp, struct cgroup_taskset *tset)
(cgroup_mutex held by caller)
Called after the task has been attached to the cgroup, to allow any post-attachment activity that requires memory allocations or blocking. The parameters are identical to can_attach()。
void fork(struct task_struct *task)
Called when a task is forked into a cgroup.
void exit(struct task_struct *task)
Called during task exit.
void free(struct task_struct *task)
Called when the task_struct is freed.
void bind(struct cgroup *root)
(cgroup_mutex held by caller)
当一个cgroup子系统重新绑定到一个不同的hierarchy和root cgroup的时候需要调用这个回调函数。Currently this will only involve movement between the default hierarchy (which never has sub-cgroups) and a hierarchy that is being created/destroyed (and hence has no sub-cgroups)。
cgroup文件系统在它的目录和文件上支持一些类型的扩展属性,当前支持:
- Trusted (XATTR_TRUSTED)
- Security (XATTR_SECURITY)
两个都需要CAP_SYS_ADMIN能力来设置。
正如tmpfs一样,cgroup文件系统的扩展属性存储在memory中,因此建议尽少使用。这也是为什么不支持用户自定义的扩展属性的原因。
The current known users for this feature are SELinux to limit cgroup usage in containers and systemd for assorted meta data like main PID in a cgroup (systemd creates a cgroup per service)。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。