# 系統程式設計 - Cgroup v2 [TOC] ## References > 以下影片的講者 *Michael Kerrisk* 是著名的 TLPI 一書的作者,同時也是 `man-pages` 的維護者。投影片可以在 man7.org/conf/ 找到 如果需要一些 cgroup v1 的歷史回顧的話,可以看同一位講者的 [*What's new in control groups (cgroups) version 2?*](https://youtu.be/yZpNsDe4Qzg) 為了方便,以下講到 cgroup 時,都是指 cgroup v2。 ### An introduction to control groups (cgroups) version 2 - Michael Kerrisk - NDC TechTown 2021 {%youtube kcnFQgg9ToY %} ### Diving deeper into control groups (cgroups) v2 - Michael Kerrisk - NDC TechTown 2021 {%youtube Clr_MQwaJtA %} ## 簡介 cgroup 這個子系統可以將多個行程組成集合(在 cgroup 中這樣的集合稱為...也是 cgroup)。並且以這些集合為單位,限制該集合中的行程能夠使用的總資源上限。 這樣的集合可以具有階層關係,即一個 cgroup 可以包含在另外一個 cgroup 當中。這個些 cgroup 間的包含關係,則是由 cgroup 檔案系統中的目錄階層來決定,並且能藉由改變其中的目錄來新增/移除 cgroup。 ## 基本架構 ### `/sys/fs/cgroup` --- cgroupfs 的預設掛載點 ```clike ubuntu@ubuntu:/sys/fs/cgroup$ ls -.mount cpu.stat memory.stat cgroup.controllers cpuset.cpus.effective misc.capacity cgroup.max.depth cpuset.mems.effective sys-fs-fuse-connections.mount cgroup.max.descendants dev-hugepages.mount sys-kernel-config.mount cgroup.procs dev-mqueue.mount sys-kernel-debug.mount cgroup.stat init.scope sys-kernel-tracing.mount cgroup.subtree_control io.pressure system.slice cgroup.threads io.stat user.slice cpu.pressure memory.pressure ``` ### `mkdir` --- 新增 cgroup 在這個目錄底下新增一個目錄: ``` $ sudo mkdir AAAAAAAAAA ``` 這時,裡面的目錄會出現剛剛新增的目錄: ```shell $ ls -.mount cgroup.max.descendants cgroup.threads cpuset.mems.effective io.pressure misc.capacity sys-kernel-tracing.mount AAAAAAAAAA cgroup.procs cpu.pressure dev-hugepages.mount io.stat sys-fs-fuse-connections.mount system.slice cgroup.controllers cgroup.stat cpu.stat dev-mqueue.mount memory.pressure sys-kernel-config.mount user.slice cgroup.max.depth cgroup.subtree_control cpuset.cpus.effective init.scope memory.stat sys-kernel-debug.mount ```` 這個目錄中會出現不同的檔案,寫入這些檔案可以改變這個 cgroup 中對形成的限制: ```shell $ cd AAAAAAAAAA/ $ ls cgroup.controllers cgroup.procs cpu.max cpuset.cpus io.max memory.events.local memory.oom.group memory.swap.high cgroup.events cgroup.stat cpu.pressure cpuset.cpus.effective io.pressure memory.high memory.pressure memory.swap.max cgroup.freeze cgroup.subtree_control cpu.stat cpuset.cpus.partition io.stat memory.low memory.stat pids.current cgroup.max.depth cgroup.threads cpu.weight cpuset.mems memory.current memory.max memory.swap.current pids.events cgroup.max.descendants cgroup.type cpu.weight.nice cpuset.mems.effective memory.events memory.min memory.swap.events pids.max ubuntu@ubuntu:/sys/fs/cgroup/AAAAAAAAAA$ ``` 如果在一個這樣的目錄底下再建立一個目錄,那麼就會在這個 cgroup 下再建立一個 cgroup。所以這所有的 cgroup 會依照目錄的包含關係形成一個階層。舉例來說,如果再建立 `A1`, `A2`, `A3`, `A4`, `A5`: ```shell $ sudo mkdir A1 A2 A3 A4 A5 $ ls A1 cgroup.controllers cgroup.procs cpu.max cpuset.cpus io.max memory.events.local memory.oom.group memory.swap.high A2 cgroup.events cgroup.stat cpu.pressure cpuset.cpus.effective io.pressure memory.high memory.pressure memory.swap.max A3 cgroup.freeze cgroup.subtree_control cpu.stat cpuset.cpus.partition io.stat memory.low memory.stat pids.current A4 cgroup.max.depth cgroup.threads cpu.weight cpuset.mems memory.current memory.max memory.swap.current pids.events A5 cgroup.max.descendants cgroup.type cpu.weight.nice cpuset.mems.effective memory.events memory.min memory.swap.events pids.max ``` 這時裡面也會有 ### `cgroup.procs` --- 在這個 cgroup 中的行程 將 PID 的數值寫入這個檔案就可以把該 PID 對應的行程加入這個 cgroup。舉例來說,剛剛的 `AAAAAAAAAA` 這個 cgroup,如果查看 ``` # cat cgroup.procs ``` 會發現沒有任何輸出。這時候可以把現在的 shell 加入這個 cgroup: ``` # echo $$ > cgroup.procs ``` 這時會發現出現兩個行程: ``` # cat cgroup.procs 15729 15736 ``` 其中一個是現在的 shell,而另外一個則是 `cat` 本身。 ### `cgroup.controllers` --- 這個 cgroup 最多可以控制哪些東西? 舉例來說: ``` # cat cgroup.controllers cpuset cpu io memory pids ``` 這些 controller 會以 `<controller_name>.*` 的形式出現。比如說當 `pids` 控制器出現在裡面時,就會出現 `pids.max`、`pids.current`、`pids.events` 等檔案。使用一個控制器的意思就是 ### `cgroup.subtree_control` --- 這個 cgroup 目前允許控制哪些東西? 就是從剛剛的 `cgroup.controllers` 中挑要使用哪些控制器。這個會影響到這個 cgroup 底下的 cgroup 可以控制哪些東西。另外一方面,這一層的 cgroup 只能使用所屬 cgroup 允許的那些 controller。也就是其所能使用的控制器是由其祖先 cgroup 決定的。 ## 例子 ### 例子一:使用 `pids` 控制器限制 cgroup 中的總行程數目 cgroup 中的 PID 控制器指得是那些「名稱具有 `pids.*`」形式的檔案。這些檔案的內容中,各自代表這個 cgroup 裡面的資源限制。舉例來說,`pids.max` 這個檔案,表示最多能有多少行程在這個 cgroup 當中。需注意的是:若在 `clone` 時沒有另外設定,則一個行程的子行程會自動跟其處於同一個 cgroup。 接下來使用 `pids` 控制 cgroup 中的行程數目。首先,把目前的 shell 放進這個 cgroup 中: ``` $ echo $$ > cgroup.procs ``` 接著改變 `pids.max`,讓它最多只能允許 2 個行程: ```shell # cat pids.max max # echo 2 > pids.max # cat pids.max 2 ``` 這時候如果在這個 shell 裡面使用 fork bomb: ``` # :(){ :|:& };: ``` 就會發現它沒辦法 fork 出更多行程: ``` # :(){ :|:& };: bash: fork: retry: Resource temporarily unavailable bash: fork: retry: Resource temporarily unavailable bash: fork: retry: Resource temporarily unavailable bash: fork: retry: Resource temporarily unavailable bash: fork: Resource temporarily unavailable # ``` ### 例子二:Nested Cgroup Cgroup 中可以包含其他 cgroup。舉例來說: ``` # mkdir A B C # mkdir B/X B/Y # tree -d . ├── A ├── B │   ├── X │   └── Y └── C 5 directories ``` 要注意的是:整個 cgroup 檔案系統的目錄階層中,行程只能被加入整個樹狀階層的 leaf node (也就是沒有子節點的那些節點)中。以這個例子來說,行程只能加入 `A`、`C` 與 `X`、`Y` 這 4 個目錄中的 `cgroup.procs` 中,而 `B` 則不能加入行程。可以參考 [`cgroup(7)`](https://man7.org/linux/man-pages/man7/cgroups.7.html) 中的 *Cgroups v2 "no internal processes" rule* 章節: > ...a (nonroot) cgroup can't both (1) have member processes, and (2) distribute resources into child cgroups—that is, have a nonempty cgroup.subtree_control file. ### 例子三:`cpu` controller ![](https://i.imgur.com/TeHnSMK.png) ## 更多細節 ### `cgroup.events` 中的 `populated` --- 這個 cgroup 中有沒有行程? 可以藉由如 `inotify` 或 `epoll` 的系統呼叫監測這個檔案有沒有變動。如果有變動,而且 `populated` 欄位的數值是 `0`,就表示這個 cgroup 的最後一個行程離開了。反之,如果有變動且 `populated` 的欄位是 `1`,則表示有第一個行程進入了這個 cgroup。 ### Delegation = 改變目錄的 Ownership ![](https://i.imgur.com/lzfN3Vy.png) ### `cgroup.type` --- 決定是否要以執行緒為單位控制 前面介紹的 cgroup 都是以行程所構成的集合作為控制的單位,這種類型的 cgroup 叫做 `domain`。事實上 cgroup 也可以用「執行緒的集合」作為控制的單位。這時要更改 `cgroup.type` 中的內容做調整。可以參考 `cgroups(7)` 中的 [*CGROUPS VERSION 2 THREAD MODE*](https://man7.org/linux/man-pages/man7/cgroups.7.html#CGROUPS_VERSION_2_THREAD_MODE) 章節。 ![](https://i.imgur.com/hqtsUal.png)