Artikel ini akan menganalisis prinsip pelaksanaan CGroup secara terperinci dengan mengkaji kod sumber (Linux versi 2.6.25 digunakan di sini). Sebelum menyelami kod sumber, mari kita fahami beberapa struktur data utama, kerana CGroup menggunakan struktur data ini untuk mengurus penggunaan pelbagai sumber oleh kumpulan proses.
Seperti yang dinyatakan sebelum ini, cgroup digunakan untuk mengawal penggunaan pelbagai sumber oleh kumpulan proses. Dalam kernel, cgroup diterangkan oleh struktur cgroup Mari kita lihat definisinya:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
Mari perkenalkan cgroup
tujuan setiap bidang struktur:
flags
: 用于标识当前 cgroup
status. count
: 引用计数器,表示有多少个进程在使用这个 cgroup
. sibling、children、parent
: 由于 cgroup
是通过 层级
来进行管理的,这三个字段就把同一个 层级
的所有 cgroup
连接成一棵树。parent
指向当前 cgroup
的父节点,sibling
连接着所有兄弟节点,而 children
连接着当前 cgroup
. dentry
: 由于 cgroup
是通过 虚拟文件系统
来进行管理的,在介绍 cgroup
使用时说过,可以把 cgroup
当成是 层级
中的一个目录,所以 dentry
Medan digunakan untuk menerangkan direktori ini.subsys
: 前面说过,子系统
能够附加到 层级
,而附加到 层级
的 子系统
都有其限制进程组使用资源的算法和统计数据。所以 subsys
字段就是提供给各个 子系统
存放其限制进程组使用资源的统计数据。我们可以看到 subsys
字段是一个数组,而数组中的每一个元素都代表了一个 子系统
相关的统计数据。从实现来看,cgroup
只是把多个进程组织成控制进程组,而真正限制资源使用的是各个 子系统
. root
: 用于保存 层级
的一些数据,比如:层级
的根节点,附加到 层级
的 子系统
列表(因为一个 层级
可以附加多个 子系统
),还有这个 层级
有多少个 cgroup
Nod dll. top_cgroup
: 层级
Nod akar (root cgroup). Kami menggunakan gambar berikut untuk menerangkan hubungan seperti pokok yang terdiri daripada 层级
中各个 cgroup
:
cgroup-links
cgroup_subsys_state
结构体每个 子系统
都有属于自己的资源控制统计信息结构,而且每个 cgroup
都绑定一个这样的结构,这种资源控制统计信息结构就是通过 cgroup_subsys_state
结构体实现的,其定义如下:
1 2 3 4 5 |
|
下面介绍一下 cgroup_subsys_state
结构各个字段的作用:
cgroup
: 指向了这个资源控制统计信息所属的 cgroup
。refcnt
: 引用计数器。flags
: 标志位,如果这个资源控制统计信息所属的 cgroup
是 层级
的根节点,那么就会将这个标志位设置为 CSS_ROOT
表示属于根节点。从 cgroup_subsys_state
结构的定义看不到各个 子系统
相关的资源控制统计信息,这是因为 cgroup_subsys_state
结构并不是真实的资源控制统计信息结构,比如 内存子系统
真正的资源控制统计信息结构是 mem_cgroup
,那么怎样通过这个 cgroup_subsys_state
结构去找到对应的 mem_cgroup
结构呢?我们来看看 mem_cgroup
结构的定义:
1 2 3 4 5 6 7 |
|
从 mem_cgroup
结构的定义可以发现,mem_cgroup
结构的第一个字段就是一个 cgroup_subsys_state
结构。下面的图片展示了他们之间的关系:
cgroup-state-memory
从上图可以看出,mem_cgroup
结构包含了 cgroup_subsys_state
结构,内存子系统
对外暴露出 mem_cgroup
结构的 cgroup_subsys_state
部分(即返回 cgroup_subsys_state
结构的指针),而其余部分由 内存子系统
自己维护和使用。
由于 cgroup_subsys_state
部分在 mem_cgroup
结构的首部,所以要将 cgroup_subsys_state
结构转换成 mem_cgroup
结构,只需要通过指针类型转换即可。
cgroup
结构与 cgroup_subsys_state
结构之间的关系如下图:
cgroup-subsys-state
css_set
结构体由于一个进程可以同时添加到不同的 cgroup
中(前提是这些 cgroup
属于不同的 层级
)进行资源控制,而这些 cgroup
附加了不同的资源控制 子系统
。所以需要使用一个结构把这些 子系统
的资源控制统计信息收集起来,方便进程通过 子系统ID
快速查找到对应的 子系统
资源控制统计信息,而 css_set
结构体就是用来做这件事情。css_set
结构体定义如下:
1 2 3 4 5 6 7 |
|
下面介绍一下 css_set
结构体各个字段的作用:
ref
: 引用计数器,用于计算有多少个进程在使用此 css_set
。list
: 用于连接所有 css_set
。tasks
: 由于可能存在多个进程同时受到相同的 cgroup
控制,所以用此字段把所有使用此 css_set
的进程连接起来。subsys
: 用于收集各种 子系统
的统计信息结构。进程描述符 task_struct
有两个字段与此相关,如下:
1 2 3 4 5 6 |
|
可以看出,task_struct
结构的 cgroups
字段就是指向 css_set
结构的指针,而 cg_list
字段用于连接所有使用此 css_set
结构的进程列表。
task_struct
结构与 css_set
结构的关系如下图:
cgroup-task-cssset
cgroup_subsys
结构CGroup
通过 cgroup_subsys
结构操作各个 子系统
,每个 子系统
都要实现一个这样的结构,其定义如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
|
cgroup_subsys
结构包含了很多函数指针,通过这些函数指针,CGroup
可以对 子系统
进行一些操作。比如向 CGroup
的 tasks
文件添加要控制的进程PID时,就会调用 cgroup_subsys
结构的 attach()
函数。当在 层级
中创建新目录时,就会调用 create()
函数创建一个 子系统
的资源控制统计信息对象 cgroup_subsys_state
,并且调用 populate()
函数创建 子系统
相关的资源控制信息文件。
除了函数指针外,cgroup_subsys
结构还包含了很多字段,下面说明一下各个字段的作用:
subsys_id
: 表示了子系统的ID。active
: 表示子系统是否被激活。disabled
: 子系统是否被禁止。name
: 子系统名称。root
: 被附加到的层级挂载点。sibling
: 用于连接被附加到同一个层级的所有子系统。private
: 私有数据。内存子系统
定义了一个名为 mem_cgroup_subsys
的 cgroup_subsys
结构,如下:
1 2 3 4 5 6 7 8 9 10 |
|
另外 Linux 内核还定义了一个 cgroup_subsys
结构的数组 subsys
,用于保存所有 子系统
的 cgroup_subsys
结构,如下:
1 2 3 4 5 6 7 8 |
|
CGroup
的挂载前面介绍了 CGroup
相关的几个结构体,接下来我们分析一下 CGroup
的实现。
要使用 CGroup
功能首先必须先进行挂载操作,比如使用下面命令挂载一个 CGroup
:
1 |
|
在上面的命令中,-t
参数指定了要挂载的文件系统类型为 cgroup
,而 -o
参数表示要附加到此 层级
的子系统,上面表示附加了 内存子系统
,当然可以附加多个 子系统
。而紧随 -o
参数后的 memory
指定了此 CGroup
的名字,最后一个参数表示要挂载的目录路径。
挂载过程最终会调用内核函数 cgroup_get_sb()
完成,由于 cgroup_get_sb()
函数比较长,所以我们只分析重要部分:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
cgroup_get_sb()
函数会调用 kzalloc()
函数创建一个 cgroupfs_root
结构。cgroupfs_root
结构主要用于描述这个挂载点的信息,其定义如下:
1 2 3 4 5 6 7 8 9 10 11 |
|
下面介绍一下 cgroupfs_root
结构的各个字段含义:
sb
: Superblock sistem fail dipasang. subsys_bits/actual_subsys_bits
: Bendera subsistem dilampirkan pada tahap ini. subsys_list
: Senarai subsistem (cgroup_subsys) yang dilampirkan pada tahap ini. top_cgroup
: Cgroup akar tahap ini. number_of_cgroups
: Berapa banyak cgroup dalam hierarki. root_list
: Sambungkan semua cgroupfs_roots dalam sistem. flags
: Bendera bit. Yang paling penting ialah medan subsys_list
和 top_cgroup
字段,subsys_list
表示了附加到此 层级
的所有 子系统
,而 top_cgroup
表示此 层级
的根 cgroup
dan
subsistem
, dan 🎜 mewakiliAkar hierarki
cgroup
. 🎜
接着调用 rebind_subsystems()
函数把挂载时指定要附加的 子系统
添加到 cgroupfs_root
结构的 subsys_list
链表中,并且为根 cgroup
的 subsys
字段设置各个 子系统
的资源控制统计信息对象,最后调用 cgroup_populate_dir()
函数向挂载目录创建 cgroup
的管理文件(如 tasks
文件)和各个 子系统
的管理文件(如 memory.limit_in_bytes
文件)。
CGroup
添加要进行资源控制的进程通过向 CGroup
的 tasks
文件写入要进行资源控制的进程PID,即可以对进程进行资源控制。例如下面命令:
1 |
|
向 tasks
文件写入进程PID是通过 attach_task_by_pid()
函数实现的,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
|
attach_task_by_pid()
函数首先会判断是否指定了进程pid,如果指定了就通过进程pid查找到进程描述符,如果没指定就使用当前进程,然后通过调用 cgroup_attach_task()
函数把进程添加到 cgroup
中。
我们接着看看 cgroup_attach_task()
函数的实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
|
cgroup_attach_task()
函数首先会调用 find_css_set()
函数查找或者创建一个 css_set
对象。前面说过 css_set
对象用于收集不同 cgroup
上附加的 子系统
资源统计信息对象。
因为一个进程能够被加入到不同的 cgroup
进行资源控制,所以 find_css_set()
函数就是收集进程所在的所有 cgroup
上附加的 子系统
资源统计信息对象,并返回一个 css_set
对象。接着把进程描述符的 cgroups
字段设置为这个 css_set
对象,并且把进程添加到这个 css_set
对象的 tasks
链表中。
最后,cgroup_attach_task()
函数会调用附加在 层级
上的所有 子系统
的 attach()
函数对新增进程进行一些其他的操作(这些操作由各自 子系统
去实现)。
CGroup
的资源使用本文主要是使用 内存子系统
作为例子,所以这里分析内存限制的原理。
可以向 cgroup
的 memory.limit_in_bytes
文件写入要限制使用的内存大小(单位为字节),如下面命令限制了这个 cgroup
只能使用 1MB 的内存:
1 |
|
向 memory.limit_in_bytes
写入数据主要通过 mem_cgroup_write()
函数实现的,其实现如下:
1 2 3 4 5 6 7 8 |
|
其主要工作就是把 内存子系统
的资源控制对象 mem_cgroup
的 res.limit
字段设置为指定的数值。
当设置好 cgroup
的资源使用限制信息,并且把进程添加到这个 cgroup
的 tasks
列表后,进程的资源使用就会受到这个 cgroup
的限制。这里使用 内存子系统
作为例子,来分析一下内核是怎么通过 cgroup
来限制进程对资源的使用的。
当进程要使用内存时,会调用 do_anonymous_page()
来申请一些内存页,而 do_anonymous_page()
函数会调用 mem_cgroup_charge()
函数来检测进程是否超过了 cgroup
设置的资源限制。而 mem_cgroup_charge()
最终会调用 mem_cgroup_charge_common()
函数进行检测,mem_cgroup_charge_common()
函数实现如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
|
mem_cgroup_charge_common()
函数会对进程内存使用情况进行检测,如果进程已经超过了 cgroup
设置的限制,那么就会尝试进行释放一些不用的内存,如果还是超过限制,那么就会发出 OOM (out of memory)
的信号。
Atas ialah kandungan terperinci Asas Linux: prinsip cgroup dan pelaksanaan. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!