歡迎您光臨本站 註冊首頁

雲端計算時代,容器底層 cgroup 的程式碼實現分析

←手機掃碼閱讀     admin @ 2020-05-21 , reply:0

作者:姜亞華(@二如公子 ),一直從事與 Linux 內核和 Linux 程式設計相關的工作,研究核心程式碼十多年,對多數模組的細節如數家珍。曾負責華為手機 Touch、Sensor 的驅動和軟體最佳化(包括 Mate、榮耀等系列),以及 Intel 安卓平臺 Camera 和 Sensor 的驅動開發(包括 Baytrail、Cherrytrail、Cherrytrail CR、Sofia 等)。現負責 DMA、Interrupt、Semaphore 等模組的最佳化與驗證(包括 Vega、Navi 系列和多款 APU 產品)。

上期回顧:點選檢視

上一篇文章裡,我們探討了容器底層 cgroup 的作用與資料結構,本文我們將深入分析 cgroup 的程式碼實現。

一、cgroup 的初始化和 mount

測試環境版本與第一篇一致:

Ubuntu

(lsb_release -a)

Distributor ID: Ubuntu

Description:    Ubuntu 19.10

Release:        19.10

Linux

(uname -a)

Linux yahua 5.5.5 #1 SMP … x86_64 x86_64 x86_64 GNU/Linux

本篇開始我們將分析 cgroup 的程式碼實現,與書(《精通Linux核心—智慧裝置開發核心技術》,下同)中的原則一致,我們重點分析核心和難點程式碼,其他部分在不影響理解的情況下一筆帶過。

1.1 cgroup 的初始化

cgroup 的初始化分為兩個階段。 

第一階段:初始化 cgrp_dfl_root 和系統支援的 ss,由cgroup_init_early 函式完成。cgrp_dfl_root,看名字就知道,default cgroup_root,預設的 cgroup 層級結構,它在 cgroup v1 中戲份有限,在 v2 中是 c 位。至於 ss 的初始化,主要是 id 和 name,如果 ss 的 early_init 為真,呼叫 cgroup_init_subsys 完善它與cgrp_dfl_root 的關係。  

cgroup_init_subsys 有助於我們理解 cgroup 和 ss 之間的關係,此處展開討論,程式碼如下:

void cgroup_init_subsys(struct cgroup_subsys *ss, bool early)
 {
 	ss->root = &cgrp_dfl_root;
 	css = ss->css_alloc(cgroup_css(&cgrp_dfl_root.cgrp, ss));
 	init_and_link_css(css, ss, &cgrp_dfl_root.cgrp);
 
 
 	if (early) {
 		css->id = 1;
 	} else {
 		css->id = cgroup_idr_alloc(&ss->css_idr, css, 1, 2, GFP_KERNEL);
 	}
 
 
 	init_css_set.subsys[ss->id] = css;    //***,三個星號哈
 
 
 	BUG_ON(online_css(css)); 

我們在第一篇中說過,ss 和 cgroup 是多對多的關係,透過 css 實現,cgroup_init_subsys 就是完成這個任務的。cgrp_dfl_root 是一個 cgroup_root,它本身內嵌了一個 cgroup,所以具體點就是申請一個 css,建立它與 cgrp_dfl_root.cgrp 的聯絡。 

它先回調 ss->css_alloc 函式申請 css,css_alloc 的引數表示將要產生的 css 的父css(我們在第一篇講過,css 的兩方面作用),呼叫 cgroup_init_subsys 的時候,父 css 還不存在,所以最終傳遞的引數是 NULL。 

我們分析的 cgroup 子系統 cpuset 的 css_alloc 回撥函式是 cpuset_css_alloc,當它發現傳遞的引數 parent_css 等於 NULL 的時候,直接返回 &top_cpuset.css,也就是一個全域性的 css。全域性,意味著牽一髮動全身,隱約中找到了第一篇課堂作業第一題的答案。 

有了 css 後,呼叫 init_and_link_css 和 online_css 建立 cgroup 和 ss 的關係就是水到渠成的事情了。online_css 會回撥 ss->css_online 函式,對 cpuset 而言,因為 css_alloc 返回的是全域性的 css,此處 css_online 並沒有實際操作。 

init_css_set(三顆星,重點)是 init css_set,是一個全域性的 css_set,這裡使用申請到的 css 為相應的欄位賦值。 

第二階段:繫結 ss 與 cgrp_dfl_root,也就是說系統啟動的初期所有的 ss 都與預設的 cgroup 層級結構繫結。由 cgroup_init 函式完成,主要邏輯如下:

int cgroup_init(void)
 {
 	BUG_ON(cgroup_init_cftypes(NULL, cgroup_base_files));    //1
 	BUG_ON(cgroup_init_cftypes(NULL, cgroup1_base_files));
 
 
 	BUG_ON(cgroup_setup_root(&cgrp_dfl_root, 0));    //2
 
 
 	for_each_subsys(ss, ssid) {
 		if (ss->early_init) {    //3
 			struct cgroup_subsys_state *css = init_css_set.subsys[ss->id];
 			css->id = cgroup_idr_alloc(&ss->css_idr, css, 1, 2, GFP_KERNEL);
 		} else {
 			cgroup_init_subsys(ss, false);
 		}
 
 
 		cgrp_dfl_root.subsys_mask |= 1 << ss->id;
 
 
 		if (ss->dfl_cftypes == ss->legacy_cftypes) {    //4
 			WARN_ON(cgroup_add_cftypes(ss, ss->dfl_cftypes));
 		} else {
 			WARN_ON(cgroup_add_dfl_cftypes(ss, ss->dfl_cftypes));
 			WARN_ON(cgroup_add_legacy_cftypes(ss, ss->legacy_cftypes));
 		}
 
 
 		if (ss->bind)    //5
 			ss->bind(init_css_set.subsys[ssid]);
 		css_populate_dir(init_css_set.subsys[ssid]);
 	}
 	WARN_ON(sysfs_create_mount_point(fs_kobj, "cgroup"));    //6
 	WARN_ON(register_filesystem(&cgroup_fs_type));
 	WARN_ON(register_filesystem(&cgroup2_fs_type));
 	WARN_ON(!proc_create_single("cgroups", 0, NULL, proc_cgroupstats_show));
 #ifdef CONFIG_CPUSETS
 	WARN_ON(register_filesystem(&cpuset_fs_type));
 #endif
 	return 

cgroup_base_files 和 cgroup1_base_files 都是 cftype 陣列,內核定義它們的時候並沒有提供檔案操作相關的回撥函式,第 1 步中呼叫 cgroup_init_cftypes 為相關操作賦值。

核心裡面有一些程式碼是 BUG_ON、WARN_ON 等括起來的,cgroup_init 就出現了兩種。一般情況下這類程式碼不涉及具體邏輯,但是少數情況下,工程師可能考慮到程式碼的簡潔和美觀這麼做了。實際上,不推薦這麼做,因為閱讀程式碼的工程師可能看到 XXX_ON 會跳過,影響理解。改成 ret = cgroup_init_cftypes(NULL, cgroup_base_files); BUG_ON(ret); 效果可能好些。

寫書與寫部落格很大的不同點在於寫書需要考慮篇幅,寫多了顯得囉嗦,寫部落格就不同了,不影響理解的情況下可以說一些有幫助的題外話,所以我會插播一些理解和建議,希望大家不要介意。 

第 2 步,呼叫 cgroup_setup_root 繼續設定 cgrp_dfl_root,cgroup_setup_root 我們在 mount 的時候著重介紹。前面說了 cgrp_dfl_root 戲份有限,這裡就不給出境機會了。

接下來遍歷系統支援的 ss(for_each_subsys)。 

第 3 步,遍歷系統支援的 ss,如果在 early init 的階段沒有初始化,呼叫 cgroup_init_subsys 初始化。 

第 4 步,設定 ss 相關的 cftype 。cftype 我們在第一篇中就提過了,mount 和 mkdir 時,cgroup 為我們建立的檔案就是它來表示的。每個 ss 的 cftype 是 ss 自行定義的,比如 cpuset 的定義如下。

struct cgroup_subsys cpuset_cgrp_subsys = {
 …
 .legacy_cftypes	= legacy_files,
 .dfl_cftypes	= dfl_files,
 …
 };

每個cftype都有一個flags欄位,可以是多種標誌的組合,常見的標誌如下:

標誌

含義

CFTYPE_ONLY_ON_ROOT

只出現在cgroup_root內嵌的cgroup中,也就是cgroup層級結構的根中

CFTYPE_NOT_ON_ROOT

不會出現在cgroup_root內嵌的cgroup中

CFTYPE_NO_PREFIX

根據cftype的名字建立檔案時,不需要加字首

__CFTYPE_ONLY_ON_DFL

只出現在預設cgroup層級結構中

__CFTYPE_NOT_ON_DFL

不會出現在預設cgroup層級結構中

cgroup_add_cftypes 不會改變 flags 欄位,cgroup_add_dfl_cftypes 和 cgroup_add_legacy_cftypes 呼叫 cgroup_add_cftypes 實現,只不過會分別給 dfl_cftypes 和 legacy_cftypes 新增 __CFTYPE_ONLY_ON_DFL 和__CFTYPE_NOT_ON_DFL 標誌。cpuset 的 dfl_cftypes 和 legacy_cftypes 不同,所以它的 dfl_files 和 legacy_files 會被新增標誌。

cgroup_add_cftypes 會遍歷我們在第二個引數中指定的 cftype 陣列,根據 cftype 的 flags 決定是否在當前目錄下建立 cftype 對應的檔案,以 cpuset 的 legacy_files 名為“cpus”的 cftype 為例:

static struct cftype legacy_files[] = {
 	{
 		.name = "cpus",
 		.seq_show = cpuset_common_seq_show,
 		.write = cpuset_write_resmask,
 		.max_write_len = (100U + 6 * NR_CPUS),
 		.private = FILE_CPULIST,
 	},
 …
 }

它是 legacy_files,被添加了 __CFTYPE_NOT_ON_DFL 標誌,除此之外並沒有其他標誌,所以 cpuset 的目錄只要不屬於預設的 cgroup 層級結構,都會建立它。另外,它並沒有 CFTYPE_NO_PREFIX 標誌,所以它的檔名最終是“cpuset.cpus”,也就是我們在第一篇的例子中看到的樣子。 

除了各個 ss 專屬的 cftype 之外,cgroup 定義了 cgroup1_base_files 和cgroup_base_files(適用於預設層級結構)兩個通用 cftype 陣列,它們不屬於某一個ss,cftype 的 ss 欄位自然也是 NULL,建立它們的時候不會加字首,比如例子中的tasks、notify_on_release 和 cgroup.procs(原名就叫 cgroup.procs)。 

第 5 步,回撥 ss->bind,繫結 ss 和 cgroup。cgroup_init_subsys 函式已經為init_css_set.subsys[ssid] 賦值了(提示,三顆星),ss->bind 的引數是 css,也就是 ss和 cgroup,cpuset 的 bind 實現簡化如下:

void cpuset_bind(struct cgroup_subsys_state *root_css)
 {
 	cpumask_copy(top_cpuset.cpus_allowed,
 			    top_cpuset.effective_cpus);
 	top_cpuset.mems_allowed = top_cpuset.effective_mems;
 }

對於 top_cpuset,如果你沒有啥印象了,提醒下,cpuset 的 css_alloc 在傳遞的父 css為 NULL 的情況下,返回的就是 top_cpuset.css,劇透一下,它們在 mount 的時候還會重複一遍。 

不得不再提一遍,我會在需要特別注意的地方“囉嗦”一點,讀完第一遍如果能對它們有大概的印象就算是有收穫了。

第 6 步,建立 sysfs 的 fs/cgroup(也就是我們看到的 /sys/fs/cgroup 目錄),註冊檔案系統。cpuset_fs_type 檔案系統和我們分析的 cpuset ss 有什麼關係呢?cpuset_fs_type 本質上就是一個空殼,完全是由 cpuset ss 實現的。

初始化完畢,接下來我們就可以 mount cgroup 檔案系統了。此刻系統支援的 ss 都繫結在預設的 cgroup 層級結構上。

1.2 cgroup 的 mount

mount 的流程在 5.5.5 版本的核心中已經發生了很大變化,有機會我們在後續的篇章中討論,這裡直接進入正題。 

mount 的時候可以指定一些引數,由 cgroup1_parse_param 函式解析,除了指定 ss 的名字外,還支援以下引數:

const struct fs_parameter_spec cgroup1_param_specs[] = {
 	fsparam_flag  ("all",		Opt_all),
 	fsparam_flag  ("clone_children", Opt_clone_children),
 	fsparam_flag  ("cpuset_v2_mode", Opt_cpuset_v2_mode),
 	fsparam_string("name",		Opt_name),
 	fsparam_flag  ("none",		Opt_none),
 	fsparam_flag  ("noprefix",	Opt_noprefix),
 	fsparam_string("release_agent",	Opt_release_agent),
 	fsparam_flag  ("xattr",		Opt_xattr),
 	{}
 };

mount 的時候透過 -o 指定即可,比如我們可以指定 name:

love_cc@yahua:~$ sudo mount -t cgroup -o cpuset,name=cs abcd test/
 love_cc@yahua:~$ mount
 abcd on /home/love_cc/test type cgroup (rw,relatime,cpuset,name=cs)

需要注意的是,abcd 並不是指定 name 的,它實際是 dev_name,這是在 cgroup 中這個名字隨意而已。但如果我們 mount 的是 ext4 等檔案系統,就不能隨意了,比如 sudo mount -t ext4 /dev/sdb1 dir.

cgroup 檔案系統 mount 的核心邏輯由 cgroup1_get_tree 函式實現,詳細討論它之前有必要弄清一件事情,對 cgroup 而言,一個 mount 的意義何在,對應什麼資料結構?回顧第一章,系統啟動後,Ubuntu 已經為我們 mount 了很多子系統,每一個 mount 都可以管理一類資源,我們可以利用它們建立子目錄,構建一個層級結構。所以 cgroup 的mount 實際上是構建了 cgroup 層級結構,進一步講,就是構建了一個 cgroup_root(層級結構的根)。

我們在第一篇中強調過,一個 ss 最多隻能繫結一個 cgroup 層級結構,那麼 mount 的過程需要解決的問題就明朗了。

  1.  是否可以複用已經存在的 cgroup_root,如果之前的 cgroup_root 可以滿足我們的需要,直接複用。
  2.  如果之前的 mount 的 cgroup_root 不能滿足我們的需要,本次 mount 會失敗,或者替代之前的 mount ?
  3.  如果 ss 繫結的 cgroup_root 不存在?沒有這個如果,初始化的時候 ss 就已經綁定了 cgrp_dfl_root。

cgroup1_get_tree 呼叫 cgroup1_root_to_use 解決以下這幾個問題:

首先是 mount 引數檢查,比如指定了 ss 名字(比如 cpuset)的情況下,就不能再指定all 或者 none;不指定 name 的情況下,不能指定 none;ss 名字、none 和 name 都沒有指定的情況下,預設為 all。 

然後,檢視是否可以複用已有的 cgroup_root,程式碼片段如下:

for_each_root(root) {
 		bool name_match = false;
 		if (root == &cgrp_dfl_root)    //#1
 			continue;
 		if (ctx->name) {
 			if (strcmp(ctx->name, root->name))
 				continue;
 			name_match = true;
 		}
 		if ((ctx->subsys_mask || ctx->none) &&
 		    (ctx->subsys_mask != root->subsys_mask)) {
 			if (!name_match)
 				continue;
 			return -EBUSY;    //#2
 		}
 		ctx->root = root;
 		return 0;
 	}
 	if (!ctx->subsys_mask && !ctx->none)    //#3
 		return cg_invalf(fc, "cgroup1: No subsys list or none specified"

ctx->name 是 mount 時指定的 name,ctx->subsys_mask 是 mount 時指定的 ss 的掩碼(可以指定多個)。

這段程式碼遍歷已經存在的 cgroup_root 。

不能複用 cgrp_dfl_root(標號 #1),簡單的解釋是 cgrp_dfl_root 主要是給 cgroup v2 用的。

mount 的時候指定了名字,與它同名的 cgroup_root 的掩碼一致,則可複用,否則失敗(標號 #2)。 

mount 的時候指定了名字,不存在與它同名的 cgroup_root,沒有指定 ss,且沒有指定 none,失敗(標號#3)。當然,即使指定了 ss 也不一定成功,還有下一關。 

mount 的時候沒有指定名字,目標 cgroup_root 的 ss 掩碼相同即可。

如果沒有找到目標 cgroup_root,也沒有失敗,cgroup1_root_to_use 接下來就建立一個 cgroup_root,為它初始化,然後呼叫 cgroup_setup_root 完成設定。

cgroup_setup_root 第二次出現了,它繫結 ss(可以是多個)和 mount 時建立的cgroup_root(cgroup 層級結構),主要邏輯如下:

int cgroup_setup_root(struct cgroup_root *root, u16 ss_mask)
 {
 	LIST_HEAD(tmp_links);
 	struct cgroup *root_cgrp = &root->cgrp;
 	struct kernfs_syscall_ops *kf_sops;
 	struct css_set *cset;
 
 	ret = allocate_cgrp_cset_links(2 * css_set_count, &tmp_links);
 
 	kf_sops = root == &cgrp_dfl_root ?    //1
 		&cgroup_kf_syscall_ops : &cgroup1_kf_syscall_ops;
 
 	root->kf_root = kernfs_create_root(kf_sops,
 					   KERNFS_ROOT_CREATE_DEACTIVATED |
 					   KERNFS_ROOT_SUPPORT_EXPORTOP,
 					   root_cgrp);
 	root_cgrp->kn = root->kf_root->kn;
 
 	ret = css_populate_dir(&root_cgrp->self);    //2
 
 	ret = rebind_subsystems(root, ss_mask);    //3
 	if (ret)
 		goto destroy_root;    //省略出錯處理
 	list_add(&root->root_list, &cgroup_roots);
 	cgroup_root_count++;
 
 	hash_for_each(css_set_table, i, cset, hlist) {    //4
 		link_css_set(&tmp_links, cset, root_cgrp);
 	}
 
 	kernfs_activate(root_cgrp->kn);
 	free_cgrp_cset_links(&tmp_links);
 	return 

第 1 步與後續的檔案操作有關,提供 mkdir 等操作。

第 2 步,建立 root_cgrp->self 的 cftype 檔案,self 是內嵌的 css,它沒有關聯任何ss(!css->ss成立),css_populate_dir 建立的檔案來自 cgroup1_base_files,第一篇的例子中的 cgroup.procs、tasks 等檔案都屬於它。

第 3 步,呼叫 rebind_subsystems 重新繫結指定的 ss,在此之前可能已經和其他cgroup_root 綁定了,所以叫做 rebind。

rebind_subsystems 是重點,主要邏輯如下:

int rebind_subsystems(struct cgroup_root *dst_root, u16 ss_mask)
 {
 	struct cgroup *dcgrp = &dst_root->cgrp;
 
 	do_each_subsys_mask(ss, ssid, ss_mask) {    //3.1
 		if (css_next_child(NULL, cgroup_css(&ss->root->cgrp, ss)) &&
 		    !ss->implicit_on_dfl)    //#1
 			return -EBUSY;
 		if (ss->root != &cgrp_dfl_root && dst_root != &cgrp_dfl_root)    //#2
 			return -EBUSY;
 	} while_each_subsys_mask();
 
 	do_each_subsys_mask(ss, ssid, ss_mask) {
 		struct cgroup_root *src_root = ss->root;
 		struct cgroup *scgrp = &src_root->cgrp;
 		struct cgroup_subsys_state *css = cgroup_css(scgrp, ss);
 
 		src_root->subsys_mask &= ~(1 << ssid);    //3.2
 		WARN_ON(cgroup_apply_control(scgrp));
 		cgroup_finalize_control(scgrp, 0);
 
 		RCU_INIT_POINTER(scgrp->subsys[ssid], NULL);
 		rcu_assign_pointer(dcgrp->subsys[ssid], css);    //3.3
 		ss->root = dst_root;
 		css->cgroup = dcgrp;
 
 		hash_for_each(css_set_table, i, cset, hlist)
 			list_move_tail(&cset->e_cset_node[ss->id],
 				       &dcgrp->e_csets[ss->id]);
 
 		dst_root->subsys_mask |= 1 << ssid;
 		if (dst_root == &cgrp_dfl_root) {    //#3
 		} else {
 			dcgrp->subtree_control |= 1 << ssid;
 		}
 
 		ret = cgroup_apply_control(dcgrp);    //#3
 
 		if (ss->bind)    //3.4
 			ss->bind(css);
 	} while_each_subsys_mask();
 
 	kernfs_activate(dcgrp->kn);
 	ret

又一大段程式碼……不過請相信我,跟寫書一樣,我已經把不影響理解的程式碼縮減了,非重點和難點的邏輯也不會引入程式碼。

這段程式碼與 cgroup_init 的邏輯有點類似,不同點在於 cgroup_init 將 ss 和 cgrp_dfl_root 繫結,rebind_subsystems 嘗試將 ss 與當前繫結的 cgroup_root(層級結構)解綁,然後繫結到新的 cgroup_root 上。 

第 3.1 步,條件檢查。

標號 #1,如果嘗試繫結的某個 ss 目前繫結的 cgroup_root 已經有子目錄了,不允許重新繫結其他 cgroup_root,除非將所有子目錄刪除。

標號 #2,被解綁和將要繫結的雙方至少有一個是 cgrp_dfl_root,否則失敗。 

前面已經說了,cgrp_dfl_root 在 cgroup v1 中是不會被複用的,所以 mount 的時候不能指定使用 cgrp_dfl_root,但是標號 #2 好像隱含著可以指定 cgrp_dfl_root 的意思?我們只是說了 cgroup v1 不會複用 cgrp_dfl_root,沒有說 cgroup v2 不可以啊。v2 確實可以,而且 v1 和 v2 可以共存。“cgroup 的實現將 v1 和 v2 交織在了一起”,有體會了吧,還有幾個地方也是這樣的(比如標號 #3),單純從 v1 的角度去理解甚至會覺得程式碼費解,大家自行閱讀程式碼的時候注意下。

3.2 和 3.3 步就是解綁與綁定了,需要注意的是 css 是複用的,因為標號 #1 已經要求原cgroup_root 不能有子目錄了,所以它的 css 其實就是“光桿司令”,整個層級結構只有它自己,複用是沒問題,修改下相關的指標即可。

標號 #3,cgroup_apply_control 最終也會呼叫 css_populate_dir,與 cgroup_setup_root 呼叫的 css_populate_dir(&root_cgrp->self) 不同,dcgrp 的 css 已經有對應的 ss 了,建立的 cftype 由 ss 決定,在我們的例子中就是 cpuset 的legacy_files,比如 cpuset.cpus、cpuset.mems 等檔案。 

所以 mount 的時候 cgroup 為我們建立的檔案分為兩部分,一部分是 ss 無關的,比如 cgroup1_base_files(cgroup.procs、tasks),所有的 ss 一般都有這些檔案,另一部分由 ss 自行決定。

除此之外,cgroup_apply_control 還可以涉及到程序的遷移(migrate),原層級結構管理的程序遷移到新的層級結構中,migrate 過程我們在下一篇中討論。系統啟動後,不做任何改動的情況下,我們嘗試在 cpuset 目錄下讀取 tasks 檔案:

love_cc@yahua:/sys/fs/cgroup/cpuset$ cat tasks
 1
 2
 3
 …
 1235
 …

這說明 Ubuntu mount cpuset 的過程中,程序已經從預設的層級結構遷移到 cpuset 層級結構上了。

3.4 步與 cgroup_init 的第 5 步一致。 

回到 cgroup_setup_root 的第 4 步,已有的 css_set 在 mount 之前都與原 cgroup_root關聯(cgroup_root.cgrp),rebind_subsystems 成功後,呼叫 link_css_set 建立它們與新 cgroup_root 的關係,原理就是第一篇中說的使用 cgrp_cset_link 實現 css_set 和cgroup 多對多的關係。 

我們用 Ubuntu 為我們 mount cpuset 子系統為例總結整個過程。

  1.  初始化結束後,init_css_set 是唯一的 css_set,cgrp_dfl_root 是唯一的cgroup_root,那麼與 init_css_set 關聯的自然是 cgrp_dfl_root.cgrp。
  2.  mount cpuset,假設在這之前還未 mount 過其他 ss。mount 過程首先建立了一個新的 cgroup_root,不妨稱為 new_root,然後呼叫 cgroup_setup_root,重新繫結cpuset 到 new_root。
  3.  init_css_set 仍然是唯一的 css_set(並沒有建立新的),但此刻 cpuset 已經綁定了 new_root,呼叫 link_css_set 建立它與 new_root 的關係。

cgroup 並不是一個簡單的模組,如果第一遍沒有完全讀懂,記住一句話就夠了,mount 建立 cgroup_root,也就是新的層級結構

 

往期回顧

《雲端計算時代,容器底層 cgroup 如何實現資源分組》


[admin ]

來源:OsChina
連結:https://www.oschina.net/question/2918182_2316593
雲端計算時代,容器底層 cgroup 的程式碼實現分析已經有210次圍觀

http://coctec.com/news/soft/show-post-235192.html