mirror of
https://github.com/MintCN/linux-insides-zh.git
synced 2026-04-24 18:50:42 +08:00
15.1 翻译修订。调整了几处翻译,"控制组群" 统一译为 "控制组"。
This commit is contained in:
@@ -1,33 +1,33 @@
|
||||
控制组群
|
||||
控制组
|
||||
================================================================================
|
||||
|
||||
简介
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
这是 [linux 内核揭密](http://0xax.gitbooks.io/linux-insides/content/) 的新一章的第一部分。你可以根据这部分的标题猜测 - 这一部分将涉及 Linux 内核中的 [控制组群](https://en.wikipedia.org/wiki/Cgroups) 或 `cgroups` 机制。
|
||||
这是 [linux 内核揭密](http://0xax.gitbooks.io/linux-insides/content/) 的新一章的第一部分。你可以根据这部分的标题猜测 - 这一部分将涉及 Linux 内核中的 [控制组](https://en.wikipedia.org/wiki/Cgroups) 或 `cgroups` 机制。
|
||||
|
||||
`Cgroups` 是由 Linux 内核提供的一种机制,它允许我们分配诸如处理器时间、每组进程的数量、每个控制组群的内存大小,或者针对一个或一组进程的上述资源的组合。`Cgroups` 是按照层级结构组织的,这种机制类似于通常的进程,他们也是层级结构,并且子 `cgroups` 会继承其上级的一些属性。但实际上他们还是有区别的。`cgroups` 和进程之间的主要区别在于,多个不同层级的控制组群可以同时存在,而进程树则是单一的。同时存在的多个不同层级的控制组群并不是任意的,因为每个控制组群层级都要附加到一组控制组群"子系统"中。
|
||||
`Cgroups` 是由 Linux 内核提供的一种机制,它允许我们分配诸如处理器时间、每组进程的数量、每个控制组的内存大小,或者针对一个或一组进程的上述资源的组合。`Cgroups` 是按照层级结构组织的,这种机制类似于通常的进程,他们也是层级结构,并且子 `cgroups` 会继承其上级的一些属性。但实际上他们还是有区别的。`cgroups` 和进程之间的主要区别在于,多个不同层级的控制组可以同时存在,而进程树则是单一的。同时存在的多个不同层级的控制组并不是任意的,因为每个控制组层级都要附加到一组控制组"子系统"中。
|
||||
|
||||
每个“控制组群子系统”代表一种资源,如针对某个“控制组群”的处理器时间或者 [pid](https://en.wikipedia.org/wiki/Process_identifier) 的数量,也叫进程数。Linux 内核提供对以下12种“控制组群子系统”的支持:
|
||||
每个“控制组子系统”代表一种资源,如针对某个“控制组”的处理器时间或者 [pid](https://en.wikipedia.org/wiki/Process_identifier) 的数量,也叫进程数。Linux 内核提供对以下 12 种“控制组子系统”的支持:
|
||||
|
||||
* `cpuset` - 为“控制组群”内的任务分配独立的处理器和内存节点;
|
||||
* `cpu` - 使用调度程序对“控制组群”内的任务提供 CPU 资源的访问;
|
||||
* `cpuacct` - 生成“控制组群”中所有任务的处理器使用情况报告;
|
||||
* `cpuset` - 为“控制组”内的任务分配独立的处理器和内存节点;
|
||||
* `cpu` - 使用调度程序对“控制组”内的任务提供 CPU 资源的访问;
|
||||
* `cpuacct` - 生成“控制组”中所有任务的处理器使用情况报告;
|
||||
* `io` - 限制对[块设备](https://en.wikipedia.org/wiki/Device_file)的读写操作;
|
||||
* `memory` - 限制“控制组群”中的一组任务的内存使用;
|
||||
* `devices` - 限制“控制组群”中的一组任务访问设备;
|
||||
* `freezer` - 允许“控制组群”中的一组任务挂起/恢复;
|
||||
* `net_cls` - 允许对“控制组群”中的任务产生的网络数据包进行标记;
|
||||
* `net_prio` - 针对“控制组群”中的每个网络接口提供一种动态修改网络流量优先级的方法;
|
||||
* `perf_event` - 支持访问“控制组群”中的[性能事件](https://en.wikipedia.org/wiki/Perf_(Linux));
|
||||
* `hugetlb` - 为“控制组群”开启对[大页内存](https://www.kernel.org/doc/Documentation/vm/hugetlbpage.txt)的支持;
|
||||
* `pid` - 限制“控制组群”中的进程数量。
|
||||
* `memory` - 限制“控制组”中的一组任务的内存使用;
|
||||
* `devices` - 限制“控制组”中的一组任务访问设备;
|
||||
* `freezer` - 允许“控制组”中的一组任务挂起/恢复;
|
||||
* `net_cls` - 允许对“控制组”中的任务产生的网络数据包进行标记;
|
||||
* `net_prio` - 针对“控制组”中的每个网络接口提供一种动态修改网络流量优先级的方法;
|
||||
* `perf_event` - 支持访问“控制组”中的[性能事件](https://en.wikipedia.org/wiki/Perf_(Linux));
|
||||
* `hugetlb` - 为“控制组”开启对[大页内存](https://www.kernel.org/doc/Documentation/vm/hugetlbpage.txt)的支持;
|
||||
* `pid` - 限制“控制组”中的进程数量。
|
||||
|
||||
每个“控制组群子系统”是否被支持均与相关配置选项有关。例如,`cpuset` 子系统应该通过 `CONFIG_CPUSETS` 内核配置选项启用,`io` 子系统通过 `CONFIG_BLK_CGROUP` 内核配置选项等。所有这些内核配置选项都可以在 `General setup → Control Group support` 菜单里找到:
|
||||
每个“控制组子系统”是否被支持均与相关配置选项有关。例如,`cpuset` 子系统应该通过 `CONFIG_CPUSETS` 内核配置选项启用,`io` 子系统通过 `CONFIG_BLK_CGROUP` 内核配置选项等。所有这些内核配置选项都可以在 `General setup → Control Group support` 菜单里找到:
|
||||
|
||||

|
||||
|
||||
你可以通过 [proc](https://en.wikipedia.org/wiki/Procfs) 虚拟文件系统在计算机上查看已经启用的控制组群:
|
||||
你可以通过 [proc](https://en.wikipedia.org/wiki/Procfs) 虚拟文件系统在计算机上查看已经启用的控制组:
|
||||
|
||||
```
|
||||
$ cat /proc/cgroups
|
||||
@@ -68,7 +68,7 @@ dr-xr-xr-x 5 root root 0 Dec 2 22:37 pids
|
||||
dr-xr-xr-x 5 root root 0 Dec 2 22:37 systemd
|
||||
```
|
||||
|
||||
正如你所猜测的那样,“控制组群”机制不只是针对 Linux 内核的需求而创建的,更多的是用户空间层面的需求。要使用“控制组群”,需要先创建它。我们可以通过两种方式来创建。
|
||||
正如你所猜测的那样,“控制组”机制不只是针对 Linux 内核的需求而创建的,更多的是用户空间层面的需求。要使用“控制组”,需要先创建它。我们可以通过两种方式来创建。
|
||||
|
||||
第一种方法是在 `/sys/fs/cgroup` 目录下的任意子系统中创建子目录,并将任务的 pid 添加到 `tasks` 文件中,这个文件在我们创建子目录后会自动创建。
|
||||
|
||||
@@ -131,7 +131,7 @@ total 0
|
||||
-rw-r--r-- 1 root root 0 Dec 3 22:55 tasks
|
||||
```
|
||||
|
||||
现在我们重点关注 `tasks` 和 `devices.deny` 这两个文件。第一个文件 `tasks` 包含的是要附加到 `cgroup_test_group` 控制组群的 pid,第二个文件 `devices.deny` 包含的是拒绝访问的设备列表。新创建的控制组群默认对设备没有任何访问限制。为了禁止访问某个设备(在我们的示例中是 `/dev/tty`),我们应该向 `devices.deny` 写入下面这行:
|
||||
现在我们重点关注 `tasks` 和 `devices.deny` 这两个文件。第一个文件 `tasks` 包含的是要附加到 `cgroup_test_group` 控制组的 pid,第二个文件 `devices.deny` 包含的是拒绝访问的设备列表。新创建的控制组默认对设备没有任何访问限制。为了禁止访问某个设备(在我们的示例中是 `/dev/tty`),我们应该向 `devices.deny` 写入下面这行:
|
||||
|
||||
```
|
||||
# echo "c 5:0 w" > devices.deny
|
||||
@@ -155,7 +155,7 @@ print line
|
||||
...
|
||||
```
|
||||
|
||||
没变化。再把这个进程的 pid 加到我们控制组群的 `devices/tasks` 文件:
|
||||
没变化。再把这个进程的 pid 加到我们控制组的 `devices/tasks` 文件:
|
||||
|
||||
```
|
||||
# echo $(pidof -x cgroup_test_script.sh) > /sys/fs/cgroup/devices/cgroup_test_group/tasks
|
||||
@@ -211,13 +211,13 @@ Control group /:
|
||||
│ └─6404 /bin/bash
|
||||
```
|
||||
|
||||
现在我们了解了一些关于“控制组群”的机制,如何手动使用它,以及这个机制的用途。是时候深入 Linux 内核源码来了解这个机制的实现了。
|
||||
现在我们了解了一些关于“控制组”的机制,如何手动使用它,以及这个机制的用途。是时候深入 Linux 内核源码来了解这个机制的实现了。
|
||||
|
||||
控制组群的早期初始化
|
||||
控制组的早期初始化
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
现在,在我们刚刚看到关于 Linux 内核的“控制组群”机制的一些理论之后,我们可以开始深入到 Linux 的内核源码,以便更深入的了解这种机制。
|
||||
与往常一样,我们将从“控制组群”的初始化开始。在 Linux 内核中,`cgroups` 的初始化分为两个部分:早期和晚期。在这部分我们只考虑“早期”的部分,“晚期”的部分会在下一部分考虑。
|
||||
现在,在我们刚刚看到关于 Linux 内核的“控制组”机制的一些理论之后,我们可以开始深入到 Linux 的内核源码,以便更深入的了解这种机制。
|
||||
与往常一样,我们将从“控制组”的初始化开始。在 Linux 内核中,`cgroups` 的初始化分为两个部分:早期和晚期。在这部分我们只考虑“早期”的部分,“晚期”的部分会在下一部分考虑。
|
||||
|
||||
`Cgroups` 的早期初始化是在 Linux 内核的早期初始化期间从 [init/main.c](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/init/main.c) 中调用:
|
||||
|
||||
@@ -275,7 +275,7 @@ struct cgroup_subsys {
|
||||
}
|
||||
```
|
||||
|
||||
例如,`ccs_online` 和 `ccs_offline` 回调分别在 cgroup 成功完成所有分配之后和 cgroup 释放之前调用,`early_init` 标志位用来标记子系统是否要提前初始化,`id` 和 `name` 字段分别表示在 cgroup 中已注册的子系统的唯一标识和子系统的”名称“。最后的 `root` 字段表示指向 cgroup 层级结构的根的指针。
|
||||
例如,`css_online` 和 `css_offline` 回调分别在 cgroup 成功完成所有分配之后和 cgroup 释放之前调用,`early_init` 标志位用来标记子系统是否要提前初始化,`id` 和 `name` 字段分别表示在 cgroup 中已注册的子系统的唯一标识和子系统的”名称“。最后的 `root` 字段指向 cgroup 层级结构的根。
|
||||
|
||||
当然,`cgroup_subsys` 结构体还有一些其他字段,比上面展示的要多,不过目前了解这么多已经够了。现在我们了解了与 `cgroups` 机制有关的重要结构体,让我们再回到 `cgroup_init_early` 函数。这个函数的主要目的是对一些子系统进行早期初始化。你可能已经猜到了,这些需要”早期“初始化的子系统的 `cgroup_subsys->early_init` 字段应该为 `1`。来看看哪些子系统可以提前初始化吧。
|
||||
|
||||
@@ -292,7 +292,7 @@ cgrp_dfl_root.cgrp.self.flags |= CSS_NO_REF;
|
||||
struct cgroup_root cgrp_dfl_root;
|
||||
```
|
||||
|
||||
这里的 `cgrp` 字段是 `cgroup` 结构体,你也许已经猜到了,它表示一个 `cgroup`,`cgroup` 定义在 [include/linux/cgroup-defs.h](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/include/linux/cgroup-defs.h) 头文件中。我们知道一个进程是由 Linux 内核中的 `task_struct` 结构体表示, `task_struct` 并不包含直接访问这个任务所属的 `cgroup` 的链接,但是可以通过 `task_struct` 的 `ccs_set` 字段访问。这个 `ccs_set` 结构体拥有指向子系统状态数组的指针:
|
||||
这里的 `cgrp` 字段是 `cgroup` 结构体,你也许已经猜到了,它表示一个 `cgroup`,`cgroup` 定义在 [include/linux/cgroup-defs.h](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/include/linux/cgroup-defs.h) 头文件中。我们知道一个进程在 Linux 内核中是用 `task_struct` 结构体表示的, `task_struct` 并不包含直接访问这个任务所属的 `cgroup` 的链接,但是可以通过 `task_struct` 的 `css_set` 字段访问。这个 `css_set` 结构体拥有指向子系统状态数组的指针:
|
||||
|
||||
```C
|
||||
struct css_set {
|
||||
@@ -357,13 +357,13 @@ struct cgroup_subsys_state {
|
||||
|
||||
|
||||
|
||||
因此,`init_cgroup_root` 函数使用默认值设置 `cgrp_dfl_root`。接下来的工作是把初始化的 `ccs_set` 分配给 `init_task`,它表示系统中的第一个进程:
|
||||
因此,`init_cgroup_root` 函数使用默认值设置 `cgrp_dfl_root`。接下来的工作是把初始化的 `css_set` 分配给 `init_task`,它表示系统中的第一个进程:
|
||||
|
||||
```C
|
||||
RCU_INIT_POINTER(init_task.cgroups, &init_css_set);
|
||||
```
|
||||
|
||||
`cgroup_init_early` 函数里最后一件重要的任务是 `early cgroups` 的初始化。在这里,我们遍历所有已注册的子系统,并分配唯的一标识号、子系统名称,并且对标记为早期的子系统调用 `cgroup_init_subsys` 函数:
|
||||
`cgroup_init_early` 函数里最后一件重要的任务是 `early cgroups` 的初始化。在这里,我们遍历所有已注册的子系统,给子系统分配一个唯一的标识号和名称,并且对标记为早期的子系统调用 `cgroup_init_subsys` 函数:
|
||||
|
||||
```C
|
||||
for_each_subsys(ss, i) {
|
||||
@@ -400,7 +400,7 @@ SUBSYS(cpu)
|
||||
...
|
||||
```
|
||||
|
||||
可以这样是因为第一个 `SUBSYS` 的宏定义之后的 `#undef` 语句。来看看 `&_x ## _cgrp_subsys` 表达式,`##` 操作符在 `C` 语言的宏定义中连接宏左右两边的表达式,所以当我们把 `cpuset`、`cpu` 等参数传给 `SUBSYS` 宏时,其实是在定义 `cpuset_cgrp_subsys`、`cp_cgrp_subsys`。这是真的。如果你看一下 [kernel/cpuset.c](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/kernel/cpuset.c) 源文件,你会看到这个定义:
|
||||
可以这样定义是因为第一个 `SUBSYS` 的宏定义后面的 `#undef` 语句。来看看 `&_x ## _cgrp_subsys` 表达式,在 `C` 语言的宏定义中,`##` 操作符连接左右两边的表达式,所以当我们把 `cpuset`、`cpu` 等参数传给 `SUBSYS` 宏时,其实是在定义 `cpuset_cgrp_subsys`、`cp_cgrp_subsys`。确实如此,在 [kernel/cpuset.c](https://github.com/torvalds/linux/blob/16f73eb02d7e1765ccab3d2018e0bd98eb93d973/kernel/cpuset.c) 源文件中你可以看到这些结构体的定义:
|
||||
|
||||
```C
|
||||
struct cgroup_subsys cpuset_cgrp_subsys = {
|
||||
@@ -424,7 +424,7 @@ struct cgroup_subsys cpuset_cgrp_subsys = {
|
||||
结束语
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
这是第一部分的结尾,它描述了 Linux 内核中“控制组群”机制的引入,我们讨论了与“控制组群”机制相关的一些理论和初始化步骤,在接下来的部分中,我们将继续深入讨论“控制组群”更实际的方面。
|
||||
这是第一部分的结尾,它描述了 Linux 内核中“控制组”机制的引入,我们讨论了与“控制组”机制相关的一些理论和初始化步骤,在接下来的部分中,我们将继续深入讨论“控制组”更实用的方面。
|
||||
|
||||
如果你有任何问题或建议,可以写评论给我,也可以在 [twitter](https://twitter.com/0xAX) 上联系我。
|
||||
|
||||
|
||||
Reference in New Issue
Block a user