控制群組(cgroup)會記帳並控制 Linux 工作的集合。cgroup 不是程序樹節點:工作在 fork 時繼承父項的 cgroup,但管理器之後可以遷移它。因此,cgroup 提供服務管理器與容器背後的資源邊界,而命名空間提供全域資源的檢視。
本文描述 cgroup 第二版(cgroup v2)。其文件化介面是 cgroup2 檔案系統;檔案與支援的控制器取決於執行中的核心設定與可用硬體。
統一階層與成員資格 #
Cgroup v2 具有一個統一階層。每個工作都恰好屬於該階層中的一個 cgroup,包括未命名的根 cgroup。子 cgroup 會繼承祖先允許的限制與控制器設定,因此子項不能取得其父項未委派的 CPU、記憶體或裝置。
階層由 cgroup2 掛載表示,慣例位置為 /sys/fs/cgroup:
findmnt -t cgroup2
cat /proc/self/cgroup
ps -o pid,cgroup,comm -p $$每個目錄都是 cgroup。建立目錄會建立子 cgroup,將 PID 寫入其 cgroup.procs 會遷移程序的執行緒群組:
sudo mkdir /sys/fs/cgroup/build
printf '%s\n' "$$" | sudo tee /sys/fs/cgroup/build/cgroup.procs請使用服務管理器,而不要手動移動其自身程序。例如,systemd 為 slice、scope 與服務建立並管理 cgroup;手動遷移可能違反管理器的記帳與生命週期假設。
網域與執行緒 cgroup #
一般階層由網域 cgroup 組成,其控制器將程序視為單位套用。內部無程序規則防止非根網域 cgroup 同時包含程序,並將網域控制器分發給子項。將工作移至葉節點,使用空的中間節點在子 cgroup 之間配置控制器:
根 cgroup 不受此規則限制。透過 cgroup.type 設定的執行緒子樹,允許使用 cgroup.threads 放置個別執行緒。它適用於執行緒感知控制器,並具有額外結構限制;它不會將每個控制器變成每執行緒限制。混合執行緒與網域子樹前,請檢查 cgroup.type 與核心文件。
控制器與啟用 #
控制器是實作資源原則的核心程式碼。可用性與啟用是不同概念:
cgroup.controllers列出父項已提供給 cgroup 子項的控制器。cgroup.subtree_control列出已啟用以控制那些子項的控制器。寫入+name或-name來變更它。- 控制器介面檔案只會在控制器、設定與裝置上下文使其適用的位置出現。
對新的子樹,先在父項啟用控制器,再設定子 cgroup。非根網域 cgroup 為其子項啟用網域控制器時,不得包含程序。
cd /sys/fs/cgroup
cat cgroup.controllers
sudo sh -c 'printf "+cpu +memory +pids\n" > cgroup.subtree_control'
sudo mkdir build
sudo sh -c 'printf "50000 100000\n" > build/cpu.max'
sudo sh -c 'printf "2G\n" > build/memory.max'
sudo sh -c 'printf "512\n" > build/pids.max'範例中的 cpu.max 允許每 100,000 微秒週期使用 50,000 微秒 CPU 時間。它會限制彙總 CPU 頻寬,不會保留專用 CPU。有效控制器集合仍受每個祖先限制。
控制器介面 #
根部的控制器名稱識別特定核心暴露的內容。目前 v2 文件涵蓋下列資源控制器家族:
| 控制器 | 代表檔案 | 原則 |
|---|---|---|
| CPU | cpu.weight、cpu.max、cpu.stat | 比例共用、頻寬上限與節流統計。 |
| 記憶體 | memory.current、memory.min、memory.low、memory.high、memory.max、memory.swap.max、memory.events | 記帳、保護、節流/回收壓力、硬限制、交換限制與 OOM 事件。 |
| I/O | io.weight、io.max、io.stat、io.pressure | 比例與每裝置頻寬/IOPS 控制及統計。 |
| Cpuset | cpuset.cpus、cpuset.cpus.effective、cpuset.mems、cpuset.mems.effective | 要求與有效的 CPU 及 NUMA 節點放置。 |
| PIDs | pids.current、pids.max、pids.events | 目前程序數、硬性程序限制與觸及限制事件。 |
| HugeTLB | hugetlb.<size>.current、hugetlb.<size>.max、hugetlb.<size>.events | 每種巨大頁面大小的使用量與限制。 |
| RDMA | rdma.max、rdma.current、rdma.events | 每個 RDMA 裝置 HCA 物件與控制代碼的限制、使用量與失敗。 |
| Misc | misc.current、misc.max、misc.events | 已註冊純量資源的限制。 |
memory.high 是回收與節流邊界,而 memory.max 是硬限制;當回收無法進展時,可能導致 cgroup 本地 OOM 處理。memory.oom.group 可以要求以群組為導向終止工作。變更限制前,請讀取 memory.events、memory.stat 與壓力停滯資訊;符合穩態 RSS 的限制,在頁面錯誤、fork 或快取成長期間仍可能失敗。
cpuset.cpus 與 cpuset.mems 表達受父項限制的要求。其 *.effective 對應項顯示實際授予的 CPU 與 NUMA 節點,包括熱插拔效果。CPU 親和性與 cpuset 會取交集,因此任一機制都能縮小工作執行的位置。
常見 Cgroup 檔案 #
除控制器檔案外,cgroup v2 提供階層範圍的檔案:
cgroup.procs列出 cgroup 中的執行緒群組 ID,並接受 PID 遷移。cgroup.threads列出執行緒子樹中的執行緒,並接受執行緒 ID。cgroup.events回報 cgroup 或其後代是否已填入等狀態;poll 或 inotify 可以觀察變更。cgroup.freeze停止並恢復非根子樹中的執行,而cgroup.kill終止非根子樹中的所有程序。cgroup.max.depth與cgroup.max.descendants限制階層成長。cgroup.stat、cgroup.type與cgroup.controllers暴露階層及控制器狀態。
檔案寫入是作業,不是設定交易。管理器必須處理 EBUSY、權限失敗、熱插拔變更、並行遷移與部分設定。介面允許無界值時,限制可以寫成 max。
委派、命名空間與容器 #
委派讓下屬管理器控制子樹,而不授予其祖先控制權。委派者建立空 cgroup、在其父項啟用必要控制器、將擁有權或存取權授予子樹,並允許受委派者建立與管理後代。受委派者必須只在該邊界以下運作,因為祖先設定決定其可用資源。
systemd 的 Delegate=yes 是建立此類邊界的常見方式。容器執行階段接著組合數個機制:
- 建立或選取 cgroup,並套用主機資源原則;
- 建立使用者、掛載、PID、網路及其他命名空間;
- 安排私有掛載檢視,並在 PID 命名空間存在後掛載
/proc; - 執行工作負載,並保留主機側的生命週期作業控制代碼。
Cgroup 命名空間會變更程序看到的 cgroup 路徑檢視:/proc/PID/cgroup 與 cgroup 檔案系統路徑會相對於該命名空間為根。它不會建立獨立控制器階層、放寬祖先限制或隔離資源記帳。主機側管理器仍保留權威階層。
核心表示 #
使用者可見的樹由核心中的 cgroup 物件支援。task_struct 指向 css_set;css_set 將作用中控制器的 cgroup_subsys_state(CSS)參照分組。控制器會將其狀態嵌入 CSS 或與 CSS 關聯,並在其記帳與強制路徑中使用該關聯。工作可以共用 css_set,避免為每個工作重複相同的控制器狀態組合。
這些實作細節說明遷移必須一致更新控制器成員資格,但它們不是穩定的使用者空間 ABI。特定核心版本請參閱 kernel/cgroup/cgroup.c 與 include/linux/cgroup-defs.h。
與 Cgroup 第一版比較 #
第一版允許多個獨立階層,並將控制器附加至不同階層。因此,一個工作可以對不同控制器具有不同成員資格,讓應用程式的資源邊界難以推理。Cgroup v2 使用單一階層、共用委派規則與一致的控制器介面。系統可以在遷移期間掛載 v1 與 v2,但控制器不能同時繫結至兩個版本。
權威且特定版本的參考資料是 cgroup v2 核心文件。請檢查目標核心的 cgroup.controllers 與該文件,而不要假設控制器或介面檔案存在。
評論
在 GitHub 上閱讀或評論(發表評論需要登入)