閱讀891 返回首頁    go 微軟 go windows


阿裏內核月報2014年7月-8月

Capsicum for Linux

Capsicum: 一種基於文件句柄的新安全模型

Capsicum是一種源自FreeBSD的安全模型,與Linux下眾多LSM的相同之處在於它們都是基於權限管理的,而不同之處在於LSM針對的操作對象非常豐富,有進程、VMA、端口、帶有標簽的文件等等,而Capsicum操作的對象非常單一:文件句柄。例如,一個fd必須帶有CAP_READ才能被讀取,必須帶有CAP_SEEK才能被lseek(),必須帶有CAP_MMAP_W才能被mmap()建立可寫映射,針對ioctl()和fcntl()它還有一些特殊約定的權限。
可以想象,既然這些限製都是綁定在某些fd上的,那麼如果一個被限製的進程可以隨意地打開新的fd操作文件,這些限製自然就沒什麼用處了。為解決這個問題,Capsicum引入了一個名為cap_enter()的操作,一個進程執行cap_enter()之後它基本就不能再訪問文件係統的全局名字空間了,因此隻能使用在cap_enter()之前已經打開的並且被設置好了權限約束的句柄。但是cap_entery()這個操作在Capsicum的第一版patchset中並沒有被實現,隻是提出了這個概念而已。
在內核裏,用戶空間傳來的fd會喂給fdget(),再由它返回struct file *,Capsicum的hook點就在這裏。作者定義了一個struct file的wrapper結構,包含原始的struct file和額外的一些權限信息;然後提出了一個fdget()的新變種,形式如下:

struct file *fdgetr(unsigned int fd, int caps …); 

注意到它還是一個參數數量可變的函數,所有的cap會由最後一串參數傳入。內核中原先調用fdget()的大約100個調用者都需要改成這個新接口,同時調用者還得處理新接口的返回值。因為原先的fdget()在出錯時隻返回NULL,不會有進一步的錯誤值返回,而fdgetr()的錯誤返回值要豐富得多,這意味著這個patchset侵入性相當強,估計很難被接受。 目前Capsicum是基於LSM框架之上實現的,有評論認為Capsicum與LSM的耦合性很低,完全可以抽出來獨立實現。另有評論認為Capsicum完全可以由新的seccomp-bpf實現,不需要額外加patch。考慮到用seccomp-bpf寫代碼很麻煩,實現這些功能肯定不會簡單,但這麼做應該是可行的。總的來說,大家普遍覺得Capsicum這套patchset想被接受是相當困難。主要的優勢在於FreeBSD既然已經有了這種安全模型,那麼可能會方便一些FreeBSD上的代碼移植到Linux上來,如此而已。

Control groups, part 1: On the history of process grouping

即便Control groups不是Linux中最具爭議的特性,在各種郵件列表和論壇上隨處可見對control groups熱火朝天的討論,甚至完全否認特性的價值.這一係列的文章介紹圍繞control group(cgroups)的爭議.

要理解這些爭議,既需要廣闊的視野,也需要對詳細分析.前兩篇文章通過介紹Unix的曆史,分析cgroups給進程組帶來了什麼問題.然後分析cgroups的層次結構,借助Unix和Unix之外的係統,為衡量cgroups的層次結構提供標準.
後幾篇文章深入分析cgroups.

第六版的Unix

曆史上,Unix對進程組的管理經曆了一些演變.了解這些演變對我們很有幫助,這裏不從最初介紹演變過程,而是從第六版的Unix開始(後麵叫"V6 Unix").

V6 Unix誕生自20世紀70年代中期,是走出Bell實驗室獲得巨大關注的第一版.它支持兩種進程分組管理,開始之前我們先說明"進程分組"的含義.

(作者借數論裏麵的群group介紹這裏的process groups)在數論中,不是所有的集合都是群(group).一組能用質數唯一標識的進程是一個組.然而在Unix中(無論是當時或是現在),不存在把這種進程組和其他以合數標識的進程組區分開的方法.另外的進程,既不能用質數標識也不能用合數標識的進程,其行為與這兩組也有區別.由於它僅僅包含1號進程,不單獨把它考慮為一個進程組.

數論中的群,包含基於群中元素的操作.類似的,對於進程組來說也需要與進程組相關的操作.
另一種不太直接的分組方法是根據進程所有者的ID(UID).這並不是V6 Unix一個真正的組,盡管有些操作對一些組和另外的組的影響是有區別的,但它們不是一個整體.
一種真正意義上的組是進程的子進程組.V6 Unix中與子進程組相關的唯一操作是wait()係統調用,它檢查這個組是否為空.如果wait()係統調用返回ECHILD,那麼這個組為空.如果wait()不返回或者正常返回,那麼係統調用時這個組不為空.

類似的操作也可以在進程的後代進程上定義,即進程的子進程和其他後代進程.當且僅當沒有這樣的進程,wait()係統調用返回ECHILD.需要注意的是,子進程組中進程隻能在執行完成的時候才能從組中退出.在後代組中,它的任何一個祖先進程執行完成,這個進程就可以退出組.
進程能否退出組是不是一個重要特點取決於具體情況.在V6 Unix中,1號進程的後代不能退出,而其他進程的後代可以.這種情況延續到了類Unix係統Linux,一直持續到Linux3.4在prctl()中加入了PR_SET_CHILD_SUBREAPER選項.它允許限製子進程退出組.一個進程停止時,它的子進程被這時這個選項的進程繼承.

V6 Unix中的另一種控製組,是進程結構中的p_ttyp域指定的"controlling tty".當進程打開一個tty設備時(通常是一個串口數據連接),如果這個域沒有被設置,它會被設置指向一個新打開的設備.這個域可以通過fork()或者exec()繼承而來,因此一旦進程打開了設備,它的子孫後代也會繼承這個設備.

p_ttyp的作用是定向所有到/dev/tty的I/O到controlling tty.由於它對每個進程的影響是獨立的,這不是使進程成為組的原因.controlling tty使進程成為組的原因與信號處理有關係.當DEL或則FS(control-)被輸出到tty,SIGINT或者SIGQIT信號被送到組中所有使用這個tty作為controlling tty的進程.類似的,如果鏈接中斷,SIGHUP信號也被送到組中的所有進程.還可以使用sigkill()係統調用發送信號.發送到0號進程的信號也會被發送到與發送進程具有同樣controlling tty的進程.

這種組策略已經與control groups非常類似了.盡管隻通過信號傳遞體現出來,進程的分組和管理已經非常明顯.組是自動產生的,與行為有關,並且是永久的(一旦成為組成員,進程不能脫離).但這並不完美,下一節介紹其改進.

第七版Unix

雖然V6 Unix已經支持process groups,卻沒有提出這一術語.V7 Unix中不僅提出這個名詞,還豐富了group的含義.p_ttyp域仍然存在,它對/dev/tty訪問的管理被限製了.它被重命名為u_ttyp,並且被移動到struct user中,user結構體可能隨進程的其他部分被換到磁盤中.proc結構體使用新的p_pgrp域來管理process groups.在第一次使用open()打開tty的時候p_pgrp域被置位,它被用來傳遞SIGINT,SIGQUIT和SIGHUP信號,也被用來向0號進程傳遞信號.V7帶來很多複雜的變化.

最大的變化是使process groups有了獨立的名字,至少與tty無關.一個沒有controlling tty的進程第一次打開tty時,會以進程ID為名字創建一個process group.當進程退出時,這個組仍然可以存在.活動的子進程可以防止ID被重用.

這麼做的結果是,從tty退出後重新登陸時,會新創建一個process group,tty結構體中的t_pgrp域會被修改.與V6 Unix不同,送到一個進程組的信號決不會被送到同一個tty已經登陸的進程上.

另一個影響是process groups可以被用在tty之外.第七版的Unix有一個短暫存在的"multiplexor driver",現在仍在stat()的變更手冊裏

3000 S_IFMPC 030000 multiplexed character special (V7)
[...]
7000 S_IFMPB 070000 multiplexed block special (V7)

multiplexor和socket接口類似,運行不同的進程相互連接.它還提供了到多個互聯進程的接口,允許管理進程發送一個信號到group中的其他進程.

V7 Unix的process groups仍然是封閉的,在一個group中的進程無法脫離group.mpxchan的確允許進程離開原本group加入一個新group,但不能確定這是設計者有意為之的結果.

Unix Berkeley第四版

Fourth Berkeley Software Distribution(4BSD)

從V7 Unix到4BSD經曆了巨大的跨越,進程組也發生了很大改變.在BSD4.3中,擁有同樣UID的進程變成一個組,可以將一個信號發送到這個組的所有進程.向PID -1的進程發送的信號則會傳遞到與當前進程擁有同樣UID的進程中(特權進程向PID -1發出的信號則會被送到所有的進程).更重要的是,在4.4BSD中出現了進程組的層次結構.

Berkeley版本Unix的一大貢獻是"作業控製".這裏的作業指的是完成某個任務的一個或多個進程.Unix能夠把一些進程放到後台,但其實現方式相當特別.這些進程會忽略任何信號,shell也不能等待進程完成.大多數情況下時沒有問題的,但是進程不能從後台恢複到前台,和後台程序的輸出會和前台程序混在一起.這些都是存在的問題.

在BSD"作業控製"下,shell可以控製哪個作業在前台,哪個作業放到後台.

在BSD之前,進程組與每次登陸之間是對應的,從與AT&T開發的"System V"Unix兼容的角度來說,這是有意義的.在4.4BSD中,這些與login對應的進程組被定義為"會話".進程屬於進程組,進程組屬於會話.每個終端有一個前台進程組t_pgrp和一個控製會話t_session.

會話和V7 Unix的進程組有點類似,但它們也有區別.一個區別是,不能向一個會話的所有進程組發送信號.另一個區別是進程可以離開當前會話,用set_sid係統調用創建新的會話.

這些區別使logout時殺死所有進程的操作變得複雜.其中有些問題在當時的版本中無法解決.

在現代基於窗口的桌麵係統中,會話和進程組仍然存在,但與當時的含義不完全相同.用ps命令可以看到sess和pgrp域

ps -axgo comm,sess,pgrp

進程組和login會話之間再也沒有直接的關係.每個終端窗口擁有自己的會話,包括其他產生終端的應用.每個從shell提示符下啟動的job擁有其進程組,停止這些job的需求小的多.不需要停止當前終端上的活動任務.

要呈現現代桌麵係統的進程分組,需要更複雜的層次結構.其中一層表示login會話,一層表示運行在會話上的進程,一層表示某個應用的作業.從4.4BSD發展來的Linux隻提供了其中兩層,我們可以在cgroups中尋找第三層.

問題

開始總結.在形成對cgroups的認識前,需要考慮一些問題

  • groups命名:V6 Unix中唯一的名稱來自tty,並且與進程ID的命令空間相同.這種共享有點笨拙,雖然看起來很方便.
  • overlapping:信號傳遞和向/dev/tty的I/O請求最初用同樣的機製.但是很快被區別開,它們並不完全相同.
  • 進程能否脫離進程組?曆史上經過了從"不能"到"能"的轉變.二者各有利弊.
  • 層次結構起了什麼作用?

最後一個問題,層級結構至關重要.cgroups最近的一些改變和其反對意見都跟層級結構有關.現在離真正理解層級結構還差很遠,接下來的文章中將要繼續介紹.

Control groups, part 2: On the different sorts of hierarchies

滿世界都是各種層級(hierarchy)關係,作者舉了一堆例子,其中有一個挺有意思的: 如果你在 Wikipedia 上麵打開一個文章,然後連續的點接下來的文章鏈接,94.52% 的概率你最終會找到一個詞,i.e. 哲學 (Philosophy)。

Hierarchy 在 Control groups (cgroups) 中的設計,其實是引起了很多的爭論和挑戰,[譯:可能是因為 use case 太多,總要 balance 各種好處和壞處,而作者很 enjoy 這一點。下麵是他給的一些 case,也提出了一些看法]

  • 層級在賬戶權限中的應用

以前在澳洲一家大學的計算機學院做係統管理員的時候,總要針對學生,和教員以及其他後勤行政人員做相關的資源管理,權限分配。不通的部門和課程有很多交集,作者是在計算機支持組。而在這些交集中,角色 (role) 也不同,權限也不通。比如,教員可能比學生擁有更高的打印權限,或者有些打印機僅僅被小部分人使用來打印保密文件,或者有些需要彩色打印稿。
為了管理這些,我們整了兩個層級,一個是“理由” (包含各種角色,基於這個來分配賬戶),一個是“組織” (角色所在的組織,比如:學校的部門)。然後每個賬戶也都是可以在不同的組中的,教員和學生都可以參與不同的課程,一些高年級學生可以代低年級的學生。在這些權限控製中,賬戶層級也是可以有繼承關係的。
[譯:隻是一個例子,算是比較 high level 的,讀者有興趣也可以看下 RBAC (role based access control) 的規範,算是一個理論基礎吧]

  • 可控的複雜度

多層級比單層級的好處,就是比較彈性,一旦做好了,這樣在添加一個賬戶的時候就不用太費心,說白了就是後麵的操作會比較簡化。
後麵就是說,其實係統稍微複雜的成本足夠 cover 後麵操作的複雜度,雖然兩個層級,但是後麵管理(增,刪,改,查)比較一個層級的容易很多。

  • 兩種層級類型

這兩個層級在內在和細節上都有很多不同。

“Reason” 層級其實是一種分類,每個個體都有自己的角色,把相似的角色歸小類,然後再給小類劃分大類,比如:生物學中的,門,綱,目,科,屬,種。在樹這種結構中,葉子節點,就相當於種。

“Organisation“ 層級就很不一樣,很多時候分組方式,是基於方便的方式,所以這個應該是動態的,而不是有和 “Reason” 一樣的比較固定的從屬關係。還有一個屬性就是,學校或者科研項目的頭,基本就代表這一個學校或者項目,而不用把他們在關聯到具體的項目的課題中。

  • /sys 下麵的設備

Sysfs 文件係統(通常掛載在 /sys 下麵), 這裏隻關注一下設備信息,不 care 其他的(模塊,文件係統細節等)。主要有三個設備相關的層級關係在 sysfs 裏麵,設備一般是目錄樹,Unix 文件係統一般不支持一個目錄再擁有多個父目錄,所以一般用的是軟鏈接。

設備根在 /sys/dev 。早期主要就是塊設備和字符設備,設備文件一般在 /dev (串口,並口,磁盤等),主次設備號來標識。

設備樹分為三個層級,例如:/sys/dev/block/8:0,最後一個層級用冒號分隔主次設備號,而不是用斜杠,這是一個軟鏈接。 (譯:指向目錄 /sys/block/sda)

一個用處是,如果你隻有一個 /dev 下麵的設備名或者一個打開的設備文件描述符,可以用 stat() 或者 fstat() 係統調用拿到設備類型,主從設備號等信息,然後就可以轉換到對應 /sys/dev 下麵,再拿到其他的需要的信息。

[譯:下麵這段好像有點問題,subsystem 指向的不一定是 class/bus, 還要具體看]

/sys/class 目錄裏麵就是一堆設備分類,(用的是設備名,不是設備號);/sys/bus 裏麵的信息比較多,有其他的例如模塊信息什麼的。比如 塊設備通過 usb 掛在 pci 下,這種物理層級就可以被描述出來。

簡單分層很難描述出所有設備的內在連接,有的設備很可能從一個地方拿到控製信號,而從另外一個地方獲得 power。這個在今年內核峰會之前,已經被廣泛討論過了。

從分類的角度,/sys/dev 是比較簡單的,而 /sys/class 其實也是比較簡單的,雖然它包含更完整的信息。

/sys/bus 也是一個隻有兩層分類。class 層級主要是功能層麵的(例如,設備提供net, sound watch dog 功能),而 bus 層級主要是訪問的角度 (如何被訪問的,比如:物理順序)。

這麼理解的話,基本上就還是兩個分類。

/sys/devices 分類包含所有類型的設備,有的簡單的掛在物理總線下,否則(沒有對應的物理總線)就在 /sys/devices/virtual

  • Linux 源代碼樹

Linux 源代碼樹的層級出發點很不一樣,我們來看看 (其他代碼樹類似)。這種層級更加關注於組織而不是分類 (相比:sysfs) 。[譯:作者一直強調 classification 和 organization 的區別,大概就是一個傾向於靜態(分類之後的成員比較固定),一個傾向於動態(分類之後的成員尚可調整)]

頂層目錄,fs 為文件係統,mm 為內存管理等 [譯:kernel 目錄我理解就是個雜項]。有些子係統比較小,比如 time ,就放在 kernel 裏麵,如果變得足夠大了之後,就可能有自己的目錄,但是不會跑出 kernel 目錄。

fs 子樹多半包含的都是不同種類的文件係統,也有一些輔助的模塊,如:exportfs 用來輔助文件服務器,dlm 鎖定集群文件係統,ad hoc 實現高層的係統調用,沒有專門的雜項目錄在 fs 目錄裏,都是直接放在 fs 下麵。

具體怎麼分類也不一定有準確的答案,不過在對 cgroups 分類爭論的時候,不妨參考一下。

源碼樹也包含其他的分類,比如:scripts, firmware, 頭文件在 include。最近幾年也有在討論把頭文件放到相應的 c 文件附近。考慮一下 nfs 和 ext3 文件係統。 每個文件係統有自己的 c 文件和頭文件,問題是這些頭文件應該都放在 include 下麵嗎? 要不要把 .c 和 .h 對應起來放在一起? 這種情況在 3.4 版本內核上麵已經變了, ext3 的 4 個頭文件已經從 include/linux/ 搬到了 fs/ext3/ext3.h

層級分類的話隻有當需要並且必要的時候才做。

  • 進程層級

理解 cgroup 才是這一係列文章的真正目的,依賴於層級如何來管理這些不同進程的角色。上麵中的例子沒有一個是關於進程的,不過他們引出了很多問題在深入討論 cgroups 之前:

  • 單層級的簡化比多層級的靈活重要?是否他們是獨立的還是相互作用的?
  • 主要目標是給進程分類還是簡單的組織一下他們?或者兩個目的都有,如何把這兩者有機結合起來?
  • 我們可否允許一些非層級機製,例如:符號連接,或者文件名後綴[譯:某種程度上,打破了分級]來提供分類或者組織一些元素?
  • 可否把進程附加到在分層的內部節點上?或者應該強製他們在葉子節點上?即使葉子代表雜項?

上次一個進程組的例子,是一個單一的分級,先會話進程,然後是工作組,這樣的進程組裏麵的進程都是葉子,比如從來不會打開 tty 的係統進程就不在分層裏麵。

找到這些問題的答案需要理解 cgroups 怎麼來組織這些進程的,並且這些分組是用來幹嘛的,後麵會分析 subsystems 包含資源控製和其他的操作來回答這些問題,看下回分解把。。。。

Control groups, part 3: First steps to control

July 16, 2014 This article was contributed by Neil Brown
在cgroup之前就已經有nice用於控製每個進程可以使用的CPU time,以及通過setrlimit()係統調用限製內存及其他資源的使用。然而這些控製隻能針對每個獨立的進程,無法對進程組進行控製。

Cgroup子係統

Linux的cgroup能夠允許設立獨立的子係統用於對進程組控製,更適合的術語是“資源控製器”。

Linux3.15現已經有12個cgroup子係統。我們主要關注下這些子係統--特別是子係統之間的層級組織,如何工作。

在進入細節前,先快速描述下一個子係統能做的事情。每個子係統能做的包括: 1. 在每個cgroup中存儲一些任意的狀態數據 2. 在cgroup文件係統裏提供一些屬性文件用於查看或者修改狀態數據,或者其他狀態細節 3. 接受或者拒絕進程加入一個給定的cgroup的請求 4. 接受或拒絕在一個已經存在的組裏麵創建一個子組的請求 5. 一些cgroup有任何進程創建或者銷毀時獲得通知

這些隻是用於和進程組進行交互的通用接口,實際上子係統還會有其他一些方式和進程組以及內核交互,以實現期望的結果。

A simple debugging aid

debug子係統並不實際“控製”任何東西,也不移除bug(很不幸)。它既不給任何的cgroup添加額外的數據,也不拒絕"attach"或者"create"請求,甚至也不關心進程創建和銷毀。

開啟這個子係統唯一的影響是使得許多獨立的組或者整個cgroup係統的內部細節能夠通過cgroup文件係統的虛擬文件查看到。細節包括部分數據結構當前的引用計數,以及內部標識項的設置情況。這些細節隻有cgroup工作者可能全部關注。

Identity - the first step to control

Robert Heinlein首先向我表達了這樣一個想法:讓每個人帶上ID是走向控製他們的第一步。雖然這樣做對於控製人類來說會很不受歡迎,但提供明確的身份認證對於控製進程組是很實際和有用的。這是net_cl和net_prio cgroup子係統最主要關注的。

這兩個子係統都含一個小的標識數字,組內的進程創建socket的時候會將該數字拷貝給創建的socket。net_prio使用每個cgroup的id(cgroupo->id)作為sequence number,並將這個存儲在sk_cgrp_prioidx中。這個對每個cgroup都是獨特的。net_cl允許為每個cgroup設置一個特點的數字,然後存儲在sk_classid裏麵。這個數字並不需要每個cgroup都不同。這兩個不同cgroup的標識被三個不同進程使用。

net_cl設置的sk_classid可以被iptables使用,根據包對應socket屬於那個cgroup對包進行選擇性過濾。sk_classid也可以在network調度用於對包進行分類。包分類器能夠基於cgroup以及其他一些細節作出一些決定,這些決定會影響很多調度細節,包括設置每個信息(message)的優先級。 sk_cgrp_prioidx這個是單純的用於設置網絡包的優先級,使用這個之後將會覆蓋之前通過SO_PRIORITY socket選項或者其他方式設置的值。設置這個之後的效果和sk_classid及包分類器共同完成的類似。然而根據引入net_prio子係統的commit所說,包分類器並不總是能夠勝任,特別是對開啟了data center bridging(DCB)的係統。

同時擁有兩個不同的子係統對socket以三種不同方式進行控製,而且還有相互重合的地方,這看起來很奇葩。各個子係統是否需要變得更加輕量級,以使得添加它們比較容易,或者各個子係統需要更加的強大,這樣一個子係統就可以用於各種場景--這個在目前還並不是很清晰。後麵還會碰到更多的子係統之間有交集的情況,也許能夠幫助更加清晰的認識這個問題。
當前,最重要的問題還是這些子係統如何實現cgroup原有的層與層之間的交互。答案是,大部分都沒有。

無論是net_cl設置的class ID,或者net_prio設置的優先級(per-device)隻是應用於對應的cgroup以及所有和這個cgroup裏麵進程相關的socket。這些設置並不會對子cgroup裏麵進程的socket產生影響。因此對於這些子係統,嵌套關係是無意義的。
這種對層級的忽視使得cgroup樹看起來更像是一個組織層級關係 -- 子組並不是子類,而不是分類層次結構。

其他子係統對層級結構更加注重,值得看的三個子係統是device, freezer和perf_event。

Ways of working with hierarchy

從整體上考慮cgroup的話,一個挑戰是不同使用場景會有存在非常大的差異,給當前架構帶來很多不同的需求。下麵三個子係統都使用了cgroup的分層,但是控製流如何影響到進程這些細節上卻有很大不同。

devices

device子係統將訪問控製托管給設備相關文件。每個組可以運行或者禁止所有的訪問,隨後給出一組訪問被禁止會在運行的異常信息列表。

更新異常列表的代碼會保證子組的權限不會比他父親的多--設置或者傳播父親組不允許的權限給子組時會被拒絕。因此需要進行權限檢查時,隻需要檢查自己組內的,並不需要遍曆檢查祖先是否允許這個訪問,也不需要檢查祖先組的規則是否已經在每個組裏麵。

當然這樣也是有一定代價的,權限檢查簡單帶來的是更新進程的權限會變得更加複雜。但由於權限檢查相比權限更新更加頻繁,這個代價是值得的。

對device子係統,Cgroup裏麵的配置會影響所有子cgroup,一直到層級的最下麵。每個進程需要檢查訪問權限時,仍然回到相應的cgroup中。

freezer

freezer子係統的需求和device的完全不一樣。這個子係統在每個cgroup裏麵提供了一個freezer.state文件,用於寫入FROZEN或者THAWED。這類似發送SIGSTOP和SIGCONT個ie一個進程組,這樣整個進程組將會stop或者restart。

freezer子係統在對進程組進行freeze或者thaw的時候,也跟device子係統的類似,會遍曆所有子cgroup至最低層級,設置這些group為frozen或者thawed。然後還需一步,進程並不會定期檢查所在的cgroup是否frozen,因此需要遍曆所有cgroup裏麵的進程,顯示的將它們移動給freeze處理者或者移出。為了保證沒有進程逃離,freeze要求進程fork的時候會得到通知,這樣就能夠得到新建立的進程。

因此freeze子係統對cgroup層級管理提出另外一個要求,需要能夠讓配置一直下發到裏麵的每個進程。 perf_event還有另外一個需求。

perf_event

perf收集某組進程的各種性能數據,這個可以是係統裏麵的所有進程,或者某個特定用戶的所有進程,或者某個特定父進程派生的所有進程,亦或perf_event cgroup子係統的所有進程。
為了檢查是否在一個group裏麵,perf_event使用cgroup_is_descendant()函數簡單的遍曆->parent直到找到一個匹配的或者是root。這個操作並不是特別開銷大,特別是在層級不深的情況,當然相比簡單比較兩個數字的開銷肯定更大些。網絡代碼的開發者在對添加任何有性能開銷代碼的敏感性方麵是出名的,特別是這些開銷是給到每個包的。因此網絡代碼不使用cgroup_is_descendant()也不會有啥讓人驚訝。

對於perf,配置並不會下發到各個層級。任何時候當需要一個控製抉擇(比如這個事件是否需要統計),會從進程開始遍曆這個樹以找到答案。

讓我們會到net_cl和net_prio,試問它們怎麼放進這個圖譜裏麵--將配置從cgroup一直到進程接受到控製,和device子係統一樣。進程在創建socket的時候是能夠找到對應的cgroup,但是並不按層級往上回溯。區別是下發配置留給用戶態,而不是讓內核提供。

cpuset: where it all began

最後的一個cgroup子係統是cpuset,也是Linux最早加入的一個子係統。

和net_cl不同的是對於cpuset子係統,當一個進程從一個cgroup移動到另外一個cgroup時,如果兩個cgroup允許運行的處理器集不一樣,進程可以簡單的被放置到一個新的被允許的處理器對應runqueue上,而當允許的內存node修改了的話,將內存從一個node遷移到另外一個node就不是那麼簡單。

和device子係統不同的是,cpuset cgroup裏麵每個進程都保存有自己的CPU集,另外cpuset子係統需要跟freeze子係統一樣將新的配置下發到每個獨立的進程。還有一個不同的是,cpuset並不需要在fork的時候被通知。

此外,cpuset有時也需要往上遍曆層級以找到一個合適的父親。其中的一個例子是當一個進程發現所在的cgroup沒有可以運行的CPU(可能是由於一個CPU已經offline)。另外一個是一個高優先級的內存申請發現mems_allowed裏麵所有node的內存都已經耗盡。這兩種例子裏麵,從祖先節點裏麵借一些資源可以用於度過當前的緊要關頭。

可以看到有的子係統需要配置下發,有的卻需要沿著cgroup樹往上搜索,對於cpuset來說這兩種都需要。

The story so far

對當前這7個子係統,可以看到部分子係統會提供控製(device, freezer, cpuset),部分子係統僅僅給一個標識用於啟動特定的控製(net_cl, net_prio),而部分子係統並不引入任何控製(debug, perf_event)。一些子係統(device)提供的控製是要求內核代碼檢查cgroup子係統裏麵的每個訪問,同時另外一些子係統(cpuset, net_cl)提供對內核數據結構(threads, sockets)進行設置,其他一些內核子係統從那裏獲取設置。 一部分控製是沿著層級樹往下分發給子cgroup,一些是沿層級樹往上檢查父親節點,也有一些是兩種都有或者兩種都沒有。

沒有太多細節直接和我們在層級係統裏麵發現的許多問題相關,盡管如此the emphasis on using cgroups to identify processes perhaps suggests that classification rather than an organization is expected.

對這些子係統,稍微更傾向於使用分類層級,但是對於多層級並沒有特殊的需求。

當然,我們還沒有結束,後麵我們還會對其他幾個子係統:cpu cpuacct blkio memory hugetlb進行分析,看是否能夠從這些子係統中學習到什麼樣的層級會更適合他們。

Control groups, part 4: On accounting

Linux和Unix對資源使用計數並不陌生,即使在V6 Unix,每個進程使用的CPU時間都被計數且運行總時間可以通過times()係統調用獲得。這也擴展到了進程組,V6 Unix裏一個進程派生出來的所有進程可以組成一個組,當組內的所有進程都退出時,使用的總CPU時間可以通過times()獲得。在進程退出或者等待前,它的CPU時間隻有自己知道。在2.10BSD裏,被計數的資源種類擴展到包括內存使用、缺頁中斷數、磁盤IO等,和CPU時間統計類似,當子進程等待時這些計數會被加進父進程裏。getrusage()調用可以訪問這些計數,現在的linux裏還存在。

getrusage()後有了setrlimit(),它可限製資源的使用數目,如CPU時間和內存。這些限製隻能加在單獨的進程上而非組:一個組的計數隻能在進程退出時累加,但顯然這時太晚了而沒法達到限製的目的。

cpuacct--為統計而統計

cpuacct是最簡單的統計子係統,部分原因是因為它隻做統計,而不施加任何限製。cpuacct的出現最初是為了證明cgroup的能力,並沒有想合進mainline,但它和其他cgroup代碼一起被合進了2.6.24-rc1,但由於最初設計初衷馬上又被移出去了,最後又因為看起來很有用又被重新加進了2.6.24-final。知道了這段曆史,我們可能就不會期望cpuacct能滿足那些大而全的需求。

cpuacct有兩種不同的統計信息,第一個是組內所有進程的總CPU時間,它被調度器統計且精度是很高的納秒級。這個信息以per-CPU來統計,且可以per-CPU和總時間兩種形式呈現。第二個是組內所有進程的總CPU時間被拆分成“user”和“system”(從2.6.30開始),它們的統計方式和times()係統調用相同,都是以時鍾滴答或“jiffies”為粒度。因此它們和CPU時間的納秒級別相比沒有那麼精確。

從2.6.29開始按層級進行統計。當一些計數被加到一個組裏時,它也會被加至這個組的所有祖先組裏。因此,一個組內的使用統計是當前組和所有子組裏進程使用之和。這是所有子係統的一個關鍵特點:按層級統計。雖然perf_event也做一些統計,但是這些統計隻加進當前組,而不會向祖先組裏累加。

對於cpuacct和perf_event兩個子係統而言,按層級統計是否必要尚不清楚。內核並不使用總的統計,隻對應用程序可用,但是它也不太可能以很高的速率頻繁讀取數據。這就以為著對於需要整個組計數信息的程序而言,一個有效的辦法就是遍曆所有子組並累加得到總和。當一個組被刪除後,可以將它的使用計數累加近父組,就像進程退出後將cpu時間加進父進程一樣。更早地累加也沒有什麼好處。

即使在cgroup文件裏應該直接顯示總和,內核是在需要而不是每次變化的時候計算總和更加切實可行。是應用程序在需要的時候遍曆各個組得到總和,還是內核在每個計數時都遍曆每個祖先加進總和,這之間存在明顯的權衡。對這種權衡的分析需要將樹的深度和更新的頻率考慮在內,對於cpuacct,每個調度器事件或時鍾滴答都會產生一次更新,即在一個繁忙的機器上每毫秒都會產生一次或更多。雖然這種事件已經很頻繁了,但還有其他更頻繁的事件。

無論cpuacct和perf_event的計數方法是否合理,這對理解cgroup都不是那麼重要,值得關注的是如何權衡不同的選擇。這些子係統可以自主選擇方法,因為內核內部並不使用統計數字。但對於其餘需要控製資源使用的子係統而言,它們需要準確無誤的統計。

內存相關

有兩個cgroup係統是用來對內存使用進行計數和限製的:memory和hugetlb,這兩個子係統使用通用的數據結構記錄及限製內存:"resource counter" 即 res_counter。res_counter的定義在include/linux/res_counter.h,實現在kernel/res_counter.c,它包含一些內存資源的使用計數和兩個限製:limit和soft limit,還包含一個內存使用的曆史最高值、申請失敗的請求次數。同時,res_counter包含一個用來防止並發訪問的spinlock和指向父組指針,這些父指針一起組成了一個樹狀的結構。

memory cgroup有三個res_counter,一個用來記錄用戶程序的內存使用,一個用來記錄總內存和swap使用,另一個用來記錄因為該進程使得內核方麵的內存使用。hugetlb也還有一個res_counter,這意味著當memory和hugetlb都開啟時共有四個父指針,cgroup的這種層級式設計也許並不能滿足用戶的需求。當進程申請一種內存資源時,res_counter需要向上遍曆每個父指針,檢查每個祖先的內存限製並更新當前使用量。這需要拿每層的spinlock,因此代價比較大,特別是層級比較深的情況下。Linux在內存分配做了很好的優化,除了per-cpu的空閑鏈表,還有分配釋放的批量操作來減小單次分配的代價。內存分配有時候會很頻繁,性能需要足夠好,因此在每次內存申請時都要拿一係列的spinlock來更新計數顯然不是一個好主意。慶幸的是,memory子係統不是這麼做的。

當內存申請少於32個頁時(大多數請求都隻有1個頁),memory cgroup會從res_counter一次請求32個頁。如果請求成功,多申請的部分會被記錄在一個per-cpu的“存量”裏,它會記錄每個cpu上最後申請的是哪個cgroup及剩餘多少。如果請求不成功,它會隻申請需要的頁個數。當同一個進程在同一個cpu上有後續的內存分配時就會使用存量,直到用完。如果另外一個cgroup的新進程被調度到當前cpu上分配內存,原來的存量會被退回同時會為該cgroup創建一個新的存量。內存釋放同樣也是批量進行,但是是不同的機製,這是因為釋放的量經常會更大且不會失敗。批量釋放使用per-process計數器(而不是per-cpu),且需要在代碼裏顯式地被開啟,調用順序是:

   mem_cgroup_uncharge_start()
   repeat mem_cgroup_uncharge_page()
   mem_cgroup_uncharge_end()

這可以使用批量釋放,單獨一個mem_cgroup_uncharge_page()則不行。
以上可以看出對資源使用的計數代價可能會很大,而在不同的環境下有不同的方法來減小代價,因此不同的cgroup對這個問題應保持中立態度,並根據自己的實際需求找到最合適的辦法。

另一個CPU子係統

有幾個cgroup子係統和CPU相關,除了之前提到的用來限製進程可運行cpu的cpuset,記錄cpu允許時間的cpuacct,第三個相 關的子係統就叫做cpu,它是調度器用來控製不同進程和不同cgroup間的運行時間比例。

Linux調度器的設計思想很簡單,它的模型是基於一個設想——CPU是理想的多任務調度,可以同時跑任意多個線程,隨著線程數目的增多運行速度遞減。在這個模型下,調度器可以計算出每個線程應該得到多少CPU時間,同時選擇實際運行時間最少的線程服務。如果所有的進程平等,且有N個可允許進程,那麼每個進程會有1/N的實際運行時間。當然如果調度優先級或者nice值分配的權重不同,進程會有不同比例的運行時間,它們的時間比例總和是1。如果用cpu cgroup進行組調度時,運行時間比例就是基於組層級進行計算,因此一個上層組會被分配一個時間比例,並在該組進程和子組中共享。

另外,一個組的運行時間應該等於該組所有進程運行時間的總和,但是如果有進程退出,它多使用或少使用的時間信息就會丟失,為了防止這個因素導致的組間不公平,調度器會和每個進程類似也記錄每個組的使用時間。“虛擬運行時間”就是記錄理想和實際允許時間的偏差。為了管理不同層級上的值,cpu子係統建立了一套並行於層級的sched_entity結構,調度器用它來記錄不同的權重和虛擬運行時間。每個CPU都有一套此層級結構,這意味著運行時間可以無鎖地向上推送,因此比memory cgroup使用的res_counter更加高效。

CPU子係統還允許對每個組限製最大的CPU帶寬,帶寬的計算方法是CPU時間除以牆上實際時間。CPU時間(quota)和牆上實際時間(period)都需要設置,當設置quota和period時,子係統會檢查父組的限製是否允許子組能充分使用這些quota,不行的話就會拒絕設置。帶寬限製大多是在sched_entity下實現的,當調度器更新每個sched_entity使用了多少虛擬時間時,也會一並更新帶寬使用並檢查是否需要進行限製。

從我們提到的例子中可以看出,限製通常是從上到下檢查層級,而資源計數是從下到上遍曆層級。

blkio

Linux 3.15裏blkio有兩種策略:“throttle”和“cfq-iosched”,和cpu子係統的兩種策略很類似(帶寬和調度優先級),但是實現細節差別很大。許多想法在其他子係統中都已經提到了,但是另外兩個點值得一提:

一個是blkio子係統為每個組增加了一個新的ID。之前提到cgroup框架為每個組分配了一個ID且net_prio用它來區分組,blkio增加的新ID也是類似的作用但是有一點區別。blkio ID是64位且從不重用,但cgroup框架的ID是int類型(32位)且可以被重用。唯一的ID是一個通用的特性,更應被cgroup框架提供。增加了blkio ID一年之後,cgroup框架也提供了一個非常類似的serial_nr,但是目前blkio還沒有修改去重用這個域。注意當前的代碼下,blkio也被稱為blkcg。

另外一個特性是關於blkio的cfq-iosched策略。每個組都被分配一個不同的權重,類似於CPU調度器的權重,它用來平衡本組和兄弟組進程請求的調度。但是blkio還有一個leaf_weight,用來平衡組內進程和子組進程的請求。當非葉子cgroup包含進程時,cfq-iosched策略會將這些進程當作在一個虛擬組裏並用leaf_weight作為它的權重。CPU調度器沒有這個概念,兩種調度行為也沒有正確或錯誤之分,但如果他們行為一致是最好不過了,其中一個辦法便是非葉子cgroup不能包含單獨的進程。

Control groups, part 5: The cgroup hierarchy

July 30, 2014 This article was contributed by Neil Brown

Control groups

在之前的文章裏麵,我們已經看過一般情況下的層級,以及特定cgroup子係統如何處理層級。現在是時候將這些匯總起來,以理解那種層級是需要的,已經如何在當前的實現中進行支持。如我們最近報道的,3.16 Linux內核正在開發對“統一層級”的支持。那個開發引入的新想法在這將不進行討論,因為我們暫時沒法知道它們可能帶來的意義,除非我們已經完全知道我們擁有的。後麵還會有文章剖析統一層級,先前我們先開始理解我們稱之為"classic"的cgroup層級。

Classic cgroup hierarchies

在classic的模式,會有許多單獨的cgroup層級。每個層級都會有一個root cgroup,所有進程都包含在這個root cgroup裏麵。root節點是通過mount一個cgroup虛擬文件係統實例創建的,所有的修改都是通過操作這個文件係統進行的,比如通過mkdir創建cgroup,rmdir刪除cgroup,以及mv對cgroup進行重命名。一旦cgroup創建,進程可以通過將pid寫入特定的文件在cgroup之間移動。如一個特權用戶將PID寫入一個cgroup的cgroup.procs文件裏麵,那麼這個進程就被從當前的cgroup裏麵移入到目標cgroup裏。

這是一種有組織的層級管理:創建一個新的組,然後找到相應進程的放進這個組裏麵。這種方式對於基於文件係統層級組織來說是很自然的,但不能說這就是最好的管理層級的方式。在4.4 BSD裏麵的基於會話和進程組的簡單層級組織工作方式就是相當不一樣。

classic層級方式最大的問題是專製的選擇。子係統之間有著大量不同的組合方式:一些在一個層級,一些在另外一個,也有的一個也沒有。問題是這些選擇一旦作出,影響是係統級的,很難改變。假如有的人需要某個特定的子係統組合方式,而同時另外一個人需要另外一種,這個時候兩個需求可能並無法同時在一個宿主機上得到滿足。這對基於container實現的在一個宿主機上同時支持多個各自獨立的管理域來說是個嚴重的問題。所有的管理域隻能看到相同的cgroup子係統層級組織。

顯而易見的選擇是隻有一個層級(“統一層級”方式的目標),或者每個子係統有一個獨立的層級(比如隻有cpu和cpuacct兩個是組合在一起的)。根據我們所學習到的關於cgroup子係統的知識,我們可以試著理解一些保持子係統相互獨立或者相互組合的具體實現。

有一些子係統並不做統計,或者雖然做統計,但並不利用統計進行任何控製。這些子係統包括:debug,net_cl,net_perf,device,freezer,perf_event,cpuset,以及cpuacct。它們都沒有重度使用層級,在幾乎所有的用例中層級提供的功能可以獨自實現。
這些子係統裏麵有兩個使用層級的地方不是很好移除。第一個是cpuset子係統。這個子係統在緊急情況下會沿層級向上查看,以找到額外的資源進行使用。當然正如之前有提到的,類似的功能可以不依賴於層級關係進行提供,因此這隻是個小問題。

另外一個是device子係統。它對層級的使用不是在控製方麵,而是在配置授權上:子組不允許訪問父組禁止的配置。區域層次結構(administrative hierarchy)在權限分配方麵是很高效的,無論是對用戶分組,或者針對獨立的使用者,亦或對有自己用戶集的container。對於非統計類(non-accounting)子係統,提供一個唯一的區域層次結構是很自然的選擇,也很適合。

Network Trafic Control -- another control hierarchy

網絡流量實際是由一個獨立的層級進行管理,這個層級甚至獨立於cgroup。為了便於理解,需要簡單介紹下網絡流量控製(Network Traffic Contro,NTC)。NTC機製是通過tc進行實現。這個工具允許為每個網絡接口添加一個排隊模型(或稱為qdisc),有些qdisc是有類的(classful),它們允許有其他針對不同類別包的qdisc掛在下麵。如果第二層的qdisc也是有類,那麼意味著qdisc也是能夠按層級組織的,甚至可以有很多層級,每個網絡接口一個。

Tc也允許配置過濾器,這些過濾器用於指導網絡包如何分配給不同的類(也意味著不同的隊列)。過濾器可以使用很多值,包括每個包的大小、包使用的協議、產生這個包的socket。net_cl cgroup子係統能夠每個cgroup裏麵進程創建的socket設定一個類ID(class ID),通過這個ID將包分類到不同的網絡隊列中。

每個包經過諸多過濾器被分類到某個隊列,然後向上傳遞到根,也許會被限流(比如Token Bucket Filter,tfb, qdisc),或者被競爭調度(比如Stochastic Fair Queueing, sfq, qdisc)。一旦到達了根,包就被發送出去。

這個例子說明了層級對於資源調度和使用限製的意義。它也向我們展示獨立的cgroup層級並不需要,本地的資源層級就能很好的滿足需求。

對於CPU、memory、block I/O和network I/O,每個資源主控製器都維護有自己的獨立層級。前三個都是通過cgroup管理的,但網絡是單獨管理的。這樣看有兩種不同類型的層級:一些用於組織資源,一些用於組織進程。

Cgroup層級其中有一個在NTC層級中不是很明顯能做的是,在使用container時將層級分到一個獨立的區域域。部分container的名字空間中僅僅掛載一個cgroup層級的子樹,這個container被限製隻能影響這個子樹,這樣的話container裏麵就沒法實現類ID被設定給不同cgroup。

對於網絡,這個問題通過虛擬化或者間接的方式能夠解決。虛擬網絡接口"veth"能夠提供給container,這樣就能夠按照自己喜歡的方式進行配置。Container的流量都會被路由到真實的接口,並能夠根據流量源自哪個container進行分類。同樣的機製也對block I/O有效,但是CPU和內存資源的管理沒辦法,除非有類似KVM這樣的全虛擬化。

How separate is too separate

正如我們上次提到的,資源統計控製器需要對祖先cgroup的信息可見才能夠高效的實現限速,也需要對相鄰cgroup的可見以實現公平共享,因此完整的層級對於這些子係統來說是很重要的。

以NTC作為例子,可能會引發爭論的點是這些層級需要為每種資源分離開。NTC做得比cgroup更深遠,能夠允許每個接口擁有一個獨立的層級。blkio可能也會想要對不同的塊設備提供不同的調度結構(swap vs database vs logging),但這個目前cgroup還不支持。

盡管如此,過多的資源控製隔離會帶來一定開銷。統一層級支持方的Tejun Heo指出這部分開銷是由於缺少“有效的合作”。

當一個進程往文件中寫數據時,數據先到page cache,這樣會消耗內存。在之後的某個時間,內存將會被寫出到存儲設備,這樣會消耗一些塊設備I/O帶寬,或者也有可能一些網絡帶寬。因此這些子係統並不是完全分開的。

當內存被寫出時,很可能這動作並不是由寫這部分數據的進程執行,也可能不是由這個cgroup裏麵的其他進程執行。那麼如何能夠使得塊設備I/O的統計更加精確呢?

memory cgroup子係統為每個內存頁附加了一些額外的信息,這樣能夠在頁被釋放時知道應該找誰退款。似乎當頁被寫入的時候,我們可以在這個cgroup裏麵統計I/O使用。但是有一個問題,這個cgroup是和memory子係統一起的,因此有可能在完全不同的層級裏。這樣的話,這個cgroup裏麵對內存的統計可能對blkio子係統來說完全沒意義。

還有其他一些方式來解決這種分離:
在每個頁裏麵記錄進程的ID,由於兩個子係統都知道PID,因此可用這個計算內存和塊設備的使用。這個方法有一個問題是進程可能存活時間很短,當進程退出時,我們要麼需要將進程未歸還的資源轉移給其他進程或者cgroup,要麼直接丟棄。這個問題和CPU調度裏麵的類似,隻對進程進行統計很難實現合理進程組的公平性。合理的保存未歸還的資源是一個挑戰。 引入一些其他的標識,要求能夠存活任意時間,能夠和多個進程關聯起來,能夠被每個不同的cgroup子係統。這種間接法眾所周知能夠解決任何計算機科學的問題。 用於連接net_cl子係統和NTC的class ID就是這樣一個標識。當有很多層級,每個接口一個時,隻有一個class ID標識的名字空間。

為每個page存儲多個標識,一個用於內存使用,一個用於I/O吞吐。當前用於存儲額外的memory控製器信息的page_cgroup結構體在64位係統上每頁消耗128字節--64字節是一個執行歸屬cgroup的指針,另外64字節用於做標識位(目前已經使用3位)。假如能夠用一個數組的索引替換指針,十億個組目前看是足夠的,這樣兩個索引和一個額外的bit能夠存儲在之前一半的空間中。是否一個索引就能夠提供足夠的效率,這個留給感興趣的讀者練習。

對這個問題的解決方式也許能夠使用與其他情況:任何有一個進程代替其他進程消耗資源的地方。Linux裏麵的md RAID驅動通常會在初始化該請求的進程上下文中將I/O請求直接下傳給下層設備。但其他一些情況下,一些工作需要由一個協助進程完成,用於在將來提交請求。目前,完成這部分工作的CPU時間和該請求消耗的I/O帶寬都被算到md而不是最初的進程上。假如能夠為每個I/O請求加上消耗者標識,md和其他類似的驅動將有可能據此分配資源使用。

不幸的是,目前的實現下這個問題沒有好的解決方式。過度的隔離會帶來性能損耗,這些損耗並不能通過簡單將所有子係統放到一個相同的層級得到減緩。

目前的實現,最好是將cpu blkio memory和hugetlb這些統計子係統放入單獨的層級,而網絡方麵謝謝NTC使得已經有一個獨立的層級,同時也最好將所有非統計類的子係統一起放在一個區域層級。這樣需要的時候依賴於智能的工具對這些獨立的層級進行有效的組合。

Answers ...

現在需要回答一些以前文章提到的問題。其中一個是如何命名組。正如我們上麵看到的,這個是執行mkdir命令進程的職責。這個和任務控製進程組及會話組不一樣,這些組是在進程調用setsid()或者setpgid(0,0)時內核會為組設定一個名字。這之間的區別能夠得到解決,不過這裏需要闡述下期望的權力結構。對於任務控製進程組,形成一個新組的決定來自於新組的一個成員。而對cgroup,這個決定更期望的是來自於外部。先前我們已經觀察到在cgroup層級裏麵包含一個區域層級看起來很有道理。而與這個觀察一致的是名字是從外部給予的這個事實。

另外一個問題是,是否允許從一個組移入到另外一個組。移動一個進程需要將進程ID寫入cgroup文件係統的一個文件中,這個有可能由任意有權限對這個文件進行寫的進程執行,這樣需要更進一步檢查執行寫操作進程的所有者是否也是將被添加進程的所屬者,或者是更高權限的。這意味著任何用戶能夠將他們的任意進程放入任意他們有對cgroup.procs寫權限的組裏麵,而不顧跨越了多少個層級。

換句話說,我們可以限製一個進程可以移動到哪,但是對於進程從哪裏移動過來的控製卻很少。

... and questions

這個討論引出的最大問題是,是否真的有對不同的資源使用不同層級管理的需求。NTC提供的靈活性是否很好的超越了需求,或者它是否為其他提供一個可以追隨的有價值的模型?第二個問題關心的是假如不同的需求都使用一個層級,是否會引起組合爆炸,同時這個帶來的開銷是否和其價值成比例。在任意情況,我們需要清楚的知道如何計算請求實際發起者的資源消耗。

這些問題中,中間可能是最早的:擁有多cgroup在實現上有哪些開銷?下麵一個topic我們需要講的就是這個。

Control groups, part 6: A look under the hood

這篇文章扼要地介紹了cgroup subsystem設計的背後原因,老哥還蠻謙虛的,說僅僅是霧裏看花,錯了莫怪。

首先,在配置上cgroup subsystem麵臨著組合爆炸的可能,可以算個流水帳:如果某個係統上有Q個管理員,而每個管理員則希望用N種方式分割M種資源,於是就需要Q x M x N個cgroup。但如果我們能夠把這種層次從水平方向上切成多個層次就可以緩解這個現象了,比如把M個資源的N種切換方式與Q個管理員的層次分別獨立出來,就隻需要Q + M x N個cgroup了。

無論怎麼說,cgroup的組合都顯然是一種樹狀結構,相信讀者瞬間就可以在腦子裏畫出那圖像來,單就樹狀結構來看,確實沒有什麼新鮮的。這裏的複雜性其實在於cgroup以線程為控製單元並非進程,以及cgroup是如何與線程關聯起來的。

然後,作者從500年前開始講起,好吧,其實是1975年的UNIX v6講起,一步一步地介紹了UNIX是如何逐步為進程增加維度的:session, process group, process, thread。對於Linux,process實際上是所謂的thread_group,而PID其實是其中leader線程的TID。無論是進程還是線程,在Linux下都是用task_struct(任務)表示的。Linux的PID namespace又把事情變得更複雜了,一個任務在不同的PID namespace下的ID很可能並不一樣。所以,每個任務的PID並不是一個簡單的數字,而是三個鏈表:

<source lang="c">
enum pid_type { PIDTYPE_PID, PIDTYPE_PGID, PIDTYPE_SID, PIDTYPE_MAX };
struct hlist_head tasks[PIDTYPE_MAX];
struct pid_link
   {
struct hlist_node node; struct pid *pid;
   } pids[PIDTYPE_MAX];
</source>

後麵作者介紹了不少社區為了做到高並發所做的N多努力。
OK,該是cgroup內部結構登場的時候了:

  • 每個cgroup結構都有cset_links字段,把其中的所有threads串起來。
  • 每個css_set結構則都有一個cgrp_links字段,把其中thread涉及到的cgroup串起來。另外,對每一個層次結構還有一個cgrp_cset_link字段。

這裏麵使用了大量內核中的鏈表技巧,親們google之?

文章裏講了以上這些數據結構中鎖機製的一些設計細節,也值得一看的。

Control groups, part 7: To unity and beyond

這一係列文章的目的是為了讓我們理解linux Cgroup,能夠參與到我們周圍關於cgroup的討論中去。 現在是檢驗我們成果的時候了,我可以參與到cgroup的討論裏,並發表自己的建議和質疑。 (譯:下麵就是作者自己的一些評價和質疑)。

The unified hierarchy: A score card

Unification of hierarchy 毫無疑問傳統的cgroup允許太多的繼承個數。將個數減少到一個是理想化的情況。在調查中,我們發現兩種截然不同的繼承用法:一些子係統利用control向下,而另外一些則相反。根據涉及 到不同的實現所關注的繼承又很大的不同。 令人欣喜的是統一繼承正朝著去除多餘的重複的方向努力。看起來它不像要承認不同的子係統可能有真的不相容的需求,但是它完全關閉分割繼承的大門。 得分B 有待進一步提高。

Processes only permitted in the leaves 統一繼承要求隻能在進程退出時才能退出。這個強製看起來很不合理。葉子是”不能把子係統擴展到孩子節點的節點“,在繼承裏創建新的level需要分兩部走。 進程首先被下移,讓後子係統被往下擴展。這個問題給個 C。這裏要強調一個設計上的缺陷。進程被排除在內部cgroups外,除了root cgroup,顯然root 需要特殊對待。基於這一點可以給個C+

Taming the chaotic subsystems 我們已經看到了cgroup子係統和各個功能單元之間是相當混亂的。這不是新觀點,2011年的內核開發大會上, Paul Turne就提高了這一點: google 基於自己的經驗,以更好的形式重新組織了許多控製器。 明確的列表使得 cgroup.controllers 裏的子係統使得問題更糟。 所以這一點給個D

Providing a resource-consumer ID

在part 5裏我們看到當內存的頁被釋放時,能夠標識出誰獲得了它們。但是在內容被寫出時候,不知道誰負責IO。所有的子係統使用一個繼承,一個cgroup可以作為所有資源類型的資源消耗 者ID。這是解決這個問題的一個清晰的方案,但很難說他是一個很好的方案(不同的資源可能差別很大)。 所以我給B

Processes or threads 傳統的cgroup允許一個進程裏的線程屬於不同的cgoups。想象一個可靠的應用場景很困難,但是也不是一點可能都沒有。 cpusetcgroup能夠限製進程到多個cpu或者numa係統裏的內存節點。前者可以通過在任一個線程上使用係統調用sched_setaffinity()或者程序taskset,而不用涉及到cgroups。但是內存節點 隻能通過cgroups配置。 cgroup可以更細粒度的控製單個的線程優先級,它允許單個線程或者cgroup有100000個weight分級,而不像傳統linux調度器40個nice分級那樣。統一繼承隻允許進程(線程集合)能夠在不同 的cgroups。這個主意看起來不錯,但是又引發了一個問題:我們是使用控製?線程還是進程?或者其他別的呢? 無論結果如何,不再支持單個線程的轉移是個好主意。 A

Code simplicity 統一繼承隻是漫長過程中的一步,還有很多要提高的地方。我們很明確隻有進程而不是線程最終在cgroups裏,並且他們隻需要呆在一個單獨cgroup裏, 這當然會帶來簡化。 所以給A

Summary: 統一繼承所依賴的基礎還在建設中,現在還不能要求太多。

Auto-group Scheduling 正如2010年總結的,除了cgroups,還有其他的方法去批量控製進程。使用為cgroups開發的group調度,Mike Galbraith創建了一個不同的自動的把程序分組調度的機製。標準的unix調度器和 許多追隨者試圖公平的對待進程,但是進程並不是很需要公平的對待。 進程組自動調度有兩個相關的問題: 1. Linus Torvalds提出的。他建議為了這個目的而使用進程組,粒度有些細了。創建一個新的調度組有一定的花銷,所以做的太頻繁會引入難以接受的遲鈍。可惜沒有任何人做任何的測試來 說明這一點。最終的實現使用了”sessions”而不是“process groups”, 這就不會被創建的太頻繁。但並不能完美解決這個問題。 2. 第二個問題是有Lennart Poettering提出的。“在桌麵係統上,這是完全不相幹的”。auto-group現在是基於“sessions”做的,許多桌麵會話管理都沒有把每個應用程序放到不同的會話 裏。一個正在開發中的會話管理:systemd 使用setsid()。 當時,Lennart的言論在當時沒有引起重視。

與最初cgroups開發者們相比,我們有自己的優勢,幾年的經驗和可運行的代碼。這是一筆可以轉化成為我們優勢的財富。所以,去測試你對資源管理最新的認識。挑戰是受到自動分組調度的 鼓舞,你怎麼在linux上實現進程控製和資源管理。

Hindsight groups: highlighting some issues through contrast.

Hindsight groups 進程組是基本的控製單元,可通過交互式shell被創建,通過systemd,或通過其他session管理模塊。也可以通過prlimit或者類似的命令控製單個的進程,但是組控製沒有比進程組更細粒度的控製。在pid繼承中引進了一個新的level,來提供一個管理這些進程組的管理結構。“process domain”被引入到“session”和進程組級別上。通過domains組織的繼承很好的限製了進程。per-process-group, 是新的數據結構,定義了進程組的角色,很像signal_struct被分配給每個進程。它裏麵包含了對組裏的進程各種限製, 例如可訪問的設別列表,可被使用的進程的集合。這些限製可以被任何有合適用戶id或者超級用戶許可的進程改變。各種可被共享的資源:內存,cpu,網絡可塊設備io。每個都有特殊需求,並被單獨管理。網絡和塊設備io比較類似,他們通常涉及覆蓋或者共享數據,他們很容易被虛擬化,所以一個子域可以被授權訪問一個虛擬設備,這個虛擬設備有可以把數據收發到一個真實設備。他們可以管理多個單獨的設備,不僅僅是進程所涉及到的。網絡係統需要管理自己的鏈路控製流量和從另外設備轉發來的流量。塊設備io子係統已經內部區分了metadata(使用REQ_META)和其他數據,對不同情景進行分類。結果,這兩個係統有他們自己的隊列管理結構。各種不同的度列算法可以根據原始的域將請求進行分類。或者支持標記單個的進程,這已經超出了hgroups的範圍。

內存使用管理跟其他的資源共享很不一樣,因為它是通過空間而不是時間進行測量的。一個進程可以啟停使用這三種資源(網絡,塊設備io,cpu),或者暫時脫離擁有這些資源,而沒有任何 負麵影響。而內存不能這樣。

Croups內存控製引入了兩個限製:硬性的限製(絕對不允許超越)和軟性的限製(隻有在內存非常緊張的時候才能超越)。 cpu的限製跟內存的限製很類似,唯一的不同是對本地進程組的限製也可以在域上使用。任何有合適特權的進程都可以發出限製。 cpu調度可能是最複雜的資源管理。調度組大體有域,進程組,進程繼承組成,但是在每個級別都有組選項。

Filesystem notification, part 1: An overview of dnotify and inotify

文件係統通知API提供了一個讓應用程序監測一個文件打開、修改、刪除、重命名等操作事件的方法。過去,Linux中共有三種不同的文件係統通知API,了解和掌握這三種API之間的區別是十分有用的。同時在了解這些API的過程中,我們也能夠學習到很多API設計上的經驗。

本文是一些列文章的第一部分。我們首先介紹最原始的API:dnotify以及這個API的諸多不足。然後我們將討論inotify以及它對於dnotify的改進。在著一係列的最後,文章將介紹fanotify。

Filesystem notification use cases

為了比較這三種不同API之間的區別,首先我們來看一下這些API的常見用例。

  • 緩存文件係統對象模型

應用程序經常需要在自己內部維護一個精確反映文件係統當前狀態的模型。比如一個文件管理器就需要通過圖形界麵來反饋

最後更新:2017-06-06 21:37:59

  上一篇:go  私有雲真的比公有雲便宜?
  下一篇:go  6月6日雲棲精選夜讀:阿裏雲ECS部署Grafana接入zabbix