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之間進行雙向查找了。