Namespace簡(jiǎn)介
NEMESS(命名空間)是Linux提供的內(nèi)核級(jí)環(huán)境隔離方法,許多編程語(yǔ)言也有namespace等功能,如C、Java等。編程語(yǔ)言的namespace旨在允許在項(xiàng)目中的不同命名空間中使用相同的函數(shù)名或類名。
而Linux的 namespace 也是為了實(shí)現(xiàn)資源能夠在不同的命名空間里有相同的名稱,譬如在 A命名空間 有個(gè)pid為1的進(jìn)程,而在 B命名空間 中也可以有一個(gè)pid為1的進(jìn)程。有了 namespace 就可以實(shí)現(xiàn)基本的容器功能,著名的 Docker 也是使用了 namespace 來(lái)實(shí)現(xiàn)資源隔離的。
Linux支持6種資源的 namespace,分別為(文檔):
在調(diào)用 clone() 系統(tǒng)調(diào)用時(shí),傳入以上的不同類型的參數(shù)就可以實(shí)現(xiàn)復(fù)制不同類型的namespace。比如傳入 CLONE_NEWPID參數(shù)時(shí),就是復(fù)制 pid命名空間,在新的 pid命名空間 里可以使用與其他 pid命名空間 相同的pid。代碼如下:
#define _GNU_SOURCE #include <; #include <; #include <uni; #include <sy; #include <sy; #include <; #include <; #include <errno.h> char child_stack[5000]; int child(void* arg) { printf("Child - %d\n", getpid()); return 1; } int main() { printf("Parent - fork child\n"); int pid = clone(child, child_stack+5000, CLONE_NEWPID, NULL); if (pid == -1) { perror("clone:"); exit(1); } waitpid(pid, NULL, 0); printf("Parent - child(%d) exit\n", pid); return 0; }輸出如下:
Parent - fork child Parent - child(9054) exit Child - 1從運(yùn)行結(jié)果可以看出,在子進(jìn)程的 pid命名空間 里當(dāng)前進(jìn)程的pid為1,但在父進(jìn)程的 pid命名空間 中子進(jìn)程的pid卻是9045。
namespace實(shí)現(xiàn)原理
為了讓每個(gè)進(jìn)程都可以從屬于某一個(gè)namespace,Linux內(nèi)核為進(jìn)程描述符添加了一個(gè) struct nsproxy 的結(jié)構(gòu),如下:
struct task_struct { ... /* namespaces */ struct nsproxy *nsproxy; ... } struct nsproxy { atomic_t count; struct uts_namespace *uts_ns; struct ipc_namespace *ipc_ns; struct mnt_namespace *mnt_ns; struct pid_namespace *pid_ns; struct user_namespace *user_ns; struct net *net_ns; };從 struct nsproxy 結(jié)構(gòu)的定義可以看出,Linux為每種不同類型的資源定義了不同的命名空間結(jié)構(gòu)體進(jìn)行管理。比如對(duì)于 pid命名空間 定義了 struct pid_namespace 結(jié)構(gòu)來(lái)管理 。由于 namespace 涉及的資源種類比較多,所以本文主要以 pid命名空間 作為分析的對(duì)象。
我們先來(lái)看看管理 pid命名空間 的 struct pid_namespace 結(jié)構(gòu)的定義:
struct pid_namespace { struct kref kref; struct pidmap pidmap[PIDMAP_ENTRIES]; int last_pid; struct task_struct *child_reaper; struct kmem_cache *pid_cachep; unsigned int level; struct pid_namespace *parent; #ifdef CONFIG_PROC_FS struct vfsmount *proc_mnt; #endif };因?yàn)?struct pid_namespace 結(jié)構(gòu)主要用于為當(dāng)前 pid命名空間 分配空閑的pid,所以定義比較簡(jiǎn)單:
- kref 成員是一個(gè)引用計(jì)數(shù)器,用于記錄引用這個(gè)結(jié)構(gòu)的進(jìn)程數(shù)
- pidmap 成員用于快速找到可用pid的位圖
- last_pid 成員是記錄最后一個(gè)可用的pid
- level 成員記錄當(dāng)前 pid命名空間 所在的層次
- parent 成員記錄當(dāng)前 pid命名空間 的父命名空間
由于 pid命名空間 是分層的,也就是說(shuō)新創(chuàng)建一個(gè) pid命名空間 時(shí)會(huì)記錄父級(jí) pid命名空間 到 parent 字段中,所以隨著 pid命名空間 的創(chuàng)建,在內(nèi)核中會(huì)形成一顆 pid命名空間 的樹(shù),如下圖(圖片來(lái)源):
第0層的 pid命名空間 是 init 進(jìn)程所在的命名空間。如果一個(gè)進(jìn)程所在的 pid命名空間 為 N,那么其在 0 ~ N 層pid命名空間 都有一個(gè)唯一的pid號(hào)。也就是說(shuō) 高層pid命名空間 的進(jìn)程對(duì) 低層pid命名空間 的進(jìn)程是可見(jiàn)的,但是 低層pid命名空間的進(jìn)程對(duì) 高層pid命名空間 的進(jìn)程是不可見(jiàn)的。
由于在 第N層pid命名空間 的進(jìn)程其在 0 ~ N層pid命名空間 都有一個(gè)唯一的pid號(hào),所以在進(jìn)程描述符中通過(guò) pids 成員來(lái)記錄其在每個(gè)層的pid號(hào),代碼如下:
struct task_struct { ... struct pid_link pids[PIDTYPE_MAX]; ... } enum pid_type { PIDTYPE_PID, PIDTYPE_PGID, PIDTYPE_SID, PIDTYPE_MAX }; struct upid { int nr; struct pid_namespace *ns; struct hlist_node pid_chain; }; struct pid { atomic_t count; struct hlist_head tasks[PIDTYPE_MAX]; struct rcu_head rcu; unsigned int level; struct upid numbers[1]; }; struct pid_link { struct hlist_node node; struct pid *pid; };這幾個(gè)結(jié)構(gòu)的關(guān)系如下圖:
我們主要關(guān)注 struct pid 這個(gè)結(jié)構(gòu),struct pid 有個(gè)類型為 struct upid 的成員 numbers,其定義為只有一個(gè)元素的數(shù)組,但是其實(shí)是一個(gè)動(dòng)態(tài)的數(shù)據(jù),它的元素個(gè)數(shù)與 level 的值一致,也就是說(shuō)當(dāng) level 的值為5時(shí),那么 numbers 成員就是一個(gè)擁有5個(gè)元素的數(shù)組。而每個(gè)元素記錄了其在每層 pid命名空間 的pid號(hào),而 struct upid 結(jié)構(gòu)的 nr 成員就是用于記錄進(jìn)程在不同層級(jí) pid命名空間 的pid號(hào)。
我們通過(guò)代碼來(lái)看看怎么為進(jìn)程分配pid號(hào)的,在內(nèi)核中是用過(guò) alloc_pid() 函數(shù)分配pid號(hào)的,代碼如下:
struct pid *alloc_pid(struct pid_namespace *ns) { struct pid *pid; enum pid_type type; int i, nr; struct pid_namespace *tmp; struct upid *upid; pid = kmem_cache_alloc(ns->pid_cachep, GFP_KERNEL); if (!pid) goto out; tmp = ns; for (i = ns->level; i >= 0; i--) { nr = alloc_pidmap(tmp); // 為當(dāng)前進(jìn)程所在的不同層級(jí)pid命名空間分配一個(gè)pid if (nr < 0) goto out_free; pid->numbers[i].nr = nr; // 對(duì)應(yīng)i層namespace中的pid數(shù)字 pid->numbers[i].ns = tmp; // 對(duì)應(yīng)i層namespace的實(shí)體 tmp = tmp->parent; } get_pid_ns(ns); pid->level = ns->level; atomic_set(&pid->count, 1); for (type = 0; type < PIDTYPE_MAX; ++type) INIT_HLIST_HEAD(&pid->tasks[type]); spin_lock_irq(&pidmap_lock); for (i = ns->level; i >= 0; i--) { upid = &pid->numbers[i]; // 把upid連接到全局pid中, 用于快速查找pid hlist_add_head_rcu(&upid->pid_chain, &pid_hash[pid_hashfn(upid->nr, upid->ns)]); } spin_unlock_irq(&pidmap_lock); out: return pid; ... }上面的代碼中,那個(gè) for (i = ns->level; i >= 0; i--) 就是通過(guò) parent 成員不斷向上檢索為不同層級(jí)的 pid命名空間分配一個(gè)唯一的pid號(hào),并且保存到對(duì)應(yīng)的 nr 字段中。另外,還會(huì)把進(jìn)程所在各個(gè)層級(jí)的pid號(hào)添加到全局pid哈希表中,這樣做是為了通過(guò)pid號(hào)快速找到進(jìn)程。
現(xiàn)在我們來(lái)看看怎么通過(guò)pid號(hào)快速找到一個(gè)進(jìn)程,在內(nèi)核中 find_get_pid() 函數(shù)用來(lái)通過(guò)pid號(hào)查找對(duì)應(yīng)的 struct pid結(jié)構(gòu),代碼如下(find_get_pid() -> find_vpid() -> find_pid_ns()):
struct pid *find_get_pid(pid_t nr) { struct pid *pid; rcu_read_lock(); pid = get_pid(find_vpid(nr)); rcu_read_unlock(); return pid; } struct pid *find_vpid(int nr) { return find_pid_ns(nr, current->nsproxy->pid_ns); } struct pid *find_pid_ns(int nr, struct pid_namespace *ns) { struct hlist_node *elem; struct upid *pnr; hlist_for_each_entry_rcu(pnr, elem, &pid_hash[pid_hashfn(nr, ns)], pid_chain) if (pnr->nr == nr && pnr->ns == ns) return container_of(pnr, struct pid, numbers[ns->level]); return NULL; }通過(guò)pid號(hào)查找 struct pid 結(jié)構(gòu)時(shí),首先會(huì)把進(jìn)程pid號(hào)和當(dāng)前進(jìn)程的 pid命名空間 傳入到 find_pid_ns() 函數(shù),而在 find_pid_ns() 函數(shù)中通過(guò)全局pid哈希表來(lái)快速查找對(duì)應(yīng)的 struct pid 結(jié)構(gòu)。獲取到 struct pid 結(jié)構(gòu)后就可以很容易地獲取到進(jìn)程對(duì)應(yīng)的進(jìn)程描述符,例如可以通過(guò) pid_task() 函數(shù)來(lái)獲取 struct pid 結(jié)構(gòu)對(duì)應(yīng)進(jìn)程描述符,由于代碼比較簡(jiǎn)單,這里就不再分析了。
1.《【#NAME?】容器原理之 - namespace》援引自互聯(lián)網(wǎng),旨在傳遞更多網(wǎng)絡(luò)信息知識(shí),僅代表作者本人觀點(diǎn),與本網(wǎng)站無(wú)關(guān),侵刪請(qǐng)聯(lián)系頁(yè)腳下方聯(lián)系方式。
2.《【#NAME?】容器原理之 - namespace》僅供讀者參考,本網(wǎng)站未對(duì)該內(nèi)容進(jìn)行證實(shí),對(duì)其原創(chuàng)性、真實(shí)性、完整性、及時(shí)性不作任何保證。
3.文章轉(zhuǎn)載時(shí)請(qǐng)保留本站內(nèi)容來(lái)源地址,http://f99ss.com/jiaoyu/2101295.html