英文原文:https://www.kernel.org/doc/html/latest/admin-guide/cgroup-v1/cgroups.html 1 控制組 1.1 什麼是控制組? 控制組(Control Group)提供一種機制,把一組任務(task)及其子任務整合/分割成具有特殊行為 ...
英文原文:https://www.kernel.org/doc/html/latest/admin-guide/cgroup-v1/cgroups.html
1 控制組
1.1 什麼是控制組?
控制組(Control Group)提供一種機制,把一組任務(task)及其子任務整合/分割成具有特殊行為的分層化的分組(groups)。
定義:
控制組(cgroup),把一組任務跟一個或者多個子系統(subsystem)的一組參數進行關聯。
分組(subsystem),是一種模塊,利用cgroup提供的任務分組功能,以特定的方式來實現任務組。分組(subsystem)一般是資源控制器(resource controller),調度資源或者設置資源限制,但是他也可以是管理一組進程的任何方法,例如虛擬子系統。
分層(hierachy),是一組樹形結構的控制組,系統中的每個任務都會處在分層的控制組和子系統中,每個子系統有控制組相關的系統狀態。每個分層都有一個控制組虛擬文件系統實例。
在任何時候,可以有多個激活的任務組分層。每個分層是系統中所有任務的一種隔離。
用戶級別的代碼可以根據cgroup虛擬文件系統中的名稱創建和銷毀控制組,指定和查詢任務被分配給哪個控制組,枚舉分配給控制組的任務PID。這些創建和分配隻影響該控制組文件系統相關的分層。
控制組的唯一作用就是簡化任務跟蹤。她的目標是其他子系統能掛載到通用控制組上,而這些通用控制組提供了新的控制組屬性,例如對控制組進程能訪問資源的統計和限制。舉個例子,cpusets允許把cpu和記憶體節點和每個控制組的任務進行關聯。
1.2 為什麼需要控制組?
在linux內核中,有多種用來做資源跟蹤的進程聚合方式,像cpusets,CKRM/ResGroups,UserBeanCounters和虛擬服務命名空間(namespace)。這些方式都需要瞭解進程分組/分割的基本概念,而這些在同一個分組(cgroup)中新產生的子進程作為他們的父進程。
內核的控制組補丁提供了最小的基本內核機制,根據需求有效的實現這種控制組。它對系統快速路徑(system fast paths)只有很小的影響,並提供了針對特定子系統的鉤子,像cpusets。
多級分層支持,允許分割任務到控制組,它明顯不同於一些有平行分層的子系統,允許每個分層作為自然的任務分隔,不必處理複雜的任務組合,而這些任務組合是出現在幾個不相關的子系統需要強制分配到同一個控制組樹的情況下。
在極端情況下,每個資源控制器或者子系統可能在單獨的分層中;另一種極端情況,所有的子系統可能隸屬於同一個分層。
有一個應用場景示例可能對多分層的理解有好處。假設一個有很多用戶(學生,教授,系統任務等)的大學伺服器,這個伺服器的資源規劃應該是下麵這樣子的:
CPU : "Top cpuset"
/ \
CPUSet1 CPUSet2
| |
(Professors) (Students)
In addition (system tasks) are attached to topcpuset
(so that they can run anywhere) with a limit of 20%
Memory : Professors (50%), Students (30%), system (20%)
Disk : Professors (50%), Students (30%), system (20%)
Network : WWW browsing (20%), Network File System (60%), others (20%)
/ \
Professors (15%) students (5%)
瀏覽器firefox/lynx算作WWW網路類,而 (k)nfsd算作NFS網路類。與此同時,取決於誰來運行它,Firefox/Lynx將共用cpu/memory類。
為了能對不同的資源劃分任務,管理員很容易就能運行腳本來接收執行通知,然後根據是誰來運行的瀏覽器,他可以執行下麵的命令:
# echo browser_pid > /sys/fs/cgroup/<restype>/<userclass>/tasks
在只有單個分層情況下,他現在可能必須要為每個啟動的瀏覽器創建一個單獨的控制組,然後關聯合適的網路和其他的資源類。這可能導致這種控制組的激增。
管理員可能臨時為一個學生的瀏覽器增加網路訪問,或者給一個學生的模擬器應用增加CPU算力。
下麵的方式可以直接寫PIDs到資源類中:
# echo pid > /sys/fs/cgroup/network/<new_class>/tasks
(after some time)
# echo pid > /sys/fs/cgroup/network/<orig_class>/tasks
沒有這種機制的話,管理員將不得不切分控制組成多個單獨的控制組,然後關聯新的控制組和新的資源類。
1.3 控制組是如何實現的?
控制組在內核中的擴展方式如下:
- 系統中每個任務task一個引用計數指針指向css_set。
- css_set包含一個指向cgroup_subsys_state對象的引用計數指針集合,每個控制組子系統在系統中註冊一個該對象。(省略部分)
- 一個控制組分層文件系統能被從用戶空間掛載出來進行瀏覽和操作。
- 可以根據PID枚舉隸屬於任何控制組的任務。
控制組的實現需要幾個簡單的鉤子鉤到內核其餘部分,但是不在關鍵性能的路徑上:
- 在init/main.c中來初始化根控制組(root cgroups),在系統啟動時初始化css_set.
- 在fork和exit時,從css_set中attach和detach任務。
除此之外,一個新的文件系統類型cgroup可以被掛載出來,以便能夠瀏覽和修改控制組。當掛載一個控制組分層(cgroup hierachy)的時候,你可以指定一個逗號分隔符的子系統列表來作為掛載選項。預設情況下,掛載控制組文件系統會試著掛載一個包含所有已註冊子系統的分層。
如果存在一個激活的分層有相同的子系統集合,它將被重用做新的掛載。如果沒有現存的分層匹配,以及現存分層中的任何子系統正在被使用,那麼掛載將會失敗(失敗號-EBUSY),否則,一個新的分層就被激活,跟請求的子系統關聯起來。
綁定新的子系統到激活的控制組分層中,或者從激活的控制組分層中解綁子系統,在當前是不可能,但是未來是可能的,但是它會充滿著嚴重的錯誤恢復(errot-recovery)問題。
當控制組文件系統被卸載時,如果有任何子控制組被創建在頂級控制組下,即使已經卸載完畢,分層仍會保持激活;如果沒有子控制組,那麼分層將會被停用。
沒有為控制組增加新的系統調用,對控制組的所有的查詢和修改的操作支持都是通過控制組文件系統。
在/proc下的每個任務都有一個新增的cgroup文件,對每個激活的分層來說,子系統命名和控制組名稱路徑都是相對控制組文件系統根路徑的。
每個控制組是由控制組文件系統中的目標表示的,它包含如下的文件來描述控制組:
- tasks:隸屬於控制組的任務列表(以PID來表示),這個列表不是按序排列的。寫入線程ID到這個文件就表示移動線程到這個控制組。
- cgroup.procs:線程組ID,這個列表不保證按序排列或者沒有重覆的TGIDs,如果需要的話,用戶空間應當排序或者uniquify這個列表。寫線程組ID到這個文件就會移動這個組中的所有線程到本控制組。
- notify_on_release: 在exit退出時運行release agent。
- release_agent: 用來釋放通知的路徑。(這個文件僅僅存在頂層控制組中)
其他的子系統,像cpusets可能會在每個控制組路目錄下添加額外的文件。
新的控制組可以通過mkdir系統調用或者shell命令來創建。控制組屬性,例如標簽,可以通過寫入該控制組目錄下的文件來修改。
嵌套控制組的命名分層結構允許分割大系統成嵌套的、動態可變的軟分區(soft-partitions)。
每個任務綁定到控制組時,在fork的時候會被該任務的子任務自動繼承,允許在系統中組織工作負載到相關的任務集合中。如果必要的控制組文件系統目錄允許,一個任務可以被重新綁定到任何控制組,
當任務從一個控制組移動到另一個控制組,他就會獲得一個新的css_set指針。如果有現存的css_set,帶有預期的可重用的控制組集合,那麼就可以重用,否則,就分配一個新的css_set。現有的css_set通過查詢哈希表來定位。
要允許從控制組來訪問css_sets,一個g_cgroup_link對象集合形成一個格柵(lattice);每個g_cgroup_link被鏈接到一個g_cgroup_links列表(省略......)
控制組中的任務集合可以通過引用該控制組的css_set來列舉。
使用linux虛擬文件系統vfs來表示控制組分層,最小化改動內核代碼,為控制組提供了常見的許可權和命名空間。
1.4 notify_on_release是做什麼的?
如果控制組中的notify_on_release標記被使能,那麼只要控制組中的最後一個任務離開(退出或者綁定到其他的控制組)並且最後的子控制組被移除,內核就會運行分層根目錄下的release_agent文件內容中定義的命令,提供廢棄的控制組的路徑名(相對控制組文件系統的掛載點)。這樣能自動移除廢棄的控制組。
在系統啟動的時候,根控制組中的notify_on_release的預設值是diabaled(0).其他的控制組在創建時候的預設值是他們的父控制組的notify_on_release的當前值。
控制組分層的release_agent路徑的預設值是空。
1.5 clone_children是做什麼的?
這個標簽僅僅影響cpuset控制器。如果clone_children標記在控制組中被使能enbale(1),一個新的cpuset控制組在初始化的時候就能複製父控制組的配置。
1.6 如何使用控制組?
要啟動一個將要包含在某個控制組中的新工作任務,使用cpuset控制組子系統,操作步驟如下:
1) mount -t tmpfs cgroup_root /sys/fs/cgroup
2) mkdir /sys/fs/cgroup/cpuset
3) mount -t cgroup -ocpuset cpuset /sys/fs/cgroup/cpuset
4) Create the new cgroup by doing mkdir's and write's (or echo's) in
the /sys/fs/cgroup/cpuset virtual file system.
5) Start a task that will be the "founding father" of the new job.
6) Attach that task to the new cgroup by writing its PID to the
/sys/fs/cgroup/cpuset tasks file for that cgroup.
7) fork, exec or clone the job tasks from this founding father task.
舉個例子,下麵的命令序列將會創建一個名稱為“Charlie”的控制組,僅僅包含CPU2和3,記憶體節點1,在控制組中啟動一個子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
# The subshell 'sh' is now running in cgroup Charlie
# The next line should display '/Charlie'
cat /proc/self/cgroup
2 應用示例和語法
2.1 基本用法
創建、修改和使用控制組可以通過控制組虛擬文件系統來完成。
要掛載一個所有子系統都可用的控制組分層,輸入命令:
# mount -t cgroup xxx /sys/fs/cgroup
內核代碼解讀不了“xxx”,但是它會出現在/proc/mounts中,因此它就可以是你想用的有用的身份字元串。
註意:沒有用戶提前輸入的話,一些子系統不能工作。例如,如果cpusets被使能,用戶必須為每個已經創建但是還沒使用的控制組寫入數據到cpu和mem文件中。
正如1.2章節所述,我們為什麼需要控制組?你應該為每個你想要控制的資源或者資源組創建不同的控制組分層。因此,你可以掛載在/sys/fs/cgroup中tmpfs,然後為每個控制組資源或者資源組創建目錄:
# mount -t tmpfs cgroup_root /sys/fs/cgroup
# mkdir /sys/fs/cgroup/rg1
要掛載只有cpuset和memory子系統的控制組分層,輸入如下命令:
# mount -t cgroup -o cpuset,memory hier1 /sys/fs/cgroup/rg1
重新掛載控制組當前是支持的,但是不推薦使用。重新掛載允許改變子系統和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,將會返回失敗。
註意,子系統集合的變更當前被支持,僅限於由單個(根)控制組組成的分層。能夠隨時從現存的控制組分層綁定/解綁子系統,未來會考慮支持實現。
然後在/sys/fs/cgroup/rg1下,你能找到系統中的控制組樹。例如/sys/fs/cgroup/rg1也可以是容納整個系統的控制組。
如果你想要更改release_agent的值:
# echo "/sbin/new_release_agent" > /sys/fs/cgroup/rg1/release_agent
它也可以在重新掛載時更改。
如果你想在/sys/fs/cgroup/rg1下創建新的控制組:
# cd /sys/fs/cgroup/rg1
# mkdir my_cgroup
現在你想要用這個控制組來做些什麼的話:
# cd my_cgroup
在這個目錄下,你可以找到幾個文件:
# ls
cgroup.procs notify_on_release tasks
(plus whatever files added by the attached subsystems)
現在綁定你的當前shell到這個控制組:
# /bin/echo $$ > tasks
你也能在你的控制組內部創建控制組,在這個目錄下使用mkdir:
# mkdir my_sub_cs
要移除控制組,只要使用rmdir就可以:
# rmdir my_sub_cs
如果控制組正在使用中(內部有控制組,或者有進程綁定綁定,或者其他子系統相關的引用保持激活狀態),這操作就會失敗。
2.2 綁定進程
# /bin/echo PID > tasks
註意,這裡是PID而不是PIDs,一次只能綁定一個任務。如果你有幾個任務,只能一個個的綁定:
# /bin/echo PID1 > tasks
# /bin/echo PID2 > tasks
...
# /bin/echo PIDn > tasks
你也可以通過寫入0來綁定當前的shell任務:
# echo 0 > tasks
你也可以使用cgroup.procs文件來代替tasks文件,一次性移除線程組中的所有任務。寫入線程組中任何的任務PID到cgroup.procs中,線程組中的所有任務將會被綁定到該控制組。寫入0到cgroup.procs中就會移動當前寫任務的線程組中的所有任務。
註意:因為每個任務總是某個已掛載分層下的控制組的成員,要從當前控制組移除任務,你必須移動它到新的控制組(可能是根控制組),就是通過寫入新控制組的tasks文件的方式。
註意:由於受到一些控制組子系統的強制限制,移動進程到另外的控制組可能會失敗。
2.3 按名字掛載分層
當掛載控制組分層時傳遞name=
名字應當匹配 [w.-]+
當傳遞name=
子系統的名字作為分層的一部分會出現在/proc/mounts和/proc/
3 Kernel API
省略原文大概90行!我個人並不關註這一部分,所以沒有翻譯!!!
4 擴展屬性用法
控制組文件系統支持它的目錄和文件中擴展屬性的特定類型,當前支持的類型是:
Trusted (XATTR_TRUSTED)
Security (XATTR_SECURITY)
他們都需要設置CAP_SYS_ADMIN功能。
跟在tmpfs中一樣,控制組文件系統中的擴展屬性使用內核記憶體來存儲,建議保持最小使用。這就是為什麼用戶定義的擴展屬性不支持的原因,因為任何用戶都能這麼做並且沒有大小限制。
這個功能當前的已知用戶是SELINUX,用來限制控制組在容器中和systemd的使用,以便對諸如控制組(systemd為每個服務創建的控制組)中的主PID這樣的meta數據進行分類。
5 答疑
Q: 為什麼要使用'/bin/echo'?
A: bash內嵌的echo命令不會檢查對write()調用的錯誤,如果你在控制組文件系統中使用它,你將不知道命令是否執行成功還是失敗。
Q: 當我綁定很多進程時,只有第一行被真正綁定?
A: 每次對write()的調用只能返回一個錯誤,所以你應該就放一個PID。