控制組 【ChatGPT】

控制組

由Paul Menage [email protected]編寫,基於CPUSETS

來自CPUSETS的原始版權聲明:

部分版權所有(C)2004 BULL SA。

部分版權所有(c)2004-2006 Silicon Graphics, Inc.

由Paul Jackson [email protected]修改

由Christoph Lameter [email protected]修改

1. 控制組

1.1 什麼是控制組?

控制組提供了一種將一組任務及其所有未來的子任務聚合/分區到具有特殊行爲的分層組中的機制。

定義:

控制組將一組任務與一個或多個子系統的一組參數關聯起來。

子系統是利用控制組提供的任務分組功能來以特定方式處理任務組的模塊。子系統通常是“資源控制器”,用於調度資源或應用每個控制組的限制,但也可以是希望對一組進程進行操作的任何內容,例如虛擬化子系統。

層次結構是一組以樹狀排列的控制組,使得系統中的每個任務都恰好位於層次結構中的一個控制組中,並且一組子系統;每個子系統都附加到層次結構中的每個控制組上的特定狀態。每個層次結構都有與之關聯的控制組虛擬文件系統的實例。

在任何時候,可能存在多個活動的任務控制組層次結構。每個層次結構都是系統中所有任務的一個分區。

用戶級代碼可以在控制組虛擬文件系統的實例中按名稱創建和銷燬控制組,指定和查詢任務分配到哪個控制組,以及列出分配給控制組的任務PID。這些創建和分配隻影響與該控制組文件系統實例相關聯的層次結構。

單獨使用時,控制組的唯一用途是進行簡單的作業跟蹤。打算讓其他子系統連接到通用控制組支持,以爲控制組提供新的屬性,例如對控制組中的進程訪問資源進行會計/限制。例如,cpusets(參見CPUSETS)允許您將一組CPU和一組內存節點與每個控制組中的任務關聯起來。

1.2 爲什麼需要控制組?

Linux內核中存在多個用於進程聚合的努力,主要用於資源跟蹤目的。這些努力包括cpusets、CKRM/ResGroups、UserBeanCounters和虛擬服務器命名空間。這些都需要基本的進程分組/分區概念,使新分叉的進程最終進入與其父進程相同的組(控制組)。

內核控制組補丁提供了有效實現這些組所需的最小基本內核機制。它對系統快速路徑幾乎沒有影響,併爲特定子系統提供了鉤子,例如cpusets,以提供所需的附加行爲。

提供多層次結構支持,以允許在不同子系統中將任務分割爲完全不同的情況 - 具有並行層次結構允許每個層次結構成爲任務的自然分割,而無需處理如果幾個不相關的子系統需要強制進入相同的控制組樹中將存在的複雜組合任務。

在一個極端情況下,每個資源控制器或子系統可以位於單獨的層次結構中;在另一個極端情況下,所有子系統都將附加到相同的層次結構中。

作爲一個可以受益於多層次結構的場景的例子(最初由[email protected]提出),考慮一個具有各種用戶(學生、教授、系統任務等)的大型大學服務器的資源規劃,可以如下進行:

CPU:          “頂級cpuset”
                /       \
        CPUSet1         CPUSet2
           |               |
        (教授)       (學生)

此外(系統任務)附加到topcpuset(以便它們可以在任何地方運行),並限制爲20%

內存:教授(50%),學生(30%),系統(20%)

磁盤:教授(50%),學生(30%),系統(20%)

網絡:WWW瀏覽(20%),網絡文件系統(60%),其他(20%)
                        / \
        教授(15%)    學生(5%)

像Firefox/Lynx這樣的瀏覽器進入WWW網絡類,而(k)nfsd進入NFS網絡類。

同時,Firefox/Lynx將根據啓動它的人(教授/學生)共享適當的CPU/內存類。

通過爲不同資源不同地對任務進行分類(通過將這些資源子系統放入不同的層次結構中),管理員可以輕鬆設置一個腳本,該腳本接收執行通知,並根據啓動瀏覽器的人來執行以下操作:

# echo browser_pid > /sys/fs/cgroup/<restype>/<userclass>/tasks

現在只有一個層次結構,他可能需要爲每個啓動的瀏覽器創建一個單獨的控制組,並將其與適當的網絡和其他資源類關聯起來。這可能導致這些控制組的激增。

另外,假設管理員希望臨時爲學生的瀏覽器提供增強的網絡訪問權限(因爲現在是晚上,用戶想要進行在線遊戲:))或者爲學生的模擬應用程序提供增強的CPU性能。

通過直接將PID寫入資源類,只需:

# echo pid > /sys/fs/cgroup/network/<new_class>/tasks
(一段時間後)
# echo pid > /sys/fs/cgroup/network/<orig_class>/tasks

如果沒有這種能力,管理員將不得不將控制組分割爲多個單獨的控制組,然後將新的控制組與新的資源類關聯起來。

1.3 控制組是如何實現的?

控制組擴展了內核,具體如下:

  • 系統中的每個任務都有一個引用計數指針指向css_set。

  • css_set包含一組引用計數指針,指向系統中註冊的每個控制組子系統的cgroup_subsys_state對象。從任務到其在每個層次結構中的控制組的直接鏈接不存在,但可以通過cgroup_subsys_state對象的指針跟蹤來確定這一點。這是因爲訪問子系統狀態是預期頻繁發生且在性能關鍵代碼中發生的事情,而需要任務的實際控制組分配(特別是在控制組之間移動)的操作不太常見。通過css_set->tasks錨定的鏈接列表通過css_set運行。

  • 可以掛載控制組層次結構文件系統以進行瀏覽和操作。

  • 可以列出附加到任何控制組的所有任務(按PID)。

控制組的實現需要在內核的其餘部分中添加一些簡單的鉤子,但不會影響性能關鍵路徑:

  • 在init/main.c中,用於在系統引導時初始化根控制組和初始css_set。

  • 在fork和exit中,用於將任務附加到其css_set並從中分離。

此外,可以掛載類型爲“cgroup”的新文件系統,以便從用戶空間瀏覽和修改內核當前已知的控制組。掛載控制組文件系統時,可以通過文件系統掛載選項指定以逗號分隔的子系統列表。默認情況下,掛載控制組文件系統會嘗試掛載包含所有已註冊子系統的層次結構。

如果已經存在具有完全相同子系統集的活動層次結構,則將重用該層次結構用於新的掛載。如果沒有現有的層次結構與之匹配,並且任何請求的子系統在現有層次結構中正在使用,則掛載將失敗,並顯示-EBUSY。否則,將激活新的層次結構,並與請求的子系統關聯。

目前不可能將新的子系統綁定到活動的控制組層次結構,也不可能從活動的控制組層次結構中解綁子系統。這可能在將來可能實現,但存在嚴重的錯誤恢復問題。

當卸載控制組文件系統時,如果在頂級控制組下創建了任何子控制組,則即使卸載了,該層次結構仍將保持活動狀態;如果沒有子控制組,則該層次結構將被停用。

控制組沒有添加新的系統調用 - 所有查詢和修改控制組的支持都通過控制組文件系統進行。

/proc下的每個任務都有一個名爲'cgroup'的附加文件,顯示每個活動層次結構的子系統名稱和作爲相對於控制組文件系統根的路徑的控制組名稱。

每個控制組由控制組文件系統中的一個目錄表示,其中包含描述該控制組的以下文件:

  • tasks:附加到該控制組的任務(按PID)列表。不能保證此列表已排序。將線程ID寫入此文件會將線程移動到此控制組中。

  • cgroup.procs:控制組中線程組ID的列表。不能保證此列表已排序或不包含重複的TGID,如果需要此屬性,則用戶空間應對列表進行排序/去重。將線程組ID寫入此文件會將該組中的所有線程移動到此控制組中。

  • notify_on_release標誌:在退出時運行釋放代理程序?

  • release_agent:用於釋放通知的路徑(此文件僅存在於頂級控制組中)

其他子系統(例如cpusets)可能會在每個控制組目錄中添加其他文件。

使用mkdir系統調用或shell命令創建新的控制組。通過寫入該控制組目錄中的適當文件來修改控制組的屬性,例如其標誌,如上所述。

嵌套控制組的命名層次結構允許將大型系統劃分爲嵌套的、動態可變的“軟分區”。

每個任務的附加,由該任務的任何子任務在分叉時自動繼承,允許將系統上的工作負載組織成相關的任務集。如果允許在必要的控制組文件系統目錄上進行操作,則可以將任務重新附加到任何其他控制組。

當將任務從一個控制組移動到另一個控制組時,它將獲得一個新的css_set指針 - 如果已經存在具有所需控制組集合的css_set,則將重用該組,否則將分配一個新的css_set。通過查找哈希表來定位適當的現有css_set。

爲了允許從控制組訪問組成它的css_sets(因此任務),一組cg_cgroup_link對象形成一個格點;每個cg_cgroup_link鏈接到其cgrp_link_list字段上的單個控制組的cg_cgroup_links列表,以及鏈接到其cg_link_list字段上的單個css_set的cg_cgroup_links列表。

因此,可以通過迭代引用控制組的每個css_set,並在每個css_set的任務集上進行子迭代,列出控制組中的任務集。

使用Linux虛擬文件系統(vfs)表示控制組層次結構提供了熟悉的權限和名稱空間,幾乎不需要額外的內核代碼。

1.4 notify_on_release 是什麼作用?

如果在一個 cgroup 中啓用了 notify_on_release 標誌(1),那麼每當該 cgroup 中的最後一個任務離開(退出或附加到其他 cgroup)並且該 cgroup 的最後一個子 cgroup 被移除時,內核會運行該層次結構根目錄中“release_agent”文件中指定的命令,同時提供被遺棄 cgroup 的路徑名(相對於 cgroup 文件系統的掛載點)。這使得自動移除被遺棄的 cgroups 成爲可能。系統啓動時,根 cgroup 中 notify_on_release 的默認值爲禁用(0)。其他 cgroups 在創建時的默認值爲其父級 notify_on_release 設置的當前值。cgroup 層次結構的 release_agent 路徑的默認值爲空。

1.5 clone_children 是什麼作用?

此標誌僅影響 cpuset 控制器。如果在一個 cgroup 中啓用了 clone_children 標誌(1),則在初始化期間,新的 cpuset cgroup 將從其父級複製其配置。

1.6 如何使用 cgroups?

要啓動一個要包含在 cgroup 中的新作業,使用“cpuset” cgroup 子系統的步驟如下:

  1. 掛載 -t tmpfs cgroup_root /sys/fs/cgroup
  2. mkdir /sys/fs/cgroup/cpuset
  3. mount -t cgroup -ocpuset cpuset /sys/fs/cgroup/cpuset
  4. 通過在 /sys/fs/cgroup/cpuset 虛擬文件系統中進行 mkdir 和 write(或 echo)來創建新的 cgroup。
  5. 啓動一個將成爲新作業“創始人”的任務。
  6. 通過將其 PID 寫入該 cgroup 的 /sys/fs/cgroup/cpuset tasks 文件,將該任務附加到新的 cgroup。
  7. 從這個創始人任務 fork、exec 或 clone 作業任務。

例如,以下命令序列將設置一個名爲“Charlie”的 cgroup,其中只包含 CPU 2 和 3,以及內存節點 1,然後在該 cgroup 中啓動一個子 shell 'sh':

mount -t tmpfs cgroup_root /sys/fs/cgroup
mkdir /sys/fs/cgroup/cpuset
mount -t cgroup cpuset -ocpuset /sys/fs/cgroup/cpuset
cd /sys/fs/cgroup/cpuset
mkdir Charlie
cd Charlie
/bin/echo 2-3 > cpuset.cpus
/bin/echo 1 > cpuset.mems
/bin/echo $$ > tasks
sh
# 子 shell 'sh' 現在正在 cgroup Charlie 中運行
# 下一行應顯示 '/Charlie'
cat /proc/self/cgroup

2. 用法示例和語法

2.1 基本用法

通過 cgroup 虛擬文件系統可以創建、修改和使用 cgroups。

要掛載具有所有可用子系統的 cgroup 層次結構,請輸入:

# mount -t cgroup xxx /sys/fs/cgroup

“xxx” 不會被 cgroup 代碼解釋,但會出現在 /proc/mounts 中,因此可以是任何有用的標識字符串。

注意:某些子系統在沒有用戶輸入的情況下無法工作。例如,如果啓用了 cpusets,則用戶必須在每個新創建的 cgroup 中填充 cpus 和 mems 文件,然後才能使用該組。

如 1.2 節“爲什麼需要 cgroups?”中所述,應爲要控制的每個單一資源或資源組創建不同的 cgroup 層次結構。因此,應在 /sys/fs/cgroup 上掛載一個 tmpfs,併爲每個 cgroup 資源或資源組創建目錄:

# mount -t tmpfs cgroup_root /sys/fs/cgroup
# mkdir /sys/fs/cgroup/rg1

要掛載具有 cpuset 和 memory 子系統的 cgroup 層次結構,請輸入:

# mount -t cgroup -o cpuset,memory hier1 /sys/fs/cgroup/rg1

目前支持重新掛載 cgroups,但不建議使用。重新掛載允許更改綁定的子系統和 release_agent。重新綁定幾乎沒有用處,因爲它僅在層次結構爲空時起作用,而 release_agent 本身應該被常規 fsnotify 替換。將來將刪除重新掛載的支持。

要指定層次結構的 release_agent:

# mount -t cgroup -o cpuset,release_agent="/sbin/cpuset_release_agent" \
  xxx /sys/fs/cgroup/rg1

請注意,指定 'release_agent' 多於一次將返回失敗。

請注意,目前僅在層次結構由單個(根)cgroup 組成時才支持更改子系統集。支持從現有 cgroup 層次結構中任意綁定/解綁子系統的能力預計將來會實現。

然後,在 /sys/fs/cgroup/rg1 下,您可以找到與系統中 cgroups 樹相對應的樹。例如,/sys/fs/cgroup/rg1 是保存整個系統的 cgroup。

如果要更改 release_agent 的值:

# echo "/sbin/new_release_agent" > /sys/fs/cgroup/rg1/release_agent

也可以通過重新掛載來更改。

如果要在 /sys/fs/cgroup/rg1 下創建一個新的 cgroup:

# cd /sys/fs/cgroup/rg1
# mkdir my_cgroup

現在您想對此 cgroup 做些什麼:

# cd my_cgroup

在此目錄中,您可以找到幾個文件:

# ls
cgroup.procs notify_on_release tasks
(加上由附加子系統添加的任何其他文件)

現在將您的 shell 附加到此 cgroup:

# /bin/echo $$ > tasks

還可以通過在此目錄中使用 mkdir 來在您的 cgroup 中創建 cgroups:

# mkdir my_sub_cs

要刪除 cgroup,只需使用 rmdir:

# rmdir my_sub_cs

如果 cgroup 正在使用中(包含子 cgroups,或已附加進程,或被其他特定於子系統的引用保持活動),則此操作將失敗。

2.2 附加進程

# /bin/echo PID > tasks

請注意,這是 PID,而不是 PIDs。一次只能附加一個任務。如果要附加多個任務,必須依次執行:

# /bin/echo PID1 > tasks
# /bin/echo PID2 > tasks
        ...
# /bin/echo PIDn > tasks

可以通過 echo 0 來附加當前 shell 任務:

# echo 0 > tasks

您可以使用 cgroup.procs 文件而不是 tasks 文件一次移動線程組中的所有線程。將線程組中任何任務的 PID 回顯到 cgroup.procs 會導致將該線程組中的所有任務附加到 cgroup。將 0 寫入 cgroup.procs 會將寫入任務的線程組中的所有任務移動到 cgroup。

注意:由於每個任務始終是每個已掛載層次結構中的一個 cgroup 的成員,因此要將任務從當前 cgroup 中移除,必須將其移動到一個新的 cgroup(可能是根 cgroup)中,方法是將其寫入新 cgroup 的 tasks 文件。

注意:由於某些 cgroup 子系統強制執行的一些限制,將進程移動到另一個 cgroup 可能會失敗。

2.3 按名稱掛載層次結構

在掛載 cgroups 層次結構時傳遞 name=<x> 選項會將給定名稱與該層次結構關聯起來。這可用於掛載預先存在的層次結構,以便通過名稱而不是其一組活動子系統來引用它。每個層次結構要麼沒有名稱,要麼具有唯一名稱。

名稱應匹配 [w.-]+

在爲新層次結構傳遞 name=<x> 選項時,需要手動指定子系統;當爲子系統指定名稱時,不支持當未顯式指定子系統時自動掛載所有子系統的傳統行爲。

子系統的名稱將作爲層次結構描述的一部分出現在 /proc/mounts/proc/<pid>/cgroups 中。

3. 內核 API

3.1 概述

每個想要連接到通用 cgroup 系統的內核子系統都需要創建一個 cgroup_subsys 對象。這包含各種方法,這些方法是從 cgroup 系統的回調,以及一個子系統 ID,該 ID 將由 cgroup 系統分配。

cgroup_subsys 對象中的其他字段包括:

  • subsys_id:子系統的唯一數組索引,指示該子系統應該管理 cgroup->subsys[] 中的哪個條目。
  • name:應初始化爲唯一的子系統名稱。不應超過 MAX_CGROUP_TYPE_NAMELEN。
  • early_init:指示子系統是否需要在系統引導時進行早期初始化。

系統創建的每個 cgroup 對象都有一個指針數組,由子系統 ID 索引;這個指針完全由子系統管理;通用 cgroup 代碼永遠不會觸及這個指針。

3.2 同步

cgroup 系統使用全局互斥鎖 cgroup_mutex。任何想要修改 cgroup 的內容都應該獲取該鎖。也可以獲取該鎖以防止 cgroup 被修改,但在這種情況下可能更適合使用更具體的鎖。

有關更多詳細信息,請參閱 kernel/cgroup.c。

子系統可以通過函數 cgroup_lock()/cgroup_unlock() 獲取/釋放 cgroup_mutex。

可以通過以下方式訪問任務的 cgroup 指針:

  • 在持有 cgroup_mutex 時
  • 在持有任務的 alloc_lock(通過 task_lock())時
  • 通過 rcu_read_lock() 段內部

3.3 子系統 API

每個子系統應該:

  • 在 linux/cgroup_subsys.h 中添加一個條目
  • 定義一個名爲 <name>_cgrp_subsys 的 cgroup_subsys 對象

每個子系統可以導出以下方法。唯一強制的方法是 css_alloc/free。其他任何爲空的方法都被認爲是成功的空操作。

struct cgroup_subsys_state *css_alloc(struct cgroup *cgrp)(由調用者持有的 cgroup_mutex)

用於爲 cgroup 分配子系統狀態對象的方法。子系統應該爲傳入的 cgroup 分配其子系統狀態對象,並在成功時返回指向新對象的指針或 ERR_PTR() 值。在成功時,子系統指針應該指向一個 cgroup_subsys_state 類型的結構體(通常嵌入在一個更大的子系統特定對象中),該結構體將由 cgroup 系統進行初始化。請注意,這將在初始化時調用,爲該子系統創建根子系統狀態;可以通過傳入的 cgroup 對象具有 NULL 父對象(因爲它是層次結構的根)來識別此情況,並且可能是初始化代碼的適當位置。

int css_online(struct cgroup *cgrp)(由調用者持有的 cgroup_mutex)

在 @cgrp 成功完成所有分配並對 cgroup_for_each_child/descendant_*() 迭代器可見後調用。子系統可以選擇通過返回 -errno 來失敗創建。此回調可用於實現可靠的狀態共享和沿層次結構傳播。有關詳細信息,請參閱 cgroup_for_each_descendant_pre() 的註釋。

void css_offline(struct cgroup *cgrp)(由調用者持有的 cgroup_mutex)

這是 css_online() 的對應方法,僅當 css_online() 在 @cgrp 上成功調用時才調用。這表示 @cgrp 的結束開始。@cgrp 正在被移除,子系統應該開始放棄對 @cgrp 的所有引用。當所有引用都被釋放後,cgroup 的移除將繼續到下一步 - css_free()。在此回調之後,@cgrp 應被視爲對子系統無效。

void css_free(struct cgroup *cgrp)(由調用者持有的 cgroup_mutex)

cgroup 系統即將釋放 @cgrp;子系統應該釋放其子系統狀態對象。在調用此方法時,@cgrp 完全未使用;@cgrp->parent 仍然有效。(注意 - 如果在爲新的 cgroup 調用此子系統的 create() 方法後發生錯誤,也可以調用此方法來釋放新創建的 cgroup)。

int can_attach(struct cgroup *cgrp, struct cgroup_taskset *tset)(由調用者持有的 cgroup_mutex)

在將一個或多個任務移入 cgroup 之前調用;如果子系統返回錯誤,則會中止附加操作。@tset 包含要附加的任務,並保證至少有一個任務在其中。

如果任務集中有多個任務,則:

  • 可保證所有任務來自同一線程組
  • @tset 包含線程組中的所有任務,無論它們是否在切換 cgroup
  • 第一個任務是領導者

每個 @tset 條目還包含任務的舊 cgroup,不切換 cgroup 的任務可以使用 cgroup_taskset_for_each() 迭代器輕鬆跳過。請注意,這不會在 fork 上調用。如果此方法返回 0(成功),則在調用者持有 cgroup_mutex 的同時,這應該保持有效,並確保將來將調用 attach() 或 cancel_attach()。

void css_reset(struct cgroup_subsys_state *css)(由調用者持有的 cgroup_mutex)

一個可選操作,應將 @css 的配置恢復到初始狀態。目前,這僅在統一層次結構上使用,當通過 "cgroup.subtree_control" 在 cgroup 上禁用子系統時,但應保持啓用,因爲其他子系統依賴於它。cgroup 核心通過刪除相關的接口文件使這樣的 css 不可見,並調用此回調,以便隱藏的子系統可以返回到初始中性狀態。這樣可以防止隱藏的 css 對資源控制產生意外影響,並確保在以後再次可見時配置處於初始狀態。

void cancel_attach(struct cgroup *cgrp, struct cgroup_taskset *tset)(由調用者持有的 cgroup_mutex)

在 can_attach() 成功後,任務附加操作失敗時調用。具有一些副作用的子系統應提供此函數,以便子系統可以實現回滾。如果不提供,則不需要。這僅將調用已成功執行 can_attach() 操作的子系統。參數與 can_attach() 相同。

void attach(struct cgroup *cgrp, struct cgroup_taskset *tset)(由調用者持有的 cgroup_mutex)

在任務附加到 cgroup 後調用,以允許需要內存分配或阻塞的任何後附加活動。參數與 can_attach() 相同。

void fork(struct task_struct *task)

在任務被 fork 到 cgroup 時調用。

void exit(struct task_struct *task)

在任務退出時調用。

void free(struct task_struct *task)

在 task_struct 被釋放時調用。

void bind(struct cgroup *root)(由調用者持有的 cgroup_mutex)

在將 cgroup 子系統重新綁定到不同的層次結構和根 cgroup 時調用。目前,這隻涉及在默認層次結構(永遠沒有子 cgroup)和正在創建/銷燬的層次結構之間的移動(因此沒有子 cgroup)。

4. 擴展屬性使用

cgroup 文件系統支持其目錄和文件中的某些類型的擴展屬性。當前支持的類型有:

  • Trusted(XATTR_TRUSTED)
  • Security(XATTR_SECURITY)

都需要 CAP_SYS_ADMIN 權限來設置。

與 tmpfs 中一樣,cgroup 文件系統中的擴展屬性使用內核內存存儲,建議保持使用最小。這也是爲什麼不支持用戶定義的擴展屬性,因爲任何用戶都可以這樣做,並且值大小沒有限制。

目前已知使用此功能的用戶是 SELinux,用於限制容器中 cgroup 的使用,以及 systemd,用於各種元數據,如 cgroup 中的主 PID(systemd 爲每個服務創建一個 cgroup)。

5. 問題

Q: 關於這個 '/bin/echo' 是怎麼回事?
A: bash 內建的 'echo' 命令不會檢查對 write() 的調用是否出錯。如果你在 cgroup 文件系統中使用它,你將無法知道命令是成功還是失敗。

Q: 當我附加進程時,只有行中的第一個真正被附加了!
A: 我們每次調用 write() 只能返回一個錯誤代碼。因此,你也應該只放置一個 PID。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章