五分鐘技術趣談 | Linux Cgroup層級規則簡析
Part 01 cgroup概述
cgroup是Control Groups的縮寫,是Linux內核提供的一種可以對進程或進程組進行物理資源(如:CPU,內存,設備IO等)限制、隔離和統計的機制。cgroup的用戶空間管理是通過cgroup文件系統來實現,得益于Linux的虛擬文件系統,其文件系統的細節被隱藏,用戶通過相關的控制文件來實現該功能的使用。
cgroup于2.6內核時期由Google公司主導引入,它是Linux內核實現資源虛擬化的技術基礎,是LXC(Linux Containers)和Docker容器的技術基石。cgroup中有如下相關概念:
- 任務(task):進程的別稱;
- 控制組(control group):按照某種標準劃分的進程集合。Cgroup中的資源控制都是以控制組為單位來實現。進程可以加入到某個控制組,也可以從一個進程組遷移到另一個控制組中。一個進程組的進程可以使用cgroups以控制組為單位分配的資源,同時受到cgroup以控制組為單位設置的資源限制。
- 層級(hierarchy):控制組的層級關系,采用樹的結構方式組織,子節點的控制組繼承父節點的資源設置屬性。
- 子系統(subsystem):一個子系統就是一種資源控制器,比如cpu子系統可以控制進程CPU使用時間分配,如圖1所示。子系統必須附件到一個層級上才能起作用,一個子系統附加到某個層級以后,這個層級上的所有控制組都受到這個子系統的控制。
Part 02 cgroup子系統
cgroup子系統和內核版本有關,隨著內核的迭代,能限制的資源也越來越多,一般包括如下子系統。
? blkio:對輸入/輸出訪問存取塊設備設定限制,比如物理設備(磁盤,固態硬盤,USB等等)。
? cpu:限制進程的cpu使用,涉及cpu調度時間片分配。
? cpuacct:自動生成cgroup中任務所使用的cpu報告。
? cpuset:為cgroup中的任務分配獨立cpu(多核系統)和內存節點。
? devices:允許或者拒絕cgroup中的任務訪問設備。
? freezer:掛起或恢復cgroup中的任務。
? memory:設定cgroup中任務使用的內存限制,并自動生成由那些任務使用的內存資源報告。
? net_cls:使用等級識別符標記網絡數據包,可允許Linux流浪控制程序識別從具體cgroup中生成的數據包。
? ns:namespace子系統。
Part 03 cgroup層級規則
結合cgroup層級(hierarchy)可以理解為一顆樹,樹的每個節點就是一個進程組,每棵樹都會與一到多個子系統關聯。在一棵樹里,會包含Linux系統中的所有進程,但每個進程只能屬于一個節點(進程組)。系統中可以有很多顆cgroup樹,每棵樹都和不同的subsystem關聯,一個進程可以屬于多棵樹,即一個進程可以屬于多個進程組,只是這些進程組和不同的子系統關聯。目前Linux最多可以建十二顆cgroup樹,每棵樹關聯一個子系統,當然也可以只建一棵樹,然后讓這棵樹關聯到所有的子系統。當一顆cgroup樹不和任何子系統關聯的時候,意味著這棵樹只是將進程進行分組,至于要在分組的基礎上做些什么,將由應用程序自己決定,systemd就是這樣一個例子。
層級的組成規則有四個,描述如下:
規則1:單個層次結構可以具有一個或多個子系統。如圖1所示,/cpu_memory_cg這個層級對cgroup1,cgroup2設置了cpu和memory兩個子系統。
圖1 層級規則1
規則2:如果任何一個子系統已經附加到了一個層次,則不能將他們附加到另一個層次的結構中。如圖2所示,層級A的cpu_cg首先管理cpu子系統,那么層級B的cpu_mem_cg就無法管理cpu子系統。
圖2 cgroup層級規則2
規則3:每次在系統上創建新的層次結構時,系統上的所有任務最初都是該層次結構的默認cgroup(稱為根cgroup)成員。對于創建的任何單個層次結構,系統上的每個任務都可以是該層次結構中的一個cgroup成員。一個任務可以位于多個cgroup中,只要這些cgroup中的每個處于不同的子系統層次結構中即可。任務一旦成為同一層次結構中的第二個cgroup成員,就會將其從該層次結構中的第一個cgroup中刪除,即在同一層次結構中的兩個不通cgroup,絕不會有同一任務,也即是對某進程某類cgroup子系統的限制方式只能有一種。創建第一個層次結構時,系統上的每個任務都是至少一個cgroup(根cgroup)的成員,因此,在使用cgroup時,每個系統任務始終至少位于一個cgroup中,如圖3所示。
圖3 cgroup層級規則3
規則4:系統上派生的任何進程都會創建一個子進程(或線程)。子進程自動繼承其父級的cgroup成員資格,但可以根據需要移動到其他cgroup中,移動后父子進程完全獨立,如圖4所示。
圖4 cgroup層級規則4
Part 04 cgroup層級關系分析
我們從進程的角度出發,結合源碼中的數據結構來解析cgroups相關數據之間的關系。首先在Linux中,管理進程的數據結構是task_struct,其中與cgroups有關的成員如下:
其中cgroup指向一個css_set結構,其存儲了與進程相關的cgroups信息。cg_list為使用同一個css_set的進程鏈表。css_set結構如下:
結構體的元素信息解釋如下:
- refcount是css_set的引用計數,其可以被多個進程共用,只要這些進程的cgroups信息相同。比如,在所有已經創建的層級里面都在同一個cgroup里的進程。
- hlist用于把所有css_set構建成一個hash表,內核能快速查找特定的css_set。
- tasks將所有引用此css_set的進程鏈接成鏈表。
- cg_links指向一個由struct cg_group_link組成的鏈表
- subsys為一個指針數組,存儲一組指向cgroup_subsys_state的指針。一個cgroup_subsys_state就是進程與一個特定的子系統相關的信息。通過這個指針,進程就可以獲得相應的cgroups控制信息。
接下來我們看一下cgroup_subsys_state結構體情況:
結構體中cgroup指針指向一個cgroup結構,進程受到子系統的資源控制,實際上是通過加入特定的cgroup子系統實現,因為cgroup在特定的層級上,而子系統又是附加到層級上的。
我們來看看cgroup的結構,
- sibling,children和parent三個鏈表負責將同一層級的cgroup連接成一棵樹。
- susys為之前描述過的子系統指針數組。
- root指向了一個cgroupfs_root的結構,就是cgroup所在的層級對應的結構體。
- root->top_cgroup指向所在層級的根cgroup,也就是幻劍層級時自動創建的那個cgroup。獲取層級的根cgroup可以通過cgroup->root->top_cgroup。
- css_sets指向一個由cg_cgroup_link的鏈表,和css_set中cg_links一致。
為了理清楚css_set和cgroup的關系,我們還需對中間層的cg_cgroup_link結構進行分析,結構體數據如下:
結構體中的數據說明如下:
cgrp_link_list鏈接到cgroup->css_sets指向的鏈表。
cgrp則指向此cg_cgroup_link相關的group。
cg_link_list則鏈接到css_set->cg_links指向的鏈表。
cg則指向cg_cgroup_link相關的css_set。
可以看出cgroup和css_set實際上是一個多對多的關系,需要添加一個中間結構將兩者結合,cg_group_link中的cgrp和cg元素就是結合部,cgrp_link_list和cg_link_list兩個鏈表即為掛接的cgroup和css_set實體,方便輪詢。
從cgroup的層級規則中可以看出,一組進程可以同屬于不在同一層級的cgroup,相結合理解,一個css_set存儲了一組進程根各個子系統相關的信息,子系統來自不通的cgroup層級,因此一個css_set存儲的cgroup_subsys_state可以對應多個cgroup。 另一方面,cgroup層級也存儲了一組cgroup_subsys_state,其從cgroup所在的層級附加的子系統中獲得,一個cgroup可以有多個進程,進程的css_set不一定相同,因為進程可能使用了多個層級,所以一個cgroup也需要對應多個css_set。圖5詳細描述了多對多的掛接關系。
圖5 進程和cgroup多對多關系圖
Part 05 結語
本文在cgroup概念基礎上,對其和進程之間多對多的關系進行了拆解,從相關結構體中變量的掛接分析其具體代碼實現方式,希望能幫助讀者對cgroup層級關系和使用方式有更好的理解。