Cgroup用法解析 ----小魔怪出品 rhel6已經發布,6.1已經發布了。網上也出現了對其的介紹文章,但都是些表面上的,譬如:在安裝過程中出現的一些改動,以及默認的文件系統類型使用的是ext4,等等。安裝完rhel6,不知到你發現在根目錄下面默認會存在一個cgroup的文件夾麼,這個是個啥玩意嘛?這個就是rhel6的------資源管理。其實不算是啥新特性了。在內核2.6.26裏面就有了。 一、cgroup介紹: 據官方文檔介紹,rhel6爲內核準備了一個新特性------資源控制,也即cgroup。此服務的軟件包是libcgroup。有了這個,你就可以分配資源,例如:CPU time,系統內存, 網絡帶寬等。這個是被cgconfig服務所控制的。如果此服務沒有啓動,在根目錄下的cgroup文件夾裏就不會存在內容。 [root@susir]#/etc/init.d/cgconfig status [root@susir]#/etc/init.d/cgconfig stop [root@susir]#ls /cgroup 啓動此服務之後,就會在/cgroup目錄下面多出一些內容,如圖所示: [root@susir cgroup]# /etc/init.d/cgconfig restart Stopping cgconfig service: [ OK ] Starting cgconfig service: [ OK ] [root@susir cgroup]# pwd /cgroup [root@susir cgroup]# ls blkio cpu cpuacct cpuset devices freezer memory net_cls 二、cgroup各個模塊的介紹: cgroup類似與進程,他們是分等級的,各個屬性都是從父進程哪裏繼承過來。cgroup包含了多個孤立的子系統,沒一個子系統代表一個單一的資源。rhel6中一共準備了9個子系統,以下是每個子系統的詳細說明: blkio 這個子系統設置限制每個塊設備的輸入輸出控制。例如:磁盤,光盤以及usb等等。 cpu 這個子系統使用調度程序爲cgroup任務提供cpu的訪問。 cpuacct 產生cgroup任務的cpu資源報告。 cpuset 如果是多核心的cpu,這個子系統會爲cgroup任務分配單獨的cpu和內存。 devices 允許或拒絕cgroup任務對設備的訪問。 freezer 暫停和恢復cgroup任務。 memory 設置每個cgroup的內存限制以及產生內存資源報告。 net_cls 標記每個網絡包以供cgroup方便使用。 ns 名稱空間子系統。 以上九個子系統之間也存在着一定的關係.詳情參閱官方文檔。 三、cgroup的使用: 1、cgroup的安裝: 在安裝系統的時候,默認已經安裝了libcgroup軟件包,如果沒有安裝可以使用以下命令進行安裝: [root@susir /]# rpm -q libcgroup libcgroup-0.36.1-6.el6.x86_64 如果沒有安裝請用YUM安裝。 2、cgroup服務的控制: 將其更改爲伴隨系統的啓動而啓動: [root@susir /]# chkconfig --list cgconfig cgconfig 0:off 1:off 2:off 3:off 4:off 5:off 6:off [root@susir /]# chkconfig cgconfig on [root@susir /]# chkconfig --list cgconfig cgconfig 0:off 1:off 2:on 3:on 4:on 5:on 6:off 服務的停止和啓動 [root@susir cgroup]# /etc/init.d/cgconfig stop/start 3、cgroup的配置文件cgconfig.conf文件介紹: 在cgconfig.conf文件中,主要包含了兩個主要類型:mount和group。mount是指創建以及掛載哪些層次爲虛擬文件系統,並附上子系統的層次結構。cgconfig.conf文件的內容如下所示: [root@susir /]# cat /etc/cgconfig.conf | more # # Copyright IBM Corporation. 2007 # # Authors: Balbir Singh <[email protected]> # This program is free software; you can redistribute it and/or modify it # under the terms of version 2.1 of the GNU Lesser General Public License # as published by the Free Software Foundation. # # This program is distributed in the hope that it would be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. # # See man cgconfig.conf for further details. # # By default, mount all controllers to /cgroup/<controller> mount { cpuset = /cgroup/cpuset; cpu = /cgroup/cpu; cpuacct = /cgroup/cpuacct; memory = /cgroup/memory; devices = /cgroup/devices; freezer = /cgroup/freezer ......省略。。。。。。。 例如cpuset=/cgroup/cputest,就可以理解爲以下幾條命令 [root@susir /]# mount -t cgroup -o cpuset cpuset /cgroup/cpuset mount: cpuset already mounted or /cgroup/cpuset busy [root@susir /]# umount /cgroup/cpu cpu/ cpuacct/ cpuset/ [root@susir /]# umount /cgroup/cpuset [root@susir /]# mount -t cgroup -o cpuset cpuset /cgroup/cpuset 4、與cgroup相關的幾個命令: lssubsys-----顯示已經存在的子系統。 [root@susir /]# lssubsys -am cpu /cgroup/cpu cpuacct /cgroup/cpuacct memory /cgroup/memory devices /cgroup/devices freezer /cgroup/freezer net_cls /cgroup/net_cls blkio /cgroup/blkio cpuset /cgroup/cpuset ns 上所示,除ns子系統沒有在/etc/cgconfig.conf文件裏設置外,其他的都掛載在對應的目錄下面。 lscgroup-----顯示所有的cgroup。 [root@susir /]# lscgroup cpu:/ cpuacct:/ memory:/ devices:/ freezer:/ net_cls:/ blkio:/ cpuset:/ 5、掛載與卸載子系統層次: 如上所示,各個模塊均以掛載與對於的目錄下面。以下卸載了cpu子系統層次: [root@susir /]# umount /cgroup/cpu [root@susir /]# lssubsys -am #(-a代表所有,-m代表顯示掛載點。) cpuacct /cgroup/cpuacct memory /cgroup/memory devices /cgroup/devices freezer /cgroup/freezer net_cls /cgroup/net_cls blkio /cgroup/blkio cpuset /cgroup/cpuset ns cpu 其他的卸載與掛載都一樣。使用以下命令將其掛載回去: [root@susir /]# mount -t cgroup -o cpu cpu /cgroup/cpu [root@susir /]# lssubsys -am cpuacct /cgroup/cpuacct memory /cgroup/memory devices /cgroup/devices freezer /cgroup/freezer net_cls /cgroup/net_cls blkio /cgroup/blkio cpuset /cgroup/cpuset cpu /cgroup/cpu ns 6、創建一個自定義的cgroup: 這裏要用到cgcreate命令,具體用法如下所示:先來看個示例: [root@susir /]# cgcreate Usage is cgcreate -t <tuid>:<tgid> -a <agid>:<auid> -g <list of controllers>:<relative path to cgroup> [root@susir /]# cgcreate -g cpu,net_cls:/test-subgroup [root@susir /]# lssubsys -am cpuacct /cgroup/cpuacct memory /cgroup/memory devices /cgroup/devices freezer /cgroup/freezer net_cls /cgroup/net_cls blkio /cgroup/blkio cpuset /cgroup/cpuset cpu /cgroup/cpu ns 沒有出來。看到麼。其實掛載上去之後,使用lssubsys命令是看不到的。因爲啊。創建完了之後,系統還不能掛載,因爲掛載點不存在,等掛載點創建了之後,系統會自動掛載上去的。 7、刪除cgroup [root@susir /]# cgdelete Usage is cgdelete [-r] <list of controllers>:<relative path to cgroup> [...] 一: 前言 前段時間,一直在寫操作系統和研究Solaris kernel.從而對linux kernel關心甚少.不久前偶然收到富士通的面試,由於諸多原因推辭掉了這次機會.不過招聘要求給我留下了較深的印像.其中涉及到了cgroup機制.cgroup對我來說並不陌生,在LKML上看到過它的path.在2008 AKA大會上也有人對它做爲專題分析.不過一直都沒有深入代碼研究.這段時間打算將kernel中新加的功能整理一下,就先從cgroup開始吧. Cgroup是近代linux kernel出現的.它爲進程和其後續的子進程提供了一種性能控制機制.在這裏不打算對cgroup的作用和使用做過多的描述.本文從linux kernel的源代碼出發分析cgroup機制的相關實現.在本節中,主要分析cgroup的框架實現.在後續的部份再來詳細分析kernel中的幾個重要的subsystem.關於cgroup的使用和介紹可以查看linux-2.6.28-rc7/Documentation/cgroups/cgroup.txt.另外,本文的源代碼分析基於linux kernel 2.6.28版本.分析的源文件基本位於inux-2.6.28-rc7/kernel/cgroup.c和inux-2.6.28-rc7/kernel/debug_cgroup.c中. 二:cgroup中的概念 在深入到cgroup的代碼分析之前.先來了解一下cgroup中涉及到的幾個概念: 1:cgroup: 它的全稱爲control group.即一組進程的行爲控制.比如,我們限制進程/bin/sh的CPU使用爲20%.我們就可以建一個cpu佔用爲20%的cgroup.然後將/bin/sh進程添加到這個cgroup中.當然,一個cgroup可以有多個進程. 2:subsystem: 它類似於我們在netfilter中的過濾hook.比如上面的CPU佔用率就是一個subsystem.簡而言之.subsystem就是cgroup中可添加刪除的模塊.在cgroup架構的封裝下爲cgroup提供多種行爲控制.subsystem在下文中簡寫成subsys. 3: hierarchy: 它是cgroup的集合.可以把它理解成cgroup的根.cgroup是hierarchy的結點.還是拿上面的例子: 整個cpu佔用爲100%.這就是根,也就是hierarchy.然後,cgroup A設置cpu佔用20%,cgroup B點用50%,cgroup A和cgroup B就是它下面的子層cgroup. 三:cgroup中的重要數據結構 我們先來看cgroup的使用.有三面一個例子: [root@localhost cgroups]# mount -t cgroup cgroup -o debug /dev/cgroup [root@localhost cgroups]# mkdir /dev/cgroup/eric_test 如上所示,用debug subsystem做的一個測試. /dev/cgroup是debug subsys的掛載點.也就是我們在上面所分析的hierarchy.然後在hierarchy下又創建了一個名爲eric_test的cgroup. 在kernel的源代碼中.掛載目錄,也就是cgroup的根目錄用數據結構struct cgroupfs_root表示.而cgroup用struct cgroup表示. 分別來看一下這兩個結構的含義,struct cgroupfs_root定義如下: struct cgroupfs_root { //cgroup文件系統的超級塊 struct super_block *sb; /* * The bitmask of subsystems intended to be attached to this * hierarchy */ //hierarchy相關聯的subsys 位圖 unsigned long subsys_bits; /* The bitmask of subsystems currently attached to this hierarchy */ //當前hierarchy 中的subsys位圖 unsigned long actual_subsys_bits; /* A list running through the attached subsystems */ //hierarchy中的subsys鏈表 struct list_head subsys_list; /* The root cgroup for this hierarchy */ //hierarchy中的頂層cgroup struct cgroup top_cgroup; /* Tracks how many cgroups are currently defined in hierarchy.*/ //hierarchy中cgroup的數目 int number_of_cgroups; /* A list running through the mounted hierarchies */ //用來鏈入全局鏈表roots struct list_head root_list; /* Hierarchy-specific flags */ //hierarchy的標誌 unsigned long flags; /* The path to use for release notifications. */ char release_agent_path[PATH_MAX]; }; 注意cgroupfs_root中有個struct cgroup結構的成員:top_cgroup.即在每個掛載點下面都會有一個總的cgroup.而通過mkdir創建的cgroup是它的子結點. 其中,release_agent_path[ ]的成員含義.我們在後面再來詳細分析. Struct cgroup的定義如下: struct cgroup { //cgroup的標誌 unsigned long flags; /* "unsigned long" so bitops work */ /* count users of this cgroup. >0 means busy, but doesn't * necessarily indicate the number of tasks in the * cgroup */ //引用計數 atomic_t count; /* * We link our 'sibling' struct into our parent's 'children'. * Our children link their 'sibling' into our 'children'. */ //用來鏈入父結點的children鏈表 struct list_head sibling; /* my parent's children */ //子結點鏈表 struct list_head children; /* my children */ //cgroup的父結點 struct cgroup *parent; /* my parent */ //cgroup所處的目錄 struct dentry *dentry; /* cgroup fs entry */ /* Private pointers for each registered subsystem */ struct cgroup_subsys_state *subsys[CGROUP_SUBSYS_COUNT]; //cgroup所屬的cgroupfs_root struct cgroupfs_root *root; //掛載目錄下的最上層cgroup struct cgroup *top_cgroup; …… …… } 上面並沒有將cgroup的結構全部都列出來.其它的全部我們等遇到的時候再來進行分析. 其實,struct cgroupfs_root和struct cgroup就是表示了一種空間層次關係,它就對應着掛着點下面的文件示圖. 在上面說過了,cgroup表示進程的行爲控制.因爲subsys必須要知道進程是位於哪一個cgroup. 所以.在struct task_struct和cgroup中存在一種映射. Cgroup在struct task_struct中增加了兩個成員,如下示: struct task_struct { …… …… #ifdef CONFIG_CGROUPS /* Control Group info protected by css_set_lock */ struct css_set *cgroups; /* cg_list protected by css_set_lock and tsk->alloc_lock */ struct list_head cg_list; #endif …… …… } 注意struct task_struct中並沒有一個直接的成員指向cgroup,而是指向了css_set.css_set的結構如下: struct css_set { //css_set引用計數 atomic_t refcount; //哈希指針.指向css_set_table[ ] struct hlist_node hlist; //與css_set關聯的task鏈表 struct list_head tasks; //與css_set關聯的cg_cgroup_link鏈表 struct list_head cg_links; //一組subsystem states.由subsys->create()創建而成 struct cgroup_subsys_state *subsys[CGROUP_SUBSYS_COUNT]; } 那從css_set怎麼轉換到cgroup呢? 再來看一個輔助的數據結構.struct cg_cgroup_link.它的定義如下: struct cg_cgroup_link { /* * List running through cg_cgroup_links associated with a * cgroup, anchored on cgroup->css_sets */ struct list_head cgrp_link_list; /* * List running through cg_cgroup_links pointing at a * single css_set object, anchored on css_set->cg_links */ struct list_head cg_link_list; struct css_set *cg; }; 如上所示.它的cgrp_link_list鏈入到了cgroup->css_sets. Cg_link_list鏈入到css_set->cg_links. 其中.cg就是批向cg_link_list所指向的css_set. 上面分析的幾個數據結構關係十分複雜.聯繫也十分緊密.下面以圖示的方式直觀將各結構的聯繫表示如下: 注意上圖中的css_set_table[ ].它是一個哈希數組.用來存放struct css_set.它的哈希函數爲css_set_hash().所有的衝突項都鏈入數組對應項的hlist. 四:cgroup初始化 Cgroup的初始化包括兩個部份.即cgroup_init_early()和cgroup_init().分別表示在系統初始時的初始化和系統初始化完成時的初始化.分爲這兩個部份是因爲有些subsys是要在系統剛啓動的時候就必須要初始化的. 4.1: cgroup_init_early() 先看cgroup_init_early()的代碼: int __init cgroup_init_early(void) { int i; //初始化全局量init_css_set atomic_set(&init_css_set.refcount, 1); INIT_LIST_HEAD(&init_css_set.cg_links); INIT_LIST_HEAD(&init_css_set.tasks); INIT_HLIST_NODE(&init_css_set.hlist); //css_set_count:系統中struct css_set計數 css_set_count = 1; //初始化全局變量rootnode init_cgroup_root(&rootnode); //將全局變量rootnode添加到roots鏈表 list_add(&rootnode.root_list, &roots); root_count = 1; //使系統的初始化進程cgroup指向init_css_set init_task.cgroups = &init_css_set; //將init_css_set和rootnode.top_cgroup關聯起來 init_css_set_link.cg = &init_css_set; list_add(&init_css_set_link.cgrp_link_list, &rootnode.top_cgroup.css_sets); list_add(&init_css_set_link.cg_link_list, &init_css_set.cg_links); //初始化css_set_table[ ] for (i = 0; i INIT_HLIST_HEAD(&css_set_table); //對一些需要在系統啓動時初始化的subsys進行初始化 for (i = 0; i struct cgroup_subsys *ss = subsys; BUG_ON(!ss->name); BUG_ON(strlen(ss->name) > MAX_CGROUP_TYPE_NAMELEN); BUG_ON(!ss->create); BUG_ON(!ss->destroy); if (ss->subsys_id != i) { printk(KERN_ERR "cgroup: Subsys %s id == %d\n", ss->name, ss->subsys_id); BUG(); } if (ss->early_init) cgroup_init_subsys(ss); } return 0; } 這裏主要是初始化init_task.cgroup結構.伴隨着它的初始化.相繼需要初始化rootnode和init_css_set.接着,又需要初始化init_css_set_link將rootnode.top_cgroup和init_css_set關聯起來. 接着初始化了哈希數組css_set_table[]並且將一些需要在系統剛啓動時候需要初始化的subsys進行初始化. 從上面的代碼可以看到.系統中的cgroup subsystem都存放在subsys[].定義如下: static struct cgroup_subsys *subsys[] = { #include } 即所有的subsys都定義在linux/cgroup_subsys.h中. 對照之前分析的數據結構,應該不難理解這段代碼.下面來分析一下里面所遇到的一些重要的子函數. Init_cgroup_root()代碼如下: static void init_cgroup_root(struct cgroupfs_root *root) { struct cgroup *cgrp = &root->top_cgroup; INIT_LIST_HEAD(&root->subsys_list); INIT_LIST_HEAD(&root->root_list); root->number_of_cgroups = 1; cgrp->root = root; cgrp->top_cgroup = cgrp; init_cgroup_housekeeping(cgrp); } 它先初始化root中的幾條鏈表.因爲root中有一個top_cgroup.因此將root->number_of_cgroups置爲1.然後,對root->top_cgroup進行初始化.使root->top_cgroup.root指向root. root->top_cgroup.top_cgroup指向它的本身.因爲root->top_cgroup就是目錄下的第一個cgroup. 最後在init_cgroup_housekeeping()初始化cgroup的鏈表和讀寫鎖. Cgroup_init_subsys()代碼如下: static void __init cgroup_init_subsys(struct cgroup_subsys *ss) { struct cgroup_subsys_state *css; printk(KERN_INFO "Initializing cgroup subsys %s\n", ss->name); /* Create the top cgroup state for this subsystem */ ss->root = &rootnode; css = ss->create(ss, dummytop); /* We don't handle early failures gracefully */ BUG_ON(IS_ERR(css)); init_cgroup_css(css, ss, dummytop); /* Update the init_css_set to contain a subsys * pointer to this state - since the subsystem is * newly registered, all tasks and hence the * init_css_set is in the subsystem's top cgroup. */ init_css_set.subsys[ss->subsys_id] = dummytop->subsys[ss->subsys_id]; need_forkexit_callback |= ss->fork || ss->exit; need_mm_owner_callback |= !!ss->mm_owner_changed; /* At system boot, before all subsystems have been * registered, no tasks have been forked, so we don't * need to invoke fork callbacks here. */ BUG_ON(!list_empty(&init_task.tasks)); ss->active = 1; } dummytop定義如下: #define dummytop (&rootnode.top_cgroup) 在這個函數中: 1):將每個要註冊的subsys->root都指向rootnode. 2):調用subsys->create()生成一個cgroup_subsys_state. 3):調用init_cgroup_css()將dummytop.subsys設置成ss->create()生成的cgroup_subsys_state 4):更新init_css_set->subsys()對應項的值. 5):將ss->active設爲1.表示它已經初始化了. 4.2: cgroup_init() cgroup_init()是cgroup的第二階段的初始化.代碼如下: int __init cgroup_init(void) { int err; int i; struct hlist_head *hhead; err = bdi_init(&cgroup_backing_dev_info); if (err) return err; //將剩下的(不需要在系統啓動時初始化的subsys)的subsys進行初始化 for (i = 0; i struct cgroup_subsys *ss = subsys; if (!ss->early_init) cgroup_init_subsys(ss); } /* Add init_css_set to the hash table */ //將init_css_set添加到css_set_table[ ] hhead = css_set_hash(init_css_set.subsys); hlist_add_head(&init_css_set.hlist, hhead); //註冊cgroup文件系統 err = register_filesystem(&cgroup_fs_type); if (err goto out; //在proc文件系統的根目錄下創建一個名爲cgroups的文件 proc_create("cgroups", 0, NULL, &proc_cgroupstats_operations); out: if (err) bdi_destroy(&cgroup_backing_dev_info); return err; } 這個函數比較簡單.首先.它將剩餘的subsys初始化.然後將init_css_set添加進哈希數組css_set_table[ ]中.在上面的代碼中css_set_hash()是css_set_table的哈希函數.它是css_set->subsys爲哈希鍵值,到css_set_table[ ]中找到對應項.然後調用hlist_add_head()將init_css_set添加到衝突項中. 然後,註冊了cgroup文件系統.這個文件系統也是我們在用戶空間使用cgroup時必須掛載的. 最後,在proc的根目錄下創建了一個名爲cgroups的文件.用來從用戶空間觀察cgroup的狀態. 經過cgroup的兩個階段的初始化, init_css_set, rootnode,subsys已經都初始化完成.表面上看起來它們很複雜,其實,它們只是表示cgroup的初始化狀態而已.例如,如果subsys->root等於rootnode,那表示subsys沒有被其它的cgroup所使用. 五:父子進程之間的cgroup關聯 在上面看到的代碼中.將init_task.cgroup設置爲了init_css_set.我們知道,init_task是系統的第一個進程.所有的過程都是由它創建的.init_task.cgroup到底會在它後面的子進程造成什麼樣的影響呢?接下來我們就來分析這個問題. 5.1:創建進程時的父子進程cgroup關聯 在進程創建的時候,有:do_fork()àcopy_process(),有如下代碼片段: static struct task_struct *copy_process(unsigned long clone_flags, unsigned long stack_start, struct pt_regs *regs, unsigned long stack_size, int __user *child_tidptr, struct pid *pid, int trace) { …… …… cgroup_fork(p); …… cgroup_fork_callbacks(p); …… cgroup_post_fork(p); …… } 上面的代碼片段是創建新進程的時候與cgroup關聯的函數.挨個分析如下: void cgroup_fork(struct task_struct *child) { task_lock(current); child->cgroups = current->cgroups; get_css_set(child->cgroups); task_unlock(current); INIT_LIST_HEAD(&child->cg_list); } 如上面代碼所示,子進程和父進程指向同一個cgroups.並且由於增加了一次引用.所以要調用get_css_set()來增加它的引用計數.最後初始化child->cg_list鏈表. 如代碼註釋上說的,這裏就有一個問題了:在dup_task_struct()爲子進程創建struct task_struct的時候不是已經複製了父進程的cgroups麼?爲什麼這裏還要對它進行一次賦值呢?這裏因爲在dup_task_struct()中沒有持有保護鎖.而這裏又是一個競爭操作.因爲在cgroup_attach_task()中可能會更改進程的cgroups指向.因此通過cgroup_attach_task()所得到的cgroups可能是一個無效的指向.在遞增其引用計數的時候就會因爲它是一個無效的引用而發生錯誤.所以,這個函數在加鎖的情況下進行操作.確保了父子進程之間的同步. cgroup_fork_callbacks()代碼如下, void cgroup_fork_callbacks(struct task_struct *child) { if (need_forkexit_callback) { int i; for (i = 0; i struct cgroup_subsys *ss = subsys; if (ss->fork) ss->fork(ss, child); } } } 它主要是在進程創建時調用subsys中的跟蹤函數:subsys->fork(). 首先來跟蹤一下need_forkexita_callback這個變量.在如下代碼片段中: static void __init cgroup_init_subsys(struct cgroup_subsys *ss) { …… need_forkexit_callback |= ss->fork || ss->exit; …… } 從這段代碼中我們可以看到,如果有subsys定義了fork和exit函數,就會調need_forkexit_callback設置爲1. 回到cgroup_fork_callback()這個函數中.我們發現.進程會跟所有定義了fork的subsys進行這次操作.就算進程沒有在這個subsys中,也會有這個操作. Cgroup_pos_fork()如下所示: void cgroup_post_fork(struct task_struct *child) { if (use_task_css_set_links) { write_lock(&css_set_lock); if (list_empty(&child->cg_list)) list_add(&child->cg_list, &child->cgroups->tasks); write_unlock(&css_set_lock); } 在use_task_css_set_link爲1的情況下.就將子進程鏈入到它所指向的css_set->task鏈表. 那什麼時候會將use_task_css_set_link設置爲1呢?實際上,當你往cgroup中添加進程的時候就會將其置1了. 例如我們之前舉的一個例子中: echo $$ > /dev/cgroup/eric_task/tasks 這個過程就會將use_task_css_set_link置1了.這個過程我們之後再來詳細分析. 5.2:子進程結束時的操作 子進程結束的時候,有: Do_exit() à cgroup_exit(). Cgroup_exit()代碼如下: void cgroup_exit(struct task_struct *tsk, int run_callbacks) { int i; struct css_set *cg; if (run_callbacks && need_forkexit_callback) { for (i = 0; i struct cgroup_subsys *ss = subsys; if (ss->exit) ss->exit(ss, tsk); } } /* * Unlink from the css_set task list if necessary. * Optimistically check cg_list before taking * css_set_lock */ if (!list_empty(&tsk->cg_list)) { write_lock(&css_set_lock); if (!list_empty(&tsk->cg_list)) list_del(&tsk->cg_list); write_unlock(&css_set_lock); } /* Reassign the task to the init_css_set. */ task_lock(tsk); cg = tsk->cgroups; tsk->cgroups = &init_css_set; task_unlock(tsk); if (cg) put_css_set_taskexit(cg); } 這個函數的代碼邏輯比較清晰.首先,如果以1爲調用參數(run_callbacks爲1),且有定義了exit操作的subsys.就調用這個subsys的exit操作. 然後斷開task->cg_list鏈表.將其從所指向的css_set->task鏈上斷開. 最後,斷開當前的cgroup指向.將其指向init_css_set.也就是將其回覆到初始狀態.最後,減少舊指向css_set的引用計數. 在這個函數中,我們來跟蹤分析put_css_set_taskexit(),代碼如下: static inline void put_css_set_taskexit(struct css_set *cg) { __put_css_set(cg, 1); } 跟蹤到__put_css_set()中: static void __put_css_set(struct css_set *cg, int taskexit) { int i; /* * Ensure that the refcount doesn't hit zero while any readers * can see it. Similar to atomic_dec_and_lock(), but for an * rwlock */ if (atomic_add_unless(&cg->refcount, -1, 1)) return; write_lock(&css_set_lock); if (!atomic_dec_and_test(&cg->refcount)) { write_unlock(&css_set_lock); return; } unlink_css_set(cg); write_unlock(&css_set_lock); rcu_read_lock(); for (i = 0; i struct cgroup *cgrp = cg->subsys->cgroup; if (atomic_dec_and_test(&cgrp->count) && notify_on_release(cgrp)) { if (taskexit) set_bit(CGRP_RELEASABLE, &cgrp->flags); check_for_release(cgrp); } } rcu_read_unlock(); kfree(cg); } atomic_add_unless(v,a,u)表示如果v的值不爲u就加a.返回1.如果v的值等於u就返回0 因此,這個函數首先減小css_set的引用計數.如果css_set的引用計數爲1.就會將css_set釋放掉了. 要釋放css_set.首先要釋放css_set上掛載的鏈表.再釋放css_set結構本身所佔空間. 釋放css_set上的掛載鏈表是在unlink_css_set()中完成的.代碼如下: static void unlink_css_set(struct css_set *cg) { struct cg_cgroup_link *link; struct cg_cgroup_link *saved_link; hlist_del(&cg->hlist); css_set_count--; list_for_each_entry_safe(link, saved_link, &cg->cg_links, cg_link_list) { list_del(&link->cg_link_list); list_del(&link->cgrp_link_list); kfree(link); } } 它首先將cg->hlist斷開,也就是將其從css_set_table[ ]中刪除.然後減小css_set_count計數.最後遍歷刪除與css_set關聯的cg_cgroup_link. 另外,在這個函數中還涉及到了notify_on_release的操作.在後面再來詳細分析這一過程.這裏先把它放一下. 六:cgroup文件系統的掛載 Cgroup文件系統定義如下: static struct file_system_type cgroup_fs_type = { .name = "cgroup", .get_sb = cgroup_get_sb, .kill_sb = cgroup_kill_sb, } 根據我們之前有關linux文件系統系列的文析.在掛載文件系統的時候,流程會流入file_system_type.get_sb().也就是cgroup_get_sb().由於該代碼較長.分段分析如下: static int cgroup_get_sb(struct file_system_type *fs_type, int flags, const char *unused_dev_name, void *data, struct vfsmount *mnt) { struct cgroup_sb_opts opts; int ret = 0; struct super_block *sb; struct cgroupfs_root *root; struct list_head tmp_cg_links; /* First find the desired set of subsystems */ //解析掛載參數 ret = parse_cgroupfs_options(data, &opts); if (ret) { if (opts.release_agent) kfree(opts.release_agent); return ret; } 在這一部份,解析掛載的參數,並將解析的結果存放到opts.opts-> subsys_bits表示指定關聯的subsys位圖,opts->flags:掛載的標誌: opts->release_agent表示指定的release_agent路徑. //分配並初始化cgroufs_root root = kzalloc(sizeof(*root), GFP_KERNEL); if (!root) { if (opts.release_agent) kfree(opts.release_agent); return -ENOMEM; } init_cgroup_root(root); /*root->subsys_bits: 該hierarchy上關聯的subsys*/ root->subsys_bits = opts.subsys_bits; root->flags = opts.flags; /*如果帶了release_agent參數,將其copy到root0 if (opts.release_agent) { strcpy(root->release_agent_path, opts.release_agent); kfree(opts.release_agent); } /*初始化一個super block*/ sb = sget(fs_type, cgroup_test_super, cgroup_set_super, root); /*如果發生錯誤*/ if (IS_ERR(sb)) { kfree(root); return PTR_ERR(sb); } 在這一部份,主要分配並初始化了一個cgroupfs_root結構.裏面的子函數init_cgroup_root()我們在之前已經分析過,這裏不再贅述.其實的初始化包括:設置與之關聯的subsys位圖,掛載標誌和release_agent路徑.然後再調用sget()生成一個super_block結構.調用cgroup_test_super來判斷系統中是否有機同的cgroups_root.調用cgroup_set_super來對super_block進行初始化. 在cgroup_set_super()中,將sb->s_fs_info 指向了cgroutfs_root,cgroufs_root.sb指向生成的super_block. 類似的.如果找到的super_block相關聯的cgroupfs_root所表示的subsys_bits和flags與當前cgroupfs_root相同的話,就表示是一個相同的super_block.因爲它們的掛載參數是一樣的. 舉個例子來說明一下有重複super_block的情況: [root@localhost ~]# mount -t cgroup cgroup -o debug /dev/cgroup/ [root@localhost ~]# mount -t cgroup cgroup -o debug /dev/eric_cgroup/ 在上面的例子中,在掛載到/dev/eric_cgroup目錄的時候,就會找到一個相同的super_block.這樣實例上兩者的操作是一樣的.這兩個不同掛載點所代碼的vfsmount會找到同一個super_block.也就是說對其中一個目錄的操作都會同表現在另一個目錄中. /*重複掛載*/ if (sb->s_fs_info != root) { /* Reusing an existing superblock */ BUG_ON(sb->s_root == NULL); kfree(root); root = NULL; } else { /* New superblock */ struct cgroup *cgrp = &root->top_cgroup; struct inode *inode; int i; BUG_ON(sb->s_root != NULL); /*初始化super_block對應的dentry和inode*/ ret = cgroup_get_rootdir(sb); if (ret) goto drop_new_super; inode = sb->s_root->d_inode; mutex_lock(&inode->i_mutex); mutex_lock(&cgroup_mutex); /* * We're accessing css_set_count without locking * css_set_lock here, but that's OK - it can only be * increased by someone holding cgroup_lock, and * that's us. The worst that can happen is that we * have some link structures left over */ /*分配css_set_count個cg_cgroup_link並將它們鏈入到tmp_cg_links*/ ret = allocate_cg_links(css_set_count, &tmp_cg_links); if (ret) { mutex_unlock(&cgroup_mutex); mutex_unlock(&inode->i_mutex); goto drop_new_super; } /*bind subsys 到hierarchy*/ ret = rebind_subsystems(root, root->subsys_bits); if (ret == -EBUSY) { mutex_unlock(&cgroup_mutex); mutex_unlock(&inode->i_mutex); goto drop_new_super; } /* EBUSY should be the only error here */ BUG_ON(ret); /*將root添加到roots鏈入.增加root_count計數*/ list_add(&root->root_list, &roots); root_count++; /*將掛載根目錄dentry的私有結構d_fsdata反映向root->top_cgroup*/ /*將root->top_cgroup.dentry指向掛載的根目錄*/ sb->s_root->d_fsdata = &root->top_cgroup; root->top_cgroup.dentry = sb->s_root; /* Link the top cgroup in this hierarchy into all * the css_set objects */ /*將所有的css_set都和root->top_cgroup關聯起來*/ write_lock(&css_set_lock); for (i = 0; i struct hlist_head *hhead = &css_set_table; struct hlist_node *node; struct css_set *cg; hlist_for_each_entry(cg, node, hhead, hlist) { struct cg_cgroup_link *link; BUG_ON(list_empty(&tmp_cg_links)); link = list_entry(tmp_cg_links.next, struct cg_cgroup_link, cgrp_link_list); list_del(&link->cgrp_link_list); link->cg = cg; list_add(&link->cgrp_link_list, &root->top_cgroup.css_sets); list_add(&link->cg_link_list, &cg->cg_links); } } write_unlock(&css_set_lock); /*釋放tmp_cg_links的多餘項*/ free_cg_links(&tmp_cg_links); BUG_ON(!list_empty(&cgrp->sibling)); BUG_ON(!list_empty(&cgrp->children)); BUG_ON(root->number_of_cgroups != 1); /*在root->top_cgroup下面創建一些文件,包括cgroup共有的和subsys私有的文件*/ cgroup_populate_dir(cgrp); mutex_unlock(&inode->i_mutex); mutex_unlock(&cgroup_mutex); } /*將vfsmount和super_block關聯起來*/ return simple_set_mnt(mnt, sb); drop_new_super: up_write(&sb->s_umount); deactivate_super(sb); free_cg_links(&tmp_cg_links); return ret; } 這一部份,首先判斷找到的super_block是不是之前就存在的.如果是已經存在的,那就用不着再初始化一個cgroupfs_root結構了.將之前分配的結構釋放掉.然後調用simple_set_mnt()將取得的super_block和vfsmount相關聯後退出. 如果super_block是一個新建的.那麼就必須要繼續初始化cgroupfs_root了. 首先,調用cgroup_get_rootdir()初始化super_block對應的dentry和inode. 然後,調用rebind_subsystems()將需要關聯到hierarchy的subsys和root->top_cgroup綁定起來. 最後,將所有的css_set都和root->top_cgroup關聯起來.這樣就可以從root->top_cgroup找到所有的進程了.再調用cgroup_populate_dir()在掛載目錄下創建一些文件,然後,調用simple_set_mnt()將取得的super_block和vfsmount相關聯後退出. 這個函數的流程還算簡單.下面來分析一下里面涉及到的重要的子函數: 6.1: parse_cgroupfs_options()函數分析 這個函數主要是對掛載的參數進行解析.函數代碼如下: static int parse_cgroupfs_options(char *data, struct cgroup_sb_opts *opts) { /*如果掛載的時候沒有帶參數,將o設爲"all".表示將所有 *的subsys都與之關聯 */ char *token, *o = data ?: "all"; opts->subsys_bits = 0; opts->flags = 0; opts->release_agent = NULL; /*各參數是以","分隔的*/ while ((token = strsep(&o, ",")) != NULL) { if (!*token) return -EINVAL; /*如果爲all.表示關聯所有的subsys*/ if (!strcmp(token, "all")) { /* Add all non-disabled subsystems */ int i; opts->subsys_bits = 0; for (i = 0; i struct cgroup_subsys *ss = subsys; if (!ss->disabled) opts->subsys_bits |= 1ul } } /*如果指定參數noprefix.設定ROOT_NOPREFIX標誌*/ /*在指定noprefix的情況下.subsys創建的文件不會帶subsys名稱的前綴*/ else if (!strcmp(token, "noprefix")) { set_bit(ROOT_NOPREFIX, &opts->flags); } /*如果指定了release_agent.分opt->release_agent分配內存,並將參數copy到裏面*/ else if (!strncmp(token, "release_agent=", 14)) { /* Specifying two release agents is forbidden */ if (opts->release_agent) return -EINVAL; opts->release_agent = kzalloc(PATH_MAX, GFP_KERNEL); if (!opts->release_agent) return -ENOMEM; strncpy(opts->release_agent, token + 14, PATH_MAX - 1); opts->release_agent[PATH_MAX - 1] = 0; } /*其它情況下,將所帶參數做爲一個susys名處理.到sussys[]找到 *對應的subsys.然後將opts->subsys_bits中的位置1 */ else { struct cgroup_subsys *ss; int i; for (i = 0; i ss = subsys; if (!strcmp(token, ss->name)) { if (!ss->disabled) set_bit(i, &opts->subsys_bits); break; } } if (i == CGROUP_SUBSYS_COUNT) return -ENOENT; } } /* We can't have an empty hierarchy */ /*如果沒有關聯到subsys.錯誤*/ if (!opts->subsys_bits) return -EINVAL; return 0; } 對照代碼中添加的註釋應該很容易看懂.這裏就不再做詳細分析了. 6.2: rebind_subsystems()函數分析 rebind_subsystems()用來將cgroupfs_root和subsys綁定.代碼如下: static int rebind_subsystems(struct cgroupfs_root *root, unsigned long final_bits) { unsigned long added_bits, removed_bits; struct cgroup *cgrp = &root->top_cgroup; int i; /*root->actual_subsys_bits表示當進root中所關鍵的subsys位圖*/ /*如果在root->actual_subsys_bits中.但沒有在final_bits中.表示這是 *一次remonut的操作.需要將舊的subsys移除.如果在final_bits中 *存在,但沒有在root->actual_subsys_bits中,表示是需要添加的. */ removed_bits = root->actual_subsys_bits & ~final_bits; added_bits = final_bits & ~root->actual_subsys_bits; /* Check that any added subsystems are currently free */ /*如果要關聯的subsys已經在其它的hierarchy中了.失敗. *如果ss->root != &rootnode表示ss已經鏈入了其它的cgroupfs_root */ for (i = 0; i unsigned long bit = 1UL struct cgroup_subsys *ss = subsys; if (!(bit & added_bits)) continue; if (ss->root != &rootnode) { /* Subsystem isn't free */ return -EBUSY; } } /* Currently we don't handle adding/removing subsystems when * any child cgroups exist. This is theoretically supportable * but involves complex error handling, so it's being left until * later */ /*如果root->top_cgroup->children不爲空.表示該hierarchy還要其它的cgroup *是不能被remount的.(新掛載的root->top_cgroup在初始化的時候將children置空了) */ if (!list_empty(&cgrp->children)) return -EBUSY; /* Process each subsystem */ for (i = 0; i struct cgroup_subsys *ss = subsys; unsigned long bit = 1UL /*添加subsys的情況*/ if (bit & added_bits) { /* We're binding this subsystem to this hierarchy */ /* 添加情況下.將cgrp->subsys指向dummytop->subsys * 並更新dummytop->subsys->root.將其指向要添加的root * 最後調用subsys->bind()操作 */ BUG_ON(cgrp->subsys); BUG_ON(!dummytop->subsys); BUG_ON(dummytop->subsys->cgroup != dummytop); cgrp->subsys = dummytop->subsys; cgrp->subsys->cgroup = cgrp; list_add(&ss->sibling, &root->subsys_list); rcu_assign_pointer(ss->root, root); if (ss->bind) ss->bind(ss, cgrp); } /*移除subsys的情況*/ else if (bit & removed_bits) { /* 移除操作,將對應的cgroup_subsys_state迴歸到原來的樣子.並且也需要 * 將與其subsys bind */ /* We're removing this subsystem */ BUG_ON(cgrp->subsys != dummytop->subsys); BUG_ON(cgrp->subsys->cgroup != cgrp); if (ss->bind) ss->bind(ss, dummytop); dummytop->subsys->cgroup = dummytop; cgrp->subsys = NULL; rcu_assign_pointer(subsys->root, &rootnode); list_del(&ss->sibling); } else if (bit & final_bits) { /* Subsystem state should already exist */ BUG_ON(!cgrp->subsys); } else { /* Subsystem state shouldn't exist */ BUG_ON(cgrp->subsys); } } /*更新root的位圖*/ root->subsys_bits = root->actual_subsys_bits = final_bits; synchronize_rcu(); return 0; } 從這個函數也可以看出來.rootnode就是起一個參照的作用.用來判斷subsys是否處於初始化狀態. 6.3: cgroup_populate_dir()函數分析 cgroup_populate_dir()用來在掛載目錄下創建交互文件.代碼如下: static int cgroup_populate_dir(struct cgroup *cgrp) { int err; struct cgroup_subsys *ss; /* First clear out any existing files */ /*先將cgrp所在的目錄清空*/ cgroup_clear_directory(cgrp->dentry); /*創建files所代碼的幾個文件*/ err = cgroup_add_files(cgrp, NULL, files, ARRAY_SIZE(files)); if (err return err; /*如果是頂層top_cgroup.創建cft_release_agent所代碼的文件*/ if (cgrp == cgrp->top_cgroup) { if ((err = cgroup_add_file(cgrp, NULL, &cft_release_agent)) return err; } /*對所有與cgrp->root關聯的subsys都調用populate()*/ for_each_subsys(cgrp->root, ss) { if (ss->populate && (err = ss->populate(ss, cgrp)) return err; } return 0; } 這個函數比較簡單.跟蹤cgroup_add_file().如下: nt cgroup_add_file(struct cgroup *cgrp, struct cgroup_subsys *subsys, const struct cftype *cft) { struct dentry *dir = cgrp->dentry; struct dentry *dentry; int error; char name[MAX_CGROUP_TYPE_NAMELEN + MAX_CFTYPE_NAME + 2] = { 0 }; /*如果有指定subsys.且沒有使用ROOT_NOPREFIX標誌.需要在名稱前加上 *subsys的名稱 */ if (subsys && !test_bit(ROOT_NOPREFIX, &cgrp->root->flags)) { strcpy(name, subsys->name); strcat(name, "."); } /*將cft->name鏈接到name代表的字串後面*/ strcat(name, cft->name); BUG_ON(!mutex_is_locked(&dir->d_inode->i_mutex)); /*到cgroup所在的目錄下尋找name所表示的dentry,如果不存在,則新建之*/ dentry = lookup_one_len(name, dir, strlen(name)); if (!IS_ERR(dentry)) { /*創建文件inode*/ error = cgroup_create_file(dentry, 0644 | S_IFREG, cgrp->root->sb); /*使dentry->d_fsdata指向文件所代表的cftype*/ if (!error) dentry->d_fsdata = (void *)cft; dput(dentry); } else error = PTR_ERR(dentry); return error; } cgroup_create_file()函數代碼如下: static int cgroup_create_file(struct dentry *dentry, int mode, struct super_block *sb) { static struct dentry_operations cgroup_dops = { .d_iput = cgroup_diput, }; struct inode *inode; if (!dentry) return -ENOENT; if (dentry->d_inode) return -EEXIST; /*分配一個inode*/ inode = cgroup_new_inode(mode, sb); if (!inode) return -ENOMEM; /*如果新建的是目錄*/ if (S_ISDIR(mode)) { inode->i_op = &cgroup_dir_inode_operations; inode->i_fop = &simple_dir_operations; /* start off with i_nlink == 2 (for "." entry) */ inc_nlink(inode); /* start with the directory inode held, so that we can * populate it without racing with another mkdir */ mutex_lock_nested(&inode->i_mutex, I_MUTEX_CHILD); } /*新建一般文件*/ else if (S_ISREG(mode)) { inode->i_size = 0; inode->i_fop = &cgroup_file_operations; } dentry->d_op = &cgroup_dops; /*將dentry和inode關聯起來*/ d_instantiate(dentry, inode); dget(dentry); /* Extra count - pin the dentry in core */ return 0; } 從這個函數我們可以看到.如果是目錄的話,對應的操作集爲simple_dir_operations和cgroup_dir_inode_operations.它與cgroup_get_rootdir()中對根目錄對應的inode所設置的操作集是一樣的.如果是一般文件,它的操作集爲cgroup_file_operations. 在這裏,先將cgroup中的文件操作放到一邊,我們在之後再來詳細分析這個過程. 現在.我們已經將cgroup文件系統的掛載分析完成.接下來看它下面子層cgroup的創建. 七:創建子層cgroup 在目錄下通過mkdir調用就可以創建一個子層cgroup.下面就分析這一過程: 經過上面的分析可以得知,cgroup中目錄的操作集爲: cgroup_dir_inode_operations.結構如下: static struct inode_operations cgroup_dir_inode_operations = { .lookup = simple_lookup, .mkdir = cgroup_mkdir, .rmdir = cgroup_rmdir, .rename = cgroup_rename, }; 從上面看到,對應mkdir的入口爲cgroup_mkdir().代碼如下: static int cgroup_mkdir(struct inode *dir, struct dentry *dentry, int mode) { /*找到它的上一級cgroup*/ struct cgroup *c_parent = dentry->d_parent->d_fsdata; /* the vfs holds inode->i_mutex already */ /*調用cgroup_create創建cgroup*/ return cgroup_create(c_parent, dentry, mode | S_IFDIR); } 跟蹤cgroup_create().代碼如下: static long cgroup_create(struct cgroup *parent, struct dentry *dentry, int mode) { struct cgroup *cgrp; struct cgroupfs_root *root = parent->root; int err = 0; struct cgroup_subsys *ss; struct super_block *sb = root->sb; /*分配並初始化一個cgroup*/ cgrp = kzalloc(sizeof(*cgrp), GFP_KERNEL); if (!cgrp) return -ENOMEM; /* Grab a reference on the superblock so the hierarchy doesn't * get deleted on unmount if there are child cgroups. This * can be done outside cgroup_mutex, since the sb can't * disappear while someone has an open control file on the * fs */ atomic_inc(&sb->s_active); mutex_lock(&cgroup_mutex); init_cgroup_housekeeping(cgrp); /*設置cgrp的層次關係*/ cgrp->parent = parent; cgrp->root = parent->root; cgrp->top_cgroup = parent->top_cgroup; /*如果上一級cgroup設置了CGRP_NOTIFY_ON_RELEASE.那cgrp也設置這個標誌*/ if (notify_on_release(parent)) set_bit(CGRP_NOTIFY_ON_RELEASE, &cgrp->flags); /*調用subsys_create()生成cgroup_subsys_state.並與cgrp相關聯*/ for_each_subsys(root, ss) { struct cgroup_subsys_state *css = ss->create(ss, cgrp); if (IS_ERR(css)) { err = PTR_ERR(css); goto err_destroy; } init_cgroup_css(css, ss, cgrp); } /*將cgrp添加到上一層cgroup的children鏈表*/ list_add(&cgrp->sibling, &cgrp->parent->children); /*增加root的cgroups數目計數*/ root->number_of_cgroups++; /*在當前目錄生成一個目錄*/ err = cgroup_create_dir(cgrp, dentry, mode); if (err goto err_remove; /* The cgroup directory was pre-locked for us */ BUG_ON(!mutex_is_locked(&cgrp->dentry->d_inode->i_mutex)); /*在cgrp下創建幾個交互文件*/ err = cgroup_populate_dir(cgrp); /* If err mutex_unlock(&cgroup_mutex); mutex_unlock(&cgrp->dentry->d_inode->i_mutex); return 0; err_remove: list_del(&cgrp->sibling); root->number_of_cgroups--; err_destroy: for_each_subsys(root, ss) { if (cgrp->subsys[ss->subsys_id]) ss->destroy(ss, cgrp); } mutex_unlock(&cgroup_mutex); /* Release the reference count that we took on the superblock */ deactivate_super(sb); kfree(cgrp); return err; } 在這個函數中,主要分配並初始化了一個cgroup結構.並且將它和它的上一層目錄以及整個cgroupfs_root構成一個空間層次關係.然後,再調用subsys>create()操作函數.來讓subsys知道已經創建了一個cgroup結構. 爲了理順這一部份.將前面分析的cgroup文件系統掛載和cgroup的創建.以及接下來要分析的attach_task()操作總結成一個圖.如下示: 八:cgroup中文件的操作 接下來,就來看cgroup文件的操作.在上面曾分析到:文件對應的操作集爲cgroup_file_operations.如下所示: static struct file_operations cgroup_file_operations = { .read = cgroup_file_read, .write = cgroup_file_write, .llseek = generic_file_llseek, .open = cgroup_file_open, .release = cgroup_file_release, } 7.1:cgrou文件的open操作 對應的函數爲cgroup_file_open().代碼如下: static int cgroup_file_open(struct inode *inode, struct file *file) { int err; struct cftype *cft; err = generic_file_open(inode, file); if (err) return err; /*取得文件對應的struct cftype*/ cft = __d_cft(file->f_dentry); if (!cft) return -ENODEV; /*如果定義了read_map或者是read_seq_string*/ if (cft->read_map || cft->read_seq_string) { struct cgroup_seqfile_state *state = kzalloc(sizeof(*state), GFP_USER); if (!state) return -ENOMEM; state->cft = cft; state->cgroup = __d_cgrp(file->f_dentry->d_parent); file->f_op = &cgroup_seqfile_operations; err = single_open(file, cgroup_seqfile_show, state); if (err kfree(state); } /*否則調用cft->open()*/ else if (cft->open) err = cft->open(inode, file); else err = 0; return err; } 有兩種情況.一種是定義了read_map或者是read_seq_string的情況.這種情況下,它對應的操作集爲cgroup_seqfile_operations.如果是其它的情況.調用cftype的open()函數.第一種情況,我們等以後遇到了這樣的情況再來詳細分析. 7.2:cgroup文件的read操作 對應函數爲cgroup_file_read().代碼如下: static ssize_t cgroup_file_read(struct file *file, char __user *buf, size_t nbytes, loff_t *ppos) { struct cftype *cft = __d_cft(file->f_dentry); struct cgroup *cgrp = __d_cgrp(file->f_dentry->d_parent); if (!cft || cgroup_is_removed(cgrp)) return -ENODEV; if (cft->read) return cft->read(cgrp, cft, file, buf, nbytes, ppos); if (cft->read_u64) return cgroup_read_u64(cgrp, cft, file, buf, nbytes, ppos); if (cft->read_s64) return cgroup_read_s64(cgrp, cft, file, buf, nbytes, ppos); return -EINVAL; } 如上代碼所示.read操作會轉入到cftype的read()或者read_u64或者read_s64的函數中. 7.3:cgroup文件的wirte操作 對應的操作函數是cgroup_file_write().如下示: static ssize_t cgroup_file_write(struct file *file, const char __user *buf, size_t nbytes, loff_t *ppos) { struct cftype *cft = __d_cft(file->f_dentry); struct cgroup *cgrp = __d_cgrp(file->f_dentry->d_parent); if (!cft || cgroup_is_removed(cgrp)) return -ENODEV; if (cft->write) return cft->write(cgrp, cft, file, buf, nbytes, ppos); if (cft->write_u64 || cft->write_s64) return cgroup_write_X64(cgrp, cft, file, buf, nbytes, ppos); if (cft->write_string) return cgroup_write_string(cgrp, cft, file, buf, nbytes, ppos); if (cft->trigger) { int ret = cft->trigger(cgrp, (unsigned int)cft->private); return ret ? ret : nbytes; } return -EINVAL; } 從上面可以看到.最終的操作會轉入到cftype的write或者wirte_u64或者wirte_string或者trigger函數中. 7.4:debug subsytem分析 以debug subsystem爲例來說明cgroup中的文件操作 Debug subsys定義如下: struct cgroup_subsys debug_subsys = { .name = "debug", .create = debug_create, .destroy = debug_destroy, .populate = debug_populate, .subsys_id = debug_subsys_id, } 在cgroup_init_subsys()中,會以dummytop爲參數調用debug.create().對應函數爲debug_create().代碼如下: static struct cgroup_subsys_state *debug_create(struct cgroup_subsys *ss, struct cgroup *cont) { struct cgroup_subsys_state *css = kzalloc(sizeof(*css), GFP_KERNEL); if (!css) return ERR_PTR(-ENOMEM); return css; } 這裏沒啥好說的,就是分配了一個cgroup_subsys_state結構. 然後,將cgroup掛載.指令如下: [root@localhost ~]# mount -t cgroup cgroup -o debug /dev/cgroup/ 在rebind_subsystems()中,會調用subsys的bind函數.但在debug中無此接口.故不需要考慮. 然後在cgroup_populate_dir()中會調用populate接口.對應函數爲debug_populate().代碼如下: static int debug_populate(struct cgroup_subsys *ss, struct cgroup *cont) { return cgroup_add_files(cont, ss, files, ARRAY_SIZE(files)); } Debug中的files定義如下: static struct cftype files[] = { { .name = "cgroup_refcount", .read_u64 = cgroup_refcount_read, }, { .name = "taskcount", .read_u64 = taskcount_read, }, { .name = "current_css_set", .read_u64 = current_css_set_read, }, { .name = "current_css_set_refcount", .read_u64 = current_css_set_refcount_read, }, { .name = "releasable", .read_u64 = releasable_read, }, } 來觀察一下 /dev/cgroup下的文件: [root@localhost ~]# tree /dev/cgroup/ /dev/cgroup/ |-- debug.cgroup_refcount |-- debug.current_css_set |-- debug.current_css_set_refcount |-- debug.releasable |-- debug.taskcount |-- notify_on_release |-- release_agent `-- tasks 0 directories, 8 files 上面帶debug字樣的文件是從debug subsys中創建的.其它的是cgroup.c的files中創建的. 我們先來分析每一個subsys共有的文件.即tasks,release_agent和notify_on_release. 7.5:task文件操作 Tasks文件對應的cftype結構如下: static struct cftype files[] = { { .name = "tasks", .open = cgroup_tasks_open, .write_u64 = cgroup_tasks_write, .release = cgroup_tasks_release, .private = FILE_TASKLIST, } 7.5.1:task文件的open操作 當打開文件時,流程就會轉入cgroup_tasks_open().代碼如下: static int cgroup_tasks_open(struct inode *unused, struct file *file) { /*取得該文件所在層次的cgroup*/ struct cgroup *cgrp = __d_cgrp(file->f_dentry->d_parent); pid_t *pidarray; int npids; int retval; /* Nothing to do for write-only files */ /*如果是隻寫的文件系統*/ if (!(file->f_mode & FMODE_READ)) return 0; /* * If cgroup gets more users after we read count, we won't have * enough space - tough. This race is indistinguishable to the * caller from the case that the additional cgroup users didn't * show up until sometime later on. */ /*得到該層cgroup所關聯的進程個數*/ npids = cgroup_task_count(cgrp); /*爲npids個進程的pid存放分配空間*/ pidarray = kmalloc(npids * sizeof(pid_t), GFP_KERNEL); if (!pidarray) return -ENOMEM; /* 將與cgroup關聯進程的pid存放到pid_array_load數組. * 並且按照從小到大的順序排列 */ npids = pid_array_load(pidarray, npids, cgrp); sort(pidarray, npids, sizeof(pid_t), cmppid, NULL); /* * Store the array in the cgroup, freeing the old * array if necessary */ /* 將npids,pidarray信息存放到cgroup中.如果cgroup之前 * 就有task_pids.將其佔放的空間釋放 */ down_write(&cgrp->pids_mutex); kfree(cgrp->tasks_pids); cgrp->tasks_pids = pidarray; cgrp->pids_length = npids; cgrp->pids_use_count++; up_write(&cgrp->pids_mutex); /*將文件對應的操作集更改爲cgroup_task_operations*/ file->f_op = &cgroup_tasks_operations; retval = seq_open(file, &cgroup_tasks_seq_operations); /*如果操作失敗,將cgroup中的pid信息釋放*/ if (retval) { release_cgroup_pid_array(cgrp); return retval; } ((struct seq_file *)file->private_data)->private = cgrp; return 0; } 首先,我們來思考一下這個問題:怎麼得到與cgroup關聯的進程呢? 回到在上面列出來的數據結構關係圖.每個進程都會指向一個css_set.而與這個css_set關聯的所有進程都會鏈入到css_set->tasks鏈表.而cgroup又可能通過一箇中間結構cg_cgroup_link來尋找所有與之關聯的所有css_set.從而可以得到與cgroup關聯的所有進程. 在上面的代碼中,通過調用cgroup_task_count()來得到與之關聯的進程數目,代碼如下: int cgroup_task_count(const struct cgroup *cgrp) { int count = 0; struct cg_cgroup_link *link; read_lock(&css_set_lock); list_for_each_entry(link, &cgrp->css_sets, cgrp_link_list) { count += atomic_read(&link->cg->refcount); } read_unlock(&css_set_lock); return count; } 它就是遍歷cgro->css_sets.並調其轉換爲cg_cgroup_link.再從這個link得到css_set.這個css_set的引用計數就是與這個指向這個css_set的task數目. 在代碼中,是通過pid_array_load()來得到與cgroup關聯的task,並且將進程的pid寫入數組pidarray中.代碼如下: static int pid_array_load(pid_t *pidarray, int npids, struct cgroup *cgrp) { int n = 0; struct cgroup_iter it; struct task_struct *tsk; cgroup_iter_start(cgrp, &it); while ((tsk = cgroup_iter_next(cgrp, &it))) { if (unlikely(n == npids)) break; pidarray[n++] = task_pid_vnr(tsk); } cgroup_iter_end(cgrp, &it); return n; } 我們在這裏遇到了一個新的結構:struct cgroup_iter.它是cgroup的一個迭代器,通過它可以遍歷取得與cgroup關聯的task.它的使用方法爲: 1:調用cgroup_iter_start()來初始化這個迭代碼. 2:調用cgroup_iter_next()用來取得cgroup中的下一個task 3:使用完了,調用cgroup_iner_end(). 下面來分析這三個過程: Cgroup_iter_start()代碼如下: void cgroup_iter_start(struct cgroup *cgrp, struct cgroup_iter *it) { /* * The first time anyone tries to iterate across a cgroup, * we need to enable the list linking each css_set to its * tasks, and fix up all existing tasks. */ if (!use_task_css_set_links) cgroup_enable_task_cg_lists(); read_lock(&css_set_lock); it->cg_link = &cgrp->css_sets; cgroup_advance_iter(cgrp, it); } 我們在這裏再次遇到了use_task_css_set_links變量.在之前分析cgroup_post_fork()中的時候,我們曾說過,只有在use_task_css_set_link設置爲1的時候,纔會調task->cg_list鏈入到css_set->tasks中. 所以,在這個地方,如果use_task_css_set_link爲0.那就必須要將之前所有的進程都鏈入到它所指向的css_set->tasks鏈表.這個過程是在cgroup_enable_task_cg_lists()完成的,這個函數相當簡單,就是一個task的遍歷,然後就是鏈表的鏈入,在這裏就不再詳細分析了.請自行閱讀它的代碼.*^_^* 然後,將it->cg_link指向cgrp->css_sets.我們在前面說過,可以通過cgrp->css_sets就可以得得所有的與cgroup關聯的css_set. 到這裏,這個迭代器裏面還是空的,接下來往裏面填充數據.這個過程是在cgroup_advance_iter()中完成,代碼如下示: static void cgroup_advance_iter(struct cgroup *cgrp, struct cgroup_iter *it) { struct list_head *l = it->cg_link; struct cg_cgroup_link *link; struct css_set *cg; /* Advance to the next non-empty css_set */ do { l = l->next; if (l == &cgrp->css_sets) { it->cg_link = NULL; return; } link = list_entry(l, struct cg_cgroup_link, cgrp_link_list); cg = link->cg; } while (list_empty(&cg->tasks)); it->cg_link = l; it->task = cg->tasks.next; } 通過前面的分析可得知,可通過it->cg_link找到與之關聯的css_set,然後再通過css_set找到與它關聯的task鏈表.因此每次往cgroup迭代器裏填充數據,就是找到一個tasks鏈表不爲空的css_set.取數據就從css_set->tasks中取.如果數據取完了,就找下一個tasks鏈表不爲空的css_set. 這樣,這個函數的代碼就很簡單了.它就是找到it->cg_link上tasks鏈表不爲空的css_set項. cgroup_iter_next()的代碼如下: struct task_struct *cgroup_iter_next(struct cgroup *cgrp, struct cgroup_iter *it) { struct task_struct *res; struct list_head *l = it->task; /* If the iterator cg is NULL, we have no tasks */ if (!it->cg_link) return NULL; res = list_entry(l, struct task_struct, cg_list); /* Advance iterator to find next entry */ l = l->next; if (l == &res->cgroups->tasks) { /* We reached the end of this task list - move on to * the next cg_cgroup_link */ cgroup_advance_iter(cgrp, it); } else { it->task = l; } return res; } 如果it->cg_link爲空表示it->cg_link已經遍歷完了,也就不存放在task了.否則,從it->task中取得task.如果已經是最後一個task就必須要調用cgroup_advance_iter()填充迭代器裏面的數據.最後將取得的task返回. cgroup_iter_end()用來對迭代碼進行收尾的工作,代碼如下: void cgroup_iter_end(struct cgroup *cgrp, struct cgroup_iter *it) { read_unlock(&css_set_lock); } 它就是釋放了在cgroup_iter_start()中持有的鎖. 回到cgroup_tasks_open()中.我們接下來會遇到kernel爲sequential file提供的一組接口.首先在代碼遇到的是seq_open().代碼如下: int seq_open(struct file *file, const struct seq_operations *op) { struct seq_file *p = file->private_data; if (!p) { p = kmalloc(sizeof(*p), GFP_KERNEL); if (!p) return -ENOMEM; file->private_data = p; } memset(p, 0, sizeof(*p)); mutex_init(&p->lock); p->op = op; file->f_version = 0; /* SEQ files support lseek, but not pread/pwrite */ file->f_mode &= ~(FMODE_PREAD | FMODE_PWRITE); return 0; } 從代碼中可以看出,它就是初始化了一個struct seq_file結構.並且將其關聯到file->private_data.在這裏要注意將seq_file->op設置成了參數op.在我們分析的這個情景中,也就是cgroup_tasks_seq_operations.這個在我們分析文件的讀操作的時候會用到的. 7.5.2:task文件的read操作 從上面的代碼中可看到.在open的時候,更改了file->f_op.將其指向了cgroup_tasks_operations.該結構如下: static struct file_operations cgroup_tasks_operations = { .read = seq_read, .llseek = seq_lseek, .write = cgroup_file_write, .release = cgroup_tasks_release, } 相應的,read操作就會轉入到seq_read()中.由於該函數篇幅較大,這裏就不列出了.感興趣的可以自己跟蹤看一下,其它就是循環調用seq_file->op->start() à seq_file->op->show() à seq_file->op->next() à seq_file->op->stop()的過程. 我們在上面分析task文件的open操作的時候,曾經提配過,seq_file->op被指向了cgroup_tasks_seq_operations.定義如下: static struct seq_operations cgroup_tasks_seq_operations = { .start = cgroup_tasks_start, .stop = cgroup_tasks_stop, .next = cgroup_tasks_next, .show = cgroup_tasks_show, } Cgroup_tasks_start()代碼如下: static void *cgroup_tasks_start(struct seq_file *s, loff_t *pos) { /* * Initially we receive a position value that corresponds to * one more than the last pid shown (or 0 on the first call or * after a seek to the start). Use a binary-search to find the * next pid to display, if any */ struct cgroup *cgrp = s->private; int index = 0, pid = *pos; int *iter; down_read(&cgrp->pids_mutex); if (pid) { int end = cgrp->pids_length; while (index int mid = (index + end) / 2; if (cgrp->tasks_pids[mid] == pid) { index = mid; break; } else if (cgrp->tasks_pids[mid] index = mid + 1; else end = mid; } } /* If we're off the end of the array, we're done */ if (index >= cgrp->pids_length) return NULL; /* Update the abstract position to be the actual pid that we found */ iter = cgrp->tasks_pids + index; *pos = *iter; return iter; } 它以二分法從cgrp->tasks_pids[ ]中去尋找第一個大於或者等於參數*pos值的項.如果找到了,返回該項.如果沒找到.返回NULL. cgroup_tasks_show()代碼如下: static int cgroup_tasks_show(struct seq_file *s, void *v) { return seq_printf(s, "%d\n", *(int *)v); } 它就是將pid轉換爲了字符串. cgroup_tasks_next()就是找到數組中的下一項.代碼如下: static void *cgroup_tasks_next(struct seq_file *s, void *v, loff_t *pos) { struct cgroup *cgrp = s->private; int *p = v; int *end = cgrp->tasks_pids + cgrp->pids_length; /* * Advance to the next pid in the array. If this goes off the * end, we're done */ p++; if (p >= end) { return NULL; } else { *pos = *p; return p; } } cgroup_tasks_stop()代碼如下: static void cgroup_tasks_stop(struct seq_file *s, void *v) { struct cgroup *cgrp = s->private; up_read(&cgrp->pids_mutex); } 它只是釋放了在cgroup_tasks_start()中持有的讀寫鎖. 7.5.3:task文件的close操作 Task文件close時,調用的相應接口爲cgroup_tasks_release().代碼如下: static int cgroup_tasks_release(struct inode *inode, struct file *file) { struct cgroup *cgrp = __d_cgrp(file->f_dentry->d_parent); if (!(file->f_mode & FMODE_READ)) return 0; release_cgroup_pid_array(cgrp); return seq_release(inode, file); } 它就是將cgroup中的pid信息與seqfile信息釋放掉. 到這裏,我們已經分析完了task文件的open,read,close操作.我們現在就可以實現一下,看上面的分析是否正確. 在前面已經分析中cgroupfs_root.top_cgroup會將系統中的所有css_set與之關聯起來,那麼通過cgroupfs_root_top_cgroup找到的進程應該是系統當前的所有進程.那麼相應的,在掛載目錄的task文件的內容.應該是系統中所有進程的pid. 如下所示: [root@localhost cgroup]# cat tasks 1 2 3 ……… ……… 2578 其實,這樣做是cgroup子系統開發者特意設置的.它表示所有的進程都在hierarchy的控制之下. 反過來,當我們在掛載目錄mkdir一個目錄,它下面的task文件內容應該是空的.因爲在mkdir後,它對應的cgroup並沒有關聯任何task. 如下所示: [root@localhost cgroup]# mkdir eric [root@localhost cgroup]# cat eric/tasks [root@localhost cgroup]# 下面我們來看一下task文件的寫操作,也就是怎樣將進程添加進cgroup. 7.5.4:task文件的write操作 根據上面的文件,可得知task文件的write操作對應的函數爲int cgroup_tasks_write().代碼如下: static int cgroup_tasks_write(struct cgroup *cgrp, struct cftype *cft, u64 pid) { int ret; /*如果cgroup已經被移除了,非法*/ if (!cgroup_lock_live_group(cgrp)) return -ENODEV; /*將PID爲pid的進程與cgroup關聯*/ ret = attach_task_by_pid(cgrp, pid); cgroup_unlock(); return ret; } Attach_task_by_pid()的代碼如下: static int attach_task_by_pid(struct cgroup *cgrp, u64 pid) { struct task_struct *tsk; int ret; /*如果pid不爲0.尋找PID爲pid的task.並增加其引用計數*/ if (pid) { rcu_read_lock(); tsk = find_task_by_vpid(pid); if (!tsk || tsk->flags & PF_EXITING) { rcu_read_unlock(); return -ESRCH; } get_task_struct(tsk); rcu_read_unlock(); if ((current->euid) && (current->euid != tsk->uid) && (current->euid != tsk->suid)) { put_task_struct(tsk); return -EACCES; } } /*如果pid爲0.表示是將當前進程添加進cgroup*/ else { tsk = current; get_task_struct(tsk); } /*將cgroup與task相關聯*/ ret = cgroup_attach_task(cgrp, tsk); /*操作完成,減少其引用計數*/ put_task_struct(tsk); return ret; } 如果寫入的是一個不這0的數,表示的是進程的PID值.如果是寫入0,表示是將當前進程.這個操作的核心操作是cgroup_attach_task().代碼如下: int cgroup_attach_task(struct cgroup *cgrp, struct task_struct *tsk) { int retval = 0; struct cgroup_subsys *ss; struct cgroup *oldcgrp; struct css_set *cg = tsk->cgroups; struct css_set *newcg; struct cgroupfs_root *root = cgrp->root; int subsys_id; /*得到與cgroup關聯的第一個subsys的序號*/ get_first_subsys(cgrp, NULL, &subsys_id); /* Nothing to do if the task is already in that cgroup */ /*找到這個進程之前所屬的cgroup*/ oldcgrp = task_cgroup(tsk, subsys_id); /*如果已經在這個cgrp裏面了.*/ if (cgrp == oldcgrp) return 0; /* 遍歷與hierarchy關聯的subsys * 如果subsys定義了can_attach函數,就調用它 */ for_each_subsys(root, ss) { if (ss->can_attach) { retval = ss->can_attach(ss, cgrp, tsk); if (retval) return retval; } } /* * Locate or allocate a new css_set for this task, * based on its final set of cgroups */ /*找到這個task所關聯的css_set.如果不存在,則新建一個*/ newcg = find_css_set(cg, cgrp); if (!newcg) return -ENOMEM; task_lock(tsk); /*如果task正在執行exit操作*/ if (tsk->flags & PF_EXITING) { task_unlock(tsk); put_css_set(newcg); return -ESRCH; } /*將tak->cgroup指向這個css_set*/ rcu_assign_pointer(tsk->cgroups, newcg); task_unlock(tsk); /* Update the css_set linked lists if we're using them */ /*更改task->cg_list*/ write_lock(&css_set_lock); if (!list_empty(&tsk->cg_list)) { list_del(&tsk->cg_list); list_add(&tsk->cg_list, &newcg->tasks); } write_unlock(&css_set_lock); /* 遍歷與hierarchy關聯的subsys * 如果subsys定義了attach 函數,就調用它 */ for_each_subsys(root, ss) { if (ss->attach) ss->attach(ss, cgrp, oldcgrp, tsk); } set_bit(CGRP_RELEASABLE, &oldcgrp->flags); synchronize_rcu(); /*減小舊指向的引用計數*/ put_css_set(cg); return 0; } 這個函數邏輯很清楚,它就是初始化task->cgroup.然後將它和subsys相關聯.可自行參照代碼中的註釋進行分析.這裏就不再贅述了. 在這裏,詳細分析一下find_css_set()函數,這個函數有點意思.代碼如下: static struct css_set *find_css_set( struct css_set *oldcg, struct cgroup *cgrp) { struct css_set *res; struct cgroup_subsys_state *template[CGROUP_SUBSYS_COUNT]; int i; struct list_head tmp_cg_links; struct cg_cgroup_link *link; struct hlist_head *hhead; /* First see if we already have a cgroup group that matches * the desired set */ read_lock(&css_set_lock); /*尋找從oldcg轉換爲cgrp的css_set.如果不存在,返回NULL */ res = find_existing_css_set(oldcg, cgrp, template); /*如果css_set已經存在,增加其引用計數後退出*/ if (res) get_css_set(res); read_unlock(&css_set_lock); if (res) return res; 這一部份,先從哈希數組中搜索從oldcg轉換cgrp的css_set.如果不存在,返回NULL.如果在哈希數組中存放,增加其引用計數返回即可. Find_existing_css_set()的代碼如下: static struct css_set *find_existing_css_set( struct css_set *oldcg, struct cgroup *cgrp, struct cgroup_subsys_state *template[]) { int i; struct cgroupfs_root *root = cgrp->root; struct hlist_head *hhead; struct hlist_node *node; struct css_set *cg; /* Built the set of subsystem state objects that we want to * see in the new css_set */ for (i = 0; i if (root->subsys_bits & (1UL /* Subsystem is in this hierarchy. So we want * the subsystem state from the new * cgroup */ template = cgrp->subsys; } else { /* Subsystem is not in this hierarchy, so we * don't want to change the subsystem state */ template = oldcg->subsys; } } hhead = css_set_hash(template); hlist_for_each_entry(cg, node, hhead, hlist) { if (!memcmp(template, cg->subsys, sizeof(cg->subsys))) { /* All subsystems matched */ return cg; } } /* No existing cgroup group matched */ return NULL; } 如果subsys與新的cgroup相關聯,那麼它指向新的cgroup->subsys[]中的對應項.否則指向舊的cgrop的對應項.這樣做主要是因爲,該進程可能還被關聯在其它的hierarchy中.所以要保持它在其它hierarchy中的信息. 最後,在css_set_table[ ]中尋找看是否有與template相等的項.有的話返回該項.如果沒有.返回NULL. /*分配一個css_set*/ res = kmalloc(sizeof(*res), GFP_KERNEL); if (!res) return NULL; /* Allocate all the cg_cgroup_link objects that we'll need */ /*分配root_count項cg_cgroup_link*/ if (allocate_cg_links(root_count, &tmp_cg_links) kfree(res); return NULL; } /* 初始化剛分配的css_set */ atomic_set(&res->refcount, 1); INIT_LIST_HEAD(&res->cg_links); INIT_LIST_HEAD(&res->tasks); INIT_HLIST_NODE(&res->hlist); /* Copy the set of subsystem state objects generated in * find_existing_css_set() */ /*設置css_set->subsys*/ memcpy(res->subsys, template, sizeof(res->subsys)); 運行到這裏的話.表示沒有從css_set_table[ ]中找到相應項.因此需要分配並初始化一個css_set結構.並且設置css_set的subsys域. write_lock(&css_set_lock); /* Add reference counts and links from the new css_set. */ /*遍歷所有的subsys以及css_set 中的subsys[ ]. *建立task所在的cgroup到css_set的引用 */ for (i = 0; i struct cgroup *cgrp = res->subsys->cgroup; struct cgroup_subsys *ss = subsys; atomic_inc(&cgrp->count); /* * We want to add a link once per cgroup, so we * only do it for the first subsystem in each * hierarchy */ if (ss->root->subsys_list.next == &ss->sibling) { BUG_ON(list_empty(&tmp_cg_links)); link = list_entry(tmp_cg_links.next, struct cg_cgroup_link, cgrp_link_list); list_del(&link->cgrp_link_list); list_add(&link->cgrp_link_list, &cgrp->css_sets); link->cg = res; list_add(&link->cg_link_list, &res->cg_links); } } /*似乎沒有地方會更改rootnode.subsys_list.?這裏的判斷大部份情況是滿足的*/ if (list_empty(&rootnode.subsys_list)) { /*建立這個css_set到dumytop的引用*/ /* 這樣做,是爲了讓新建的hierarchy能夠關聯到所有的進程*/ link = list_entry(tmp_cg_links.next, struct cg_cgroup_link, cgrp_link_list); list_del(&link->cgrp_link_list); list_add(&link->cgrp_link_list, &dummytop->css_sets); link->cg = res; list_add(&link->cg_link_list, &res->cg_links); } BUG_ON(!list_empty(&tmp_cg_links)); 這一部份的關鍵操作都在代碼中添加了相應的註釋.如果系統中存在多個hierarchy.那麼這個進程肯定也位於其它的hierarchy所對應的cgroup中.因此需要在新分配的css_set中保存這些信息,也就是建立從cgroup到css_set的引用. 另外,關於ist_empty(&rootnode.subsys_list)的操作.似乎沒看到有什麼地方會更改rootnode.subsys_list.不過,如果rootnode.subsys_list不爲空的話,也會在它前面的for循環中檢測出來. 總而言之.系統中有root_count個hierarchy.上述的引用保存過程就會進行root_count次.因此.到最後.tmp_cg_links肯定會空了.如果不爲空.說明某處發生了錯誤. /*增加css_set計數*/ css_set_count++; /* Add this cgroup group to the hash table */ /*將其添加到全局哈希數組: css_set_table[ ]*/ hhead = css_set_hash(res->subsys); hlist_add_head(&res->hlist, hhead); write_unlock(&css_set_lock); return res; } 最後,將生成的css_set添加到哈希數組css_set_table[ ]中. 到這裏,task文件的操作已經分析完了. 7.6: notify_on_release文件操作 notify_on_release文件對應的cftype結構如下: { .name = "notify_on_release", .read_u64 = cgroup_read_notify_on_release, .write_u64 = cgroup_write_notify_on_release, .private = FILE_NOTIFY_ON_RELEASE, } 從此得知.文件的讀操作接口爲cgroup_read_notify_on_release().代碼如下: static u64 cgroup_read_notify_on_release(struct cgroup *cgrp, struct cftype *cft) { return notify_on_release(cgrp); } 繼續跟進notify_on_release().如下示: static int notify_on_release(const struct cgroup *cgrp) { return test_bit(CGRP_NOTIFY_ON_RELEASE, &cgrp->flags); } 從此可以看到,如果當前cgroup設置了CGRP_NOTIFY_ON_RELEASE標誌.就會返回1.否則.就是爲0. 從當前系統中測試一下,如下: [root@localhost cgroup]# cat notify_on_release 0 [root@localhost cgroup]# 文件內容爲零.因爲top_cgroup上沒有設置CGRP_NOTIFY_ON_RELEASE的標誌. notify_on_release文件讀操作接口爲cgroup_write_notify_on_release().代碼如下: static int cgroup_write_notify_on_release(struct cgroup *cgrp, struct cftype *cft, u64 val) { clear_bit(CGRP_RELEASABLE, &cgrp->flags); if (val) set_bit(CGRP_NOTIFY_ON_RELEASE, &cgrp->flags); else clear_bit(CGRP_NOTIFY_ON_RELEASE, &cgrp->flags); return 0; } 從上面的代碼可以看到.如果我們寫入的是1.就會設置cgroup標誌的CGRP_NOTIFY_ON_RELEASE位.否則.清除CGRP_NOTIFY_ON_RELEASE位.測試如下: [root@localhost cgroup]# echo 1 > notify_on_release [root@localhost cgroup]# cat notify_on_release 1 [root@localhost cgroup]# echo 0 > notify_on_release [root@localhost cgroup]# cat notify_on_release 0 [root@localhost cgroup]# 7.7: release_agent文件操作 release_agent只有在頂層目錄纔會有.它所代表的cftype結構如下: static struct cftype cft_release_agent = { .name = "release_agent", .read_seq_string = cgroup_release_agent_show, .write_string = cgroup_release_agent_write, .max_write_len = PATH_MAX, .private = FILE_RELEASE_AGENT, }; 由此可以看到.讀文件的接口爲cgroup_release_agent_show.代碼如下: static int cgroup_release_agent_show(struct cgroup *cgrp, struct cftype *cft, struct seq_file *seq) { if (!cgroup_lock_live_group(cgrp)) return -ENODEV; seq_puts(seq, cgrp->root->release_agent_path); seq_putc(seq, '\n'); cgroup_unlock(); return 0; } 從代碼中可以看到.就是打印出root的release_agent_path. 寫文件的接口爲cgroup_release_agent_write().如下示: static int cgroup_release_agent_write(struct cgroup *cgrp, struct cftype *cft, const char *buffer) { BUILD_BUG_ON(sizeof(cgrp->root->release_agent_path) if (!cgroup_lock_live_group(cgrp)) return -ENODEV; strcpy(cgrp->root->release_agent_path, buffer); cgroup_unlock(); return 0; } 由此得知.往這個文件中寫內容,就是設置root的release_agent_path.如下做個測試: [root@localhost cgroup]# cat release_agent [root@localhost cgroup]# echo /bin/ls > release_agent [root@localhost cgroup]# cat release_agent /bin/ls [root@localhost cgroup]# 7.8:debug創建的文件分析 下面分析一下debug subsys中的文件.由於我們掛載的時候沒有帶noprefix.因爲.debug生成的文件都帶了一個”debug_”前綴.由debug創建的文件如下示: debug.cgroup_refcount debug.current_css_set_refcount debug.taskcount debug.current_css_set debug.releasable 挨個分析如下: 7.8.1: cgroup_refcount文件操作 Cgroup_refcount所代表的cftype結構如下示: { .name = "cgroup_refcount", .read_u64 = cgroup_refcount_read, }, 可以看到,該文件不能寫,只能讀.讀操作接口爲cgroup_refcount_read().代碼如下: static u64 cgroup_refcount_read(struct cgroup *cont, struct cftype *cft) { return atomic_read(&cont->count); } 它就是顯示出當前cgroup的引用計數. 測試如下: [root@localhost cgroup]# cat debug.cgroup_refcount 0 [root@localhost cgroup]# 頂層的cgroup是位於cgroupfs_root.top_cgroup.它的引用計數爲0. 接下來,我們在下層創建一個子層cgroup.如下示: [root@localhost cgroup]# mkdir /dev/cgroup/eric [root@localhost cgroup]# cat /dev/cgroup/eric/debug.cgroup_refcount 0 [root@localhost cgroup]# 可見創建子層cgroup不會增加其引用計數.因爲它只是與它的上一層cgroup構成指針指向關係. 現在我們讓子層cgroup關聯一個進程 [root@localhost cgroup]# echo 1673 > /dev/cgroup/eric/tasks [root@localhost cgroup]# cat /dev/cgroup/eric/debug.cgroup_refcount 1 [root@localhost cgroup]# 可以看到.它的計數比爲了1.這裏在關聯進程的css_set和所在的cgroup時增加的. 7.8.2: current_css_set文件操作 current_css_set對應的cftype結構如下示: { .name = "current_css_set", .read_u64 = current_css_set_read, }, 可看出.它也是一個只讀的.讀接口爲current_css_set_read().代碼如下: static u64 current_css_set_read(struct cgroup *cont, struct cftype *cft) { return (u64)(long)current->cgroups; } 它就是顯示了當前進程關聯的css_set的地址. 測試如下: [root@localhost cgroup]# cat debug.current_css_set 18446744072645980768 7.8.3: current_css_set_refcount文件操作 current_css_set_refcount文件對應的ctype結構如下: { .name = "current_css_set_refcount", .read_u64 = current_css_set_refcount_read, }, 照例.它也是隻讀的.接口如下: static u64 current_css_set_refcount_read(struct cgroup *cont, struct cftype *cft) { u64 count; rcu_read_lock(); count = atomic_read(¤t->cgroups->refcount); rcu_read_unlock(); return count; } 它就是顯示出與當前進程關聯的css_set的引用計數. 測試如下: [root@localhost cgroup]# cat debug.current_css_set_refcount 56 表示已經有56個進程關聯到這個css_set了. 7.8.3: taskcount文件操作 Taskcount文件對應cftype結構如下: { .name = "taskcount", .read_u64 = taskcount_read, }, 只讀文件.接口如下: static u64 taskcount_read(struct cgroup *cont, struct cftype *cft) { u64 count; cgroup_lock(); count = cgroup_task_count(cont); cgroup_unlock(); return count; } 其中,子函數cgroup_task_count()我們在之前已經分析過了.它就是計算與當前cgroup關聯的進程數目.這裏就不再分析了.測試如下: [root@localhost cgroup]# cat debug.taskcount 56 7.8.4: releasable文件操作 Releasable文件對應的ctype結構如下示: { .name = "releasable", .read_u64 = releasable_read, }, 只讀,讀接口代碼如下: static u64 releasable_read(struct cgroup *cgrp, struct cftype *cft) { return test_bit(CGRP_RELEASABLE, &cgrp->flags); } 它用來查看當前cgroup是否有CGRP_RELEASABLE標誌.如果有.顯示爲1.否則顯示爲0. 測試如下: [root@localhost cgroup]# cat debug.releasable 0 經過上面的分析.可以知道.如果往cgroup中刪除一個關聯進程,就會將其設置CGRP_RELEASABLE標誌.有下面測試: [root@localhost cgroup]# mkdir eric [root@localhost cgroup]# cat eric/debug.releasable 0 [root@localhost cgroup]# echo 1650 > eric/tasks [root@localhost cgroup]# echo 1701 > eric/tasks [root@localhost cgroup]# cat eric/debug.releasable 0 [root@localhost cgroup]# echo 1650 >tasks [root@localhost cgroup]# cat eric/debug.releasable 1 到這裏爲止,各subsys共有的文件和debug中的文件操作就已經分析完了.其它的subsys遠遠比debug要複雜.之後再給出專題分析.詳情請關注本站更新.*^_^* 九: notify_on_release操作 下面我們來分析在之前一直在忽略的一個問題.也就是涉及到CGRP_NOTIFY_ON_RELEASE標誌和root-> release_agent_path[]部份. 它的重用,就是在cgroup中最後的一個進程離開(包括進程退出.進程關聯到其它同類型的cgroup),或者是在最後一個子層cgroup被移除的時候.就會調用用戶空間的一個程序.這個程序的路徑是在root-> release_agent_path[]中指定的. 下面我們從代碼的角度來跟蹤一下. 9.1:進程退出 我們在之前在分析父子進程之間的cgroup關係的時候.忽略掉了__put_css_set函數中的一個部份.現在是時候來剝開它了. 次__put_css_set()被忽略的代碼片段列出,如下: static void __put_css_set(struct css_set *cg, int taskexit) { ...... ...... for (i = 0; i struct cgroup *cgrp = cg->subsys->cgroup; if (atomic_dec_and_test(&cgrp->count) && notify_on_release(cgrp)) { if (taskexit) set_bit(CGRP_RELEASABLE, &cgrp->flags); check_for_release(cgrp); } } ...... ...... } 首先,進程退出時,調用__put_css_set時.taskexit參數是爲1的,因此在這裏,它會將cgroup的flag的CGRP_RELEASABLE位置1. atomic_dec_and_test(&cgrp->count)返回爲真的話,說明進程所屬的cgroup中已經沒有其它的進程了.因此即將要退出的子進程就是cgroup中的最後一個進程. notify_on_release(cgrp)代碼如下: static int notify_on_release(const struct cgroup *cgrp) { return test_bit(CGRP_NOTIFY_ON_RELEASE, &cgrp->flags); } 它用來判斷cgroup有沒有設定CGRP_NOTIFY_ON_RELEASE標誌 綜合上面的分析.如果cgroup中最後一個進程退出.且cgroup設定了CGRP_NOTIFY_ON_RELEASE標誌.流程就會轉到check_for_release()中.該函數代碼如下: static void check_for_release(struct cgroup *cgrp) { /* All of these checks rely on RCU to keep the cgroup * structure alive */ if (cgroup_is_releasable(cgrp) && !atomic_read(&cgrp->count) && list_empty(&cgrp->children) && !cgroup_has_css_refs(cgrp)) { /* Control Group is currently removeable. If it's not * already queued for a userspace notification, queue * it now */ int need_schedule_work = 0; spin_lock(&release_list_lock); if (!cgroup_is_removed(cgrp) && list_empty(&cgrp->release_list)) { list_add(&cgrp->release_list, &release_list); need_schedule_work = 1; } spin_unlock(&release_list_lock); if (need_schedule_work) schedule_work(&release_agent_work); } } 首先,在這裏必須要滿足以下四個條件才能繼續下去: 1:cgroup_is_releasable()返回1. 代碼如下: static int cgroup_is_releasable(const struct cgroup *cgrp) { const int bits = (1 (1 return (cgrp->flags & bits) == bits; } 它表示當前cgroup是含含有CGRP_RELEASABLE和CGRP_NOTIFY_ON_RELEASE標誌.結合我們在上面分析的. CGRP_RELEASABLE標誌是進程在退出是就會設置的. 2:cgroup的引用計數爲0 3:cgroup沒有子層cgroup 4: cgroup_has_css_refs()返回0.代碼如下: static int cgroup_has_css_refs(struct cgroup *cgrp) { int i; for (i = 0; i struct cgroup_subsys *ss = subsys; struct cgroup_subsys_state *css; /* Skip subsystems not in this hierarchy */ if (ss->root != cgrp->root) continue; css = cgrp->subsys[ss->subsys_id]; if (css && atomic_read(&css->refcnt)) return 1; } return 0; } 也就是說,cgroup關聯的css_set引用計數必須要爲0 滿足上面幾個條件之後.就說明該cgroup是可以釋放的.因此將cgroup鏈接到了release_list.接着調度了工作隊列.在工作隊列中會完成餘下的工作. 下面跟蹤看看這個工作隊列是怎麼處理餘下任務的. release_agent_work定義如下: static DECLARE_WORK(release_agent_work, cgroup_release_agent); 該工作隊列對應的處理函數爲cgroup_release_agent().代碼如下: static void cgroup_release_agent(struct work_struct *work) { BUG_ON(work != &release_agent_work); mutex_lock(&cgroup_mutex); spin_lock(&release_list_lock); /*遍歷鏈表,直到其爲空*/ while (!list_empty(&release_list)) { char *argv[3], *envp[3]; int i; char *pathbuf = NULL, *agentbuf = NULL; /*取得鏈表項對應的cgroup*/ struct cgroup *cgrp = list_entry(release_list.next, struct cgroup, release_list); /*將cgroup從release_list中斷開*/ list_del_init(&cgrp->release_list); spin_unlock(&release_list_lock); /*將cgroup的路徑存放到pathbuf中*/ pathbuf = kmalloc(PAGE_SIZE, GFP_KERNEL); if (!pathbuf) goto continue_free; if (cgroup_path(cgrp, pathbuf, PAGE_SIZE) goto continue_free; /*agentbuf存放release_agent_path的內容*/ agentbuf = kstrdup(cgrp->root->release_agent_path, GFP_KERNEL); if (!agentbuf) goto continue_free; /*初始化運行參數和環境變量*/ i = 0; argv[i++] = agentbuf; argv[i++] = pathbuf; argv = NULL; i = 0; /* minimal command environment */ envp[i++] = "HOME=/"; envp[i++] = "PATH=/sbin:/bin:/usr/sbin:/usr/bin"; envp = NULL; /* Drop the lock while we invoke the usermode helper, * since the exec could involve hitting disk and hence * be a slow process */ /*調用用戶空間的進程*/ mutex_unlock(&cgroup_mutex); call_usermodehelper(argv[0], argv, envp, UMH_WAIT_EXEC); mutex_lock(&cgroup_mutex); continue_free: kfree(pathbuf); kfree(agentbuf); spin_lock(&release_list_lock); } spin_unlock(&release_list_lock); mutex_unlock(&cgroup_mutex); } 該函數遍歷release_list中的cgroup.然後以其路徑做爲參數.調用root->release_agent_path對應的程序. 我們來做如下的實驗: 爲了配合這次實驗.必須要寫兩個測試的程序.代碼如下: Test.c #include #include main() { int i = 30; while(i){ i--; sleep(1); } } 這個進程睡眠30s之後退出.編譯成test 另外一個程序代碼如下: Main.c #include #include int main(int argc,char *argv[]) { char buf[125] = ""; int i = 0; sprintf(buf,"rm -f /var/eric_test"); system(buf); while(i sprintf(buf,"echo %s >> /var/eric_test",argv); system(buf); i++; } } 它就是將調用參數輸出到/var/eric_test下面. 下面就可以開始我們的測試了.掛載目錄下已經有一個子層cgroup.如下示: . |-- debug.cgroup_refcount |-- debug.current_css_set |-- debug.current_css_set_refcount |-- debug.releasable |-- debug.taskcount |-- eric | |-- debug.cgroup_refcount | |-- debug.current_css_set | |-- debug.current_css_set_refcount | |-- debug.releasable | |-- debug.taskcount | |-- notify_on_release | `-- tasks |-- notify_on_release |-- release_agent `-- tasks 接下來設置realesse_agent_path和CGRP_NOTIFY_ON_RELEASE標誌,指令如下: [root@localhost cgroup]# echo /root/main > release_agent [root@localhost cgroup]# echo 1 > eric/notify_on_release 下面往子層cgroup中添加一個進程.指令如下: [root@localhost cgroup]# /root/test & [1] 4350 [root@localhost cgroup]# echo 4350 > eric/tasks [root@localhost cgroup]# [1]+ Done /root/test 等/root/test運行完之後.就會進行notify_on_release的操作了.印證一下: [root@localhost cgroup]# cat /var/eric_test /root/main /eric 一切都如我們上面分析的一樣 9.2:取消進程與cgroup的關聯 當cgroup中的最後一個進程取消關聯的時候,也會有notify_on_release過程.見下面的代碼片段: int cgroup_attach_task(struct cgroup *cgrp, struct task_struct *tsk) { int retval = 0; struct cgroup_subsys *ss; struct cgroup *oldcgrp; struct css_set *cg = tsk->cgroups; ...... ...... set_bit(CGRP_RELEASABLE, &oldcgrp->flags); synchronize_rcu(); put_css_set(cg); } 這個函數我們在之前分析過,不過也把notify_on_release的過程去掉了.現在也把它加上. 代碼中的cg是指向進程原本所引用的css_set Oldcgrp是過程之前所在的cgroup 在代碼中,會將oldcgrp標誌設爲CGRP_RELEASABLE.之後也會調用put_css_set().put_css_set()就是我們在上面分析的過程了.如果cgroup爲空的話,就會產生notify_on_release的操作. 同樣做個測試: 接着上面的測試環境.我們先來看下環境下的相關文件內容: [root@localhost cgroup]# cat release_agent /root/main [root@localhost cgroup]# cat eric/tasks [root@localhost cgroup]# cat eric/notify_on_release 1 [root@localhost cgroup]# pwd /dev/cgroup 好了,測試開始了: [root@localhost cgroup]# rm -rf /var/eric_test [root@localhost cgroup]# echo 1701 > eric/tasks [root@localhost cgroup]# echo 1701 >tasks [root@localhost cgroup]# cat /var/eric_test /root/main /eric 在上面的測試過程中.爲了避免影響測試效果.先將/var/eric_test文件刪了.然後將進程1701關聯到eric所表示的cgroup.然後再把1701再加最上層cgroup.這樣就會造成eric下關聯進程爲空.相應的會發生notify_on_release過程.上面的測試也印證了這一說話. 9.3:移除cgroup 當移除cgroup下的最後一個子層cgroup時.也會發生notify_on_release. 看一下移除cgroup時的代碼片段: static int cgroup_rmdir(struct inode *unused_dir, struct dentry *dentry) { ...... ...... set_bit(CGRP_RELEASABLE, &parent->flags); check_for_release(parent); ...... } 代碼中,parent表示cgroup的上一層.在移除cgroup時,會設置上一層的cgroup標誌的CGRP_RELEASABLE位.然後流程同樣會轉入到check_for_release().這樣,如果上一層cgroup是空的話.就會生notify_on_release操作了. 測試如下: 還是用上層的測試環境.先來看一下初始環境: [root@localhost cgroup]# pwd /dev/cgroup [root@localhost cgroup]# cat release_agent /root/main [root@localhost cgroup]# cat eric/notify_on_release 1 在eric下面再加一層cgroup. [root@localhost cgroup]# mkdir eric/test [root@localhost cgroup]# tree . |-- debug.cgroup_refcount |-- debug.current_css_set |-- debug.current_css_set_refcount |-- debug.releasable |-- debug.taskcount |-- eric | |-- debug.cgroup_refcount | |-- debug.current_css_set | |-- debug.current_css_set_refcount | |-- debug.releasable | |-- debug.taskcount | |-- notify_on_release | |-- tasks | `-- test | |-- debug.cgroup_refcount | |-- debug.current_css_set | |-- debug.current_css_set_refcount | |-- debug.releasable | |-- debug.taskcount | |-- notify_on_release | `-- tasks |-- notify_on_release |-- release_agent `-- tasks 2 directories, 22 files 接着運行如下指令: [root@localhost cgroup]# rm -rf /var/eric_test [root@localhost cgroup]# rmdir eric/test/ [root@localhost cgroup]# cat /var/eric_test /root/main /eric 如上所示.把eric下的唯一一個cgroup移除的時候.就發生了notity_on_release過程. 十:cgroup的proc節點 10.1:/proce/cgroups 在前面分析cgroup初始化的時候.在cgroup_init()中有下面代碼片段: int __init cgroup_init(void) { ...... ...... proc_create("cgroups", 0, NULL, &proc_cgroupstats_operations) ...... ...... } 也就是說.會在proc根目錄下創建一個名爲cgroups的文件.如下示: [root@localhost cgroup]# ls /proc/cgroups /proc/cgroups 接下來就來分析這個文件的操作. 該文件對應的操作集爲 proc_cgroupstats_operations.定義如下: static struct file_operations proc_cgroupstats_operations = { .open = cgroupstats_open, .read = seq_read, .llseek = seq_lseek, .release = single_release, } 從上面看到,這個文件是隻讀的. 先來看open時的操作,對應接口爲cgroupstats_open.代碼如下: static int cgroupstats_open(struct inode *inode, struct file *file) { return single_open(file, proc_cgroupstats_show, NULL); } Single_open()函數十分簡單.它也是sequences file中提供的一個接口.有關sequences file部份我們在上面已經分析過了. 這裏就不再詳細分析了.它將seq_file的show操作指向了proc_cgroupstats_show. 我們在上面的proc_cgroupstats_operations結構中可看到,它提供的read操作爲seq_read().它就是調用seq_file中的相關操作.在open的時候,已經將seq_file的show接口指向了proc_cgroupstats_show().代碼如下: static int proc_cgroupstats_show(struct seq_file *m, void *v) { int i; seq_puts(m, "#subsys_name\thierarchy\tnum_cgroups\tenabled\n"); mutex_lock(&cgroup_mutex); for (i = 0; i struct cgroup_subsys *ss = subsys; seq_printf(m, "%s\t%lu\t%d\t%d\n", ss->name, ss->root->subsys_bits, ss->root->number_of_cgroups, !ss->disabled); } mutex_unlock(&cgroup_mutex); return 0; } 從代碼中看到,它就是將系統中每subsys名稱.所在hierarchy的位碼. Hierarchy下面的cgroup數目和subsys的啓用狀態. 測試如下: [root@localhost cgroup]# cat /proc/cgroups #subsys_name hierarchy num_cgroups enabled cpuset 0 1 1 debug 2 2 1 ns 0 1 1 cpuacct 0 1 1 memory 0 1 1 devices 0 1 1 freezer 0 1 1 從這裏可以看到所有的subsys和hierarchy的情況.在上面顯示的debug和其它的subsys不同.是因爲用的是之前測試notify_on_release的環境.如下示: [root@localhost cgroup]# tree ../cgroup/ ../cgroup/ |-- debug.cgroup_refcount |-- debug.current_css_set |-- debug.current_css_set_refcount |-- debug.releasable |-- debug.taskcount |-- eric | |-- debug.cgroup_refcount | |-- debug.current_css_set | |-- debug.current_css_set_refcount | |-- debug.releasable | |-- debug.taskcount | |-- notify_on_release | `-- tasks |-- notify_on_release |-- release_agent `-- tasks 1 directory, 15 files 10.2:proc下進程鏡像中的cgroup 除了在proc頂層目錄創建cgroup外.另外在每個進程鏡像下都有一個cgroup的文件.如下示: [root@localhost cgroup]# ls /proc/648/cgroup /proc/648/cgroup 來看一下這個文件對應的操作,如下示: static const struct pid_entry tid_base_stuff[] = { ...... ...... #ifdef CONFIG_CGROUPS REG("cgroup", S_IRUGO, cgroup), #endif ...... } #define REG(NAME, MODE, OTYPE) \ NOD(NAME, (S_IFREG|(MODE)), NULL, \ &proc_##OTYPE##_operations, {}) 從上面可以看到.Cgroup對應的操作爲&proc_cgroup_operations 定義如下: struct file_operations proc_cgroup_operations = { .open = cgroup_open, .read = seq_read, .llseek = seq_lseek, .release = single_release, }; Open對應的操作爲cgroup_open.定義如下: static int cgroup_open(struct inode *inode, struct file *file) { struct pid *pid = PROC_I(inode)->pid; return single_open(file, proc_cgroup_show, pid); } 又見到single_open()了.如上面的分析一樣,read操作的時候會轉入到proc_cgroup_show().代碼如下: static int proc_cgroup_show(struct seq_file *m, void *v) { struct pid *pid; struct task_struct *tsk; char *buf; int retval; struct cgroupfs_root *root; retval = -ENOMEM; buf = kmalloc(PAGE_SIZE, GFP_KERNEL); if (!buf) goto out; retval = -ESRCH; pid = m->private; tsk = get_pid_task(pid, PIDTYPE_PID); if (!tsk) goto out_free; retval = 0; mutex_lock(&cgroup_mutex); /*遍歷所有的cgroupfs_root*/ for_each_root(root) { struct cgroup_subsys *ss; struct cgroup *cgrp; int subsys_id; int count = 0; /* Skip this hierarchy if it has no active subsystems */ /*如果hierarchy中沒有subsys.就繼續下一個rootnode就是這樣的情況*/ if (!root->actual_subsys_bits) continue; /*打印hierarchy中的subsys位圖*/ seq_printf(m, "%lu:", root->subsys_bits); /*打印hierarchy中的subsys名稱*/ for_each_subsys(root, ss) seq_printf(m, "%s%s", count++ ? "," : "", ss->name); seq_putc(m, ':'); /*進程所在cgroup的path*/ get_first_subsys(&root->top_cgroup, NULL, &subsys_id); cgrp = task_cgroup(tsk, subsys_id); retval = cgroup_path(cgrp, buf, PAGE_SIZE); if (retval goto out_unlock; seq_puts(m, buf); seq_putc(m, '\n'); } out_unlock: mutex_unlock(&cgroup_mutex); put_task_struct(tsk); out_free: kfree(buf); out: return retval; } 它的核心操作在這個for循環中,它的操作在註釋中已經詳細的說明了.在這裏不做詳細分析. 我將虛擬機重啓了 *^_^*,所以現在的環境不是我們之前的測試環境了 測試一下: [root@localhost ~]# cat /proc/646/cgroup [root@localhost ~]# 說明當前系統中還沒有hierarchy. 接下來掛載上一個: [root@localhost ~]# mkdir /dev/cgroup [root@localhost ~]# mount -t cgroup cgroup -o debug /dev/cgroup/ [root@localhost ~]# cat /proc/6 6/ 609/ 646/ [root@localhost ~]# cat /proc/646/cgroup 2:debug:/ [root@localhost ~]# 從上面可以看到.系統已經有一個hierarchy.且綁定的是debug subsys.當前進程是位於它的頂層. 繼續測試: [root@localhost ~]# mkdir /dev/cgroup/eric [root@localhost ~]# echo 646 > /dev/cgroup/eric/tasks [root@localhost ~]# cat /proc/646/cgroup 2:debug:/eric [root@localhost ~]# 可以看到,當前進程是位於eric這個cgroup中. 十一:小結 在這一節裏,用大篇幅詳細的描述了整個cgroup的框架.cgroup框架並不複雜,只是其中的數據結構和大量的全局變量弄的頭昏眼花.因此理順這些數據結構和變量是閱讀cgroup代碼的關鍵.另外在cgroup中對於RCU和rw_mutex的使用也有值得推敲的地方.不過由於篇幅關係,就不再分析這一部份.在接下來專題裏.以cgroup框架爲基礎來分析幾個重要的subsys. |
進程_控制進程的資源使用
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.