《CPU调度器相关技术:cgroups v2的应用与实践》上半部分
在系统资源管理中,cgroups(控制组)是一项非常重要的技术,尤其是cgroups v2版本,它为我们管理系统资源提供了更强大的功能。本文将详细介绍cgroups v2的相关工具和应用,帮助大家更好地理解和使用这一技术。
1. cgroups v2探索脚本
现有的cgroups可视化工具存在一些问题,比如我们无法立即判断一个cgroup是否有进程,即使有进程,也不能直接看出其中哪些控制器是启用或禁用的。为了解决这些问题,我们有一个Bash脚本 cgroupsv2_explore ( 脚本链接 ),它可以帮助我们更清晰地了解cgroup的信息。
该脚本的主要功能如下:
- 递归遍历 :可以将一个起始cgroup作为参数传入,递归遍历所有嵌套的cgroups。如果不指定起始cgroup,则从cgroup树的根目录 /sys/fs/cgroup 开始扫描整个树。
- 信息展示 :对于每个解析的cgroup,首先检查它是否有活动进程。如果没有,则跳过该cgroup;如果有,则显示以下相关内容:
- 子控制器 :即该cgroup的 cgroup.subtree_control 伪文件的内容。
- cgroup类型 :如domain、domain threaded、threaded等。
- 冻结状态 :用0或1表示,对应no或yes。
- 所属进程 :默认显示进程数量(括号内)和PID列表。如果使用 -p 选项,则通过 ps 命令显示进程信息。
- 所属线程 :默认显示线程数量(括号内)和PID列表。如果使用 -t 选项,则通过 ps 命令显示线程信息。
- 部分控制器数据 :目前包括CPU和内存相关数据,且这部分功能还在不断发展。
此外,该脚本还接受以下选项开关:
- -d :控制扫描树的深度。
- -v :以详细模式显示信息。
- -p / -t :显示每个cgroup所属的进程和/或线程。
下面是一个简单的流程图,展示了脚本的基本工作流程:
graph TD;
A[开始] --> B{指定起始cgroup?};
B -- 是 --> C[从指定cgroup开始遍历];
B -- 否 --> D[从根目录开始遍历];
C --> E{当前cgroup有进程?};
D --> E;
E -- 是 --> F[显示相关信息];
E -- 否 --> G[跳过该cgroup];
F --> H[继续遍历下一个cgroup];
G --> H;
H --> I{遍历结束?};
I -- 否 --> E;
I -- 是 --> J[结束];
我们可以运行该脚本,设置深度为1,以减少输出信息。例如:
# 运行脚本,深度为1
# 这里省略了具体的输出截图,输出格式与上述描述对应
从输出中我们可以看到,不同的cgroup具有不同的特性。比如 /sys/fs/cgroup/init.scope cgroup没有子控制器,这意味着没有对其施加资源约束;而 /sys/fs/cgroup/system.slice cgroup启用了内存和pids子控制器,系统会根据其中指定的约束来管理这些资源。同时,还能发现一些cgroup(如从 machine.slice 到 sys-kernel-tracing.mount 等)是空的,没有活动进程。
我们可以将特定的cgroup作为参数传递给脚本,它会递归显示该cgroup及其所有子cgroup的元数据。大家可以自己尝试在不同的cgroup上运行这个脚本,加深对其功能的理解。
2. 通过cgroups v2限制CPU资源
在CPU(任务)调度方面,我们可以通过两种方式为cgroup内的进程指定CPU使用的资源约束:
- 利用systemd(简单方式) :借助在启动时启动的服务来设置约束。
- 手动方式 :通过Bash脚本创建和管理cgroup(v2),为演示应用设置CPU使用的资源约束。
接下来我们先介绍如何利用systemd设置CPU资源约束。
2.1 利用systemd设置CPU资源约束
Systemd可以作为cgroups之上的抽象层,让系统管理员能够轻松地为cgroups设置资源约束。管理员或root用户可以定义systemd服务,这些服务会在系统启动时自动启动,并且可以在服务单元文件中指定进程的各种属性和约束。
一个systemd服务单元文件通常命名为 <foo>.service ,一般可以在 /etc/systemd/system 目录下找到系统定义的服务单元文件。它是一个纯ASCII文本文件,分为几个部分,如 [Unit] 、 [Service] 、 [Install] 等。例如,在 [Service] 部分的 ExecStart= 指令可以指定要运行的应用程序的路径。
在 [Service] 部分,我们可以使用以下指令来设置CPU相关的约束:
- CPUQuota= :可以设置服务最多可分配的处理器周期(CPU时间)的百分比。
- AllowedCPUs=n :设置服务在执行时最多可使用的CPU核心数。
此外,还可以对服务应用内存、进程、I/O和网络等方面的约束,具体可以查看 systemd.resource-control(5) 的手册页。同时,我们还可以使用systemd轻松设置服务单元的nice值、CPU调度策略、优先级和CPU亲和性掩码等。
为了演示,我们有一个C程序用于生成质数(从2到指定的最大值),该程序接受两个参数:
- 生成质数的最大数值。
- 允许运行的最大时间(以秒为单位,使用闹钟机制,时间到了程序会自动终止)。
当该程序在没有(CPU)资源约束的情况下运行时,在给定的时间内可以生成大量的质数;而在通过systemd使用cgroups v2内核技术设置了严格的CPU约束后,生成的质数数量会相对较少。
下面是一个没有任何约束的质数生成器服务单元文件示例:
$ cd <book_src>/ch11/cgroups/cpu_constrain/systemd_svcunit
$ ls
run_primegen svc1_primes_normal.service svc3_primes_lowram.service
setup_service svc2_primes_lowcpu.service
$ cat svc1_primes_normal.service
# svc1_primes_normal.service
# NORMAL version, no artificial CPU (or other) constraints applied
[Unit]
Description=My test prime numbers generator app to launch at boot (normal
version)
[ … ]
After=mount.target
[Service]
# run_primegen: the script that launches the app.
# Our setup_service script ensures it copies all required files to this
location.
# UPDATE the /path/to/executable if required.
ExecStart=/usr/local/bin/systemd_svcunit_demo/run_primegen
# Optional: Apply 'better' cpu sched settings for this process
CPUSchedulingPolicy=fifo
CPUSchedulingPriority=83
# (Below) So that the child process - primegen - runs with these sched
settings!
# (well it's anyway the default)
CPUSchedulingResetOnFork=false
# Nice value applies for only the default 'other/normal' cpu sched policy
#Nice=-20
[ … ]
# UPDATE to your preference
[Install]
WantedBy=graphical.target
#WantedBy=multi-user.target
$
一个小的bash脚本 run_primegen 会被执行,它会调用质数生成器程序,具体命令如下:
/usr/local/bin/systemd_svcunit_demo/primegen 100000 3
这意味着该程序会尝试在3秒内生成最多到100,000的质数。
2.2 无资源约束的测试运行
我们执行设置脚本并传递服务单元文件作为参数,让systemd执行 run_primegen 程序:
# 执行设置脚本
# 这里省略了具体的输出截图,使用systemctl status <service.unit>命令可以查看服务的状态和输出信息
在这次运行中,程序在没有资源(CPU)约束的情况下,以SCHED_FIFO策略和高优先级运行,在3秒内成功生成了从2到99,991的质数。不过,生成的质数数量会因硬件系统的不同而有所差异。可以使用 journalctl -b 命令查看完整的输出信息。
我们的脚本在运行一次后会故意禁用该服务,如果想让服务在每次系统启动时都运行,可以将 setup_service 脚本中的 KEEP_PROGRAM_ENABLED_ON_BOOT 变量设置为1。
使用 systemctl show <service-unit-name> 命令可以查看服务单元的所有设置,我们可以通过 grep 命令过滤出与CPU相关的设置:
# 查看服务单元的CPU相关设置
# 这里省略了具体的输出截图,我们可以看到在服务单元文件中明确指定的CPU设置
2.3 有资源约束的测试运行
现在我们使用另一个服务单元文件 svc2_primes_lowcpu.service 来运行相同的质数生成程序,但这次设置了一些明确的CPU约束:
#--- Apply CPU constraints ---
CPUQuota=10%
AllowedCPUs=1
同时,移除了 CPUSchedulingPolicy=fifo 和 CPUSchedulingPriority=83 这两行,将进程的调度策略设置为默认的SCHED_OTHER,实时优先级设置为0,并将其CPU带宽限制为10%,只能使用1个核心。
运行该服务并检查状态:
$ ./setup_service svc2_primes_lowcpu.service
[ … ]
$ systemctl status svc2_primes_lowcpu.service --no-pager -l
○ svc2_primes_lowcpu.service - My test prime numbers generator app to launch at
boot (CPU constrained version)
Loaded: loaded (/usr/lib/systemd/system/svc2_primes_lowcpu.service;
disabled; preset: disabled)
Drop-In: /usr/lib/systemd/system/service.d
└─10-timeout-abort.conf
Active: inactive (dead)
Aug 27 17:26:27 fedora run_primegen[52673]: 30347, 30367, 30389, 30391,
30403, 30427, 30431, 30449, 30467, 30469, 30491, 30493, 30497, 30509,
30517, 30529,
[ … ]
Aug 27 17:26:28 fedora run_primegen[52673]: 31531, 31541, primegen.c:buzz()
Aug 27 17:26:28 fedora run_primegen[52672]: Terminated
Aug 27 17:26:28 fedora systemd[1]: svc2_primes_lowcpu.service: Deactivated
successfully.
$
这次,程序在只有10% CPU带宽、只能使用1个核心、SCHED_OTHER调度策略和实时优先级为0的约束下,在3秒内只生成了最多到31,541的质数。与第一次无约束的情况相比,第一次生成的质数数量比第二次多了近70%,这充分证明了systemd cgroups控制的有效性。
另外,我们还提供了一个示例服务单元 svc3_primes_lowram.service ,用于演示如何在cgroup上设置内存限制。在该服务单元中,通过 MemoryHigh 和 MemoryMax 等systemd设置来指定cgroup的内存限制。服务中故意使用 stress-ng 程序分配大量内存,从而触发内存限制,可能会导致OOM杀手(或配置的 systemd-oomd 进程)杀死cgroup中的任务。在运行该服务时要小心,建议在测试虚拟机上进行。
《CPU调度器相关技术:cgroups v2的应用与实践》下半部分
3. 手动方式:cgroups v2 CPU控制器
接下来,我们将手动在系统的cgroups v2层次结构下创建一个新的cgroup,并为其设置CPU控制器,对该cgroup内的进程可使用的CPU带宽设置用户指定的上限,然后运行质数生成程序,观察其受约束的情况。
以下是通常需要采取的步骤(所有步骤都需要以root权限运行):
1. 确保内核支持cgroups v2 :建议运行4.5或更高版本的内核,并启用cgroups v2支持。可以通过以下命令检查:
mount | grep cgroup
若输出包含“type cgroup2”,则表示支持。
2. 创建cgroup :在cgroups v2层次结构下创建一个目录作为新的cgroup。例如,创建名为 test_group 的子组:
mkdir /sys/fs/cgroup/test_group
- 添加CPU控制器 :为新的cgroup添加CPU控制器,通过向
cgroup.subtree_control伪文件写入+cpu来实现:
echo "+cpu" > /sys/fs/cgroup/test_group/cgroup.subtree_control
需要注意的是,没有控制器就不会对cgroup及其子组施加资源约束。
4. 设置CPU带宽上限 :向 cpu.max 伪文件写入两个整数,以设置该cgroup内进程可使用的最大CPU带宽。该文件的格式为 $MAX $PERIOD ,表示在每个 $PERIOD 时间段内,该组最多可使用 $MAX 的CPU时间。默认情况下, MAX 等于 PERIOD ,即100%的CPU利用率。例如,设置 MAX = 300000 , PERIOD = 1000000 ,表示30%的CPU带宽利用率:
echo "300000 1000000" > /sys/fs/cgroup/test_group/cpu.max
- 将进程添加到cgroup :将进程的PID写入
cgroup.procs伪文件,即可将进程添加到新的cgroup中:
echo <PID> > /sys/fs/cgroup/test_group/cgroup.procs
完成上述步骤后,新cgroup内的进程将在设置的CPU带宽约束下运行。使用完毕后,可以使用 rmdir 命令删除cgroup:
rmdir /sys/fs/cgroup/test_group
为了方便操作,有一个Bash脚本 cgv2_cpu_ctrl.sh ( 脚本链接 ),允许将最大允许的CPU带宽作为参数传递。脚本创建并设置好新的cgroup后,会运行质数生成程序:
primegen 1000000 5
与之前使用systemd的情况类似,但这里要求生成更多的质数,并给予5秒的运行时间,以便脚本有时间查询并将其PID写入 cgroup.procs 文件。
以下是脚本的使用示例:
$ sudo ./cgv2_cpu_ctrl.sh
[+] Checking for cgroup v2 kernel support
cgv2_cpu_ctrl.sh: detected cgroup2 fs here: /sys/fs/cgroup
Usage: cgv2_cpu_ctrl.sh max-to-utilize(us) [run-cgroupsv2_explore-script]
max-to-utilize : REQUIRED: This value (microseconds) is the max amount of
time the processes in the control group we create will be allowed to utilize
the
CPU; it's relative to the period, which is set to the value 1000000.
So, f.e., passing the value 300,000 (out of 1,000,000) implies a max CPU
utiltization
of 0.3 seconds out of 1 second (i.e., 30% utilization).
The valid range for the $MAX value is [1000-1000000].
run-cgroupsv2_explore-script : OPTIONAL: OFF by default.
Passing 1 here has the script invoke our cgroupsv2_explore bash script,
passing
the new cgroup as the one to show details of.
运行脚本时,需要指定最大允许的CPU带宽(单位:微秒),该值应在1000 - 1000000之间。
以下是设置80% CPU带宽的测试运行示例:
$ sudo ./cgv2_cpu_ctrl.sh 800000
[+] Checking for cgroup v2 kernel support
cgv2_cpu_ctrl.sh: detected cgroup2 fs here: /sys/fs/cgroup
[+] Creating a cgroup here: /sys/fs/cgroup/test_group
[+] Adding a 'cpu' controller to it's cgroups v2 subtree_control file
***
Now allowing 800000 out of a period of 1000000 to all processes in this cgroup,
i.e., 80.000% !
***
[+] Launch the prime number generator process now ...
../primegen/primegen 1000000 5 &
2, 3, 5, 7, 11, 13, 17, 19, 23, 29,
31, 37, 41, 43, 47, 53,
[ … ]
3071 pts/1 00:00:00 primegen
[+] Insert the 3071 process into our new CPU ctrl cgroup
227, 229, 233, 239, 241, 251, 257, 263, 269, 271,
277, 281, 283, 293, 307, 311,
cat /sys/fs/cgroup/test_group/cgroup.procs
3071
[ … ] 7541, 7547, 7549, 7559, 7561,
............... sleep for 6 s, allowing the program to execute ................
7573, 7577, 7583, 7589, 7591, 7603, 7607, 7621, 7639, 7643,
7649, 7669, 7673, 7681, 7687, 7691,
[ … ]
41143, 41149, 41161, 41177, 41179, 41183, 41189, 41201, 41203, 41213,
41221, 41227, 41231, 41233, 41243, 41257,
primegen.c:buzz()
[+] Removing our (cpu) cgroup
从输出可以看出,脚本验证了cgroup v2支持后,在 /sys/fs/cgroup/ 下创建了名为 test_group 的cgroup,并添加了CPU控制器,将最大允许的CPU带宽设置为800000(即80%的CPU利用率)。然后启动质数生成程序,并将其PID添加到cgroup中。在这次测试运行中,在80%的CPU带宽下,程序在5秒内生成了最多到41257的质数(该数字在不同系统上可能会有所不同),最后脚本删除了cgroup。
为了更直观地感受cgroups的效果,我们再次运行脚本,但这次将最大CPU带宽设置为1000(即0.1%的CPU利用率):
graph TD;
A[开始] --> B[设置最大CPU带宽为1000]
B --> C[创建cgroup并添加CPU控制器]
C --> D[运行质数生成程序]
D --> E[将程序PID添加到cgroup]
E --> F[程序运行并生成质数]
F --> G[删除cgroup]
G --> H[结束]
运行结果显示,在只有0.1%的CPU带宽分配下,质数生成程序只能生成从2到659的质数,与80% CPU带宽时生成的质数数量形成了鲜明对比,这清楚地证明了cgroups v2 CPU控制器的有效性。
如果在运行脚本时传递第二个参数为1,将启用 cgroupsv2_explore 脚本,显示新cgroup的详细信息:
$ sudo ./cgv2_cpu_ctrl.sh 800000 1
# 这里省略具体输出截图,输出会显示新cgroup的详细信息
此外,还可以通过调整 cpu.weight 伪文件来影响cgroup内进程分配的CPU权重,该文件默认值为100。
综上所述,通过systemd和手动方式,我们可以有效地利用cgroups v2对CPU资源进行约束和管理,根据不同的需求为进程分配合适的资源,提高系统的性能和稳定性。大家可以根据实际情况选择合适的方式进行资源管理,并通过不断实践加深对cgroups v2的理解和应用。
超级会员免费看
48

被折叠的 条评论
为什么被折叠?



