Control Group v2

日期:

2015 年 10 月

作者:

Tejun Heo <tj@kernel.org>

這是關於 cgroup v2 的設計、介面和約定的權威文件。它描述了 cgroup 的所有使用者空間可見方面,包括核心和特定控制器行為。所有未來的更改都必須反映在此文件中。有關 v1 的文件可在 Documentation/admin-guide/cgroup-v1/index.rst 中找到。

簡介

術語

“cgroup” 代表 “control group”(控制組),並且永遠不應大寫。單數形式用於指代整個特性,也用作限定詞,如 “cgroup 控制器”。 當明確引用多個單獨的控制組時,使用複數形式 “cgroups”。

什麼是 cgroup?

cgroup 是一種機制,用於以分層方式組織程序,並以受控和可配置的方式沿層次結構分配系統資源。

cgroup 主要由兩部分組成 - 核心和控制器。 cgroup 核心主要負責以分層方式組織程序。 cgroup 控制器通常負責沿層次結構分配特定型別的系統資源,儘管也有一些實用程式控制器用於資源分配以外的目的。

cgroup 形成樹結構,系統中的每個程序都屬於一個且僅屬於一個 cgroup。程序的所有執行緒都屬於同一 cgroup。建立時,所有程序都放入父程序當時所屬的 cgroup 中。程序可以遷移到另一個 cgroup。程序的遷移不會影響已經存在的子程序。

遵循某些結構約束,可以在 cgroup 上選擇性地啟用或停用控制器。所有控制器行為都是分層的 - 如果在 cgroup 上啟用了控制器,它會影響屬於組成 cgroup 包含子層次結構的所有程序。當控制器在巢狀 cgroup 上啟用時,它總是進一步限制資源分配。層次結構中更靠近根的限制不能被更遠的地方覆蓋。

基本操作

掛載

與 v1 不同,cgroup v2 只有一個層次結構。可以使用以下掛載命令掛載 cgroup v2 層次結構

# mount -t cgroup2 none $MOUNT_POINT

cgroup2 檔案系統的幻數為 0x63677270 (“cgrp”)。所有支援 v2 且未繫結到 v1 層次結構的控制器都會自動繫結到 v2 層次結構並在根目錄下顯示。在 v2 層次結構中未處於活動使用狀態的控制器可以繫結到其他層次結構。這允許以完全向後相容的方式將 v2 層次結構與舊版 v1 的多個層次結構混合使用。

只有在控制器在其當前層次結構中不再被引用後,才能將其跨層次結構移動。由於每個 cgroup 的控制器狀態都是非同步銷燬的,並且控制器可能存在揮之不去的引用,因此在上次解除安裝先前層次結構後,控制器可能不會立即顯示在 v2 層次結構上。同樣,應完全停用控制器才能將其移出統一層次結構,並且停用的控制器可能需要一段時間才能用於其他層次結構;此外,由於控制器之間的依賴關係,可能還需要停用其他控制器。

雖然對於開發和手動配置很有用,但強烈建議不要在生產環境中使用在 v2 和其他層次結構之間動態移動控制器。建議在系統啟動後開始使用控制器之前,確定層次結構和控制器關聯。

在過渡到 v2 期間,系統管理軟體可能仍然會自動掛載 v1 cgroup 檔案系統,從而在手動干預之前在啟動期間劫持所有控制器。為了使測試和實驗更容易,核心引數 cgroup_no_v1= 允許停用 v1 中的控制器,並使它們始終在 v2 中可用。

cgroup v2 目前支援以下掛載選項。

nsdelegate

將 cgroup 名稱空間視為委託邊界。此選項是系統範圍的,只能在掛載時設定或透過從 init 名稱空間重新掛載來修改。在非 init 名稱空間掛載上,將忽略掛載選項。請參閱委託部分了解詳細資訊。

favordynmods

降低動態 cgroup 修改(例如任務遷移和控制器開關)的延遲,但代價是使熱路徑操作(例如 fork 和 exit)更加昂貴。建立 cgroup、啟用控制器,然後使用 CLONE_INTO_CGROUP 播種它的靜態使用模式不受此選項的影響。

memory_localevents

僅使用當前 cgroup 的資料填充 memory.events,而不填充任何子樹。這是舊版行為,沒有此選項的預設行為是包括子樹計數。此選項是系統範圍的,只能在掛載時設定或透過從 init 名稱空間重新掛載來修改。在非 init 名稱空間掛載上,將忽略掛載選項。

memory_recursiveprot

遞迴地將 memory.min 和 memory.low 保護應用於整個子樹,而無需顯式向下傳播到葉 cgroup。這允許保護整個子樹彼此隔離,同時保留這些子樹內的自由競爭。這應該是預設行為,但它是一個掛載選項,用於避免依賴原始語義的設定(例如,在更高的樹級別上指定虛假的高“繞過”保護值)。

memory_hugetlb_accounting

將 HugeTLB 記憶體使用量計入記憶體控制器的 cgroup 的總記憶體使用量(用於統計報告和記憶體保護)。這是一種新行為,可能會使現有設定倒退,因此必須使用此掛載選項顯式選擇加入。

需要記住的一些注意事項

  • 記憶體控制器不涉及 HugeTLB 池管理。預分配的池不屬於任何人。具體來說,當一個新的 HugeTLB 頁面被分配到池中時,從記憶體控制器的角度來看,它不會被計算在內。只有在實際使用時(例如在頁面錯誤時),才會向 cgroup 收費。在配置硬限制時,主機記憶體過度提交管理必須考慮這一點。一般來說,HugeTLB 池管理應該透過其他機制(例如 HugeTLB 控制器)來完成。

  • 未能向記憶體控制器收費 HugeTLB 頁面會導致 SIGBUS。即使 HugeTLB 池仍然有可用頁面(但達到 cgroup 限制且回收嘗試失敗),也可能發生這種情況。

  • 將 HugeTLB 記憶體計入記憶體控制器會影響記憶體保護和回收動態。任何使用者空間調整(例如,low、min 限制)都需要考慮這一點。

  • 在此選項未被選擇時使用的 HugeTLB 頁面將不會被記憶體控制器跟蹤(即使稍後重新掛載 cgroup v2 也是如此)。

pids_localevents

該選項恢復了 pids.events:max 的 v1 式行為,即僅計算本地(cgroup 內部)的 fork 失敗。如果沒有此選項,pids.events.max 表示 cgroup 子樹中的任何 pids.max 強制執行。

組織程序和執行緒

程序

最初,只存在根 cgroup,所有程序都屬於該 cgroup。可以透過建立子目錄來建立子 cgroup

# mkdir $CGROUP_NAME

給定的 cgroup 可以有多個子 cgroup,形成樹結構。每個 cgroup 都有一個可讀寫的介面檔案 “cgroup.procs”。讀取時,它會逐行列出屬於 cgroup 的所有程序的 PID。PID 沒有排序,如果程序被移動到另一個 cgroup 然後返回,或者在讀取時 PID 被回收,則同一個 PID 可能會多次出現。

可以透過將其 PID 寫入目標 cgroup 的 “cgroup.procs” 檔案,將程序遷移到 cgroup。在單個 write(2) 呼叫中只能遷移一個程序。如果一個程序由多個執行緒組成,則寫入任何執行緒的 PID 都會遷移該程序的所有執行緒。

當一個程序 fork 一個子程序時,新程序會出生到 fork 程序在操作時所屬的 cgroup 中。退出後,一個程序會一直與它在退出時所屬的 cgroup 相關聯,直到它被回收;但是,殭屍程序不會出現在 “cgroup.procs” 中,因此無法將其移動到另一個 cgroup。

可以透過刪除目錄來銷燬沒有任何子程序或活動程序的 cgroup。請注意,沒有任何子程序且僅與殭屍程序關聯的 cgroup 被認為是空的,可以刪除

# rmdir $CGROUP_NAME

“/proc/$PID/cgroup” 列出了程序的 cgroup 成員資格。如果系統中使用了舊版 cgroup,則此檔案可能包含多行,每行對應一個層次結構。cgroup v2 的條目始終採用 “0::$PATH” 格式

# cat /proc/842/cgroup
...
0::/test-cgroup/test-cgroup-nested

如果該程序變為殭屍程序,並且隨後刪除了與其關聯的 cgroup,則會在路徑後附加 “ (deleted)”

# cat /proc/842/cgroup
...
0::/test-cgroup/test-cgroup-nested (deleted)

執行緒

cgroup v2 支援執行緒粒度,用於支援需要在程序組的執行緒之間進行分層資源分配的用例。預設情況下,程序的所有執行緒都屬於同一 cgroup,該 cgroup 也充當託管非特定於程序或執行緒的資源消耗的資源域。執行緒模式允許執行緒分佈在子樹中,同時仍然保持它們的公共資源域。

支援執行緒模式的控制器稱為執行緒控制器。不支援執行緒模式的控制器稱為域控制器。

將 cgroup 標記為執行緒化會使其加入其父級的資源域,作為執行緒化的 cgroup。父級可能是另一個執行緒化的 cgroup,其資源域在層次結構中更高。執行緒化子樹的根,即最近的非執行緒化祖先,被稱為執行緒域或執行緒根,可以互換使用,並充當整個子樹的資源域。

線上程化子樹中,程序的執行緒可以放在不同的 cgroup 中,並且不受內部程序約束的限制 - 無論非葉 cgroup 中是否有執行緒,都可以在這些 cgroup 上啟用執行緒控制器。

由於執行緒化域 cgroup 託管子樹的所有域資源消耗,因此它被認為具有內部資源消耗,無論其中是否有程序,並且不能有未執行緒化的子 cgroup。由於根 cgroup 不受內部程序約束的限制,因此它可以同時充當執行緒化域和域 cgroup 的父級。

cgroup 的當前操作模式或型別顯示在 “cgroup.type” 檔案中,該檔案指示 cgroup 是普通域、充當執行緒化子樹域的域還是執行緒化 cgroup。

建立時,cgroup 始終是域 cgroup,可以透過將 “threaded” 寫入 “cgroup.type” 檔案來將其執行緒化。該操作是單向的

# echo threaded > cgroup.type

一旦執行緒化,cgroup 就不能再次成為域。要啟用執行緒模式,必須滿足以下條件。

  • 由於 cgroup 將加入父級的資源域。父級必須是有效的(執行緒化)域或執行緒化 cgroup。

  • 當父級是非執行緒化域時,它不能啟用任何域控制器或填充域子級。根目錄不受此要求的約束。

在拓撲方面,cgroup 可能處於無效狀態。請考慮以下拓撲

A (threaded domain) - B (threaded) - C (domain, just created)

C 被建立為一個域,但未連線到可以託管子域的父級。C 在轉換為執行緒化 cgroup 之前無法使用。在這些情況下,“cgroup.type” 檔案將報告 “domain (invalid)”。由於無效拓撲而失敗的操作使用 EOPNOTSUPP 作為 errno。

當一個域 cgroup 的子 cgroup 之一變為執行緒化時,或者當 “cgroup.subtree_control” 檔案中啟用了執行緒控制器並且 cgroup 中存在程序時,該域 cgroup 將轉換為執行緒化域。當條件清除時,執行緒化域將恢復為普通域。

讀取時,“cgroup.threads” 包含 cgroup 中所有執行緒的執行緒 ID 列表。除了操作是按執行緒而不是按程序進行之外,“cgroup.threads” 具有與 “cgroup.procs” 相同的格式和行為。雖然可以在任何 cgroup 中寫入 “cgroup.threads”,但由於它只能在同一個執行緒化域中移動執行緒,因此其操作僅限於每個執行緒化子樹內。

執行緒化域 cgroup 充當整個子樹的資源域,並且雖然執行緒可以分散在子樹中,但所有程序都被認為位於執行緒化域 cgroup 中。執行緒化域 cgroup 中的 “cgroup.procs” 包含子樹中所有程序的 PID,並且在子樹本身中不可讀。但是,可以從子樹中的任何位置寫入 “cgroup.procs”,以將匹配程序的所有執行緒遷移到 cgroup。

只有執行緒控制器可以線上程化子樹中啟用。當執行緒控制器線上程化子樹中啟用時,它僅對與 cgroup 及其後代中的執行緒關聯的資源消耗進行記帳和控制。所有未繫結到特定執行緒的消耗都屬於執行緒化域 cgroup。

由於執行緒化子樹不受內部程序約束的限制,因此執行緒控制器必須能夠處理非葉 cgroup 及其子 cgroup 中的執行緒之間的競爭。每個執行緒控制器都定義瞭如何處理此類競爭。

目前,以下控制器是執行緒化的,可以線上程化 cgroup 中啟用

- cpu
- cpuset
- perf_event
- pids

[未]填充通知

每個非根 cgroup 都有一個 “cgroup.events” 檔案,其中包含 “populated” 欄位,指示 cgroup 的子層次結構中是否存在活動程序。如果 cgroup 及其後代中沒有活動程序,則其值為 0;否則為 1。當值更改時,會觸發輪詢和 [id]notify 事件。例如,這可用於在給定子層次結構的所有程序退出後啟動清理操作。填充狀態更新和通知是遞迴的。考慮以下子層次結構,其中括號中的數字表示每個 cgroup 中的程序數

A(4) - B(0) - C(1)
            \ D(0)

A、B 和 C 的 “populated” 欄位將為 1,而 D 的欄位將為 0。在 C 中的一個程序退出後,B 和 C 的 “populated” 欄位將翻轉為 “0”,並且將在兩個 cgroup 的 “cgroup.events” 檔案上生成檔案修改事件。

控制控制器

啟用和停用

每個 cgroup 都有一個 “cgroup.controllers” 檔案,其中列出了該 cgroup 可用於啟用的所有控制器

# cat cgroup.controllers
cpu io memory

預設情況下不啟用任何控制器。可以透過寫入 “cgroup.subtree_control” 檔案來啟用和停用控制器

# echo "+cpu +memory -io" > cgroup.subtree_control

只能啟用 “cgroup.controllers” 中列出的控制器。當如上所述指定多個操作時,它們要麼全部成功,要麼全部失敗。如果指定了對同一控制器的多個操作,則最後一個操作有效。

在 cgroup 中啟用控制器表示將控制目標資源在其直接子級之間的分配。考慮以下子層次結構。啟用的控制器列在括號中

A(cpu,memory) - B(memory) - C()
                          \ D()

由於 A 啟用了 “cpu” 和 “memory”,A 將控制 CPU 週期和記憶體對其子級的分配,在本例中為 B。由於 B 啟用了 “memory” 但未啟用 “CPU”,因此 C 和 D 將自由競爭 CPU 週期,但將控制可供 B 使用的記憶體分配。

由於控制器會調節目標資源到 cgroup 子級的分配,因此啟用它會在子 cgroup 中建立控制器的介面檔案。在上面的示例中,在 B 上啟用 “cpu” 將在 C 和 D 中建立 “cpu.” 字首的控制器介面檔案。同樣,從 B 中停用 “memory” 將從 C 和 D 中刪除 “memory.” 字首的控制器介面檔案。這意味著控制器介面檔案 - 任何不以 “cgroup.” 開頭的檔案 - 歸父級所有,而不是 cgroup 本身。

自頂向下約束

資源是自頂向下分配的,只有在從父級分配了資源後,cgroup 才能進一步分配資源。這意味著所有非根 “cgroup.subtree_control” 檔案只能包含在父級的 “cgroup.subtree_control” 檔案中啟用的控制器。只有在父級啟用了控制器的情況下才能啟用控制器,並且如果一個或多個子級啟用了控制器,則無法停用控制器。

無內部程序約束

只有當非根 cgroup 沒有自己的任何程序時,才能將域資源分配給其子級。換句話說,只有不包含任何程序的域 cgroup 才能在其 “cgroup.subtree_control” 檔案中啟用域控制器。

這保證了當域控制器檢視啟用了它的層次結構部分時,程序始終只位於葉子上。這排除了子 cgroup 與父級的內部程序競爭的情況。

根 cgroup 不受此限制。根目錄包含無法與任何其他 cgroup 關聯的程序和匿名資源消耗,並且需要來自大多數控制器的特殊處理。根 cgroup 中的資源消耗如何管理取決於每個控制器(有關此主題的更多資訊,請參閱控制器章節中的非規範性資訊部分)。

請注意,如果 cgroup 的 “cgroup.subtree_control” 中沒有啟用的控制器,則該限制不會妨礙。這很重要,否則將無法建立已填充 cgroup 的子級。要控制 cgroup 的資源分配,cgroup 必須建立子級並在其 “cgroup.subtree_control” 檔案中啟用控制器之前將其所有程序轉移到子級。

委託

委託模型

可以透過兩種方式委託 cgroup。首先,透過授予使用者目錄及其 “cgroup.procs”、“cgroup.threads” 和 “cgroup.subtree_control” 檔案的寫入許可權,將其委託給許可權較低的使用者。其次,如果設定了 “nsdelegate” 掛載選項,則在建立名稱空間時自動委託給 cgroup 名稱空間。

由於給定目錄中的資源控制介面檔案控制父級的資源分配,因此不應允許被委託者寫入這些檔案。對於第一種方法,這是透過不授予對這些檔案的訪問許可權來實現的。對於第二種方法,應透過至少掛載名稱空間的方式從被委託者那裡隱藏名稱空間之外的檔案,並且核心拒絕從 cgroup 名稱空間內部寫入名稱空間根目錄上的所有檔案,除了 “/sys/kernel/cgroup/delegate” 中列出的檔案(包括 “cgroup.procs”、“cgroup.threads”、“cgroup.subtree_control” 等)。

對於兩種委託型別,最終結果是等效的。委託後,使用者可以在目錄下構建子層次結構,按照自己的意願組織內部程序,並進一步分配從父級收到的資源。所有資源控制器的限制和其他設定都是分層的,並且無論委託的子層次結構中發生什麼,都不會逃脫父級施加的資源限制。

目前,cgroup 不對委託的子層次結構中的 cgroup 數量或巢狀深度施加任何限制;但是,將來可能會明確限制這一點。

委託包含

委託的子層次結構是包含的,因為程序不能由被委託者移入或移出子層次結構。

對於委託給許可權較低的使用者,這是透過要求非 root euid 的程序滿足以下條件來實現的,即透過將其 PID 寫入 “cgroup.procs” 檔案來將目標程序遷移到 cgroup。

  • 寫入者必須具有對 “cgroup.procs” 檔案的寫入許可權。

  • 寫入者必須具有對源 cgroup 和目標 cgroup 的公共祖先的 “cgroup.procs” 檔案的寫入許可權。

以上兩個約束確保了雖然被委託者可以在委託的子層次結構中自由遷移程序,但它不能從子層次結構之外拉入或推出。

例如,讓我們假設 cgroup C0 和 C1 已委託給使用者 U0,後者在 C0 下建立了 C00、C01 並在 C1 下建立了 C10,如下所示,並且 C0 和 C1 下的所有程序都屬於 U0

~~~~~~~~~~~~~ - C0 - C00
~ cgroup    ~      \ C01
~ hierarchy ~
~~~~~~~~~~~~~ - C1 - C10

我們還假設 U0 想要將當前位於 C10 中的程序的 PID 寫入 “C00/cgroup.procs”。U0 具有對該檔案的寫入許可權;但是,源 cgroup C10 和目標 cgroup C00 的公共祖先高於委託點,並且 U0 不具有對其 “cgroup.procs” 檔案的寫入許可權,因此該寫入將被拒絕,並顯示 -EACCES。

對於委託給名稱空間,包含是透過要求源 cgroup 和目標 cgroup 都可以從嘗試遷移的程序的名稱空間訪問來實現的。如果任何一個都無法訪問,則遷移將被拒絕,並顯示 -ENOENT。

準則

組織一次並控制

跨 cgroup 遷移程序是一項相對昂貴的操作,並且有狀態資源(如記憶體)不會與程序一起移動。這是一個明確的設計決策,因為在同步成本方面,遷移和各種熱路徑之間通常存在固有的權衡。

因此,不鼓勵經常跨 cgroup 遷移程序,以此作為應用不同資源限制的手段。工作負載應根據系統在啟動時的邏輯和資源結構分配給 cgroup。可以透過更改介面檔案中的控制器配置來動態調整資源分配。

避免名稱衝突

cgroup 及其子 cgroup 的介面檔案佔用相同的目錄,並且可以建立與介面檔案衝突的子 cgroup。

所有 cgroup 核心介面檔案都以 “cgroup.” 為字首,每個控制器的介面檔案都以控制器名稱和一個點為字首。控制器的名稱由小寫字母和 “_” 組成,但永遠不會以 “_” 開頭,因此可以用作避免衝突的字首字元。此外,介面檔名不會以經常用於對工作負載進行分類的術語(如 job、service、slice、unit 或 workload)開頭或結尾。

cgroup 不會採取任何措施來防止名稱衝突,使用者有責任避免這些衝突。

資源分配模型

cgroup 控制器根據資源型別和預期用例實現多種資源分配方案。本節介紹使用中的主要方案及其預期行為。

權重

透過將所有活動子級的權重相加,然後將每個子級的權重與其權重與總和的比率相匹配的分數,來分配父級的資源。由於只有當前可以使用該資源的子級參與分配,因此這是工作量守恆的。由於其動態特性,此模型通常用於無狀態資源。

所有權重都在 [1, 10000] 範圍內,預設值為 100。這允許在足夠精細的粒度上在兩個方向上進行對稱乘法偏差,同時保持在直觀的範圍內。

只要權重在範圍內,所有配置組合都是有效的,並且沒有理由拒絕配置更改或程序遷移。

“cpu.weight” 將 CPU 週期按比例分配給活動子級,是這種型別的一個示例。

限制

子級只能消耗高達配置數量的資源。限制可以過度提交 - 子級的限制之和可以超過可供父級使用的資源量。

限制在 [0, max] 範圍內,預設為 “max”,即無操作。

由於限制可以過度提交,因此所有配置組合都是有效的,並且沒有理由拒絕配置更改或程序遷移。

“io.max” 限制 cgroup 可以在 IO 裝置上消耗的最大 BPS 和/或 IOPS,是這種型別的一個示例。

保護

只要其所有祖先的使用量都在其保護級別之下,cgroup 就會受到高達配置數量的資源保護。保護可以是硬保證或盡力而為的軟邊界。保護也可以過度提交,在這種情況下,只有高達父級可用資源量的資源才能在子級之間受到保護。

保護在 [0, max] 範圍內,預設為 0,即無操作。

由於保護可以過度提交,因此所有配置組合都是有效的,並且沒有理由拒絕配置更改或程序遷移。

“memory.low” 實施盡力而為的記憶體保護,是這種型別的一個示例。

分配

cgroup 專門分配一定數量的有限資源。分配不能過度提交 - 子級的分配之和不能超過可供父級使用的資源量。

分配在 [0, max] 範圍內,預設為 0,即無資源。

由於分配不能過度提交,因此某些配置組合是無效的,應拒絕。此外,如果該資源對於程序的執行是強制性的,則可以拒絕程序遷移。

“cpu.rt.max” 硬分配即時切片,是這種型別的一個示例。

介面檔案

格式

所有介面檔案應儘可能採用以下格式之一

New-line separated values
(when only one value can be written at once)

      VAL0\n
      VAL1\n
      ...

Space separated values
(when read-only or multiple values can be written at once)

      VAL0 VAL1 ...\n

Flat keyed

      KEY0 VAL0\n
      KEY1 VAL1\n
      ...

Nested keyed

      KEY0 SUB_KEY0=VAL00 SUB_KEY1=VAL01...
      KEY1 SUB_KEY0=VAL10 SUB_KEY1=VAL11...
      ...

對於可寫檔案,寫入格式通常應與讀取格式匹配;但是,控制器可以允許省略後面的欄位或為最常見的用例實施受限快捷方式。

對於平面鍵控檔案和巢狀鍵控檔案,一次只能寫入單個鍵的值。對於巢狀鍵控檔案,子鍵值對可以按任何順序指定,並且不必指定所有對。

約定

  • 單個功能的設定應包含在單個檔案中。

  • 根 cgroup 應免於資源控制,因此不應具有資源控制介面檔案。

  • 預設時間單位是微秒。如果使用不同的單位,則必須存在明確的單位字尾。

  • 每部分數量應使用至少兩位小數的百分比小數 - 例如 13.40。

  • 如果控制器實現了基於權重的資源分配,其介面檔案應命名為“weight”,範圍為 [1, 10000],預設值為 100。選擇這些值是為了在保持直觀(預設值為 100%)的同時,允許在兩個方向上都有足夠且對稱的偏差。

  • 如果控制器實現了絕對資源保證和/或限制,則介面檔案應分別命名為“min”和“max”。如果控制器實現了盡力而為資源保證和/或限制,則介面檔案應分別命名為“low”和“high”。

    在上述四個控制檔案中,特殊標記“max”應用於表示讀寫方向上的正無窮大。

  • 如果設定具有可配置的預設值和按鍵特定的覆蓋,則預設條目應使用“default”作為鍵,並作為檔案中的第一個條目出現。

    可以透過寫入“default $VAL”或“$VAL”來更新預設值。

    當寫入以更新特定的覆蓋時,“default”可以用作值來表示刪除該覆蓋。讀取時不得出現值為“default”的覆蓋條目。

    例如,一個按 major:minor 裝置號鍵控的設定,並具有整數值,可能如下所示

    # cat cgroup-example-interface-file
    default 150
    8:0 300
    

    可以透過以下方式更新預設值

    # echo 125 > cgroup-example-interface-file
    

    或者

    # echo "default 125" > cgroup-example-interface-file
    

    可以透過以下方式設定覆蓋

    # echo "8:16 170" > cgroup-example-interface-file
    

    可以透過以下方式清除覆蓋

    # echo "8:0 default" > cgroup-example-interface-file
    # cat cgroup-example-interface-file
    default 125
    8:16 170
    
  • 對於頻率不是很高的事件,應建立一個介面檔案“events”,其中列出事件鍵值對。每當發生可通知事件時,應在該檔案上生成檔案修改事件。

核心介面檔案

所有 cgroup 核心檔案都以“cgroup.”為字首。

cgroup.type

一個讀寫單值檔案,存在於非根 cgroup 上。

讀取時,它指示 cgroup 的當前型別,可以是以下值之一。

  • “domain”:一個普通的有效域 cgroup。

  • “domain threaded”:一個執行緒域 cgroup,用作執行緒子樹的根。

  • “domain invalid”:一個處於無效狀態的 cgroup。它無法被填充或啟用控制器。它可能被允許成為一個執行緒 cgroup。

  • “threaded”:一個執行緒 cgroup,是執行緒子樹的成員。

可以透過將“threaded”寫入此檔案,將 cgroup 轉換為執行緒 cgroup。

cgroup.procs

一個讀寫換行分隔值的檔案,存在於所有 cgroup 上。

讀取時,它列出屬於 cgroup 的所有程序的 PID,每行一個。PID 沒有排序,如果程序被移動到另一個 cgroup 然後又移回來,或者 PID 在讀取時被回收,則同一個 PID 可能會出現多次。

可以將 PID 寫入以將與該 PID 關聯的程序遷移到 cgroup。編寫者應滿足以下所有條件。

  • 它必須具有對“cgroup.procs”檔案的寫入許可權。

  • 它必須具有對源 cgroup 和目標 cgroup 的共同祖先的“cgroup.procs”檔案的寫入許可權。

當委派子層級結構時,應授予對此檔案的寫入許可權以及包含目錄。

線上程 cgroup 中,讀取此檔案會失敗並顯示 EOPNOTSUPP,因為所有程序都屬於執行緒根。支援寫入,並將程序的每個執行緒移動到 cgroup。

cgroup.threads

一個讀寫換行分隔值的檔案,存在於所有 cgroup 上。

讀取時,它列出屬於 cgroup 的所有執行緒的 TID,每行一個。TID 沒有排序,如果執行緒被移動到另一個 cgroup 然後又移回來,或者 TID 在讀取時被回收,則同一個 TID 可能會出現多次。

可以將 TID 寫入以將與該 TID 關聯的執行緒遷移到 cgroup。編寫者應滿足以下所有條件。

  • 它必須具有對“cgroup.threads”檔案的寫入許可權。

  • 執行緒當前所在的 cgroup 必須與目標 cgroup 位於同一資源域中。

  • 它必須具有對源 cgroup 和目標 cgroup 的共同祖先的“cgroup.procs”檔案的寫入許可權。

當委派子層級結構時,應授予對此檔案的寫入許可權以及包含目錄。

cgroup.controllers

一個只讀空格分隔值的檔案,存在於所有 cgroup 上。

它顯示可用於 cgroup 的所有控制器的空格分隔列表。控制器沒有排序。

cgroup.subtree_control

一個讀寫空格分隔值的檔案,存在於所有 cgroup 上。開始時為空。

讀取時,它顯示已啟用以控制從 cgroup 到其子項的資源分配的控制器的空格分隔列表。

可以寫入以“+”或“-”為字首的控制器的空格分隔列表,以啟用或停用控制器。以“+”為字首的控制器名稱啟用該控制器,以“-”為字首的控制器名稱停用該控制器。如果一個控制器在列表中出現多次,則最後一個生效。當指定多個啟用和停用操作時,要麼全部成功,要麼全部失敗。

cgroup.events

一個只讀扁平鍵檔案,存在於非根 cgroup 上。定義了以下條目。除非另有說明,否則此檔案中的值更改會生成檔案修改事件。

populated

如果 cgroup 或其後代包含任何活動的程序,則為 1;否則為 0。

frozen

如果 cgroup 被凍結,則為 1;否則為 0。

cgroup.max.descendants

一個讀寫單值檔案。預設值為“max”。

允許的最大後代 cgroup 數。如果實際後代數等於或大於此值,則嘗試在該層級結構中建立新 cgroup 將會失敗。

cgroup.max.depth

一個讀寫單值檔案。預設值為“max”。

允許的低於當前 cgroup 的最大後代深度。如果實際後代深度等於或大於此值,則嘗試建立新的子 cgroup 將會失敗。

cgroup.stat

一個只讀扁平鍵檔案,包含以下條目

nr_descendants

可見的後代 cgroup 的總數。

nr_dying_descendants

正在消亡的後代 cgroup 的總數。在使用者刪除 cgroup 後,該 cgroup 將變為消亡狀態。cgroup 將在消亡狀態中保持一段時間(時間未定義,可能取決於系統負載),然後才會被完全銷燬。

在任何情況下,程序都不能進入消亡 cgroup,消亡 cgroup 也不能復活。

消亡 cgroup 可以消耗不超過刪除 cgroup 時活動的限制的系統資源。

nr_subsys_<cgroup_subsys>

當前 cgroup 及其下方的活動 cgroup 子系統(例如,記憶體 cgroup)的總數。

nr_dying_subsys_<cgroup_subsys>

當前 cgroup 及其下方的正在消亡的 cgroup 子系統(例如,記憶體 cgroup)的總數。

cgroup.freeze

一個讀寫單值檔案,存在於非根 cgroup 上。允許的值為“0”和“1”。預設值為“0”。

將“1”寫入該檔案會導致凍結 cgroup 及其所有後代 cgroup。這意味著所有屬於它的程序都將被停止,並且在 cgroup 被顯式解凍之前不會執行。凍結 cgroup 可能需要一些時間;當此操作完成時,“cgroup.events”控制檔案中的“frozen”值將更新為“1”,並且將發出相應的通知。

可以透過自身的設定或任何祖先 cgroup 的設定來凍結 cgroup。如果任何祖先 cgroup 被凍結,則 cgroup 將保持凍結狀態。

凍結 cgroup 中的程序可以被致命訊號殺死。它們也可以進入和離開凍結 cgroup:可以透過使用者的顯式移動,或者如果 cgroup 的凍結與 fork() 競爭。如果程序被移動到凍結 cgroup,它會停止。如果程序被移出凍結 cgroup,它會開始執行。

cgroup 的凍結狀態不會影響任何 cgroup 樹操作:可以刪除凍結的(和空的)cgroup,以及建立新的子 cgroup。

cgroup.kill

一個只寫單值檔案,存在於非根 cgroup 中。唯一允許的值為“1”。

將“1”寫入該檔案會導致 cgroup 及其所有後代 cgroup 被殺死。這意味著位於受影響 cgroup 樹中的所有程序都將透過 SIGKILL 被殺死。

殺死 cgroup 樹將適當地處理併發 fork,並防止遷移。

線上程 cgroup 中,寫入此檔案會失敗並顯示 EOPNOTSUPP,因為殺死 cgroup 是一項面向程序的操作,即它會影響整個執行緒組。

cgroup.pressure

一個讀寫單值檔案,允許的值為“0”和“1”。預設值為“1”。

將“0”寫入該檔案將停用 cgroup PSI 記帳。將“1”寫入該檔案將重新啟用 cgroup PSI 記帳。

此控制屬性不是分層的,因此在 cgroup 中停用或啟用 PSI 記帳不會影響後代中的 PSI 記帳,並且不需要透過來自根的祖先傳遞啟用。

存在此控制屬性的原因是 PSI 帳戶分別停頓每個 cgroup,並在層級結構的每一層聚合它。對於某些工作負載,當在層級結構的深層級別下時,這可能會導致不可忽略的開銷,在這種情況下,可以使用此控制屬性來停用非葉 cgroup 中的 PSI 記帳。

irq.pressure

一個讀寫巢狀鍵檔案。

顯示 IRQ/SOFTIRQ 的壓力暫停資訊。有關詳細資訊,請參閱Documentation/accounting/psi.rst

控制器

CPU

“cpu”控制器調節 CPU 週期的分配。此控制器實現了正常排程策略的權重和絕對頻寬限制模型,以及即時排程策略的絕對頻寬分配模型。

在所有上述模型中,週期分配僅在時間基礎上定義,並且不考慮任務執行的頻率。(可選)利用率鉗制支援允許向 schedutil cpufreq governor 提示 CPU 應始終提供的最小期望頻率,以及 CPU 不應超過的最大期望頻率。

警告:cgroup2 cpu 控制器尚未支援即時程序的(頻寬)控制。對於啟用 CONFIG_RT_GROUP_SCHED 選項以進行即時程序組排程的核心,只有當所有 RT 程序都位於根 cgroup 中時,才能啟用 cpu 控制器。請注意,系統管理軟體可能已在系統啟動過程中將 RT 程序放入非根 cgroup 中,並且在啟用啟用了 CONFIG_RT_GROUP_SCHED 的核心之前,可能需要將這些程序移動到根 cgroup。

在停用 CONFIG_RT_GROUP_SCHED 的情況下,此限制不適用,並且某些介面檔案要麼影響即時程序,要麼將其納入考慮。有關詳細資訊,請參閱以下部分。只有 cpu 控制器受 CONFIG_RT_GROUP_SCHED 的影響。其他控制器可用於即時程序的資源控制,而與 CONFIG_RT_GROUP_SCHED 無關。

CPU 介面檔案

程序與 cpu 控制器的互動取決於其排程策略和底層排程器。從 cpu 控制器的角度來看,程序可以分為以下幾類

  • 公平類排程器下的程序

  • 具有 cgroup_set_weight 回撥的 BPF 排程器下的程序

  • 其他所有程序:SCHED_{FIFO,RR,DEADLINE} 和沒有 cgroup_set_weight 回撥的 BPF 排程器下的程序

有關程序何時處於公平類排程器或 BPF 排程器下的詳細資訊,請檢視Documentation/scheduler/sched-ext.rst

對於以下每個介面檔案,將參考上述類別。所有時間持續時間均以微秒為單位。

cpu.stat

一個只讀扁平鍵檔案。無論是否啟用控制器,此檔案都存在。

它始終報告以下三個統計資訊,這些統計資訊考慮了 cgroup 中的所有程序

  • usage_usec

  • user_usec

  • system_usec

並在啟用控制器時報告以下五個統計資訊,這些統計資訊僅考慮公平類排程器下的程序

  • nr_periods

  • nr_throttled

  • throttled_usec

  • nr_bursts

  • burst_usec

cpu.weight

一個讀寫單值檔案,存在於非根 cgroup 上。預設值為“100”。

對於非空閒組(cpu.idle = 0),權重範圍為 [1, 10000]。

如果 cgroup 已配置為 SCHED_IDLE (cpu.idle = 1),則權重將顯示為 0。

此檔案僅影響公平類排程器下的程序和具有 cgroup_set_weight 回撥的 BPF 排程器,具體取決於回撥的實際作用。

cpu.weight.nice

一個讀寫單值檔案,存在於非根 cgroup 上。預設值為“0”。

nice 值為 [-20, 19]。

此介面檔案是“cpu.weight”的替代介面,允許使用 nice(2) 使用的相同值讀取和設定權重。由於 nice 值的範圍較小且粒度較粗,因此讀取的值是當前權重的最接近近似值。

此檔案僅影響公平類排程器下的程序和具有 cgroup_set_weight 回撥的 BPF 排程器,具體取決於回撥的實際作用。

cpu.max

一個讀寫雙值檔案,存在於非根 cgroup 上。預設值為“max 100000”。

最大頻寬限制。格式如下

$MAX $PERIOD

表示該組可以在每個 $PERIOD 持續時間內消耗最多 $MAX。“max”表示 $MAX 沒有限制。如果只寫入一個數字,則更新 $MAX。

此檔案僅影響公平類排程器下的程序。

cpu.max.burst

一個讀寫單值檔案,存在於非根 cgroup 上。預設值為“0”。

突發範圍為 [0, $MAX]。

此檔案僅影響公平類排程器下的程序。

cpu.pressure

一個讀寫巢狀鍵檔案。

顯示 CPU 的壓力暫停資訊。有關詳細資訊,請參閱Documentation/accounting/psi.rst

此檔案考慮了 cgroup 中的所有程序。

cpu.uclamp.min

一個讀寫單值檔案,存在於非根 cgroup 上。預設值為“0”,即沒有利用率提升。

請求的最小利用率(保護)為百分比有理數,例如 12.34 表示 12.34%。

此介面允許讀取和設定最小利用率鉗制值,類似於 sched_setattr(2)。此最小利用率值用於鉗制任務特定的最小利用率鉗制,包括即時程序的鉗制。

請求的最小利用率(保護)始終受最大利用率(限制)的當前值限制,即 cpu.uclamp.max

此檔案影響 cgroup 中的所有程序。

cpu.uclamp.max

一個讀寫單值檔案,存在於非根 cgroup 上。預設值為“max”。即沒有利用率上限

請求的最大利用率(限制)為百分比有理數,例如 98.76 表示 98.76%。

此介面允許讀取和設定最大利用率鉗制值,類似於 sched_setattr(2)。此最大利用率值用於鉗制任務特定的最大利用率鉗制,包括即時程序的鉗制。

此檔案影響 cgroup 中的所有程序。

cpu.idle

一個讀寫單值檔案,存在於非根 cgroup 上。預設值為 0。

這是每個任務 SCHED_IDLE 排程策略的 cgroup 類似物。將此值設定為 1 將使 cgroup 的排程策略變為 SCHED_IDLE。cgroup 內的執行緒將保留其自己的相對優先順序,但 cgroup 本身將被視為相對於其對等方的非常低的優先順序。

此檔案僅影響公平類排程器下的程序。

記憶體

“memory”控制器調節記憶體的分配。記憶體是有狀態的,並實現限制和保護模型。由於記憶體使用和回收壓力以及記憶體狀態性質的交織,分配模型相對複雜。

雖然不是完全水密的,但會跟蹤給定 cgroup 的所有主要記憶體使用情況,以便可以將總記憶體消耗記入帳並控制到合理的程度。目前,跟蹤以下型別的記憶體使用情況。

  • 使用者空間記憶體 - 頁面快取和匿名記憶體。

  • 核心資料結構,如目錄項和 inode。

  • TCP 套接字緩衝區。

上述列表將來可能會擴充套件以獲得更好的覆蓋範圍。

記憶體介面檔案

所有記憶體量均以位元組為單位。如果寫入的值未與 PAGE_SIZE 對齊,則在讀回時,該值可能會向上舍入到最接近的 PAGE_SIZE 倍數。

memory.current

一個只讀單值檔案,存在於非根 cgroup 上。

cgroup 及其後代當前使用的記憶體總量。

memory.min

一個讀寫單值檔案,存在於非根 cgroup 上。預設值為“0”。

硬記憶體保護。如果 cgroup 的記憶體使用量在其有效最小邊界內,則在任何情況下都不會回收 cgroup 的記憶體。如果沒有未受保護的可回收記憶體可用,則會呼叫 OOM killer。高於有效最小邊界(或有效低邊界(如果較高)),頁面將按超額比例回收,從而降低較小超額的回收壓力。

有效最小邊界受所有祖先 cgroup 的 memory.min 值限制。如果存在 memory.min 過度承諾(子 cgroup 或多個 cgroup 需要的受保護記憶體多於父級允許的記憶體),則每個子 cgroup 將獲得父級保護的一部分,該部分與其低於 memory.min 的實際記憶體使用量成比例。

不鼓勵在此保護下放置比通常可用的記憶體更多的記憶體,並且可能導致持續的 OOM。

如果記憶體 cgroup 未填充程序,則會忽略其 memory.min。

memory.low

一個讀寫單值檔案,存在於非根 cgroup 上。預設值為“0”。

盡力而為記憶體保護。如果 cgroup 的記憶體使用量在其有效低邊界內,則除非在未受保護的 cgroup 中沒有可回收記憶體可用,否則不會回收 cgroup 的記憶體。高於有效低邊界(或有效最小邊界(如果較高)),頁面將按超額比例回收,從而降低較小超額的回收壓力。

有效低邊界受所有祖先 cgroup 的 memory.low 值限制。如果存在 memory.low 過度承諾(子 cgroup 或多個 cgroup 需要的受保護記憶體多於父級允許的記憶體),則每個子 cgroup 將獲得父級保護的一部分,該部分與其低於 memory.low 的實際記憶體使用量成比例。

不鼓勵在此保護下放置比通常可用的記憶體更多的記憶體。

memory.high

一個讀寫單值檔案,存在於非根 cgroup 上。預設值為“max”。

記憶體使用率限制。如果 cgroup 的使用率超過高邊界,則會限制 cgroup 的程序並使其承受繁重的回收壓力。

超過高限制永遠不會呼叫 OOM killer,並且在極端情況下,可能會違反該限制。高限制應用於外部程序監視受限 cgroup 以減輕繁重的回收壓力的場景。

如果 memory.high 以 O_NONBLOCK 開啟,則會繞過同步回收。這對於需要動態調整作業記憶體限制的管理員程序很有用,而無需花費自己的 CPU 資源進行記憶體回收。作業將在其下一個收費請求時觸發回收和/或受到限制。

請注意,使用 O_NONBLOCK,目標記憶體 cgroup 可能需要無限的時間才能將使用率降低到低於限制,這是因為延遲收費請求或忙於訪問其記憶體以減慢回收速度。

memory.max

一個讀寫單值檔案,存在於非根 cgroup 上。預設值為“max”。

記憶體使用率硬限制。這是限制 cgroup 記憶體使用率的主要機制。如果 cgroup 的記憶體使用率達到此限制並且無法降低,則會在 cgroup 中呼叫 OOM killer。在某些情況下,使用率可能會暫時超過限制。

在預設配置中,除非 OOM killer 選擇當前任務作為犧牲品,否則常規 0 階分配始終會成功。

某些型別的分配不會呼叫 OOM killer。呼叫者可以以不同的方式重試它們,作為 -ENOMEM 返回到使用者空間,或者在諸如磁碟預讀之類的情況下靜默忽略。

如果 memory.max 以 O_NONBLOCK 開啟,則會繞過同步回收和 oom-kill。這對於需要動態調整作業記憶體限制的管理員程序很有用,而無需花費自己的 CPU 資源進行記憶體回收。作業將在其下一個收費請求時觸發回收和/或 oom-kill。

請注意,使用 O_NONBLOCK,目標記憶體 cgroup 可能需要無限的時間才能將使用率降低到低於限制,這是因為延遲收費請求或忙於訪問其記憶體以減慢回收速度。

memory.reclaim

一個只寫巢狀鍵檔案,存在於所有 cgroup 上。

這是一個在目標 cgroup 中觸發記憶體回收的簡單介面。

示例

echo "1G" > memory.reclaim

請注意,核心可以從目標 cgroup 中過度或不足地回收記憶體。如果回收的位元組數少於指定的數量,則返回 -EAGAIN。

請注意,主動回收(由此介面觸發)並不表示記憶體 cgroup 上的記憶體壓力。因此,通常在這種情況下不會執行由記憶體回收觸發的套接字記憶體平衡。這意味著網路層不會根據記憶體.reclaim 引起的回收進行調整。

定義了以下巢狀鍵。

swappiness

用於回收的 Swappiness 值

指定 swappiness 值指示核心使用該 swappiness 值執行回收。請注意,這具有與應用於 memcg 回收的 vm.swappiness 相同的語義,並具有所有現有限制和潛在的未來擴充套件。

swappiness 的有效範圍是 [0-200, max],設定 swappiness=max 會專門回收匿名記憶體。

memory.peak

一個讀寫單值檔案,存在於非根 cgroup 上。

自建立 cgroup 或最近重置該 FD 以來,為 cgroup 及其後代記錄的最大記憶體使用量。

向此檔案寫入任何非空字串會將其重置為當前記憶體使用量,以便透過同一檔案描述符進行後續讀取。

memory.oom.group

一個讀寫單值檔案,存在於非根 cgroup 上。預設值為“0”。

確定 OOM killer 是否應將 cgroup 視為不可分割的工作負載。如果設定,則屬於 cgroup 或其後代的所有任務(如果記憶體 cgroup 不是葉 cgroup)將一起殺死或根本不殺死。這可以用於避免部分殺死,以保證工作負載的完整性。

具有 OOM 保護的任務(oom_score_adj 設定為 -1000)被視為例外,並且永遠不會被殺死。

如果在 cgroup 中呼叫了 OOM killer,則無論祖先 cgroup 的 memory.oom.group 值如何,它都不會殺死此 cgroup 之外的任何任務。

memory.events

一個只讀扁平鍵檔案,存在於非根 cgroup 上。定義了以下條目。除非另有說明,否則此檔案中的值更改會生成檔案修改事件。

請注意,此檔案中的所有欄位都是分層的,並且由於層級結構中的事件,可能會生成檔案修改事件。有關 cgroup 級別的本地事件,請參閱 memory.events.local。

low

由於記憶體壓力過大,即使 cgroup 的使用率低於低邊界,也會回收 cgroup 的次數。這通常表明低邊界已過度承諾。

high

由於超過了高記憶體邊界,因此限制 cgroup 的程序並將其路由以執行直接記憶體回收的次數。對於記憶體使用率受高限制而不是全域性記憶體壓力限制的 cgroup,預計會發生此事件。

max

cgroup 的記憶體使用率即將超過最大邊界的次數。如果直接回收無法將其降低,則 cgroup 將進入 OOM 狀態。

oom

cgroup 的記憶體使用率已達到限制並且分配即將失敗的次數。

如果 OOM killer 不被視為一種選擇,例如對於失敗的高階分配或呼叫者要求不要重試嘗試,則不會引發此事件。

oom_kill

屬於此 cgroup 的程序被任何型別的 OOM killer 殺死的次數。

oom_group_kill

發生組 OOM 的次數。

memory.events.local

類似於 memory.events,但檔案中的欄位是 cgroup 本地的,即非分層的。在此檔案上生成的檔案修改事件僅反映本地事件。

memory.stat

一個只讀扁平鍵檔案,存在於非根 cgroup 上。

這會將 cgroup 的記憶體佔用分解為不同型別的記憶體、特定於型別的詳細資訊以及有關記憶體管理系統狀態和過去事件的其他資訊。

所有記憶體量均以位元組為單位。

條目的排序是為了便於人類閱讀,並且新條目可能會出現在中間。不要依賴於專案保持在固定位置;使用鍵來查詢特定值!

如果該條目沒有每個節點計數器(或未在 memory.numa_stat 中顯示)。我們使用“npn”(非每個節點)作為標記來指示它不會在 memory.numa_stat 中顯示。

anon

在匿名對映中使用的記憶體量,例如 brk()、sbrk() 和 mmap(MAP_ANONYMOUS)。請注意,如果只有一些而不是所有此類分配的記憶體不再對映,則某些核心配置可能會考慮完整的較大分配(例如,THP)。

file

用於快取檔案系統資料的記憶體量,包括 tmpfs 和共享記憶體。

kernel (npn)

核心記憶體總量,包括(kernel_stack、pagetables、percpu、vmalloc、slab)以及其他核心記憶體用例。

kernel_stack

分配給核心堆疊的記憶體量。

pagetables

為頁表分配的記憶體量。

sec_pagetables

為輔助頁表分配的記憶體量,當前包括 x86 和 arm64 上的 KVM mmu 分配以及 IOMMU 頁表。

percpu (npn)

用於儲存每個 CPU 核心資料結構的記憶體量。

sock (npn)

在網路傳輸緩衝區中使用的記憶體量

vmalloc (npn)

用於 vmap 支援的記憶體的記憶體量。

shmem

已快取的檔案系統資料量,這些資料由交換空間支援,例如 tmpfs、shm 段、共享匿名 mmap()。

zswap

zswap 壓縮後端使用的記憶體量。

zswapped

已交換到 zswap 的應用程式記憶體量。

file_mapped

使用 mmap() 對映的已快取檔案系統資料量。請注意,如果只有一些而不是所有此類分配的記憶體不再對映,則某些核心配置可能會考慮完整的較大分配(例如,THP)。

file_dirty

已修改但尚未寫回磁碟的已快取檔案系統資料量

file_writeback

已修改且當前正在寫回磁碟的快取檔案系統資料量

swapcached

快取在記憶體中的交換空間量。交換快取同時計入記憶體和交換空間使用量。

anon_thp

由透明大頁支援的匿名對映中使用的記憶體量

file_thp

由透明大頁支援的快取檔案系統資料量

shmem_thp

由透明大頁支援的 shm、tmpfs、共享匿名 mmap() 的數量

inactive_anon, active_anon, inactive_file, active_file, unevictable

頁面回收演算法使用的內部記憶體管理列表上的記憶體量,包括交換空間支援和檔案系統支援。

由於這些代表內部列表狀態(例如,shmem頁面位於匿名記憶體管理列表上),因此 inactive_foo + active_foo 可能不等於 foo 計數器的值,因為 foo 計數器是基於型別的,而不是基於列表的。

slab_reclaimable

“slab”中可以回收的部分,例如目錄項和 inode。

slab_unreclaimable

“slab”中在記憶體壓力下無法回收的部分。

slab (npn)

用於儲存核心資料結構的記憶體量。

workingset_refault_anon

先前驅逐的匿名頁面重新發生的缺頁次數。

workingset_refault_file

先前驅逐的檔案頁面重新發生的缺頁次數。

workingset_activate_anon

立即啟用的重新發生的匿名頁面數。

workingset_activate_file

立即啟用的重新發生的檔案頁面數。

workingset_restore_anon

在被回收之前被檢測為活動工作集的恢復的匿名頁面數量。

workingset_restore_file

在被回收之前被檢測為活動工作集的恢復的檔案頁面數量。

workingset_nodereclaim

陰影節點被回收的次數

pswpin (npn)

交換到記憶體中的頁面數

pswpout (npn)

從記憶體中交換出去的頁面數

pgscan (npn)

掃描的頁面數量(在非活動 LRU 列表中)

pgsteal (npn)

回收的頁面數量

pgscan_kswapd (npn)

kswapd 掃描的頁面數量(在非活動 LRU 列表中)

pgscan_direct (npn)

直接掃描的頁面數量(在非活動 LRU 列表中)

pgscan_khugepaged (npn)

khugepaged 掃描的頁面數量(在非活動 LRU 列表中)

pgscan_proactive (npn)

主動掃描的頁面數量(在非活動 LRU 列表中)

pgsteal_kswapd (npn)

kswapd 回收的頁面數量

pgsteal_direct (npn)

直接回收的頁面數量

pgsteal_khugepaged (npn)

khugepaged 回收的頁面數量

pgsteal_proactive (npn)

主動回收的頁面數量

pgfault (npn)

發生的總缺頁次數

pgmajfault (npn)

發生的主要缺頁次數

pgrefill (npn)

掃描的頁面數量(在活動 LRU 列表中)

pgactivate (npn)

移動到活動 LRU 列表的頁面數量

pgdeactivate (npn)

移動到非活動 LRU 列表的頁面數量

pglazyfree (npn)

在記憶體壓力下推遲釋放的頁面數量

pglazyfreed (npn)

回收的延遲釋放頁面的數量

swpin_zero

交換到記憶體並填充零的頁面數量,其中由於在交換出期間檢測到頁面內容為零,因此優化了 I/O。

swpout_zero

由於內容被檢測為零而跳過 I/O 的交換出的零填充頁面數量。

zswpin

從 zswap 移動到記憶體中的頁面數量。

zswpout

從記憶體移動到 zswap 的頁面數量。

zswpwb

從 zswap 寫入到交換空間的頁面數量。

thp_fault_alloc (npn)

分配用於滿足缺頁中斷的透明巨頁的數量。 如果未設定 CONFIG_TRANSPARENT_HUGEPAGE,則不存在此計數器。

thp_collapse_alloc (npn)

為了允許摺疊現有頁面範圍而分配的透明巨頁的數量。 如果未設定 CONFIG_TRANSPARENT_HUGEPAGE,則不存在此計數器。

thp_swpout (npn)

未分割的情況下,以一個整體進行交換輸出的透明巨頁數量。

thp_swpout_fallback (npn)

交換輸出之前分割的透明巨頁的數量。通常是因為未能為大頁分配一些連續的交換空間。

numa_pages_migrated (npn)

NUMA 平衡遷移的頁面數。

numa_pte_updates (npn)

透過 NUMA 平衡修改其頁表條目的頁面數,以便在訪問時產生 NUMA 提示錯誤。

numa_hint_faults (npn)

NUMA 提示錯誤的數量。

numa_task_migrated (npn)

NUMA 平衡進行的任務遷移次數。

numa_task_swapped (npn)

NUMA 平衡進行的任務交換次數。

pgdemote_kswapd

kswapd 降級的頁面數。

pgdemote_direct

直接降級的頁面數。

pgdemote_khugepaged

khugepaged 降級的頁面數。

pgdemote_proactive

主動降級的頁面數。

hugetlb

hugetlb 頁面使用的記憶體量。 只有在 memory.current 中計算 hugetlb 使用情況時才顯示此指標(即,使用 memory_hugetlb_accounting 選項掛載 cgroup)。

memory.numa_stat

只讀巢狀鍵檔案,存在於非根 cgroup 上。

它將 cgroup 的記憶體佔用分解為不同的記憶體型別、特定於型別的詳細資訊以及記憶體管理系統狀態的每個節點上的其他資訊。

這對於提供 memcg 中 NUMA 區域性性資訊的可見性非常有用,因為允許從任何物理節點分配頁面。一個用例是將此資訊與應用程式的 CPU 分配相結合來評估應用程式效能。

所有記憶體量均以位元組為單位。

memory.numa_stat 的輸出格式為

type N0=<bytes in node 0> N1=<bytes in node 1> ...

條目的排序是為了便於人類閱讀,並且新條目可能會出現在中間。不要依賴於專案保持在固定位置;使用鍵來查詢特定值!

這些條目可以參考 memory.stat。

memory.swap.current

一個只讀單值檔案,存在於非根 cgroup 上。

cgroup 及其後代當前使用的交換空間總量。

memory.swap.high

一個讀寫單值檔案,存在於非根 cgroup 上。預設值為“max”。

交換空間使用量限制。如果 cgroup 的交換空間使用量超過此限制,則將限制其所有進一步的分配,以允許使用者空間實施自定義的記憶體不足程式。

此限制標誌著 cgroup 的不歸路。它不是為了管理工作負載在正常操作期間進行的交換量而設計的。與 memory.swap.max 相比,後者禁止交換超出設定數量,但只要可以回收其他記憶體,就允許 cgroup 繼續不受阻礙地執行。

不希望健康的工作負載達到此限制。

memory.swap.peak

一個讀寫單值檔案,存在於非根 cgroup 上。

自 cgroup 建立或最近一次重置該 FD 以來,為 cgroup 及其後代記錄的最大交換空間使用量。

向此檔案寫入任何非空字串會將其重置為當前記憶體使用量,以便透過同一檔案描述符進行後續讀取。

memory.swap.max

一個讀寫單值檔案,存在於非根 cgroup 上。預設值為“max”。

交換空間硬限制。如果 cgroup 的交換空間使用量達到此限制,則不會交換出 cgroup 的匿名記憶體。

memory.swap.events

一個只讀扁平鍵檔案,存在於非根 cgroup 上。定義了以下條目。除非另有說明,否則此檔案中的值更改會生成檔案修改事件。

high

cgroup 的交換空間使用量超過高閾值的次數。

max

cgroup 的交換空間使用量即將超過最大邊界且交換空間分配失敗的次數。

fail

由於系統範圍內的交換空間不足或達到最大限制而導致交換空間分配失敗的次數。

當減少到低於當前使用量時,現有交換空間條目會逐漸回收,並且交換空間使用量可能會在較長時間內保持高於限制。這減少了對工作負載和記憶體管理的影響。

memory.zswap.current

一個只讀單值檔案,存在於非根 cgroup 上。

zswap 壓縮後端使用的記憶體總量。

memory.zswap.max

一個讀寫單值檔案,存在於非根 cgroup 上。預設值為“max”。

Zswap 使用量硬限制。 如果 cgroup 的 zswap 池達到此限制,它將拒絕接受任何更多儲存,直到現有條目錯誤地返回或寫入磁碟。

memory.zswap.writeback

一個讀寫單值檔案。 預設值為 “1”。 請注意,此設定是分層的,即,如果較高層級這樣做,則將隱式停用子 cgroup 的回寫。

當此設定為 0 時,所有交換嘗試都將停用為交換裝置。 這包括 zswap 回寫和由於 zswap 儲存故障導致的交換。 如果 zswap 儲存故障反覆發生(例如,如果頁面不可壓縮),則使用者可以在停用回寫後觀察到回收效率低下(因為相同的頁面可能會一遍又一遍地被拒絕)。

請注意,這與將 memory.swap.max 設定為 0 略有不同,因為它仍然允許將頁面寫入 zswap 池。 如果停用 zswap,則此設定無效,除非將 memory.swap.max 設定為 0,否則允許交換。

memory.pressure

只讀巢狀鍵檔案。

顯示記憶體的壓力暫停資訊。 有關詳細資訊,請參閱 Documentation/accounting/psi.rst

使用指南

“memory.high” 是控制記憶體使用量的主要機制。 在高限制上過度提交(高限制之和 > 可用記憶體)並讓全域性記憶體壓力根據使用情況分配記憶體是一種可行的策略。

由於違反高限制不會觸發 OOM killer,而是限制違規的 cgroup,因此管理代理有足夠的機會進行監視並採取適當的措施,例如授予更多記憶體或終止工作負載。

確定 cgroup 是否有足夠的記憶體並不簡單,因為記憶體使用量並不能表明工作負載是否可以從更多記憶體中受益。 例如,將從網路接收的資料寫入檔案的工作負載可以使用所有可用記憶體,但也可以使用少量記憶體進行同樣高效的執行。 需要一種衡量記憶體壓力的方法 - 由於缺少記憶體而對工作負載產生多大影響 - 以確定工作負載是否需要更多記憶體; 遺憾的是,尚未實現記憶體壓力監控機制。

記憶體所有權

記憶體區域被計入例項化它的 cgroup,並一直計入該 cgroup,直到該區域被釋放。 將程序遷移到不同的 cgroup 不會將它在前一個 cgroup 中例項化的記憶體使用量移動到新的 cgroup。

一個記憶體區域可能被屬於不同 cgroup 的程序使用。 將該區域計入哪個 cgroup 是不確定的; 但是,隨著時間的推移,該記憶體區域可能會最終進入一個具有足夠記憶體分配以避免高回收壓力的 cgroup 中。

如果一個 cgroup 清除大量預計會被其他 cgroup 重複訪問的記憶體,則使用 POSIX_FADV_DONTNEED 來放棄屬於受影響檔案的記憶體區域的所有權以確保正確的記憶體所有權是有意義的。

IO

“io” 控制器調節 IO 資源的分配。 此控制器實現了基於權重和絕對頻寬或 IOPS 限制的分配; 但是,只有在使用 cfq-iosched 時才能使用基於權重的分配,並且這兩種方案都不適用於 blk-mq 裝置。

IO 介面檔案

io.stat

只讀巢狀鍵檔案。

行由 $MAJ:$MIN 裝置號鍵控,並且未排序。 定義了以下巢狀鍵。

rbytes

讀取的位元組數

wbytes

寫入的位元組數

rios

讀取 IO 的數量

wios

寫入 IO 的數量

dbytes

丟棄的位元組數

dios

丟棄 IO 的數量

以下是一個示例讀取輸出

8:16 rbytes=1459200 wbytes=314773504 rios=192 wios=353 dbytes=0 dios=0
8:0 rbytes=90430464 wbytes=299008000 rios=8950 wios=1252 dbytes=50331648 dios=3021
io.cost.qos

一個讀寫巢狀鍵檔案,僅存在於根 cgroup 上。

此檔案配置基於 IO 成本模型的控制器的服務質量(CONFIG_BLK_CGROUP_IOCOST),該控制器當前實現 “io.weight” 比例控制。 行由 $MAJ:$MIN 裝置號鍵控,並且未排序。 給定裝置的行會在 “io.cost.qos” 或 “io.cost.model” 上對裝置進行首次寫入時填充。 定義了以下巢狀鍵。

enable

啟用基於權重的控制

ctrl

“auto” 或 “user”

rpct

讀取延遲百分位數 [0, 100]

rlat

讀取延遲閾值

wpct

寫入延遲百分位數 [0, 100]

wlat

寫入延遲閾值

min

最小縮放百分比 [1, 10000]

max

最大縮放百分比 [1, 10000]

預設情況下停用該控制器,可以透過將 “enable” 設定為 1 來啟用。 “rpct” 和 “wpct” 引數預設為零,並且控制器使用內部裝置飽和狀態來調整 “min” 和 “max” 之間的總體 IO 速率。

當需要更好的控制質量時,可以配置延遲 QoS 引數。 例如

8:16 enable=1 ctrl=auto rpct=95.00 rlat=75000 wpct=95.00 wlat=150000 min=50.00 max=150.0

表示在 sdb 上,如果讀取完成延遲的第 95 個百分位數高於 75 毫秒或寫入 150 毫秒,則控制器已啟用,將認為裝置已飽和,並相應地調整 50% 到 150% 之間的總體 IO 釋出速率。

飽和點越低,延遲 QoS 越好,但會犧牲聚合頻寬。 “min” 和 “max” 之間的允許調整範圍越窄,IO 行為就越符合成本模型。 請注意,IO 釋出基本速率可能與 100% 相差甚遠,盲目設定 “min” 和 “max” 可能會導致裝置容量或控制質量的顯著損失。 “min” 和 “max” 適用於調節顯示出廣泛的臨時行為變化的裝置 - 例如,一個 ssd 在一段時間內以線路速度接受寫入,然後完全停止數秒。

當 “ctrl” 為 “auto” 時,引數由核心控制並且可能會自動更改。 將 “ctrl” 設定為 “user” 或設定任何百分位數和延遲引數會將其置於 “user” 模式並停用自動更改。 可以透過將 “ctrl” 設定為 “auto” 來恢復自動模式。

io.cost.model

一個讀寫巢狀鍵檔案,僅存在於根 cgroup 上。

此檔案配置基於 IO 成本模型的控制器的成本模型(CONFIG_BLK_CGROUP_IOCOST),該控制器當前實現 “io.weight” 比例控制。 行由 $MAJ:$MIN 裝置號鍵控,並且未排序。 給定裝置的行會在 “io.cost.qos” 或 “io.cost.model” 上對裝置進行首次寫入時填充。 定義了以下巢狀鍵。

ctrl

“auto” 或 “user”

model

使用的成本模型 - “linear”

當 “ctrl” 為 “auto” 時,核心可能會動態更改所有引數。 當 “ctrl” 設定為 “user” 或寫入任何其他引數時,“ctrl” 變為 “user” 並且停用自動更改。

當 “model” 為 “linear” 時,定義了以下模型引數。

[r|w]bps

最大順序 IO 吞吐量

[r|w]seqiops

每秒最大 4k 順序 IO

[r|w]randiops

每秒最大 4k 隨機 IO

從以上可以看出,內建線性模型確定了順序 IO 和隨機 IO 的基本成本以及 IO 大小的成本系數。 雖然簡單,但此模型可以令人滿意地覆蓋大多數常見裝置類別。

IO 成本模型預計在絕對意義上並不準確,並且會動態縮放到裝置行為。

如果需要,可以使用 tools/cgroup/iocost_coef_gen.py 生成特定於裝置的係數。

io.weight

一個讀寫平面鍵檔案,存在於非根 cgroup 上。 預設值為 “default 100”。

第一行是應用於沒有特定覆蓋的裝置的預設權重。 其餘的是由 $MAJ:$MIN 裝置號鍵控的覆蓋,並且未排序。 權重範圍為 [1, 10000],並指定 cgroup 可以使用的 IO 時間相對於其同級的相對量。

可以透過寫入 “default $WEIGHT” 或簡單地寫入 “$WEIGHT” 來更新預設權重。 可以透過寫入 “$MAJ:$MIN $WEIGHT” 來設定覆蓋,並透過寫入 “$MAJ:$MIN default” 來取消設定。

以下是一個示例讀取輸出

default 100
8:16 200
8:0 50
io.max

一個讀寫巢狀鍵檔案,存在於非根 cgroup 上。

基於 BPS 和 IOPS 的 IO 限制。 行由 $MAJ:$MIN 裝置號鍵控,並且未排序。 定義了以下巢狀鍵。

rbps

每秒最大讀取位元組數

wbps

每秒最大寫入位元組數

riops

每秒最大讀取 IO 運算元

wiops

每秒最大寫入 IO 運算元

寫入時,可以按任何順序指定任意數量的巢狀鍵值對。 可以將 “max” 指定為刪除特定限制的值。 如果多次指定相同的鍵,則結果未定義。

BPS 和 IOPS 在每個 IO 方向上測量,如果達到限制,則 IO 會延遲。 允許臨時突發。

將 8:16 的讀取限制設定為 2M BPS,寫入限制設定為 120 IOPS

echo "8:16 rbps=2097152 wiops=120" > io.max

讀取返回以下內容

8:16 rbps=2097152 wbps=max riops=max wiops=120

可以透過寫入以下內容來刪除寫入 IOPS 限制

echo "8:16 wiops=max" > io.max

現在讀取返回以下內容

8:16 rbps=2097152 wbps=max riops=max wiops=max
io.pressure

只讀巢狀鍵檔案。

顯示 IO 的壓力暫停資訊。 有關詳細資訊,請參閱 Documentation/accounting/psi.rst

回寫

頁面快取透過緩衝寫入和共享 mmaps 變髒,並由回寫機制非同步寫入到後備檔案系統。 回寫位於記憶體和 IO 域之間,並透過平衡髒頁和寫入 IO 來調節髒記憶體的比例。

io 控制器與記憶體控制器結合使用,實現頁面快取回寫 IO 的控制。 記憶體控制器定義了計算和維護髒記憶體比率的記憶體域,而 io 控制器定義了為記憶體域寫入髒頁的 io 域。 系統範圍和每個 cgroup 的髒記憶體狀態都會被檢查,並且會強制執行兩者中限制性更強的一個。

cgroup 回寫需要底層檔案系統的顯式支援。 目前,cgroup 回寫已在 ext2、ext4、btrfs、f2fs 和 xfs 上實現。 在其他檔案系統上,所有回寫 IO 都歸因於根 cgroup。

記憶體和回寫管理存在固有的差異,這會影響如何跟蹤 cgroup 所有權。 記憶體按頁面跟蹤,而回寫按 inode 跟蹤。 出於回寫的目的,inode 被分配給一個 cgroup,並且所有寫入來自 inode 的髒頁的 IO 請求都歸因於該 cgroup。

由於記憶體的 cgroup 所有權是按頁面跟蹤的,因此可能存在與 inode 關聯的 cgroup 不同的頁面。 這些稱為外來頁面。 回寫不斷跟蹤外來頁面,並且如果特定的外來 cgroup 在一段時間內佔據多數,則會將 inode 的所有權切換到該 cgroup。

雖然此模型對於給定的 inode 主要由單個 cgroup 弄髒的大多數用例來說已經足夠了,即使主要寫入 cgroup 隨時間變化,但不支援多個 cgroup 同時寫入單個 inode 的用例。 在這種情況下,很可能很大一部分 IO 會被錯誤地歸因。 由於記憶體控制器在第一次使用時分配頁面所有權,並且在頁面釋放之前不會更新它,因此即使回寫嚴格遵循頁面所有權,多個 cgroup 弄髒重疊區域也不會按預期工作。 建議避免此類使用模式。

影響回寫行為的 sysctl 旋鈕按如下方式應用於 cgroup 回寫。

vm.dirty_background_ratio, vm.dirty_ratio

這些比率同樣適用於 cgroup 回寫,可用記憶體量受到記憶體控制器和系統範圍內的乾淨記憶體施加的限制。

vm.dirty_background_bytes, vm.dirty_bytes

對於 cgroup 回寫,這會計算為相對於總可用記憶體的比率,並以與 vm.dirty[_background]_ratio 相同的方式應用。

IO 延遲

這是一個用於 IO 工作負載保護的 cgroup v2 控制器。 您為組提供一個延遲目標,如果平均延遲超過該目標,則控制器將限制任何延遲目標低於受保護工作負載的對等方。

這些限制僅在層次結構中的對等級別應用。 這意味著在下圖中,只有組 A、B 和 C 會相互影響,並且組 D 和 F 會相互影響。 組 G 不會影響任何人

          [root]
  /          |            \
  A          B            C
 /  \        |
D    F       G

因此,配置此功能的理想方式是在組 A、B 和 C 中設定 io.latency。 通常,您不希望設定低於您的裝置支援的延遲的值。 進行實驗以找到最適合您的工作負載的值。 從高於您的裝置的預期延遲開始,並觀察您的工作負載組的 io.stat 中的 avg_lat 值,以瞭解您在正常操作期間看到的延遲。 使用 avg_lat 值作為您的實際設定的基礎,設定為高於 io.stat 中的值 10-15%。

IO 延遲限制的工作原理

io.latency 是工作保守的; 因此,只要每個人都滿足其延遲目標,控制器就不會執行任何操作。 一旦組開始錯過其目標,它就會開始限制任何目標高於自身的對等組。 這種限制採用 2 種形式

  • 佇列深度限制。 這是允許組擁有的未完成 IO 的數量。 我們將相對快速地進行鉗制,從沒有限制開始,一直到一次 1 個 IO。

  • 人工延遲誘導。 有些型別的 IO 如果不受到限制,可能會對優先順序較高的組產生不利影響。 這包括交換和元資料 IO。 允許正常發生這些型別的 IO,但是它們會“收費”到原始組。 如果原始組受到限制,您將看到 io.stat 中的 use_delay 和 delay 欄位增加。 delay 值是新增到在此組中執行的任何程序的微秒數。 因為如果有大量交換或元資料 IO 發生,此數字可能會變得非常大,所以我們將各個延遲事件限制為一次 1 秒。

一旦受害者組再次開始滿足其延遲目標,它將開始取消限制先前受到限制的任何對等組。 如果受害者組只是停止執行 IO,則全域性計數器將適當地取消限制。

IO 延遲介面檔案

io.latency

這採用與其他控制器相似的格式。

“MAJOR:MINOR target=<以微秒為單位的目標時間>”

io.stat

如果啟用了控制器,您將在 io.stat 中看到額外的統計資訊以及正常的統計資訊。

depth

這是組的當前佇列深度。

avg_lat

這是一個指數移動平均值,其衰減率為 1/exp,受取樣間隔的限制。 可以透過將 io.stat 中的 win 值乘以基於 win 值的相應樣本數來計算衰減率間隔。

win

以毫秒為單位的取樣視窗大小。 這是評估事件之間的最短持續時間。 視窗僅在 IO 活動時消逝。 空閒期會延長最近的視窗。

IO 優先順序

單個屬性控制 I/O 優先順序 cgroup 策略的行為,即 io.prio.class 屬性。 該屬性接受以下值

no-change

不修改 I/O 優先順序類。

promote-to-rt

對於具有非 RT I/O 優先順序類的請求,將其更改為 RT。 還要將這些請求的優先順序級別更改為 4。 不修改具有優先順序類 RT 的請求的 I/O 優先順序。

restrict-to-be

對於沒有 I/O 優先順序類或具有 I/O 優先順序類 RT 的請求,將其更改為 BE。 還要將這些請求的優先順序級別更改為 0。 不修改具有優先順序類 IDLE 的請求的 I/O 優先順序類。

idle

將所有請求的 I/O 優先順序類更改為 IDLE,即最低的 I/O 優先順序類。

none-to-rt

已棄用。 只是 promote-to-rt 的別名。

以下數值與 I/O 優先順序策略相關聯

no-change

0

promote-to-rt

1

restrict-to-be

2

idle

3

與每個 I/O 優先順序類對應的數值如下

IOPRIO_CLASS_NONE

0

IOPRIO_CLASS_RT (即時)

1

IOPRIO_CLASS_BE (盡力而為)

2

IOPRIO_CLASS_IDLE (空閒)

3

設定請求 I/O 優先順序類的演算法如下:

  • 如果 I/O 優先順序類策略是 promote-to-rt,則將請求 I/O 優先順序類更改為 IOPRIO_CLASS_RT,並將請求 I/O 優先順序級別更改為 4。

  • 如果 I/O 優先順序類策略不是 promote-to-rt,則將 I/O 優先順序類策略轉換為數字,然後將請求 I/O 優先順序類更改為 I/O 優先順序類策略數字和數值 I/O 優先順序類中的最大值。

PID

程序數控制器用於允許 cgroup 在達到指定限制後停止 fork() 或 clone() 任何新任務。

cgroup 中的任務數可以透過其他控制器無法阻止的方式耗盡,因此需要自己的控制器。例如,fork 炸彈很可能在達到記憶體限制之前耗盡任務數。

請注意,此控制器中使用的 PID 指的是 TID,即核心使用的程序 ID。

PID 介面檔案

pids.max

一個讀寫單值檔案,存在於非根 cgroup 上。預設值為“max”。

程序數的硬限制。

pids.current

一個只讀單值檔案,存在於非根 cgroup 上。

cgroup 及其後代中當前的程序數。

pids.peak

一個只讀單值檔案,存在於非根 cgroup 上。

cgroup 及其後代中的程序數達到的最大值。

pids.events

一個只讀的扁平鍵檔案,存在於非根 cgroup 中。除非另有說明,否則此檔案中的值更改會生成檔案修改事件。定義了以下條目。

max

cgroup 的程序總數達到 pids.max 限制的次數(另請參見 pids_localevents)。

pids.events.local

類似於 pids.events,但檔案中的欄位是 cgroup 本地的,即非分層的。在此檔案上生成的檔案修改事件僅反映本地事件。

組織操作不會受到 cgroup 策略的阻止,因此可能會出現 pids.current > pids.max 的情況。這可以透過將限制設定為小於 pids.current,或將足夠多的程序附加到 cgroup 以使 pids.current 大於 pids.max 來實現。但是,無法透過 fork() 或 clone() 違反 cgroup PID 策略。如果建立新程序會導致違反 cgroup 策略,則這些將返回 -EAGAIN。

Cpuset

“cpuset”控制器提供了一種機制,用於將任務的 CPU 和記憶體節點放置限制為僅限於任務當前 cgroup 中 cpuset 介面檔案中指定的資源。這在大型 NUMA 系統上尤其有價值,在這些系統中,將作業放置在系統上適當大小的子集上,並仔細進行處理器和記憶體放置,以減少跨節點記憶體訪問和爭用,可以提高整體系統效能。

“cpuset”控制器是分層的。這意味著控制器不能使用其父級不允許的 CPU 或記憶體節點。

Cpuset 介面檔案

cpuset.cpus

一個讀寫多值檔案,存在於啟用 cpuset 的非根 cgroup 上。

它列出了此 cgroup 中的任務要使用的請求 CPU。但是,實際授予的 CPU 列表受其父級施加的約束的約束,並且可能與請求的 CPU 不同。

CPU 編號是逗號分隔的數字或範圍。例如

# cat cpuset.cpus
0-4,6,8-10

空值表示 cgroup 使用與最近的具有非空“cpuset.cpus”的 cgroup 祖先相同的設定,如果沒有找到,則使用所有可用的 CPU。

“cpuset.cpus”的值保持不變,直到下次更新,並且不會受到任何 CPU 熱插拔事件的影響。

cpuset.cpus.effective

一個只讀多值檔案,存在於所有啟用 cpuset 的 cgroup 上。

它列出了其父級實際授予此 cgroup 的聯機 CPU。當前 cgroup 中的任務允許使用這些 CPU。

如果“cpuset.cpus”為空,則“cpuset.cpus.effective”檔案顯示可以由該 cgroup 使用的來自父 cgroup 的所有 CPU。否則,它應該是“cpuset.cpus”的子集,除非“cpuset.cpus”中列出的 CPU 都無法授予。在這種情況下,它將被視為與空的“cpuset.cpus”一樣。

它的值將受到 CPU 熱插拔事件的影響。

cpuset.mems

一個讀寫多值檔案,存在於啟用 cpuset 的非根 cgroup 上。

它列出了此 cgroup 中的任務要使用的請求記憶體節點。但是,實際授予的記憶體節點列表受其父級施加的約束的約束,並且可能與請求的記憶體節點不同。

記憶體節點編號是逗號分隔的數字或範圍。例如

# cat cpuset.mems
0-1,3

空值表示 cgroup 使用與最近的具有非空“cpuset.mems”的 cgroup 祖先相同的設定,如果沒有找到,則使用所有可用的記憶體節點。

“cpuset.mems”的值保持不變,直到下次更新,並且不會受到任何記憶體節點熱插拔事件的影響。

將非空值設定為“cpuset.mems”會導致 cgroup 中任務的記憶體遷移到指定的節點,如果它們當前正在使用指定節點之外的記憶體。

此記憶體遷移會產生費用。遷移可能不完整,並且可能會留下一些記憶體頁。因此,建議在將新任務生成到 cpuset 中之前,應正確設定“cpuset.mems”。即使需要使用活動任務更改“cpuset.mems”,也不應頻繁進行。

cpuset.mems.effective

一個只讀多值檔案,存在於所有啟用 cpuset 的 cgroup 上。

它列出了其父級實際授予此 cgroup 的聯機記憶體節點。當前 cgroup 中的任務允許使用這些記憶體節點。

如果“cpuset.mems”為空,則它顯示將可供該 cgroup 使用的來自父 cgroup 的所有記憶體節點。否則,它應該是“cpuset.mems”的子集,除非“cpuset.mems”中列出的記憶體節點都無法授予。在這種情況下,它將被視為與空的“cpuset.mems”一樣。

它的值將受到記憶體節點熱插拔事件的影響。

cpuset.cpus.exclusive

一個讀寫多值檔案,存在於啟用 cpuset 的非根 cgroup 上。

它列出了所有允許用於建立新 cpuset 分割槽的獨佔 CPU。除非 cgroup 成為有效的分割槽根目錄,否則不使用其值。有關 cpuset 分割槽的描述,請參見下面的“cpuset.cpus.partition”部分。

當 cgroup 成為分割槽根目錄時,分配給該分割槽的實際獨佔 CPU 列在“cpuset.cpus.exclusive.effective”中,該 CPU 可能與“cpuset.cpus.exclusive”不同。如果先前已設定“cpuset.cpus.exclusive”,“cpuset.cpus.exclusive.effective”始終是它的子集。

使用者可以手動將其設定為與“cpuset.cpus”不同的值。設定它的一個約束是 CPU 列表必須相對於其同級的“cpuset.cpus.exclusive”是獨佔的。如果未設定同級 cgroup 的“cpuset.cpus.exclusive”,則其“cpuset.cpus”值(如果已設定)不能是它的子集,以便在刪除獨佔 CPU 時至少保留一個可用的 CPU。

對於父 cgroup,其任何一個獨佔 CPU 只能分配給最多一個子 cgroup。不允許一個獨佔 CPU 出現在其兩個或多個子 cgroup 中(獨佔規則)。違反獨佔規則的值將被拒絕並顯示寫入錯誤。

根 cgroup 是分割槽根目錄,其所有可用 CPU 都在其獨佔 CPU 集中。

cpuset.cpus.exclusive.effective

一個只讀多值檔案,存在於所有啟用 cpuset 的非根 cgroup 上。

此檔案顯示可用於建立分割槽根目錄的有效獨佔 CPU 集。如果其父級不是根 cgroup,則此檔案的內容始終是其父級的“cpuset.cpus.exclusive.effective”的子集。如果已設定,它也將是“cpuset.cpus.exclusive”的子集。如果未設定“cpuset.cpus.exclusive”,則在形成本地分割槽時,將其視為具有“cpuset.cpus”的隱式值。

cpuset.cpus.isolated

一個只讀且僅根 cgroup 的多值檔案。

此檔案顯示現有隔離分割槽中使用的所有隔離 CPU 的集合。如果未建立隔離分割槽,則它將為空。

cpuset.cpus.partition

一個讀寫單值檔案,存在於啟用 cpuset 的非根 cgroup 上。此標誌由父 cgroup 擁有,並且不可委託。

寫入時,它僅接受以下輸入值。

“member”

分割槽的非根成員

“root”

分割槽根目錄

“isolated”

沒有負載平衡的分割槽根目錄

cpuset 分割槽是啟用 cpuset 的 cgroup 的集合,其層次結構的頂部有一個分割槽根目錄,以及它的後代,除了那些單獨的分割槽根目錄及其後代。分割槽具有對其分配的獨佔 CPU 集的獨佔訪問許可權。該分割槽之外的其他 cgroup 不能使用該集合中的任何 CPU。

有兩種型別的分割槽 - 本地和遠端。本地分割槽是指其父 cgroup 也是有效分割槽根目錄的分割槽。遠端分割槽是指其父 cgroup 本身不是有效分割槽根目錄的分割槽。對於本地分割槽的建立,寫入“cpuset.cpus.exclusive”是可選的,因為如果未設定其“cpuset.cpus.exclusive”檔案,則將採用與其“cpuset.cpus”相同值的隱式值。在目標分割槽根目錄之前,沿 cgroup 層次結構向下寫入正確的“cpuset.cpus.exclusive”值對於遠端分割槽的建立是強制性的。

目前,無法在本地分割槽下建立遠端分割槽。遠端分割槽根目錄的所有祖先(根 cgroup 除外)都不能是分割槽根目錄。

根 cgroup 始終是分割槽根目錄,並且其狀態無法更改。所有其他非根 cgroup 都從“member”開始。

設定為“root”時,當前 cgroup 是新分割槽或排程域的根目錄。獨佔 CPU 集由其“cpuset.cpus.exclusive.effective”的值確定。

設定為“isolated”時,該分割槽中的 CPU 將處於隔離狀態,而不會受到排程程式的任何負載平衡的影響,並且會從非繫結工作佇列中排除。放置在具有多個 CPU 的此類分割槽中的任務應仔細分配並繫結到每個單獨的 CPU 以獲得最佳效能。

分割槽根目錄(“root”或“isolated”)可以處於兩種可能的狀態之一 - 有效或無效。無效的分割槽根目錄處於降級狀態,其中某些狀態資訊可能會保留,但其行為更類似於“member”。

允許在“member”、“root”和“isolated”之間進行所有可能的狀態轉換。

在讀取時,“cpuset.cpus.partition”檔案可以顯示以下值。

“member”

分割槽的非根成員

“root”

分割槽根目錄

“isolated”

沒有負載平衡的分割槽根目錄

“root invalid (<reason>)”

無效的分割槽根目錄

“isolated invalid (<reason>)”

無效的隔離分割槽根目錄

在無效分割槽根目錄的情況下,括號內包含一個描述該分割槽為何無效的描述性字串。

要使本地分割槽根目錄有效,必須滿足以下條件。

  1. 父 cgroup 是有效的分割槽根目錄。

  2. “cpuset.cpus.exclusive.effective”檔案不能為空,即使它可能包含離線 CPU。

  3. “cpuset.cpus.effective”不能為空,除非沒有與此分割槽關聯的任務。

要使遠端分割槽根目錄有效,必須滿足上述所有條件,除了第一個條件。

熱插拔或更改“cpuset.cpus”或“cpuset.cpus.exclusive”等外部事件可能會導致有效的分割槽根目錄變為無效,反之亦然。請注意,無法將任務移動到具有空“cpuset.cpus.effective”的 cgroup。

當沒有任務與之關聯時,有效的非根父分割槽可能會將其所有 CPU 分配給其子本地分割槽。

必須注意將有效的分割槽根目錄更改為“member”,因為其所有子本地分割槽(如果存在)都將變為無效,從而導致在這些子分割槽中執行的任務中斷。如果將其父級切換回在“cpuset.cpus”或“cpuset.cpus.exclusive”中具有正確值的分割槽根目錄,則可以恢復這些停用的分割槽。

每當“cpuset.cpus.partition”的狀態更改時,都會觸發輪詢和 inotify 事件。這包括寫入“cpuset.cpus.partition”、cpu 熱插拔或修改分割槽有效性狀態的其他更改所導致的更改。這將允許使用者空間代理監視對“cpuset.cpus.partition”的意外更改,而無需進行持續輪詢。

使用者可以使用“isolcpus”核心啟動命令列選項在啟動時將某些 CPU 預配置為隔離狀態,並停用負載平衡。如果要將這些 CPU 放入分割槽中,則必須在隔離分割槽中使用它們。

裝置控制器

裝置控制器管理對裝置檔案的訪問。它包括建立新裝置檔案(使用 mknod)和訪問現有裝置檔案。

Cgroup v2 裝置控制器沒有介面檔案,並且在 cgroup BPF 之上實現。要控制對裝置檔案的訪問,使用者可以建立 BPF_PROG_TYPE_CGROUP_DEVICE 型別的 bpf 程式,並使用 BPF_CGROUP_DEVICE 標誌將其附加到 cgroup。在嘗試訪問裝置檔案時,將執行相應的 BPF 程式,並根據返回值,嘗試將成功或因 -EPERM 而失敗。

BPF_PROG_TYPE_CGROUP_DEVICE 程式採用指向 bpf_cgroup_dev_ctx 結構的指標,該結構描述裝置訪問嘗試:訪問型別(mknod/read/write)和裝置(型別、主編號和次編號)。如果程式返回 0,則嘗試將失敗並顯示 -EPERM,否則它將成功。

BPF_PROG_TYPE_CGROUP_DEVICE 程式的示例可以在核心原始碼樹中的 tools/testing/selftests/bpf/progs/dev_cgroup.c 中找到。

RDMA

“rdma”控制器規範了 RDMA 資源的分配和記帳。

RDMA 介面檔案

rdma.max

一個讀寫巢狀鍵檔案,除根目錄外,所有 cgroup 都存在該檔案,該檔案描述了 RDMA/IB 裝置的當前配置資源限制。

行按裝置名稱鍵控,並且未排序。每行包含空格分隔的資源名稱及其可以分配的配置限制。

定義了以下巢狀鍵。

hca_handle

HCA 控制代碼的最大數量

hca_object

HCA 物件最大數量

以下是 mlx4 和 ocrdma 裝置的示例

mlx4_0 hca_handle=2 hca_object=2000
ocrdma1 hca_handle=3 hca_object=max
rdma.current

一個只讀檔案,描述了當前的資源使用情況。除根目錄外,所有 cgroup 都存在該檔案。

以下是 mlx4 和 ocrdma 裝置的示例

mlx4_0 hca_handle=1 hca_object=20
ocrdma1 hca_handle=1 hca_object=23

DMEM

“dmem”控制器規範了裝置記憶體區域的分配和記帳。因為每個記憶體區域可能具有自己的頁面大小,該頁面大小不必等於系統頁面大小,所以單位始終為位元組。

DMEM 介面檔案

dmem.max, dmem.min, dmem.low

一個讀寫巢狀鍵檔案,除根目錄外,所有 cgroup 都存在該檔案,該檔案描述了區域的當前配置資源限制。

以下是 xe 的示例

drm/0000:03:00.0/vram0 1073741824
drm/0000:03:00.0/stolen max

語義與記憶體 cgroup 控制器的語義相同,並且以相同的方式計算。

dmem.capacity

一個只讀檔案,描述了最大區域容量。它僅存在於根 cgroup 上。並非所有記憶體都可以由 cgroup 分配,因為核心保留一些記憶體以供內部使用。

以下是 xe 的示例

drm/0000:03:00.0/vram0 8514437120
drm/0000:03:00.0/stolen 67108864
dmem.current

一個只讀檔案,描述了當前的資源使用情況。除根目錄外,所有 cgroup 都存在該檔案。

以下是 xe 的示例

drm/0000:03:00.0/vram0 12550144
drm/0000:03:00.0/stolen 8650752

HugeTLB

HugeTLB 控制器允許限制每個控制組的 HugeTLB 使用量,並在頁面錯誤期間強制實施控制器限制。

HugeTLB 介面檔案

hugetlb.<hugepagesize>.current

顯示 “hugepagesize” hugetlb 的當前使用量。除根目錄外,所有 cgroup 都存在該檔案。

hugetlb.<hugepagesize>.max

設定/顯示 “hugepagesize” hugetlb 使用量的硬限制。預設值為 “max”。除根目錄外,所有 cgroup 都存在該檔案。

hugetlb.<hugepagesize>.events

一個只讀扁平鍵檔案,存在於非根 cgroup 上。

max

由於 HugeTLB 限制導致的分配失敗次數

hugetlb.<hugepagesize>.events.local

類似於 hugetlb.<hugepagesize>.events,但檔案中的欄位是 cgroup 本地的,即非分層的。在此檔案上生成的檔案修改事件僅反映本地事件。

hugetlb.<hugepagesize>.numa_stat

類似於 memory.numa_stat,它顯示此 cgroup 中 <hugepagesize> 的 hugetlb 頁面的 numa 資訊。僅包含正在使用的活動 hugetlb 頁面。每個節點的值以位元組為單位。

Misc

雜項 cgroup 提供了標量資源的資源限制和跟蹤機制,這些資源無法像其他 cgroup 資源那樣被抽象化。透過 CONFIG_CGROUP_MISC config 選項啟用控制器。

可以透過 include/linux/misc_cgroup.h 檔案中的 enum misc_res_type{} 將資源新增到控制器,並透過 kernel/cgroup/misc.c 檔案中的 misc_res_name[] 新增相應的名稱。資源提供者必須在使用資源之前透過呼叫 misc_cg_set_capacity() 設定其容量。

設定容量後,可以使用 charge 和 uncharge API 更新資源使用情況。與 misc 控制器互動的所有 API 都在 include/linux/misc_cgroup.h 中。

Misc 介面檔案

雜項控制器提供 3 個介面檔案。如果註冊了兩個雜項資源(res_a 和 res_b),則

misc.capacity

一個只讀的扁平鍵檔案,僅在根 cgroup 中顯示。它顯示平臺上的雜項標量資源及其數量

$ cat misc.capacity
res_a 50
res_b 10
misc.current

一個只讀的扁平鍵檔案,在所有 cgroup 中顯示。它顯示 cgroup 及其子級中資源的當前使用情況。

$ cat misc.current
res_a 3
res_b 0
misc.peak

一個只讀的扁平鍵檔案,在所有 cgroup 中顯示。它顯示 cgroup 及其子級中資源的歷史最大使用情況。

$ cat misc.peak
res_a 10
res_b 8
misc.max

一個讀寫扁平鍵檔案,在非根 cgroup 中顯示。允許 cgroup 及其子級中資源的最大使用量。

$ cat misc.max
res_a max
res_b 4

可以透過以下方式設定限制

# echo res_a 1 > misc.max

可以透過以下方式將限制設定為最大值

# echo res_a max > misc.max

限制可以設定為高於 misc.capacity 檔案中的容量值。

misc.events

一個只讀的扁平鍵檔案,存在於非根 cgroup 中。定義了以下條目。除非另有說明,否則此檔案中的值更改會生成檔案修改事件。此檔案中的所有欄位都是分層的。

max

cgroup 的資源使用量即將超過最大邊界的次數。

misc.events.local

類似於 misc.events,但檔案中的欄位是 cgroup 本地的,即非分層的。在此檔案上生成的檔案修改事件僅反映本地事件。

遷移和所有權

雜項標量資源會新增到首先使用它的 cgroup 中,並一直新增到該 cgroup 中,直到該資源被釋放。將程序遷移到不同的 cgroup 不會將費用轉移到程序已移動的目標 cgroup。

其他

perf_event

如果 perf_event 控制器未安裝在舊版層次結構上,則會在 v2 層次結構上自動啟用該控制器,以便始終可以透過 cgroup v2 路徑過濾 perf 事件。在填充 v2 層次結構後,仍然可以將控制器移動到舊版層次結構。

非規範性資訊

本節包含不被視為穩定核心 API 一部分的資訊,因此可能會更改。

CPU 控制器根 cgroup 程序行為

在根 cgroup 中分配 CPU 週期時,此 cgroup 中的每個執行緒都被視為託管在根 cgroup 的單獨子 cgroup 中。此子 cgroup 的權重取決於其執行緒的 nice 級別。

有關此對映的詳細資訊,請參見 kernel/sched/core.c 檔案中的 sched_prio_to_weight 陣列(應適當縮放此陣列中的值,因此中性值 - nice 0 - 值為 100 而不是 1024)。

IO 控制器根 cgroup 程序行為

根 cgroup 程序託管在隱式葉子子節點中。在分配 IO 資源時,此隱式子節點將被考慮在內,就好像它是根 cgroup 的普通子 cgroup,權重值為 200。

名稱空間

基礎知識

cgroup 名稱空間提供了一種機制,用於虛擬化 “/proc/$PID/cgroup” 檔案和 cgroup 掛載點的檢視。CLONE_NEWCGROUP clone 標誌可以與 clone(2) 和 unshare(2) 一起使用,以建立新的 cgroup 名稱空間。在 cgroup 名稱空間內執行的程序將使其 “/proc/$PID/cgroup” 輸出限制為 cgroupns 根目錄。cgroupns 根目錄是在建立 cgroup 名稱空間時程序的 cgroup。

如果沒有 cgroup 名稱空間,“/proc/$PID/cgroup” 檔案會顯示程序的 cgroup 的完整路徑。在容器設定中,其中一組 cgroup 和名稱空間旨在隔離程序,“/proc/$PID/cgroup” 檔案可能會將潛在的系統級資訊洩露給隔離的程序。例如

# cat /proc/self/cgroup
0::/batchjobs/container_id1

路徑 “/batchjobs/container_id1” 可以被視為系統資料,並且不希望將其暴露給隔離的程序。cgroup 名稱空間可用於限制此路徑的可見性。例如,在建立 cgroup 名稱空間之前,您會看到

# ls -l /proc/self/ns/cgroup
lrwxrwxrwx 1 root root 0 2014-07-15 10:37 /proc/self/ns/cgroup -> cgroup:[4026531835]
# cat /proc/self/cgroup
0::/batchjobs/container_id1

在取消共享新的名稱空間後,檢視會更改

# ls -l /proc/self/ns/cgroup
lrwxrwxrwx 1 root root 0 2014-07-15 10:35 /proc/self/ns/cgroup -> cgroup:[4026532183]
# cat /proc/self/cgroup
0::/

當多執行緒程序中的某個執行緒取消共享其 cgroup 名稱空間時,新的 cgroupns 將應用於整個程序(所有執行緒)。對於 v2 層次結構來說,這是很自然的;但是,對於舊版層次結構來說,這可能是意外的。

只要有程序在內部執行或掛載固定,cgroup 名稱空間就會存在。當最後一個用法消失時,cgroup 名稱空間將被銷燬。cgroupns 根目錄和實際的 cgroup 將保留。

根目錄和檢視

cgroup 名稱空間的 “cgroupns 根目錄” 是程序呼叫 unshare(2) 時執行的 cgroup。例如,如果 /batchjobs/container_id1 cgroup 中的程序呼叫 unshare,則 cgroup /batchjobs/container_id1 將成為 cgroupns 根目錄。對於 init_cgroup_ns,這是真正的根目錄 (“/”) cgroup。

即使名稱空間建立者程序稍後移動到不同的 cgroup,cgroupns 根 cgroup 也不會更改

# ~/unshare -c # unshare cgroupns in some cgroup
# cat /proc/self/cgroup
0::/
# mkdir sub_cgrp_1
# echo 0 > sub_cgrp_1/cgroup.procs
# cat /proc/self/cgroup
0::/sub_cgrp_1

每個程序都會獲得其名稱空間特定的 “/proc/$PID/cgroup” 檢視

在 cgroup 名稱空間內執行的程序將只能看到根 cgroup 內的 cgroup 路徑(在 /proc/self/cgroup 中)。從取消共享的 cgroupns 中

# sleep 100000 &
[1] 7353
# echo 7353 > sub_cgrp_1/cgroup.procs
# cat /proc/7353/cgroup
0::/sub_cgrp_1

從初始 cgroup 名稱空間中,將可以看到真實的 cgroup 路徑

$ cat /proc/7353/cgroup
0::/batchjobs/container_id1/sub_cgrp_1

從同級 cgroup 名稱空間(即,以不同 cgroup 為根的名稱空間)中,將顯示相對於其自身 cgroup 名稱空間根目錄的 cgroup 路徑。例如,如果 PID 7353 的 cgroup 名稱空間根目錄位於 “/batchjobs/container_id2”,則它將看到

# cat /proc/7353/cgroup
0::/../container_id2/sub_cgrp_1

請注意,相對路徑始終以 “/” 開頭,以指示它相對於呼叫者的 cgroup 名稱空間根目錄。

遷移和 setns(2)

如果程序具有對外部 cgroup 的正確訪問許可權,則 cgroup 名稱空間內的程序可以移入和移出名稱空間根目錄。例如,從 cgroupns 根目錄位於 /batchjobs/container_id1 的名稱空間內部,並假設全域性層次結構仍然可以在 cgroupns 中訪問

# cat /proc/7353/cgroup
0::/sub_cgrp_1
# echo 7353 > batchjobs/container_id2/cgroup.procs
# cat /proc/7353/cgroup
0::/../container_id2

請注意,不鼓勵這種設定。cgroup 名稱空間內的任務應僅暴露於其自身的 cgroupns 層次結構。

當滿足以下條件時,允許 setns(2) 到另一個 cgroup 名稱空間

  1. 該程序針對其當前使用者名稱空間具有 CAP_SYS_ADMIN

  2. 該程序針對目標 cgroup 名稱空間的使用者空間具有 CAP_SYS_ADMIN

附加到另一個 cgroup 名稱空間時,不會發生隱式的 cgroup 更改。預計某人會將附加程序移動到目標 cgroup 名稱空間根目錄下。

與其他名稱空間的互動

名稱空間特定的 cgroup 層次結構可以由在非 init cgroup 名稱空間內執行的程序掛載

# mount -t cgroup2 none $MOUNT_POINT

這將掛載統一的 cgroup 層次結構,並將 cgroupns 根目錄作為檔案系統根目錄。該程序需要針對其使用者和掛載名稱空間具有 CAP_SYS_ADMIN。

虛擬化 /proc/self/cgroup 檔案以及透過名稱空間專用 cgroupfs 掛載限制 cgroup 層次結構的檢視,可在容器內部提供正確隔離的 cgroup 檢視。

有關核心程式設計的資訊

本節包含核心程式設計資訊,這些資訊需要在與 cgroup 互動的區域中。不包括 cgroup 核心和控制器。

檔案系統對回寫的支援

檔案系統可以透過更新 address_space_operations->writepages() 以使用以下兩個函式來註釋 bio,從而支援 cgroup 回寫。

wbc_init_bio(@wbc, @bio)

應為每個攜帶回寫資料的 bio 呼叫,並將 bio 與 inode 的所有者 cgroup 和相應的請求佇列相關聯。必須在佇列(裝置)已與 bio 關聯之後和提交之前呼叫此函式。

wbc_account_cgroup_owner(@wbc, @folio, @bytes)

應該為每個正在寫出的資料段呼叫此函式。雖然此函式並不關心在回寫會話期間何時呼叫它,但最簡單和最自然的方式是在資料段新增到 bio 時呼叫它。

透過對回寫 bio 進行註釋,可以透過在 ->s_iflags 中設定 SB_I_CGROUPWB 來為每個 super_block 啟用 cgroup 支援。這允許有選擇地停用 cgroup 回寫支援,這在某些檔案系統功能(例如,journaled data mode)不相容時非常有用。

wbc_init_bio() 將指定的 bio 繫結到其 cgroup。根據配置,bio 可能會以較低的優先順序執行,並且如果回寫會話持有共享資源(例如,日誌條目),可能會導致優先順序反轉。對於這個問題沒有一個簡單的解決方案。檔案系統可以嘗試透過跳過 wbc_init_bio() 並直接使用 bio_associate_blkg() 來解決特定的問題。

已棄用的 v1 核心功能

  • 不支援包括命名層次結構在內的多個層次結構。

  • 不支援所有 v1 掛載選項。

  • 刪除了“tasks”檔案,並且 “cgroup.procs” 未排序。

  • “cgroup.clone_children” 已刪除。

  • /proc/cgroups 對於 v2 沒有意義。請改用根目錄下的 “cgroup.controllers” 或 “cgroup.stat” 檔案。

v1 的問題以及 v2 的理由

多個層次結構

cgroup v1 允許任意數量的層次結構,並且每個層次結構可以託管任意數量的控制器。雖然這似乎提供了很高的靈活性,但在實踐中並沒有用處。

例如,由於每個控制器只有一個例項,因此像 freezer 這樣的實用程式型別控制器(在所有層次結構中都可能有用)只能在一箇中使用。當層次結構填充後,控制器無法移動到另一個層次結構,這使得問題更加嚴重。另一個問題是,繫結到層次結構的所有控制器都被迫具有完全相同的層次結構檢視。無法根據特定控制器改變粒度。

在實踐中,這些問題嚴重限制了哪些控制器可以放在同一層次結構上,並且大多數配置都選擇將每個控制器放在其自己的層次結構上。只有密切相關的控制器,例如 cpu 和 cpuacct 控制器,才適合放在同一層次結構上。這通常意味著使用者空間最終會管理多個相似的層次結構,並在每次需要層次結構管理操作時在每個層次結構上重複相同的步驟。

此外,對多個層次結構的支援付出了巨大的代價。它極大地複雜化了 cgroup 核心實現,但更重要的是,對多個層次結構的支援限制了 cgroup 的總體使用方式以及控制器能夠做什麼。

對可能有多少層次結構沒有限制,這意味著無法以有限的長度描述執行緒的 cgroup 成員身份。鍵可能包含任意數量的條目,並且長度不受限制,這使得操作起來非常麻煩,並導致新增僅用於識別成員身份的控制器,這反過來又加劇了層次結構數量激增的原始問題。

此外,由於控制器無法對其他控制器可能位於的層次結構的拓撲結構有任何期望,因此每個控制器都必須假設所有其他控制器都連線到完全正交的層次結構。這使得控制器之間不可能或者至少非常難以相互協作。

在大多數用例中,將控制器放置在彼此完全正交的層次結構上是不必要的。通常需要的是根據特定控制器具有不同級別的粒度的能力。換句話說,從特定控制器看,可以從葉子向根摺疊層次結構。例如,給定的配置可能不關心記憶體如何在特定級別之外分配,但仍然希望控制 CPU 週期如何分配。

執行緒粒度

cgroup v1 允許程序的執行緒屬於不同的 cgroup。對於某些控制器來說,這沒有意義,因此這些控制器最終實現了不同的方式來忽略這種情況,但更重要的是,它模糊了暴露給各個應用程式的 API 和系統管理介面之間的界限。

通常,程序內的知識僅對程序本身可用;因此,與程序的服務級組織不同,對程序執行緒進行分類需要擁有目標程序的應用程式的積極參與。

cgroup v1 有一個模糊定義的委派模型,該模型與執行緒粒度結合使用時遭到了濫用。cgroup 被委派給各個應用程式,以便它們可以建立和管理自己的子層次結構,並控制沿它們的資源分配。這實際上將 cgroup 提升為類似系統呼叫的 API 的地位,該 API 暴露給普通程式。

首先,cgroup 具有從根本上不充分的介面,無法以這種方式暴露。為了讓程序訪問自己的旋鈕,它必須從 /proc/self/cgroup 中提取目標層次結構上的路徑,透過將旋鈕的名稱附加到路徑來構造該路徑,開啟然後讀取和/或寫入它。這不僅非常笨拙和不尋常,而且本質上是競爭的。沒有傳統的方式來定義跨所需步驟的事務,並且無法保證程序實際上會在其自己的子層次結構上執行。

cgroup 控制器實現了一些永遠不會被接受為公共 API 的旋鈕,因為它們只是將控制旋鈕新增到系統管理偽檔案系統。cgroup 最終得到了一些介面旋鈕,這些旋鈕沒有得到適當的抽象或改進,並且直接揭示了核心內部細節。這些旋鈕透過定義不明確的委派機制暴露給各個應用程式,從而有效地濫用 cgroup 作為實現公共 API 的捷徑,而無需經過必要的審查。

這對於使用者空間和核心來說都是痛苦的。使用者空間最終得到了行為不端且抽象性差的介面,而核心則無意中暴露並鎖定到構造中。

內部節點和執行緒之間的競爭

cgroup v1 允許執行緒位於任何 cgroup 中,這產生了一個有趣的問題,即屬於父 cgroup 及其子 cgroup 的執行緒爭奪資源。這是令人討厭的,因為兩種不同型別的實體發生了競爭,並且沒有明顯的方法來解決它。不同的控制器做了不同的事情。

cpu 控制器將執行緒和 cgroup 視為等效項,並將 nice 級別對映到 cgroup 權重。這適用於某些情況,但在子項想要分配特定 CPU 週期比率並且內部執行緒數量波動時會失敗 - 隨著競爭實體數量的波動,比率會不斷變化。還存在其他問題。從 nice 級別到權重的對映並不明顯或通用,並且還有各種其他旋鈕根本不適用於執行緒。

io 控制器隱式地為每個 cgroup 建立一個隱藏的葉節點來託管執行緒。隱藏的葉節點擁有所有旋鈕的自己的副本,並帶有 leaf_ 字首。雖然這允許對內部執行緒進行等效控制,但存在嚴重的缺點。它總是新增一個額外的巢狀層,否則這是沒有必要的,使介面混亂並大大簡化了實現。

記憶體控制器無法控制內部任務和子 cgroup 之間發生的事情,並且該行為沒有明確定義。有人試圖新增臨時行為和旋鈕,以使該行為適應特定的工作負載,從長遠來看,這會導致極難解決的問題。

多個控制器都在內部任務方面苦苦掙扎,並提出了不同的處理方式;不幸的是,所有方法都存在嚴重缺陷,此外,差異很大的行為使 cgroup 作為一個整體非常不一致。

這顯然是一個需要從 cgroup 核心以統一的方式解決的問題。

其他介面問題

cgroup v1 在沒有監督的情況下增長,並發展出大量怪癖和不一致之處。cgroup 核心方面的一個問題是如何通知一個空的 cgroup - 為每個事件 fork 並執行一個使用者空間助手二進位制檔案。事件傳遞不是遞迴的或可委派的。該機制的限制還導致了核心事件傳遞過濾機制,進一步複雜化了介面。

控制器介面也存在問題。一個極端的例子是控制器完全忽略層次結構組織,並將所有 cgroup 視為直接位於根 cgroup 下。一些控制器向用戶空間公開了大量不一致的實現細節。

控制器之間也沒有一致性。建立新的 cgroup 時,某些控制器預設不施加額外的限制,而另一些控制器則不允許任何資源使用,直到明確配置為止。用於相同型別控制的配置旋鈕使用差異很大的命名方案和格式。統計資訊旋鈕是任意命名的,即使在同一控制器中也使用不同的格式和單位。

cgroup v2 建立適當的通用約定,並更新控制器,以便它們公開最小且一致的介面。

控制器問題和補救措施

記憶體

原始的下限(軟限制)被定義為一個預設未設定的限制。因此,全域性回收首選的 cgroup 集合是選擇加入,而不是選擇退出。最佳化這些大多數負查詢的成本非常高,以至於該實現儘管規模龐大,但甚至沒有提供基本的可取行為。首先,軟限制沒有層次結構意義。所有配置的組都組織在一個全域性 rbtree 中,並被視為平等的對等方,而不管它們在層次結構中的位置如何。這使得子樹委派成為不可能。其次,軟限制回收過程非常激進,不僅會將高分配延遲引入系統,還會因過度回收而影響系統性能,以至於該功能適得其反。

另一方面,memory.low 邊界是自上而下分配的保留。當 cgroup 在其有效 low 內時,它可以享受回收保護,這使得子樹的委派成為可能。當它高於其有效 low 時,它還可以享受與其超額成比例的回收壓力。

原始的上限(硬限制)被定義為一個嚴格的限制,即使必須呼叫 OOM killer 也無法移動。但這通常與最大限度地利用可用記憶體的目標背道而馳。工作負載的記憶體消耗在執行時會發生變化,這需要使用者過度提交。但是,使用嚴格的上限執行此操作需要對工作集大小進行相當準確的預測,或者向限制新增鬆弛。由於工作集大小估計很困難且容易出錯,並且出錯會導致 OOM 終止,因此大多數使用者傾向於在較寬鬆的限制方面犯錯,最終浪費了寶貴的資源。

另一方面,memory.high 邊界可以設定得更加保守。當達到 high 時,它會透過強制分配進入直接回收來解決過剩問題來限制分配,但它永遠不會呼叫 OOM killer。因此,選擇過於激進的 high 邊界不會終止程序,而是會導致效能逐漸下降。使用者可以監控此情況並進行更正,直到找到仍然可以接受的效能的最小記憶體佔用量。

在極端情況下,當有許多併發分配並且組內回收進度完全崩潰時,可能會超過 high 邊界。但即使那樣,從其他組或系統其餘部分的鬆弛可用性中滿足分配也比殺死組更好。否則,memory.max 用於限制此類溢位,並最終包含有缺陷甚至惡意的應用程式。

將原始的 memory.limit_in_bytes 設定為低於當前使用量會受到競爭條件的影響,在這種情況下,併發收費可能會導致限制設定失敗。另一方面,memory.max 將首先設定限制以防止新的收費,然後進行回收和 OOM 終止,直到達到新的限制 - 或者寫入 memory.max 的任務被終止。

組合的 memory+swap 記帳和限制被對交換空間的真實控制所取代。

在原始 cgroup 設計中,組合的 memory+swap 設施的主要論點是,全域性或父級壓力始終能夠交換子組的所有匿名記憶體,而不管子組自身(可能不受信任)的配置如何。但是,不受信任的組可以透過其他方式破壞交換 - 例如,在緊密的迴圈中引用其匿名記憶體 - 並且管理員無法在過度提交不受信任的作業時假設完全可交換性。

另一方面,對於受信任的作業,組合的計數器不是一個直觀的使用者空間介面,並且它與 cgroup 控制器應該對特定物理資源進行記帳和限制的想法背道而馳。交換空間是一種與系統中所有其他資源類似的資源,這就是為什麼統一層次結構允許單獨分配它。