linux之cgroups資源限制

1. cgroups是什麼

cgroups(Control Groups)最初叫Process Container,由Google工程師(Paul Menage和Rohit Seth)於2006年提出,後來因爲Container有多重含義容易引起誤解,就在2007年更名爲Control Groups,並被整合進Linux內核。顧名思義就是把進程放到一個組裏面統一加以控制。官方的定義如下{![引自:https://www.kernel.org/doc/Documentation/cgroups/cgroups.txt]}。

cgroups是Linux內核提供的一種機制,這種機制可以根據特定的行爲,把一系列系統任務及其子任務整合(或分隔)到按資源劃分等級的不同組內,從而爲系統資源管理提供一個統一的框架。

通俗的來說,cgroups可以限制、記錄、隔離進程組所使用的物理資源(包括:CPU、memory、IO等),爲容器實現虛擬化提供了基本保證,是構建Docker等一系列虛擬化管理工具的基石。

對開發者來說,cgroups有如下四個有趣的特點:

  • cgroups的API以一個僞文件系統的方式實現,即用戶可以通過文件操作實現cgroups的組織管理。
  • cgroups的組織管理操作單元可以細粒度到線程級別,用戶態代碼也可以針對系統分配的資源創建和銷燬cgroups,從而實現資源再分配和管理。
  • 所有資源管理的功能都以“subsystem(子系統)”的方式實現,接口統一。

  • 子進程創建之初與其父進程處於同一個cgroups的控制組。
  • 本質上來說,cgroups是內核附加在程序上的一系列鉤子(hooks),通過程序運行時對資源的調度觸發相應的鉤子以達到資源追蹤和限制的目的。

    2. cgroups的作用

    實現cgroups的主要目的是爲不同用戶層面的資源管理,提供一個統一化的接口。從單個進程的資源控制到操作系統層面的虛擬化。Cgroups提供了以下四大功能{![參照自:http://en.wikipedia.org/wiki/Cgroups]}。

    • 資源限制(Resource Limitation):cgroups可以對進程組使用的資源總額進行限制。如設定應用運行時使用內存的上限,一旦超過這個配額就發出OOM(Out of Memory)。
    • 優先級分配(Prioritization):通過分配的CPU時間片數量及硬盤IO帶寬大小,實際上就相當於控制了進程運行的優先級。
    • 資源統計(Accounting): cgroups可以統計系統的資源使用量,如CPU使用時長、內存用量等等,這個功能非常適用於計費。
    • 進程控制(Control):cgroups可以對進程組執行掛起、恢復等操作。

    過去有一段時間,內核開發者甚至把namespace也作爲一個cgroups的subsystem加入進來,也就是說cgroups曾經甚至還包含了資源隔離的能力。但是資源隔離會給cgroups帶來許多問題,如PID在循環出現的時候cgroup卻出現了命名衝突、cgroup創建後進入新的namespace導致脫離了控制等等{![詳見:https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/commit/?id=a77aea92010acf54ad785047234418d5d68772e2]},所以在2011年就被移除了。

    3. 術語表

    • task(任務):cgroups的術語中,task就表示系統的一個進程。
    • cgroup(控制組):cgroups 中的資源控制都以cgroup爲單位實現。cgroup表示按某種資源控制標準劃分而成的任務組,包含一個或多個子系統。一個任務可以加入某個cgroup,也可以從某個cgroup遷移到另外一個cgroup。
    • subsystem(子系統):cgroups中的subsystem就是一個資源調度控制器(Resource Controller)。比如CPU子系統可以控制CPU時間分配,內存子系統可以限制cgroup內存使用量。
    • hierarchy(層級樹):hierarchy由一系列cgroup以一個樹狀結構排列而成,每個hierarchy通過綁定對應的subsystem進行資源調度。hierarchy中的cgroup節點可以包含零或多個子節點,子節點繼承父節點的屬性。整個系統可以有多個hierarchy。

    4. 組織結構與基本規則

    大家在namespace技術的講解中已經瞭解到,傳統的Unix進程管理,實際上是先啓動init進程作爲根節點,再由init節點創建子進程作爲子節點,而每個子節點由可以創建新的子節點,如此往復,形成一個樹狀結構。而cgroups也是類似的樹狀結構,子節點都從父節點繼承屬性。

    它們最大的不同在於,系統中cgroup構成的hierarchy可以允許存在多個。如果進程模型是由init作爲根節點構成的一棵樹的話,那麼cgroups的模型則是由多個hierarchy構成的森林。這樣做的目的也很好理解,如果只有一個hierarchy,那麼所有的task都要受到綁定其上的subsystem的限制,會給那些不需要這些限制的task造成麻煩。

    瞭解了cgroups的組織結構,我們再來了解cgroup、task、subsystem以及hierarchy四者間的相互關係及其基本規則{![參照自:https://access.redhat.com/documentation/en-US/RedHatEnterpriseLinux/6/html/ResourceManagementGuide/sec-RelationshipsBetweenSubsystemsHierarchiesControlGroupsandTasks.html]}。

    • 規則1: 同一個hierarchy可以附加一個或多個subsystem。如下圖1,cpu和memory的subsystem附加到了一個hierarchy。

    • 圖1 同一個hierarchy可以附加一個或多個subsystem

    • 規則2: 一個subsystem可以附加到多個hierarchy,當且僅當這些hierarchy只有這唯一一個subsystem。如下圖2,小圈中的數字表示subsystem附加的時間順序,CPU subsystem附加到hierarchy A的同時不能再附加到hierarchy B,因爲hierarchy B已經附加了memory subsystem。如果hierarchy B與hierarchy A狀態相同,沒有附加過memory subsystem,那麼CPU subsystem同時附加到兩個hierarchy是可以的。

      圖2 一個已經附加在某個hierarchy上的subsystem不能附加到其他含有別的subsystem的hierarchy上

    規則3: 系統每次新建一個hierarchy時,該系統上的所有task默認構成了這個新建的hierarchy的初始化cgroup,這個cgroup也稱爲root cgroup。對於你創建的每個hierarchy,task只能存在於其中一個cgroup中,即一個task不能存在於同一個hierarchy的不同cgroup中,但是一個task可以存在在不同hierarchy中的多個cgroup中。如果操作時把一個task添加到同一個hierarchy中的另一個cgroup中,則會從第一個cgroup中移除。在下圖3中可以看到,httpd進程已經加入到hierarchy A中的/cg1而不能加入同一個hierarchy中的/cg2,但是可以加入hierarchy B中的/cg3。實際上不允許加入同一個hierarchy中的其他cgroup野生爲了防止出現矛盾,如CPU subsystem爲/cg1分配了30%,而爲/cg2分配了50%,此時如果httpd在這兩個cgroup中,就會出現矛盾。

    圖3 一個task不能屬於同一個hierarchy的不同cgroup

    規則4: 進程(task)在fork自身時創建的子任務(child task)默認與原task在同一個cgroup中,但是child task允許被移動到不同的cgroup中。即fork完成後,父子進程間是完全獨立的。如下圖4中,小圈中的數字表示task 出現的時間順序,當httpd剛fork出另一個httpd時,在同一個hierarchy中的同一個cgroup中。但是隨後如果PID爲4840的httpd需要移動到其他cgroup也是可以的,因爲父子任務間已經獨立。總結起來就是:初始化時子任務與父任務在同一個cgroup,但是這種關係隨後可以改變。

    圖4 剛fork出的子進程在初始狀態與其父進程處於同一個cgroup

    5. subsystem簡介

    subsystem實際上就是cgroups的資源控制系統,每種subsystem獨立地控制一種資源,目前Docker使用如下八種subsystem,還有一種net_cls subsystem在內核中已經廣泛實現,但是Docker尚未使用。他們的用途分別如下。

    • blkio: 這個subsystem可以爲塊設備設定輸入/輸出限制,比如物理驅動設備(包括磁盤、固態硬盤、USB等)。
    • cpu: 這個subsystem使用調度程序控制task對CPU的使用。
    • cpuacct: 這個subsystem自動生成cgroup中task對CPU資源使用情況的報告。
    • cpuset: 這個subsystem可以爲cgroup中的task分配獨立的CPU(此處針對多處理器系統)和內存。
    • devices 這個subsystem可以開啓或關閉cgroup中task對設備的訪問。
    • freezer 這個subsystem可以掛起或恢復cgroup中的task。
    • memory 這個subsystem可以設定cgroup中task對內存使用量的限定,並且自動生成這些task對內存資源使用情況的報告。
    • perfevent 這個subsystem使用後使得cgroup中的task可以進行統一的性能測試。{![perf: Linux CPU性能探測器,詳見https://perf.wiki.kernel.org/index.php/MainPage]}
    • *net_cls 這個subsystem Docker沒有直接使用,它通過使用等級識別符(classid)標記網絡數據包,從而允許 Linux 流量控制程序(TC:Traffic Controller)識別從具體cgroup中生成的數據包。

    6. cgroups實現方式及工作原理簡介

    (1)cgroups實現結構講解

    cgroups的實現本質上是給系統進程掛上鉤子(hooks),當task運行的過程中涉及到某個資源時就會觸發鉤子上所附帶的subsystem進行檢測,最終根據資源類別的不同使用對應的技術進行資源限制和優先級分配。那麼這些鉤子又是怎樣附加到進程上的呢?下面我們將對照結構體的圖表一步步分析,請放心,描述代碼的內容並不多。

    (點擊放大圖像)

    圖5 cgroups相關結構體一覽

    作者 孫健波 發佈於 2015年4月20日 | 注意:QCon全球軟件開發大會(北京)2016年4月21-23日,瞭解更多詳情!4 討論

    上一篇中,我們瞭解了Docker背後使用的資源隔離技術namespace,通過系統調用構建一個相對隔離的shell環境,也可以稱之爲一個簡單的“容器”。本文我們則要開始講解另一個強大的內核工具——cgroups。他不僅可以限制被namespace隔離起來的資源,還可以爲資源設置權重、計算使用量、操控進程啓停等等。在介紹完基本概念後,我們將詳細講解Docker中使用到的cgroups內容。希望通過本文,讓讀者對Docker有更深入的瞭解。

    1. cgroups是什麼

    cgroups(Control Groups)最初叫Process Container,由Google工程師(Paul Menage和Rohit Seth)於2006年提出,後來因爲Container有多重含義容易引起誤解,就在2007年更名爲Control Groups,並被整合進Linux內核。顧名思義就是把進程放到一個組裏面統一加以控制。官方的定義如下{![引自:https://www.kernel.org/doc/Documentation/cgroups/cgroups.txt]}。

    cgroups是Linux內核提供的一種機制,這種機制可以根據特定的行爲,把一系列系統任務及其子任務整合(或分隔)到按資源劃分等級的不同組內,從而爲系統資源管理提供一個統一的框架。

    通俗的來說,cgroups可以限制、記錄、隔離進程組所使用的物理資源(包括:CPU、memory、IO等),爲容器實現虛擬化提供了基本保證,是構建Docker等一系列虛擬化管理工具的基石。

    對開發者來說,cgroups有如下四個有趣的特點:

    • cgroups的API以一個僞文件系統的方式實現,即用戶可以通過文件操作實現cgroups的組織管理。
    • cgroups的組織管理操作單元可以細粒度到線程級別,用戶態代碼也可以針對系統分配的資源創建和銷燬cgroups,從而實現資源再分配和管理。
    • 所有資源管理的功能都以“subsystem(子系統)”的方式實現,接口統一。
    • 子進程創建之初與其父進程處於同一個cgroups的控制組。

    本質上來說,cgroups是內核附加在程序上的一系列鉤子(hooks),通過程序運行時對資源的調度觸發相應的鉤子以達到資源追蹤和限制的目的。

    2. cgroups的作用

    實現cgroups的主要目的是爲不同用戶層面的資源管理,提供一個統一化的接口。從單個進程的資源控制到操作系統層面的虛擬化。Cgroups提供了以下四大功能{![參照自:http://en.wikipedia.org/wiki/Cgroups]}。

    • 資源限制(Resource Limitation):cgroups可以對進程組使用的資源總額進行限制。如設定應用運行時使用內存的上限,一旦超過這個配額就發出OOM(Out of Memory)。
    • 優先級分配(Prioritization):通過分配的CPU時間片數量及硬盤IO帶寬大小,實際上就相當於控制了進程運行的優先級。
    • 資源統計(Accounting): cgroups可以統計系統的資源使用量,如CPU使用時長、內存用量等等,這個功能非常適用於計費。
    • 進程控制(Control):cgroups可以對進程組執行掛起、恢復等操作。

    過去有一段時間,內核開發者甚至把namespace也作爲一個cgroups的subsystem加入進來,也就是說cgroups曾經甚至還包含了資源隔離的能力。但是資源隔離會給cgroups帶來許多問題,如PID在循環出現的時候cgroup卻出現了命名衝突、cgroup創建後進入新的namespace導致脫離了控制等等{![詳見:https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/commit/?id=a77aea92010acf54ad785047234418d5d68772e2]},所以在2011年就被移除了。

    3. 術語表

    • task(任務):cgroups的術語中,task就表示系統的一個進程。
    • cgroup(控制組):cgroups 中的資源控制都以cgroup爲單位實現。cgroup表示按某種資源控制標準劃分而成的任務組,包含一個或多個子系統。一個任務可以加入某個cgroup,也可以從某個cgroup遷移到另外一個cgroup。
    • subsystem(子系統):cgroups中的subsystem就是一個資源調度控制器(Resource Controller)。比如CPU子系統可以控制CPU時間分配,內存子系統可以限制cgroup內存使用量。
    • hierarchy(層級樹):hierarchy由一系列cgroup以一個樹狀結構排列而成,每個hierarchy通過綁定對應的subsystem進行資源調度。hierarchy中的cgroup節點可以包含零或多個子節點,子節點繼承父節點的屬性。整個系統可以有多個hierarchy。

    4. 組織結構與基本規則

    大家在namespace技術的講解中已經瞭解到,傳統的Unix進程管理,實際上是先啓動init進程作爲根節點,再由init節點創建子進程作爲子節點,而每個子節點由可以創建新的子節點,如此往復,形成一個樹狀結構。而cgroups也是類似的樹狀結構,子節點都從父節點繼承屬性。

    它們最大的不同在於,系統中cgroup構成的hierarchy可以允許存在多個。如果進程模型是由init作爲根節點構成的一棵樹的話,那麼cgroups的模型則是由多個hierarchy構成的森林。這樣做的目的也很好理解,如果只有一個hierarchy,那麼所有的task都要受到綁定其上的subsystem的限制,會給那些不需要這些限制的task造成麻煩。

    瞭解了cgroups的組織結構,我們再來了解cgroup、task、subsystem以及hierarchy四者間的相互關係及其基本規則{![參照自:https://access.redhat.com/documentation/en-US/RedHatEnterpriseLinux/6/html/ResourceManagementGuide/sec-RelationshipsBetweenSubsystemsHierarchiesControlGroupsandTasks.html]}。

    • 規則1: 同一個hierarchy可以附加一個或多個subsystem。如下圖1,cpu和memory的subsystem附加到了一個hierarchy。

      圖1 同一個hierarchy可以附加一個或多個subsystem

    • 規則2: 一個subsystem可以附加到多個hierarchy,當且僅當這些hierarchy只有這唯一一個subsystem。如下圖2,小圈中的數字表示subsystem附加的時間順序,CPU subsystem附加到hierarchy A的同時不能再附加到hierarchy B,因爲hierarchy B已經附加了memory subsystem。如果hierarchy B與hierarchy A狀態相同,沒有附加過memory subsystem,那麼CPU subsystem同時附加到兩個hierarchy是可以的。

      圖2 一個已經附加在某個hierarchy上的subsystem不能附加到其他含有別的subsystem的hierarchy上

    • 規則3: 系統每次新建一個hierarchy時,該系統上的所有task默認構成了這個新建的hierarchy的初始化cgroup,這個cgroup也稱爲root cgroup。對於你創建的每個hierarchy,task只能存在於其中一個cgroup中,即一個task不能存在於同一個hierarchy的不同cgroup中,但是一個task可以存在在不同hierarchy中的多個cgroup中。如果操作時把一個task添加到同一個hierarchy中的另一個cgroup中,則會從第一個cgroup中移除。在下圖3中可以看到,httpd進程已經加入到hierarchy A中的/cg1而不能加入同一個hierarchy中的/cg2,但是可以加入hierarchy B中的/cg3。實際上不允許加入同一個hierarchy中的其他cgroup野生爲了防止出現矛盾,如CPU subsystem爲/cg1分配了30%,而爲/cg2分配了50%,此時如果httpd在這兩個cgroup中,就會出現矛盾。

      圖3 一個task不能屬於同一個hierarchy的不同cgroup

    • 規則4: 進程(task)在fork自身時創建的子任務(child task)默認與原task在同一個cgroup中,但是child task允許被移動到不同的cgroup中。即fork完成後,父子進程間是完全獨立的。如下圖4中,小圈中的數字表示task 出現的時間順序,當httpd剛fork出另一個httpd時,在同一個hierarchy中的同一個cgroup中。但是隨後如果PID爲4840的httpd需要移動到其他cgroup也是可以的,因爲父子任務間已經獨立。總結起來就是:初始化時子任務與父任務在同一個cgroup,但是這種關係隨後可以改變。

      圖4 剛fork出的子進程在初始狀態與其父進程處於同一個cgroup

    5. subsystem簡介

    subsystem實際上就是cgroups的資源控制系統,每種subsystem獨立地控制一種資源,目前Docker使用如下八種subsystem,還有一種net_cls subsystem在內核中已經廣泛實現,但是Docker尚未使用。他們的用途分別如下。

    • blkio: 這個subsystem可以爲塊設備設定輸入/輸出限制,比如物理驅動設備(包括磁盤、固態硬盤、USB等)。
    • cpu: 這個subsystem使用調度程序控制task對CPU的使用。
    • cpuacct: 這個subsystem自動生成cgroup中task對CPU資源使用情況的報告。
    • cpuset: 這個subsystem可以爲cgroup中的task分配獨立的CPU(此處針對多處理器系統)和內存。
    • devices 這個subsystem可以開啓或關閉cgroup中task對設備的訪問。
    • freezer 這個subsystem可以掛起或恢復cgroup中的task。
    • memory 這個subsystem可以設定cgroup中task對內存使用量的限定,並且自動生成這些task對內存資源使用情況的報告。
    • perfevent 這個subsystem使用後使得cgroup中的task可以進行統一的性能測試。{![perf: Linux CPU性能探測器,詳見https://perf.wiki.kernel.org/index.php/MainPage]}
    • *net_cls 這個subsystem Docker沒有直接使用,它通過使用等級識別符(classid)標記網絡數據包,從而允許 Linux 流量控制程序(TC:Traffic Controller)識別從具體cgroup中生成的數據包。

    6. cgroups實現方式及工作原理簡介

    (1)cgroups實現結構講解

    cgroups的實現本質上是給系統進程掛上鉤子(hooks),當task運行的過程中涉及到某個資源時就會觸發鉤子上所附帶的subsystem進行檢測,最終根據資源類別的不同使用對應的技術進行資源限制和優先級分配。那麼這些鉤子又是怎樣附加到進程上的呢?下面我們將對照結構體的圖表一步步分析,請放心,描述代碼的內容並不多。

    (點擊放大圖像)

    圖5 cgroups相關結構體一覽

    Linux中管理task進程的數據結構爲task_struct(包含所有進程管理的信息),其中與cgroup相關的字段主要有兩個,一個是css_set *cgroups,表示指向css_set(包含進程相關的cgroups信息)的指針,一個task只對應一個css_set結構,但是一個css_set可以被多個task使用。另一個字段是list_head cg_list,是一個鏈表的頭指針,這個鏈表包含了所有的鏈到同一個css_set的task進程(在圖中使用的迴環箭頭,均表示可以通過該字段找到所有同類結構,獲得信息)。

    每個css_set結構中都包含了一個指向cgroup_subsys_state(包含進程與一個特定子系統相關的信息)的指針數組。cgroup_subsys_state則指向了cgroup結構(包含一個cgroup的所有信息),通過這種方式間接的把一個進程和cgroup聯繫了起來,如下圖6。

    圖6 從task結構開始找到cgroup結構

    另一方面,cgroup結構體中有一個list_head css_sets字段,它是一個頭指針,指向由cg_cgroup_link(包含cgroup與task之間多對多關係的信息,後文還會再解釋)形成的鏈表。由此獲得的每一個cg_cgroup_link都包含了一個指向css_set *cg字段,指向了每一個task的css_setcss_set結構中則包含tasks頭指針,指向所有鏈到此css_set的task進程構成的鏈表。至此,我們就明白如何查看在同一個cgroup中的task有哪些了,如下圖7。

    圖7 cglink多對多雙向查詢

    細心的讀者可能已經發現,css_set中也有指向所有cg_cgroup_link構成鏈表的頭指針,通過這種方式也能定位到所有的cgroup,這種方式與圖1中所示的方式得到的結果是相同的。

    那麼爲什麼要使用cg_cgroup_link結構體呢?因爲task與cgroup之間是多對多的關係。熟悉數據庫的讀者很容易理解,在數據庫中,如果兩張表是多對多的關係,那麼如果不加入第三張關係表,就必須爲一個字段的不同添加許多行記錄,導致大量冗餘。通過從主表和副表各拿一個主鍵新建一張關係表,可以提高數據查詢的靈活性和效率。

    而一個task可能處於不同的cgroup,只要這些cgroup在不同的hierarchy中,並且每個hierarchy掛載的子系統不同;另一方面,一個cgroup中可以有多個task,這是顯而易見的,但是這些task因爲可能還存在在別的cgroup中,所以它們對應的css_set也不盡相同,所以一個cgroup也可以對應多個·css_set

    在系統運行之初,內核的主函數就會對root cgroupscss_set進行初始化,每次task進行fork/exit時,都會附加(attach)/分離(detach)對應的css_set

    綜上所述,添加cg_cgroup_link主要是出於性能方面的考慮,一是節省了task_struct結構體佔用的內存,二是提升了進程fork()/exit()的速度。

    圖8 css_set與hashtable關係

    作者 孫健波 發佈於 2015年4月20日 | 注意:QCon全球軟件開發大會(北京)2016年4月21-23日,瞭解更多詳情!4 討論

    上一篇中,我們瞭解了Docker背後使用的資源隔離技術namespace,通過系統調用構建一個相對隔離的shell環境,也可以稱之爲一個簡單的“容器”。本文我們則要開始講解另一個強大的內核工具——cgroups。他不僅可以限制被namespace隔離起來的資源,還可以爲資源設置權重、計算使用量、操控進程啓停等等。在介紹完基本概念後,我們將詳細講解Docker中使用到的cgroups內容。希望通過本文,讓讀者對Docker有更深入的瞭解。

    1. cgroups是什麼

    cgroups(Control Groups)最初叫Process Container,由Google工程師(Paul Menage和Rohit Seth)於2006年提出,後來因爲Container有多重含義容易引起誤解,就在2007年更名爲Control Groups,並被整合進Linux內核。顧名思義就是把進程放到一個組裏面統一加以控制。官方的定義如下{![引自:https://www.kernel.org/doc/Documentation/cgroups/cgroups.txt]}。

    cgroups是Linux內核提供的一種機制,這種機制可以根據特定的行爲,把一系列系統任務及其子任務整合(或分隔)到按資源劃分等級的不同組內,從而爲系統資源管理提供一個統一的框架。

    通俗的來說,cgroups可以限制、記錄、隔離進程組所使用的物理資源(包括:CPU、memory、IO等),爲容器實現虛擬化提供了基本保證,是構建Docker等一系列虛擬化管理工具的基石。

    對開發者來說,cgroups有如下四個有趣的特點:

    • cgroups的API以一個僞文件系統的方式實現,即用戶可以通過文件操作實現cgroups的組織管理。
    • cgroups的組織管理操作單元可以細粒度到線程級別,用戶態代碼也可以針對系統分配的資源創建和銷燬cgroups,從而實現資源再分配和管理。
    • 所有資源管理的功能都以“subsystem(子系統)”的方式實現,接口統一。
    • 子進程創建之初與其父進程處於同一個cgroups的控制組。

    本質上來說,cgroups是內核附加在程序上的一系列鉤子(hooks),通過程序運行時對資源的調度觸發相應的鉤子以達到資源追蹤和限制的目的。

    2. cgroups的作用

    實現cgroups的主要目的是爲不同用戶層面的資源管理,提供一個統一化的接口。從單個進程的資源控制到操作系統層面的虛擬化。Cgroups提供了以下四大功能{![參照自:http://en.wikipedia.org/wiki/Cgroups]}。

    • 資源限制(Resource Limitation):cgroups可以對進程組使用的資源總額進行限制。如設定應用運行時使用內存的上限,一旦超過這個配額就發出OOM(Out of Memory)。
    • 優先級分配(Prioritization):通過分配的CPU時間片數量及硬盤IO帶寬大小,實際上就相當於控制了進程運行的優先級。
    • 資源統計(Accounting): cgroups可以統計系統的資源使用量,如CPU使用時長、內存用量等等,這個功能非常適用於計費。
    • 進程控制(Control):cgroups可以對進程組執行掛起、恢復等操作。

    過去有一段時間,內核開發者甚至把namespace也作爲一個cgroups的subsystem加入進來,也就是說cgroups曾經甚至還包含了資源隔離的能力。但是資源隔離會給cgroups帶來許多問題,如PID在循環出現的時候cgroup卻出現了命名衝突、cgroup創建後進入新的namespace導致脫離了控制等等{![詳見:https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/commit/?id=a77aea92010acf54ad785047234418d5d68772e2]},所以在2011年就被移除了。

    3. 術語表

    • task(任務):cgroups的術語中,task就表示系統的一個進程。
    • cgroup(控制組):cgroups 中的資源控制都以cgroup爲單位實現。cgroup表示按某種資源控制標準劃分而成的任務組,包含一個或多個子系統。一個任務可以加入某個cgroup,也可以從某個cgroup遷移到另外一個cgroup。
    • subsystem(子系統):cgroups中的subsystem就是一個資源調度控制器(Resource Controller)。比如CPU子系統可以控制CPU時間分配,內存子系統可以限制cgroup內存使用量。
    • hierarchy(層級樹):hierarchy由一系列cgroup以一個樹狀結構排列而成,每個hierarchy通過綁定對應的subsystem進行資源調度。hierarchy中的cgroup節點可以包含零或多個子節點,子節點繼承父節點的屬性。整個系統可以有多個hierarchy。

    4. 組織結構與基本規則

    大家在namespace技術的講解中已經瞭解到,傳統的Unix進程管理,實際上是先啓動init進程作爲根節點,再由init節點創建子進程作爲子節點,而每個子節點由可以創建新的子節點,如此往復,形成一個樹狀結構。而cgroups也是類似的樹狀結構,子節點都從父節點繼承屬性。

    它們最大的不同在於,系統中cgroup構成的hierarchy可以允許存在多個。如果進程模型是由init作爲根節點構成的一棵樹的話,那麼cgroups的模型則是由多個hierarchy構成的森林。這樣做的目的也很好理解,如果只有一個hierarchy,那麼所有的task都要受到綁定其上的subsystem的限制,會給那些不需要這些限制的task造成麻煩。

    瞭解了cgroups的組織結構,我們再來了解cgroup、task、subsystem以及hierarchy四者間的相互關係及其基本規則{![參照自:https://access.redhat.com/documentation/en-US/RedHatEnterpriseLinux/6/html/ResourceManagementGuide/sec-RelationshipsBetweenSubsystemsHierarchiesControlGroupsandTasks.html]}。

    • 規則1: 同一個hierarchy可以附加一個或多個subsystem。如下圖1,cpu和memory的subsystem附加到了一個hierarchy。

      圖1 同一個hierarchy可以附加一個或多個subsystem

    • 規則2: 一個subsystem可以附加到多個hierarchy,當且僅當這些hierarchy只有這唯一一個subsystem。如下圖2,小圈中的數字表示subsystem附加的時間順序,CPU subsystem附加到hierarchy A的同時不能再附加到hierarchy B,因爲hierarchy B已經附加了memory subsystem。如果hierarchy B與hierarchy A狀態相同,沒有附加過memory subsystem,那麼CPU subsystem同時附加到兩個hierarchy是可以的。

      圖2 一個已經附加在某個hierarchy上的subsystem不能附加到其他含有別的subsystem的hierarchy上

    • 規則3: 系統每次新建一個hierarchy時,該系統上的所有task默認構成了這個新建的hierarchy的初始化cgroup,這個cgroup也稱爲root cgroup。對於你創建的每個hierarchy,task只能存在於其中一個cgroup中,即一個task不能存在於同一個hierarchy的不同cgroup中,但是一個task可以存在在不同hierarchy中的多個cgroup中。如果操作時把一個task添加到同一個hierarchy中的另一個cgroup中,則會從第一個cgroup中移除。在下圖3中可以看到,httpd進程已經加入到hierarchy A中的/cg1而不能加入同一個hierarchy中的/cg2,但是可以加入hierarchy B中的/cg3。實際上不允許加入同一個hierarchy中的其他cgroup野生爲了防止出現矛盾,如CPU subsystem爲/cg1分配了30%,而爲/cg2分配了50%,此時如果httpd在這兩個cgroup中,就會出現矛盾。

      圖3 一個task不能屬於同一個hierarchy的不同cgroup

    • 規則4: 進程(task)在fork自身時創建的子任務(child task)默認與原task在同一個cgroup中,但是child task允許被移動到不同的cgroup中。即fork完成後,父子進程間是完全獨立的。如下圖4中,小圈中的數字表示task 出現的時間順序,當httpd剛fork出另一個httpd時,在同一個hierarchy中的同一個cgroup中。但是隨後如果PID爲4840的httpd需要移動到其他cgroup也是可以的,因爲父子任務間已經獨立。總結起來就是:初始化時子任務與父任務在同一個cgroup,但是這種關係隨後可以改變。

      圖4 剛fork出的子進程在初始狀態與其父進程處於同一個cgroup

    5. subsystem簡介

    subsystem實際上就是cgroups的資源控制系統,每種subsystem獨立地控制一種資源,目前Docker使用如下八種subsystem,還有一種net_cls subsystem在內核中已經廣泛實現,但是Docker尚未使用。他們的用途分別如下。

    • blkio: 這個subsystem可以爲塊設備設定輸入/輸出限制,比如物理驅動設備(包括磁盤、固態硬盤、USB等)。
    • cpu: 這個subsystem使用調度程序控制task對CPU的使用。
    • cpuacct: 這個subsystem自動生成cgroup中task對CPU資源使用情況的報告。
    • cpuset: 這個subsystem可以爲cgroup中的task分配獨立的CPU(此處針對多處理器系統)和內存。
    • devices 這個subsystem可以開啓或關閉cgroup中task對設備的訪問。
    • freezer 這個subsystem可以掛起或恢復cgroup中的task。
    • memory 這個subsystem可以設定cgroup中task對內存使用量的限定,並且自動生成這些task對內存資源使用情況的報告。
    • perfevent 這個subsystem使用後使得cgroup中的task可以進行統一的性能測試。{![perf: Linux CPU性能探測器,詳見https://perf.wiki.kernel.org/index.php/MainPage]}
    • *net_cls 這個subsystem Docker沒有直接使用,它通過使用等級識別符(classid)標記網絡數據包,從而允許 Linux 流量控制程序(TC:Traffic Controller)識別從具體cgroup中生成的數據包。

    6. cgroups實現方式及工作原理簡介

    (1)cgroups實現結構講解

    cgroups的實現本質上是給系統進程掛上鉤子(hooks),當task運行的過程中涉及到某個資源時就會觸發鉤子上所附帶的subsystem進行檢測,最終根據資源類別的不同使用對應的技術進行資源限制和優先級分配。那麼這些鉤子又是怎樣附加到進程上的呢?下面我們將對照結構體的圖表一步步分析,請放心,描述代碼的內容並不多。

    (點擊放大圖像)

    圖5 cgroups相關結構體一覽

    Linux中管理task進程的數據結構爲task_struct(包含所有進程管理的信息),其中與cgroup相關的字段主要有兩個,一個是css_set *cgroups,表示指向css_set(包含進程相關的cgroups信息)的指針,一個task只對應一個css_set結構,但是一個css_set可以被多個task使用。另一個字段是list_head cg_list,是一個鏈表的頭指針,這個鏈表包含了所有的鏈到同一個css_set的task進程(在圖中使用的迴環箭頭,均表示可以通過該字段找到所有同類結構,獲得信息)。

    每個css_set結構中都包含了一個指向cgroup_subsys_state(包含進程與一個特定子系統相關的信息)的指針數組。cgroup_subsys_state則指向了cgroup結構(包含一個cgroup的所有信息),通過這種方式間接的把一個進程和cgroup聯繫了起來,如下圖6。

    圖6 從task結構開始找到cgroup結構

    另一方面,cgroup結構體中有一個list_head css_sets字段,它是一個頭指針,指向由cg_cgroup_link(包含cgroup與task之間多對多關係的信息,後文還會再解釋)形成的鏈表。由此獲得的每一個cg_cgroup_link都包含了一個指向css_set *cg字段,指向了每一個task的css_setcss_set結構中則包含tasks頭指針,指向所有鏈到此css_set的task進程構成的鏈表。至此,我們就明白如何查看在同一個cgroup中的task有哪些了,如下圖7。

    圖7 cglink多對多雙向查詢

    細心的讀者可能已經發現,css_set中也有指向所有cg_cgroup_link構成鏈表的頭指針,通過這種方式也能定位到所有的cgroup,這種方式與圖1中所示的方式得到的結果是相同的。

    那麼爲什麼要使用cg_cgroup_link結構體呢?因爲task與cgroup之間是多對多的關係。熟悉數據庫的讀者很容易理解,在數據庫中,如果兩張表是多對多的關係,那麼如果不加入第三張關係表,就必須爲一個字段的不同添加許多行記錄,導致大量冗餘。通過從主表和副表各拿一個主鍵新建一張關係表,可以提高數據查詢的靈活性和效率。

    而一個task可能處於不同的cgroup,只要這些cgroup在不同的hierarchy中,並且每個hierarchy掛載的子系統不同;另一方面,一個cgroup中可以有多個task,這是顯而易見的,但是這些task因爲可能還存在在別的cgroup中,所以它們對應的css_set也不盡相同,所以一個cgroup也可以對應多個·css_set

    在系統運行之初,內核的主函數就會對root cgroupscss_set進行初始化,每次task進行fork/exit時,都會附加(attach)/分離(detach)對應的css_set

    綜上所述,添加cg_cgroup_link主要是出於性能方面的考慮,一是節省了task_struct結構體佔用的內存,二是提升了進程fork()/exit()的速度。

    圖8 css_set與hashtable關係

    當task從一個cgroup中移動到另一個時,它會得到一個新的css_set指針。如果所要加入的cgroup與現有的cgroup子系統相同,那麼就重複使用現有的css_set,否則就分配一個新css_set。所有的css_set通過一個哈希表進行存放和查詢,如上圖8中所示,hlist_node hlist就指向了css_set_table這個hash表。

    同時,爲了讓cgroups便於用戶理解和使用,也爲了用精簡的內核代碼爲cgroup提供熟悉的權限和命名空間管理,內核開發者們按照Linux 虛擬文件系統轉換器(VFS:Virtual Filesystem Switch)的接口實現了一套名爲cgroup的文件系統,非常巧妙地用來表示cgroups的hierarchy概念,把各個subsystem的實現都封裝到文件系統的各項操作中。有興趣的讀者可以在網上搜索並閱讀VFS的相關內容,在此就不贅述了。

    定義子系統的結構體是cgroup_subsys,在圖9中可以看到,cgroup_subsys中定義了一組函數的接口,讓各個子系統自己去實現,類似的思想還被用在了cgroup_subsys_state中,cgroup_subsys_state並沒有定義控制信息,只是定義了各個子系統都需要用到的公共信息,由各個子系統各自按需去定義自己的控制信息結構體,最終在自定義的結構體中把cgroup_subsys_state包含進去,然後內核通過container_of(這個宏可以通過一個結構體的成員找到結構體自身)等宏定義來獲取對應的結構體。

    圖9 cgroup子系統結構體

    (2)基於cgroups實現結構的用戶層體現

    瞭解了cgroups實現的代碼結構以後,再來看用戶層在使用cgroups時的限制,會更加清晰。

    在實際的使用過程中,你需要通過掛載(mount)cgroup文件系統新建一個層級結構,掛載時指定要綁定的子系統,缺省情況下默認綁定系統所有子系統。把cgroup文件系統掛載(mount)上以後,你就可以像操作文件一樣對cgroups的hierarchy層級進行瀏覽和操作管理(包括權限管理、子文件管理等等)。除了cgroup文件系統以外,內核沒有爲cgroups的訪問和操作添加任何系統調用。

    如果新建的層級結構要綁定的子系統與目前已經存在的層級結構完全相同,那麼新的掛載會重用原來已經存在的那一套(指向相同的css_set)。否則如果要綁定的子系統已經被別的層級綁定,就會返回掛載失敗的錯誤。如果一切順利,掛載完成後層級就被激活並與相應子系統關聯起來,可以開始使用了。

    目前無法將一個新的子系統綁定到激活的層級上,或者從一個激活的層級中解除某個子系統的綁定。

    當一個頂層的cgroup文件系統被卸載(umount)時,如果其中創建後代cgroup目錄,那麼就算上層的cgroup被卸載了,層級也是激活狀態,其後代cgoup中的配置依舊有效。只有遞歸式的卸載層級中的所有cgoup,那個層級纔會被真正刪除。

    層級激活後,/proc目錄下的每個task PID文件夾下都會新添加一個名爲cgroup的文件,列出task所在的層級,對其進行控制的子系統及對應cgroup文件系統的路徑。

    一個cgroup創建完成,不管綁定了何種子系統,其目錄下都會生成以下幾個文件,用來描述cgroup的相應信息。同樣,把相應信息寫入這些配置文件就可以生效,內容如下。

    • tasks:這個文件中羅列了所有在該cgroup中task的PID。該文件並不保證task的PID有序,把一個task的PID寫到這個文件中就意味着把這個task加入這個cgroup中。
    • cgroup.procs:這個文件羅列所有在該cgroup中的線程組ID。該文件並不保證線程組ID有序和無重複。寫一個線程組ID到這個文件就意味着把這個組中所有的線程加到這個cgroup中。
    • notify_on_release:填0或1,表示是否在cgroup中最後一個task退出時通知運行release agent,默認情況下是0,表示不運行。
    • release_agent:指定release agent執行腳本的文件路徑(該文件在最頂層cgroup目錄中存在),在這個腳本通常用於自動化umount無用的cgroup。

    除了上述幾個通用的文件以外,綁定特定子系統的目錄下也會有其他的文件進行子系統的參數配置。

    在創建的hierarchy中創建文件夾,就類似於fork中一個後代cgroup,後代cgroup中默認繼承原有cgroup中的配置屬性,但是你可以根據需求對配置參數進行調整。這樣就把一個大的cgroup系統分割成一個個嵌套的、可動態變化的“軟分區”。


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