Linux control group笔记
Linux CGroup(Control Groups)是一个强大的内核功能,用于限制、记录和隔离进程组(process groups)使用的系统资源(如 CPU、内存、磁盘 I/O、网络等)。它通过将进程分组并对这些组进行资源分配和控制,来实现资源的精细化管理。
🔧 CGroup 核心概念
CGroup 主要围绕以下几个核心概念构建:
- 子系统 (Subsystem):也称为控制器,是资源控制的具体模块。例如:
cpu
:控制 CPU 访问。memory
:限制内存使用。blkio
:限制块设备 I/O。cpuset
:分配 CPU 和内存节点。pids
:限制进程数量。
- 控制组 (CGroup):一组进程的集合。每个控制组都可以关联一个或多个子系统,并为这些子系统设置特定的资源限制参数。
- 层级结构 (Hierarchy)`:控制组以树形结构组织。子控制组默认继承父控制组的属性,但也可自定义。
- 任务 (Tasks):任务对应进程或线程。任务可以加入某个控制组,从而受到该控制组所有子系统限制规则的约束。
📊 CGroup 的版本
CGroup 主要有两个版本:v1 和 v2。v2 旨在简化设计并统一资源配置,许多现代 Linux 发行版已默认使用 cgroup v2(检查 /sys/fs/cgroup/cgroup.controllers
是否存在)。两者在操作和功能上有些差异,建议新项目优先考虑 v2。
🛠️ CGroup 的管理与使用
管理 CGroup 主要有两种方式:
-
通过虚拟文件系统 (VFS) 手动操作:CGroup 通过一个虚拟文件系统(通常挂载在
/sys/fs/cgroup
)暴露其接口。你可以通过创建目录、读写文件来创建控制组、设置参数和添加进程。 -
使用命令行工具:如
cgroup-tools
包(例如cgcreate
,cgset
,cgexec
)提供了更方便的命令行操作。
cgcreate
cgdelete
lscgroup
cgset
cggetsystemd-cgls
systemd-cgtop
常用命令示例
以下是一些使用 cgroup-tools
的常见命令示例(部分命令在 v2 中可能有所不同或需调整):
功能 | 命令示例 | 说明 |
---|---|---|
创建控制组 | sudo cgcreate -g cpu,memory:mygroup | 创建名为 mygroup 的控制组,并关联 cpu 和 memory 控制器。 |
设置 CPU 限制 | sudo cgset -r cpu.max='50000 100000' mygroup (v2) | 在 v2 中,设置 mygroup 在周期 100000us 内最多使用 50000us 的 CPU 时间(即 50% 的单核利用率)。 |
sudo cgset -r cpu.cfs_quota_us=50000 -r cpu.cfs_period_us=100000 mygroup (v1) | 在 v1 中实现同上限制。 | |
设置内存限制 | sudo cgset -r memory.max=100M mygroup (v2) | 限制 mygroup 最大内存使用为 100MB。 |
sudo cgset -r memory.limit_in_bytes=100M mygroup (v1) | v1 中的内存限制设置。 | |
在 CGroup 中运行进程 | sudo cgexec -g cpu,memory:mygroup /path/to/command | 在 mygroup 控制组中启动一个进程。 |
将现有进程加入 CGroup | sudo cgclassify -g cpu,memory:mygroup 1234 | 将 PID 为 1234 的进程加入到 mygroup 控制组。 |
`echo 1234 | sudo tee /sys/fs/cgroup/mygroup/cgroup.procs` | |
删除控制组 | sudo cgdelete mygroup | 删除名为 mygroup 的控制组。 |
直接操作文件系统示例
你也可以直接与 /sys/fs/cgroup
下的文件交互:
# 创建控制组 (v1 示例,在cpu控制器下创建mygroup)
sudo mkdir /sys/fs/cgroup/cpu/mygroup# 设置CPU限制 (v1)
echo 100000 | sudo tee /sys/fs/cgroup/cpu/mygroup/cpu.cfs_period_us
echo 50000 | sudo tee /sys/fs/cgroup/cpu/mygroup/cpu.cfs_quota_us # 限制50%# 将进程加入控制组
echo 1234 | sudo tee /sys/fs/cgroup/cpu/mygroup/cgroup.procs# 删除控制组
sudo rmdir /sys/fs/cgroup/cpu/mygroup
💻 C++ 中的 CGroup API 调用
CGroup 主要通过虚拟文件系统提供接口,因此 C++ 中操作 CGroup 本质上就是对文件系统进行读写操作。你可以使用标准的 C++ 文件操作库(如 <fstream>
)或 Linux 系统调用(如 open
, write
, read
)。
下面是一个简单的例子,演示如何使用 C++ 将当前进程添加到指定 CGroup(这里以 v2 的 cgroup.procs
为例):
#include <fstream>
#include <iostream>
#include <string>bool add_self_to_cgroup(const std::string& cgroup_path) {// 获取当前进程的 PIDpid_t pid = getpid();// 构建 cgroup.procs 文件的完整路径std::string procs_file_path = cgroup_path + "/cgroup.procs";// 以写入模式打开文件std::ofstream procs_file(procs_file_path);if (!procs_file.is_open()) {std::cerr << "Failed to open " << procs_file_path << std::endl;return false;}// 将 PID 写入文件procs_file << pid;if (procs_file.fail()) {std::cerr << "Failed to write PID to " << procs_file_path << std::endl;procs_file.close();return false;}procs_file.close();std::cout << "Successfully added PID " << pid << " to " << cgroup_path << std::endl;return true;
}int main() {// 假设你要加入的 CGroup 路径 (需要根据实际情况修改)std::string my_cgroup_path = "/sys/fs/cgroup/my_cgroup";if (add_self_to_cgroup(my_cgroup_path)) {// 进程现在已经在 CGroup 中了,后续的资源使用将受到限制// ... 在这里执行你的受限制任务 ...std::cout << "Process is now within the cgroup. Running restricted task..." << std::endl;// 模拟一些工作volatile int i = 0;while (i < 100000000) { i++; }} else {return 1;}return 0;
}
重要说明:
- 这段代码需要以 root 权限运行,因为通常只有 root 用户才能修改 CGroup 配置。
- 代码中的 CGroup 路径 (
my_cgroup_path
) 需要根据你的系统实际情况和要使用的控制器进行修改。对于 cgroup v2,通常挂载在/sys/fs/cgroup
,而你需要在其下创建或使用已有的子目录。 - 这只是一个简单的示例,演示如何添加进程。要设置资源限制(如 CPU、内存),你还需要在运行此程序前,手动使用
cgset
、echo
到相应文件等方式,提前为这个 CGroup 配置好限制参数(例如设置my_cgroup/cpu.max
)。 - 更复杂的操作(如创建 CGroup、设置各种参数)同样可以通过 C++ 代码读写其他 CGroup 接口文件来实现。逻辑类似:构建正确的文件路径,然后写入符合格式要求的字符串。
⚠️ 注意事项
- 权限问题:操作 CGroup 通常需要 root 权限。
- 资源争抢:CGroup 的 CPU 限制(如
cpu.shares
)只在进程竞争 CPU 时生效。如果一个进程空闲,它仍然可以使用所有可用 CPU;只有当多个进程需要 CPU 时,份额才起作用。 - 版本差异:务必注意你使用的 Linux 系统是 cgroup v1 还是 v2,因为它们的控制器、可调参数和文件系统结构可能不同。检查
/sys/fs/cgroup
的挂载情况。 - 系统服务管理:许多现代 Linux 发行版使用 systemd,它自身就使用 CGroup 来管理和隔离服务。你可以使用
systemctl set-property
命令来调整系统服务的资源限制。 - 错误处理:在实际的程序中,务必添加更健壮的错误处理。
💎 总结
CGroup 是 Linux 系统资源管理的利器。你可以通过:
- 命令行工具(如
cgroup-tools
)快速上手和管理。 - 直接操作
/sys/fs/cgroup
下的文件来灵活控制。 - 在 C++ 程序中使用文件操作 API 来以编程方式与 CGroup 交互,实现进程的资源限制。
希望这些介绍和示例能帮助你更好地理解和使用 Linux CGroup。
⚙️ 查看进程所属的 CGroup
每个运行中的进程在 /proc/
目录下都有对应的子目录,其中 cgroup
文件明确记录了该进程属于哪个 CGroup。
操作命令:
cat /proc/<PID>/cgroup
输出示例:
cat /proc/2467/cgroup
0::/system.slice/example.service
这里的 0::
在 cgroup v2 中表示系统使用的唯一层级。/system.slice/example.service
则指明了该进程属于 example.service
这个 systemd 单元对应的 CGroup。
对于 cgroup v1,输出会更复杂,会显示进程在各个控制器(如 cpu, memory)层级中的路径:
12:memory:/user.slice/user-1000.slice
9:blkio:/user.slice/user-1000.slice
8:net_cls,net_prio:/
7:cpu,cpuacct:/user.slice/user-1000.slice
6:perf_event:/
5:freezer:/
4:cpuset:/
3:pids:/user.slice/user-1000.slice
2:devices:/user.slice/user-1000.slice
1:name=systemd:/user.slice/user-1000.slice/session-3.scope
每行由冒号分隔的三部分组成:
- 第一列:层级ID(Hierarchy ID),对应
/proc/cgroups
中的信息。 - 第二列:绑定的控制器(subsystem)列表,多个控制器用逗号隔开。
- 第三列:该进程在此控制器层级中的路径(相对于CGroup挂载点的路径)。
若进程在容器中,此文件内容通常包含容器ID,例如:
0::/kubepods/besteffort/pod779e55c6-0467-4431-997f-25a71a8e8a8e/a9ccdd00512985cb6e6c8dff176cb3086a989e477200c9a1cfdf8749182fc1da
其中的长哈希串 a9ccdd00512985cb6e6c8dff176cb3086a989e477200c9a1cfdef8749182fc1da
通常就是容器ID。
通过进程名查找PID并查看CGroup
若不确定进程PID,可通过进程名先查找PID:
# 方法1:使用 ps 和 grep
ps -eo pid,comm | grep <进程名># 方法2:使用 pgrep(更简洁)
pgrep <进程名>
然后对找到的PID执行 cat /proc/<PID>/cgroup
。
也可写成一个简单的脚本:
#!/bin/bash
echo "查看进程 '$1' 的CGroup信息:"
for PID in $(pgrep "$1"); doecho "PID: $PID"cat /proc/$PID/cgroupecho "------"
done
保存为 find_cgroup.sh
后使用 bash find_cgroup.sh <进程名>
执行。
🔍 查看CGroup包含的进程
要查看一个CGroup包含了哪些进程,主要可通过查看CGroup文件系统中的应用文件来实现。
1. 通过 cgroup.procs
文件
在CGroup目录中,cgroup.procs
文件列出了该CGroup中的所有进程ID。
操作命令:
cat /sys/fs/cgroup/<控制器>/<CGroup路径>/cgroup.procs
例如:
# 查看 system.slice/example.service 这个CGroup中的进程(假设在v2的unified层级下)
cat /sys/fs/cgroup/system.slice/example.service/cgroup.procs# 查看 v1 中 memory 控制器下 /user.slice/user-1000.slice 的进程
cat /sys/fs/cgroup/memory/user.slice/user-1000.slice/cgroup.procs
2. 通过 tasks
文件(主要见于cgroup v1)
在cgroup v1中,tasks
文件列出了该CGroup中的所有线程ID。一个进程的所有线程可能分布在不同的CGroup中。
操作命令:
cat /sys/fs/cgroup/<控制器>/<CGroup路径>/tasks
注意:cgroup v2 中已移除了
tasks
文件,统一使用cgroup.procs
。
3. 使用 cgclassify
命令
cgclassify
命令可用于将进程分类到指定CGroup,但结合 -g
参数也能用来查询特定进程所属的CGroup(此用法更侧重于查看进程所属CGroup,而非直接列出CGroup中的所有进程)。
sudo cgclassify -g <控制器>:<CGroup路径> <进程PID>
但更常用于查看进程所属CGroup信息的是 /proc/<PID>/cgroup
文件。
4. 使用 systemd-cgtop
(systemd-cgls
)
systemd-cgtop
命令可以像一个“顶级”命令一样,以交互方式按层级实时显示各CGroup的资源使用情况(如CPU、内存),同时也会显示出对应的CGroup名称。
操作命令:
systemd-cgtop# output
Path Tasks %CPU Memory Input/s Output/s
/system.slice 85 5.0 500.0M - -
/system.slice/example.service 1 80.0 10.0M - -
/user.slice 50 2.0 200.0M - -ubuntu:/proc$ systemd-cgls
Control group /:
-.slice
├─user.slice
│ └─user-1000.slice
│ ├─user@1000.service
│ │ ├─session.slice
│ │ │ ├─org.gnome.SettingsDaemon.MediaKeys.service
│ │ │ │ └─2614 /usr/libexec/gsd-media-keys
│ │ │ ├─org.gnome.SettingsDaemon.Smartcard.service
│ │ │ │ └─2643 /usr/libexec/gsd-smartcard
│ │ │ ├─org.gnome.SettingsDaemon.Datetime.service
│ │ │ │ └─2608 /usr/libexec/gsd-datetime
│ │ │ ├─xdg-document-portal.service
│ │ │ │ ├─2048 /usr/libexec/xdg-document-portal
│ │ │ │ └─2059 fusermount3 -o rw,nosuid,nodev,fsname=portal,auto_unmount,subtype=portal>
│ │ │ ├─org.gnome.SettingsDaemon.Housekeeping.service
│ │ │ │ └─2609 /usr/libexec/gsd-housekeeping
│ │ │ ├─org.gnome.Shell@x11.service
│ │ │ │ ├─ 2406 /usr/bin/gnome-shell
│ │ │ │ ├─ 8869 cat
│ │ │ │ ├─ 8870 cat
│ │ │ │ ├─ 11977 /disk2/soft2/WindTerm_2.5.0_2nd/WindTerm
│ │ │ │ ├─ 12025 /bin/bash -i -l
│ │ │ │ ├─ 12040 /bin/bash -i -l
│ │ │ │ ├─ 12047 /bin/bash -i -l
│ │ │ │ ├─ 12120 /bin/bash -i -l
│ │ │ │ ├─ 12176 docker start -i 106850ccf3bc
│ │ │ │ ├─ 71449 gjs /usr/share/gnome-shell/extensions/ding@rastersoft.com/ding.js -E >
│ │ │ │ ├─ 434225 /bin/bash -i -l
│ │ │ │ ├─ 461638 /bin/bash -i -l
│ │ │ │ ├─ 479174 docker start -i aee853fd0881
│ │ │ │ ├─1767812 /bin/bash -i -l
│ │ │ │ ├─1767852 /bin/bash -i -l
│ │ │ │ ├─2658314 systemd-cgls
│ │ │ │ ├─2658315 pager
│ │ │ │ └─4055392 /bin/bash -i -l
这能帮你快速了解哪些CGroup正在消耗资源。
📊 快速对比:查看CGroup与进程归属的方法
下表总结了上述方法的主要用途和特点:
查询目标 | 主要方法/命令 | 关键文件或参数 | 说明 |
---|---|---|---|
进程所属的CGroup | cat /proc/<PID>/cgroup | /proc/<PID>/cgroup | 最直接的方法,同时适用于cgroup v1和v2。 |
cgclassify (查询) | -g <控制器>:<CGroup路径> <PID> | 更常用于分类进程,查询信息不如/proc 直观。 | |
CGroup包含的进程 | cat /sys/fs/cgroup/.../cgroup.procs | cgroup.procs | 列出CGroup中的所有进程ID(v1和v2均适用)。 |
cat /sys/fs/cgroup/.../tasks (v1) | tasks | 列出CGroup中的所有线程ID(主要用于cgroup v1)。 | |
监控CGroup资源与进程归属 | systemd-cgtop | - | 交互式实时监控各CGroup资源使用情况,会显示CGroup名称。 |
💎 实用技巧与注意事项
- 区分CGroup版本:你的系统可能使用 cgroup v1 或 v2,两者在文件系统结构和一些文件上有差异(如v2移除了
tasks
文件)。检查/sys/fs/cgroup
的挂载情况可以判断版本。 - 权限问题:读取
/proc/<PID>/cgroup
通常不需要特殊权限(只要你有权访问该进程)。但查看或操作/sys/fs/cgroup/
下的文件,尤其是修改,通常需要root
权限。 - 容器环境:在容器环境中,
/proc/<PID>/cgroup
文件的内容是识别进程属于哪个容器的重要依据,因为路径常包含容器ID。 - 使用
lscgroup
命令:有些发行版可能安装了cgroup-tools
包,其中包含lscgroup
命令,可用于列出所有存在的CGroup。
掌握这些方法,你就能轻松地在进程和CGroup之间进行双向查找了。