CGroup 介紹、應用例項及原理描述

天府雲創發表於2017-06-20

CGroup 介紹
CGroup 是 Control Groups 的縮寫,是 Linux 核心提供的一種可以限制、記錄、隔離程式組 (process groups) 所使用的物力資源 (如 cpu memory i/o 等等) 的機制。2007 年進入 Linux 2.6.24 核心,CGroups 不是全新創造的,它將程式管理從 cpuset 中剝離出來,作者是 Google 的 Paul Menage。CGroups 也是 LXC 為實現虛擬化所使用的資源管理手段。

CGroup 功能及組成
CGroup 是將任意程式進行分組化管理的 Linux 核心功能。CGroup 本身是提供將程式進行分組化管理的功能和介面的基礎結構,I/O 或記憶體的分配控制等具體的資源管理功能是通過這個功能來實現的。這些具體的資源管理功能稱為 CGroup 子系統或控制器。CGroup 子系統有控制記憶體的 Memory 控制器、控制程式排程的 CPU 控制器等。執行中的核心可以使用的 Cgroup 子系統由/proc/cgroup 來確認。

CGroup 提供了一個 CGroup 虛擬檔案系統,作為進行分組管理和各子系統設定的使用者介面。要使用 CGroup,必須掛載 CGroup 檔案系統。這時通過掛載選項指定使用哪個子系統。

CGroup 支援的檔案種類

表 1. CGroup 支援的檔案種類

檔名 R/W 用途
Release_agent RW 刪除分組時執行的命令,這個檔案只存在於根分組
Notify_on_release RW 設定是否執行 release_agent。為 1 時執行
Tasks RW 屬於分組的執行緒 TID 列表
Cgroup.procs R 屬於分組的程式 PID 列表。僅包括多執行緒程式的執行緒 leader 的 TID,這點與 tasks 不同
Cgroup.event_control RW 監視狀態變化和分組刪除事件的配置檔案

 

CGroup 相關概念解釋

  1. 任務(task)。在 cgroups 中,任務就是系統的一個程式;
  2. 控制族群(control group)。控制族群就是一組按照某種標準劃分的程式。Cgroups 中的資源控制都是以控制族群為單位實現。一個程式可以加入到某個控制族群,也從一個程式組遷移到另一個控制族群。一個程式組的程式可以使用 cgroups 以控制族群為單位分配的資源,同時受到 cgroups 以控制族群為單位設定的限制;
  3. 層級(hierarchy)。控制族群可以組織成 hierarchical 的形式,既一顆控制族群樹。控制族群樹上的子節點控制族群是父節點控制族群的孩子,繼承父控制族群的特定的屬性;
  4. 子系統(subsystem)。一個子系統就是一個資源控制器,比如 cpu 子系統就是控制 cpu 時間分配的一個控制器。子系統必須附加(attach)到一個層級上才能起作用,一個子系統附加到某個層級以後,這個層級上的所有控制族群都受到這個子系統的控制。

相互關係

  1. 每次在系統中建立新層級時,該系統中的所有任務都是那個層級的預設 cgroup(我們稱之為 root cgroup,此 cgroup 在建立層級時自動建立,後面在該層級中建立的 cgroup 都是此 cgroup 的後代)的初始成員;
  2. 一個子系統最多隻能附加到一個層級;
  3. 一個層級可以附加多個子系統;
  4. 一個任務可以是多個 cgroup 的成員,但是這些 cgroup 必須在不同的層級;
  5. 系統中的程式(任務)建立子程式(任務)時,該子任務自動成為其父程式所在 cgroup 的成員。然後可根據需要將該子任務移動到不同的 cgroup 中,但開始時它總是繼承其父任務的 cgroup。


圖 1. CGroup 層級圖
圖 1 所示的 CGroup 層級關係顯示,CPU 和 Memory 兩個子系統有自己獨立的層級系統,而又通過 Task Group 取得關聯關係。

CGroup 特點

  1. 在 cgroups 中,任務就是系統的一個程式。
  2. 控制族群(control group)。控制族群就是一組按照某種標準劃分的程式。Cgroups 中的資源控制都是以控制族群為單位實現。一個程式可以加入到某個控制族群,也從一個程式組遷移到另一個控制族群。一個程式組的程式可以使用 cgroups 以控制族群為單位分配的資源,同時受到 cgroups 以控制族群為單位設定的限制。
  3. 層級(hierarchy)。控制族群可以組織成 hierarchical 的形式,既一顆控制族群樹。控制族群樹上的子節點控制族群是父節點控制族群的孩子,繼承父控制族群的特定的屬性。
  4. 子系統(subsytem)。一個子系統就是一個資源控制器,比如 cpu 子系統就是控制 cpu 時間分配的一個控制器。子系統必須附加(attach)到一個層級上才能起作用,一個子系統附加到某個層級以後,這個層級上的所有控制族群都受到這個子系統的控制。

CGroup 應用架構


圖 2. CGroup 典型應用架構圖

如圖 2 所示,CGroup 技術可以被用來在作業系統底層限制物理資源,起到 Container 的作用。圖中每一個 JVM 程式對應一個 Container Cgroup 層級,通過 CGroup 提供的各類子系統,可以對每一個 JVM 程式對應的執行緒級別進行物理限制,這些限制包括 CPU、記憶體等等許多種類的資源。下一部分會具體對應用程式進行 CPU 資源隔離進行演示。

CGroup 部署及應用例項
講解 CGroup 設計原理前,我們先來做一個簡單的實驗。實驗基於 Linux Centosv6.564 位版本,JDK1.7。實驗目的是執行一個佔用 CPU 的 Java 程式,如果不用 CGroup 物理隔離 CPU 核,那程式會由作業系統層級自動挑選 CPU 核來執行程式。由於作業系統層面採用的是時間片輪詢方式隨機挑選 CPU 核作為執行容器,所以會在本機器上 24 個 CPU 核上隨機執行。如果採用 CGroup 進行物理隔離,我們可以選擇某些 CPU 核作為指定執行載體。

清單 1.Java 程式程式碼

清單 1 程式會啟動 10 個執行緒,這 10 個執行緒都在做佔用 CPU 的計算工作,它們可能會執行在 1 個 CPU 核上,也可能執行在多個核上,由作業系統決定。我們稍後會在 Linux 機器上通過命令在後臺執行清單 1 程式。本實驗需要對 CPU 資源進行限制,所以我們在 cpu_and_set 子系統上建立自己的層級“zhoumingyao”。

清單 2. 建立層級

通過 mkdir 命令新建資料夾 zhoumingyao,由於已經預先載入 cpu_and_set 子系統成功,所以當資料夾建立完畢的同時,cpu_and_set 子系統對應的資料夾也會自動建立。
執行 Java 程式前,我們需要確認 cpu_and_set 子系統安裝的目錄,如清單 3 所示。

清單 3. 確認目錄

輸出顯示 cpuset_cpu 的目錄是 cpuset,cpu:/zhoumingyao,由於本實驗所採用的 Java 程式是多執行緒程式,所以需要使用 cgexec 命令來幫助啟動,而不能如網路上有些材料所述,採用 java –jar 命令啟動後,將 pid 程式號填入 tasks 檔案即可的錯誤方式。清單 4 即採用 cgexec 命令啟動 java 程式,需要使用到清單 3 定位到的 cpuset_cpu 目錄地址。

清單 4. 執行 Java 程式

我們在 cpuset.cpus 檔案中設定需要限制只有 0-10 這 11 個 CPU 核可以被用來執行上述清單 4 啟動的 Java 多執行緒程式。當然 CGroup 還可以限制具體每個核的使用百分比,這裡不再做過多的描述,請讀者自行翻閱 CGroup 官方材料。

清單 5.cpu 核限制

接下來,通過 TOP 命令獲得清單 4 啟動的 Java 程式的所有相關執行緒 ID,將這些 ID 寫入到 Tasks 檔案。

清單 6. 設定執行緒 ID

全部設定完畢後,我們可以通過 TOP 命令檢視具體的每一顆 CPU 核上的執行情況,發現只有 0-10 這 11 顆 CPU 核上有計算資源被呼叫,可以進一步通過 TOP 命令確認全部都是清單 4 所啟動的 Java 多執行緒程式的執行緒。

清單 7. 執行結果

總體上來說,CGroup 的使用方式較為簡單,目前主要的問題是網路上已有的中文材料缺少詳細的配置步驟,一旦讀者通過反覆實驗,掌握了配置方式,使用上應該不會有大的問題。

Cgroup 設計原理分析

CGroups 的原始碼較為清晰,我們可以從程式的角度出發來剖析 cgroups 相關資料結構之間的關係。在 Linux 中,管理程式的資料結構是 task_struct,其中與 cgroups 有關的程式碼如清單 8 所示:

清單 8.task_struct 程式碼

其中 cgroups 指標指向了一個 css_set 結構,而 css_set 儲存了與程式有關的 cgroups 資訊。cg_list 是一個嵌入的 list_head 結構,用於將連到同一個 css_set 的程式組織成一個連結串列。下面我們來看 css_set 的結構,程式碼如清單 9 所示:

清單 9.css_set 程式碼

其中 refcount 是該 css_set 的引用數,因為一個 css_set 可以被多個程式公用,只要這些程式的 cgroups 資訊相同,比如:在所有已建立的層級裡面都在同一個 cgroup 裡的程式。hlist 是嵌入的 hlist_node,用於把所有 css_set 組織成一個 hash 表,這樣核心可以快速查詢特定的 css_set。tasks 指向所有連到此 css_set 的程式連成的連結串列。cg_links 指向一個由 struct_cg_cgroup_link 連成的連結串列。

Subsys 是一個指標陣列,儲存一組指向 cgroup_subsys_state 的指標。一個 cgroup_subsys_state 就是程式與一個特定子系統相關的資訊。通過這個指標陣列,程式就可以獲得相應的 cgroups 控制資訊了。cgroup_subsys_state 結構如清單 10 所示:

清單 10.cgroup_subsys_state 程式碼

cgroup 指標指向了一個 cgroup 結構,也就是程式屬於的 cgroup。程式受到子系統的控制,實際上是通過加入到特定的 cgroup 實現的,因為 cgroup 在特定的層級上,而子系統又是附和到上面的。通過以上三個結構,程式就可以和 cgroup 連線起來了:task_struct->css_set->cgroup_subsys_state->cgroup。cgroup 結構如清單 11 所示:

清單 11.cgroup 程式碼

sibling,children 和 parent 三個嵌入的 list_head 負責將統一層級的 cgroup 連線成一棵 cgroup 樹。
subsys 是一個指標陣列,儲存一組指向 cgroup_subsys_state 的指標。這組指標指向了此 cgroup 跟各個子系統相關的資訊,這個跟 css_set 中的道理是一樣的。

root 指向了一個 cgroupfs_root 的結構,就是 cgroup 所在的層級對應的結構體。這樣一來,之前談到的幾個 cgroups 概念就全部聯絡起來了。

top_cgroup 指向了所在層級的根 cgroup,也就是建立層級時自動建立的那個 cgroup。

css_set 指向一個由 struct_cg_cgroup_link 連成的連結串列,跟 css_set 中 cg_links 一樣。
下面分析一個 css_set 和 cgroup 之間的關係,cg_cgroup_link 的結構如清單 12 所示:

清單 12.cg_cgroup_link 程式碼

cgrp_link_list 連入到 cgrouo->css_set 指向的連結串列,cgrp 則指向此 cg_cgroup_link 相關的 cgroup。
cg_link_list 則連入到 css_set->cg_lonks 指向的連結串列,cg 則指向此 cg_cgroup_link 相關的 css_set。
cgroup 和 css_set 是一個多對多的關係,必須新增一箇中間結構來將兩者聯絡起來,這就是 cg_cgroup_link 的作用。cg_cgroup_link 中的 cgrp 和 cg 就是此結構提的聯合主鍵,而 cgrp_link_list 和 cg_link_list 分別連入到 cgroup 和 css_set 相應的連結串列,使得能從 cgroup 或 css_set 都可以進行遍歷查詢。

那為什麼 cgroup 和 css_set 是多對多的關係呢?

一個程式對應一個 css_set,一個 css_set 儲存了一組程式 (有可能被多個程式共享,所以是一組) 跟各個子系統相關的資訊,但是這些資訊由可能不是從一個 cgroup 那裡獲得的,因為一個程式可以同時屬於幾個 cgroup,只要這些 cgroup 不在同一個層級。舉個例子:我們建立一個層級 A,A 上面附加了 cpu 和 memory 兩個子系統,程式 B 屬於 A 的根 cgroup;然後我們再建立一個層級 C,C 上面附加了 ns 和 blkio 兩個子系統,程式 B 同樣屬於 C 的根 cgroup;那麼程式 B 對應的 cpu 和 memory 的資訊是從 A 的根 cgroup 獲得的,ns 和 blkio 資訊則是從 C 的根 cgroup 獲得的。因此,一個 css_set 儲存的 cgroup_subsys_state 可以對應多個 cgroup。另一方面,cgroup 也儲存了一組 cgroup_subsys_state,這一組 cgroup_subsys_state 則是 cgroup 從所在的層級附加的子系統獲得的。一個 cgroup 中可以有多個程式,而這些程式的 css_set 不一定都相同,因為有些程式可能還加入了其他 cgroup。但是同一個 cgroup 中的程式與該 cgroup 關聯的 cgroup_subsys_state 都受到該 cgroup 的管理 (cgroups 中程式控制是以 cgroup 為單位的) 的,所以一個 cgroup 也可以對應多個 css_set。

從前面的分析,我們可以看出從 task 到 cgroup 是很容易定位的,但是從 cgroup 獲取此 cgroup 的所有的 task 就必須通過這個結構了。每個程式都回指向一個 css_set,而與這個 css_set 關聯的所有程式都會鏈入到 css_set->tasks 連結串列,而 cgroup 又通過一箇中間結構 cg_cgroup_link 來尋找所有與之關聯的所有 css_set,從而可以得到與 cgroup 關聯的所有程式。最後,我們看一下層級和子系統對應的結構體。層級對應的結構體是 cgroupfs_root 如清單 13 所示:

清單 13.cgroupfs_root 程式碼

sb 指向該層級關聯的檔案系統資料塊。subsys_bits 和 actual_subsys_bits 分別指向將要附加到層級的子系統和現在實際附加到層級的子系統,在子系統附加到層級時使用。hierarchy_id 是該層級唯一的 id。top_cgroup 指向該層級的根 cgroup。number_of_cgroups 記錄該層級 cgroup 的個數。root_list 是一個嵌入的 list_head,用於將系統所有的層級連成連結串列。子系統對應的結構體是 cgroup_subsys,程式碼如清單 14 所示。

清單 14. cgroup_subsys 程式碼

cgroup_subsys 定義了一組操作,讓各個子系統根據各自的需要去實現。這個相當於 C++中抽象基類,然後各個特定的子系統對應 cgroup_subsys 則是實現了相應操作的子類。類似的思想還被用在了 cgroup_subsys_state 中,cgroup_subsys_state 並未定義控制資訊,而只是定義了各個子系統都需要的共同資訊,比如該 cgroup_subsys_state 從屬的 cgroup。然後各個子系統再根據各自的需要去定義自己的程式控制資訊結構體,最後在各自的結構體中將 cgroup_subsys_state 包含進去,這樣通過 Linux 核心的 container_of 等巨集就可以通過 cgroup_subsys_state 來獲取相應的結構體。

從基本層次順序定義上來看,由 task_struct、css_set、cgroup_subsys_state、cgroup、cg_cgroup_link、cgroupfs_root、cgroup_subsys 等結構體組成的 CGroup 可以基本從程式級別反應之間的響應關係。後續文章會針對檔案系統、各子系統做進一步的分析。

結束語
就象大多數開源技術一樣,CGroup 不是全新創造的,它將程式管理從 cpuset 中剝離出來。通過物理限制的方式為程式間資源控制提供了簡單的實現方式,為 Linux Container 技術、虛擬化技術的發展奠定了技術基礎,本文的目標是讓初學者可以通過自己動手的方式簡單地理解技術,將起步門檻放低。

相關文章