-
基于cpu cgroups进行CPU资源分配
-
Linux的sched_autogroup(全新内容)
-
基于cpuset cgroups进行进程CPU绑定(全新内容)
-
Docker和cgroups
-
Systemd和cgroups(全新内容)
-
Android对cgroups的利用
像服务器的场景,云计算的场景,手机的场景,对 cgroup 的使用真的很广泛,首先说下 cgroup 出现的原因。
1、cgroup 出现的原因
假设一个场景,有三个人 ABC 分别通过 SSH 连接到 一个 16 核 的编译服务器,分别编译 Android , 我们知道 进程调度的单位是 线程,如果这三个人都开16个线程去编译,那么ABC这三个人 分别将获得 1/3 的CPU资源 (普通进程,CFS完全公平调度),48个线程都是CPU消耗型。
但是 B 这个人 比较懂线程调度,他就在编译的时候,开32个线程,那么他就会获得更多的CPU资源。这时候想获得更多的CPU资源 就靠 “线程开得多” ,如果像B这样的人比较多的话,就会造成一个比较恶劣的结果,就是 线程之间调度的开销就会特别大,反而大家编译的就都很慢了,这样机会影响整体的效率和性能。
基于这样的一个情况,linux 就做了一个 group 群的的概念。
群和群之间,先按照 CFS 完全公平调度,然后 群里的线程 再按照 CFS 调度,这样就能避免之前的情况,A和 B 两个群的默认权重都是一样的,比如都是1024 ,那么 A B 两个群 就会平等的瓜分CPU资源,不管A群开了 1000个线程,还是B群开了10个线程。
演示一个程序,创建多个线程,每个线程都是一个死循环
#include <stdio.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
static void *thread_fun(void *param)
{
printf("thread pid:%d, pthread_self:%lu\n", getpid(),pthread_self());
while(1);
}
int main(int argc, char *argv[])
{
int n = atoi(argv[1]);
pthread_t tid[n];
int i;
printf("main pid:%d tid:pthread_self:%lu\n", getpid(),pthread_self());
for(i = 0; i < n; i++)
pthread_create(&tid[i], NULL, thread_fun, NULL);
for(i = 0; i < n; i++)
pthread_join(tid[i], NULL);
return 0;
}
由于现在的 linux 已经默认开启了 自动分群的功能,测试的时候需要先关闭。
启动 a.out 4 开启4个线程,应为是 4核的CPU ,所以CPU的使用率在 400%左右。
此时 再启动 a.out 8 再次开启8个线程,此时可以看到 开了 8个线程的CPU资源是 4个线程的2倍。
现在我们引入 群的概念,分别将两个程序 加入两个群A B 中 ,再来观察,两个 a.out 的 pid 分别是 3416 3411
之后就可看到,两个 a.out 的 CPU 利用率就差不多一样了,都是 200%左右,这就是群间的CFS调度。
此时可以调整 群的权重 来 设置 CPU的利用率,比如 想让群A和B的CPU 资源比例为 1:2 , 那么就可以将 群A的 权重调整为 512
sudo sh -c 'echo 512 > /sys/fs/cgroup/cpu/A/cpu.shares'
还有两个比较重要的参数,可以配置 CPU资源的 限额
默认情况 cpu.cfs_period_us = 100000 (100ms)
cpu.cfs_quota_us = -1 ( -1 最大的正整数,就是不限制的意思)
现在就可以调整 cfs_quota_us 来 调整 CPU的资源配置,比如给 群 A 配置为 30% , 给群B 配置成 50%
group 与 group 之间 除了 可以 靠 权重 cpu.shares PK以外,还可以靠 cpu.cfs_quota_us 来限制 CPU的资源配额,需要注意的地方是 cfs_quota_us 是可以 大于 cfs_period_us 的,因为 linux里面,计算 cpu 的占用率 是以 单核为单位的,两核的cpu , 如果开了2个线程,cpu就可以达到 200% ,
上图 在一个 4核 CPU上面,将 群B的cfs_quota_us 配置 为 500000(500ms) ,设置最大限额为 500% , 但是 群A 的限额是 30%, A 和 B 两群是CFS调度,4核CPU最大400%,所以 CPU的配额如上图所示。
这服务器领域,谁付的钱多,我给你的CPU资源就越多,就是通过这个原理来调整的。
2 、Linus 祖师爷 都称赞的 自动分群补丁
上面的实验,虽然说 分群是能解决问题的,但是 每次都来分群是很麻烦的,这时候不得不说,
在linux 发展历史上 一个特别小的补丁,只有200多行,但是获得了 Linus 祖师爷的赞美,
这个补丁做了什么事情呢?
每开一个 terminal ,或者 开一个 SSH terminal ,都会是一个新的 session , 都会有一个 session id , 这个 session id 是和 bash id 是相同的。每开一个 session 都会自动分群,自动创建 一个 group, 这个事情 不需要你人为的来控制。 之前的测试会成功,因为是 我们将 自动分群给关闭了。
开启自动分群后,有下面几个概念
- echo $$ 可以查看 当前的 bash shell id
- ps -C a.out o pid,ppid,pgid,sid,comm 可以查看进程的 session id
- session id = bash shell id
- 同一个shell 或者 session 里的进程,都在 同一个 群, 群内 进程 CFS调度,谁的线程多,谁的资源多。
- 不同 shell 的进程,在不同的群,默认情况权重一样,CFS 公平调度
3、 docker 和 cgroup
docker 在 启动一个容器的时候,会自动进入一个群里面,
可以看到, 启动一个 docker 容器后, 就会在 /sys/fs/cgroup/cpu/docker/ 下根据 容器id 创建一个 group , 这个group 中,可以配置权重,可以配置 cpu的资源限制
下图中,进程 5559 是在容器里跑的一个进程,在主机ps命令可以看到,cat /proc/5559/cgroup 可以看到,这个进程的 cpu group , memory group , blkio group ,这也是 linux 铁三角的内容,docker 都对这些资源都体现到了
4、systemd 是怎么使用 cgroup 的
slice 只对应一种目录结构,不对应任何进程,对应进程是 scope 和 service , service的进程是靠 配置文件启动的,而 scope 的进程是外部的进程,后来又被 systemd接管了,就这样组成了一个树形关系
systemd-cgls
命令可以查看
service 是 怎么和 cgroup 是怎么对应起来的呢?
默认情况下,如果 service 文件 没有配置 cgroup 资源,那么这个service 对应的进程,都跟随
/sys/fs/cgroup/cpu 或者 /sys/fs/cgroup/memory 或者其他group 根目录下的默认配置,就不会有新的group建立。 如果在 service 文件中配置了cpu group的资源,就会 在 /sys/fs/cgroup/cpu/a.slice/b.service 建立一个新的 group ,遵循新的资源配置。
一般来说 启动一个service 要使用 配置文件来启动,但是也是可以使用 一个命令 systemd-run 来启动。--unit 是进程的集合,就叫test,slice 就是一种目录结构
sudo systemd-run --unit=test --slice=test ./a.out 4
使用 systemd-cgls 命令查看 ,已经按照 service 启动了
top 命令查看,a.out 占用的cpu是 很大的,接近 400% , 这是因为 在启动 test service的时候,并没有对 cgroup 的资源进行限制,所以Linux 并不会 在 /sys/fs/cgroup/cpu/ 目录下新建一个 service的 group 。
同样的道理,使用 systemd-cgtop 命令 可以查看以 group 为单位的资源消耗,在这里根本找不到test.service, 只能看到 / group的消耗是最大的。
现在 停止这个 service ,重新启动 test.service ,进行 cpu 资源的限制
sudo systemctl stop test
sudo systemd-run -p CPUQuota=25% -p CPUShares=600 -p MemoryLimit=500M --unit=test --slice=test ./a.out 4
此时 systemd-cgtop 命令 就可以查看到 test.service 的组啦,而且 cpu限制到 25%
也可以使用 systemctl 来修改 一个 service 的资源
systemctl set-property --runtime test.service CPUShares=500 MemoryLimit=100M
那一般在 service 配置文件中怎么配置呢?可以按照下面的方法来。一般情况下都是按照下面的方式来设置的。按照配置文件启动的称之为 persistent service ,临时启动的service ,称之为transient service。
现在的这个时代, systemd 已经是一统天下了,在 Ubuntu 等很多发行版都是使用 systemd的。
所以在用户态编程,还是要多学习 systemd 的。
6、cpuset 限制 cpu 和 mem
在 Linux 中 可以多使用 apropos 命令根据字符串来查看 API ,下面就是一个查看 多核CPU 负载均衡API的例子:
之前 设置 多核负载均衡的方法,一种是 线程API 设置 affinity , 另外一种是 taskset 命令来设置affinity ,这当然都是在没有 cgroup 的时代采用的方法,现在虽然也都能用。
当 cgroup 出现以后,其实有一种更好的方法来做。cpuset 也是一种 cgroup
cpuset 在 NUMA 的系统架构中,使用的比较多 。
SMP的系统架构,是多个CPU 共享一片 memory 。
NUMA的系统架构,多个CPU ,有多片 memory。
NUMA系统架构的出现,是由于 SMP 架构当 CPU核数特别多的时候,比如1000个core ,但是只有一片 memory ,因为cpu 是很快的,比memory 要快很多,这样 1000个core去访问 同一片 memory的时候,memory的 bandwidth 就拖累了CPU的性能。所以NUMA架构出现了。
cpuset 可以将 cpu 的 cores 和 一些 memory 节点 都可以 拉到一个 group 中,然后 也可以将一个进程,或者一组进程 放到这个 group 中,共享CPU和MEM资源。
比如在我的 SMP 4核 1片 memory 机器上 ,这个两个值是可以设置的。
下面在 SMP 架构的 4核 上跑一个例子,创建一个 cpuset A , 将 0-1两个核加进去,因为就一个memory节点,就把memory 节点0 加进去,把a.out 4 加进去。top 命令可以看到 只在前两个核上跑,而且cpu利用率在200%
cpuset 的好处就在于,不仅可以控制 cpu 核,还可以控制内存,这样对于NUMA的,数据访问延迟,就有很大的作用。