Cgroup之cpuset子系统
Contents
最近分析了内核cpuset
的实现,发现在目前的常见的系统中应用不是很广泛。目前最火的docker
也只是使用了其最简单的功能。本文对cpuset
进行了简要总结,并总结了docker
如何使用它。
注意:本文中引用的内核代码版本为
v5.2
什么是cpuset ?
man手册中对其的描述为:cpuset - confine processes to processor and memory node subsets
,cpuset
用于限制一组进程只运行在特定的cpu
节点上和只在特定的mem
节点上分配内存。
具体为什么需要cpuset
,其和sched_setaffinity(2)
, mbind(2)
和set_mempolicy(2)
系统调用的实现关系,可以参考cpuset文档。
cpusets 是如何实现的?
由于linux kernel
在实现cpusets
之前,就有sched_setaffinity(2)
, mbind(2)
和set_mempolicy(2)
等系统调用完成类似的功能。cpusets
扩展了这些机制。
刚开始,cpusets
作为一个单独的实现,有一个文件系统类型为cpusets
。后面随着cgroup
的发展,cpusets
又集成到了cgroup
中,成了cgroup
的一个子系统。
目前,作为cgroup
的一个子系统,其遵循如下原则:
- 每个进程的
cpuset信息,保存在其
task_struct中的
cgroup`的数据结构中。 sched_setaffinity
系统调用只被允许设置为进程的cpuset
中的cpu
。mbind
andset_mempolicy
系统调用只被允许设置为进程的cpuset
中的mem
。root cpuset
包含系统上所有的cpu
和mem
。- 对于每一个
cpuset
,可以定义其子cpuset
,子cpuset
包含的mem
和cpu
是其parent
的子集。 - 可以查看某个
cpuset
中的所有进程列表。
为了实现cpuset
,只需在内核中添加一些hook
,这些hook
都不在性能关键路径中:
- 在
init/main.c
, 在内核启动时,初始化root cpuset
- 在
fork
和exit
接口中,attach
和detach
一个进程到对应的cpuset
中 - 在
sched_setaffinity
实现中,添加cpuset
的过滤 - 在
mbind
andset_mempolicy
的实现中,添加cpuset
的过滤 - 在
sched.c migrate_live_tasks()
中,限制目标cpu
为cpuset
中所允许的 - 在
page_alloc.c
,限制内存分配的节点为cpuset
中所允许的内存节点上 - 在
vmscan.c
,现在内存恢复到cpuset
中所允许的内存节点上
另外,每一个进程对应的文件 /proc/<pid>/status
中有四行信息描述其cpuset
信息:
|
|
每一个进程对应的文件 /proc/<pid>/cpuset
中展示了其对应的cgroup
路径信息。
cpuset cgroup控制文件
每一个cpuset
中,都会对应一组cgroup
的控制文件,包括如下:
文件 | 说明 |
---|---|
cpuset.cpus | 限制一组进程所能使用的cpu , 供用户进行设置 |
cpuset.mems | 限制一组进程所能使用的mem ,供用户进行设置 |
cpuset.effective_cpus | 显示实际进程可以使用的cpu |
cpuset.effective_mems | 显示实际进程可以使用的mem |
cpuset.memory_migrate | flag : 设置为1 时,使能memory migration , 即内存结点改变后迁移内存,默认为0 |
cpuset.cpu_exclusive | flag :设置为1 时,说明独占指定cpus ,默认为0 |
cpuset.mem_exclusive | flag :设置为1 时,说明独占指定mem ,默认为0 |
cpuset.mem_hardwall | flag :设置为1 时,说明cpuset 为Hardwall ,默认为0 |
cpuset.memory_pressure | 显示该cpuset 中的内存压力,只读文件 |
cpuset.memory_spread_page | flag :设置为1 时,内核page cache 将会均匀的分布在不同的节点上,默认值为0 |
cpuset.memory_spread_slab | flag :设置为1 时,内核slab cache 将会均匀的分布在不同的节点上,默认值为0 |
cpuset.sched_load_balance | flag :默认为值1 ,表示进程会在该cpuset 中允许的cpu 上进行负载均衡 |
cpuset.sched_relax_domain_level | 可设置的值为-1 到一个较小的整数值,只有sched_load_balance 为1 时生效,值越大,表示负载均衡时查找的cpu 范围越大 |
另外,在root cpuset
中除了上面提到的文件外,还包括控制文件:
文件 | 说明 |
---|---|
cpuset.memory_pressure_enabled | flag : 设置为1 ,表示计算每个cpuset 的内存压力,此时memory_pressure 的输出才有意义,该值默认为0 |
docker中cpuset的使用
docker
中对cpuset
的复杂功能都没有使用,即上面提到的各种flags
都是用的默认值(即都未使能),docker
只导出了两个接口:mems
和cpus
:
|
|
其实现请参考:runc代码
在实现中,新建cpuset
时mems
和cpus
的值默认为其parent cpuset
的mems
和cpus
的值。
cpuset 扩展功能
什么是 exclusive cpusets ?
如果一个cpuset
是cpu 或者
mem exclusive,那么没有其他
cpuset(直接
parent和其子
cpuset除外)会和其共享相同的
cpus和
mems`。
什么是 memory_pressure ?
memory_pressure
反应了cpuset
中的这组进程尝试释放内存的频率,一般用户可以监控该文件,然后做出合理的反应。默认情况下,该功能是disable
的。
什么是 memory spread ?
当进行内存分配时,默认从当前运行的cpu
所在的node
上分配内存(page cache
or slab cache
),当配置了memory_spread_page
和memory_spread_slab
后,分配内存时就会使用轮询算法。默认情况下,该功能是disable
的。
代码如下: kernel/cgroup/cpuset.c(line 3449-3472)
|
|
什么是 sched_load_balance ?
该值默认为1
,即打开调度CPU
的负载均衡,这里指的是cpuset
拥有的sched_domain
,默认全局的CPU
调度是本来就有负载均衡的。
简单说一下sched_domain
的作用,其实就是划定了负载均衡的CPU
范围,默认是有一个全局的sched_domain
,对所有CPU
做负载均衡的,现在再划分出一个sched_domain
把CPU
的某个子集作为负载均衡的单元。
对应到cpuset
中,即将该cpuset
所运行的cpu
集合作为一个负载均衡在单元。
什么是 sched_relax_domain_level ?
当sched_load_balance
使能后,该值代表寻找cpu
的范围,该值有几个等级,越大越优先,表示迁移时搜索CPU
的范围,这个主要开启了负载均衡选项的时候才有用。具体值代表的范围如下:
|
|
什么时候有用呢?
- 当进程在cpu间迁移时代价足够小
- searching cost 足够小,对我们没有影响
- 低延迟是相比cache miss更重要的场景