linux container容器技術框架性理解
我對container原理的一些理解(基於linux kernel 2.6.38)
by kin
2011.04.17
=====================================================
linux中稱謂的container在核心層面由兩個獨立的機制保證,一個保證資源的隔離性,名為namespace;一個進行資源的控制,名為cgroup。
1 namespace
inux現有的namespace有6種: uts, pid, ipc, mnt, net和user 。所有的namespace都和task_struct相關,其中uts, pid, ipc, mnt和net都處於task_struct->ns_proxy中,而user_namespace卻是由task_struct相關聯的user_struct指向決定的 。
1.1 uts
最簡單的主機名,一個namespace結構繫結這樣一個字串,uname的時候去current->nsproxy->uts_namespace下面取字串就好了
1.2 ipc
ipc維護的是一個id到struct的對映關係,這種對映關係在核心由公用設施idr提供。所謂ipc的namespace就是每個namespace都有自己獨立的idr,即獨立的key->value的對映集合,不同的namespace通過key找value會在不同的idr中尋找,核心資料結構是公共可見的,隔離的僅僅是索引關係。
1.3 mnt
每個mnt_namespace有自己獨立的vfsmount *root, 即根掛載點是互相獨立的,同時由vfsmount->mnt_child串接起來的子mnt連結串列,以及繼續往下都是彼此獨立的,產生的外在效果就是某個mnt_namespace中的mount, umount不會 對其他namespace產生影響,因為整個mount樹是每個namespace各有一份,彼此間無干擾, path lookup也在各自的mount樹中進行。這裡和chroot之類的又不一樣,chroot改變的只是 task_struct相關的fs_struct中的root,影響的是path lookup的起始點,對整個mount樹並無關係.
1.4 user
user_namespace的實現方式和ipc類似,每個namespace各自維護一個uid到user_struct的對映,用hash表實現。但是uid會在兩個地方體現,一個是user_struct中的uid,還有一個是cred中的uid。user_namespace影響範圍侷限在user_struct中,雖然clone(NEWUSER)時會把task_struct的cred中的uid,gid都設成0,然後這種關係又可以通過fork等傳遞下去,但是終究user_namespace並沒有影響到cred裡面資料,而且vfs裡面inode是隻有uid的,
不會有user_struct資訊,因此某個具體的檔案其uid是固定的,具體在某個namespace中如何顯示使用者名稱則不關核心層的事了,由/etc/passwd中定義的對映關係決定。
另外還有個問題,比如跨兩個namespace的unix套接字通訊,有個選項叫PEERCRED,是拿對方節點的ucred結構,因為不同namespace,因此拿到的uid,gid都要進行user_namespace的重對映。這裡重對映的策略就是:
1)同user_namespace,OK。不需要
2)不同,則由於每個user_namespace都會記錄建立自己的那個user_struct,因此一層層往上索引到init_user_ns,如果發現需要remap的那個user_struct是我們的祖先建立者,則map為0,否則則返回一個表示不存在的MAGIC NUMBER
1.5 pid
pid_namespace是每個namespace有一個單獨的pid_map作為bitmap進行pid的分配,因此各個pid namespace的pid互不干擾,獨立分配。同一個task_struct會從init_ns開始,到最終它所在的namespace,每一層都會有個單獨的pid(也就是深層次的task_struct建立,在每一個層次的namespace都會進行pid的alloc),而所有這些pid資訊都是棧式儲存在struct pid結構中。
pid是唯一一個底層namespace會把上層namespace資訊都保留下來的namespace, pid資訊儲存在struct pid中,而具體的(pid, ns)資訊對則儲存在upid中,pid會根據自己的namespace深度擴充套件一個upid的棧,在這個pid結構中,該task_struct從init_ns到實際所處的namespace整個樹路徑上的(pid,ns)資訊都記錄了,於是上面所說的跨namesapce unix socket通訊取PEERCRED對pid的remap就很簡單了,有父子關係,直接從pid的不同深度取另一個值就行了;如果沒父子關係,則MAGIC NUMBER。
同時pid的upid棧中每個upid都會連入對應pid namespace的hash表中,那麼該task_struct在每個namespace層次中都是可見的(可在ns對應hash表中找到),且pid號互不相關(看到的是對應棧層次上的upid->nr)。
由於歷史因素,task_struct中除了用pid索引pid, ppid, pgid,sid,結構體本身還有pid,tgid等,這裡的資料都是 取的init_ns中的對應數值.
1.6 net
net_ns是非常複雜的一塊。mainline都做了幾個版本來穩定它,主要是各個關鍵資料結構都加上了net資訊,比如sock, 路由查詢的fib, netfilter的rules,net_device等, net對於不同net的資料隔離和前面幾種每個namespace自己建索引表不同,net的一般都在同一個索引表中,只是資料多加一維net資訊,在查詢時多加上對這一維的比較。相應的網路層關鍵處理函式都會多帶一個net_namespace的引數. 在net_namespace結構體內部,主要儲存了網路各層關鍵的sysctl資訊,用於實現對不同net namespace可以進行不同的核心引數配置,以及不同的proc entry匯出。
1.7 task_struct 在不同ns之間的轉移
最新的mainline kernel: 2.6.39rc3是沒有實現這樣的一個系統呼叫的。lxc內有個lxc-attach依賴一個setns的系統呼叫,這個系統呼叫原理就是通過你提供的/proc/pid/ns這樣一個proc inode,來找到對應的namespace結構體,然後把current->nsproxy->xx_namespace設為那個,可能還要進行些額外的操作(比如pid的remap,user的remap之類的)就OK了。但是namespace本身就是提供的安全性隔離,這樣做有安全風險,所以mainline kernel並沒有merge這個patch。
1.8 如何建立新的namespace
建立一個新的程序,man 2 clone
當前程序,man 2 unshare
2. cgroup
cgroup是通過vfs的介面來進行配置的,它本身充當一個resource controller的角色,對一組程序進行資源控制,核心角色便是一組task_struct。
2.1 cgroup 的幾個核心概念
cgroup核心的有三個概念:
hierarchy : 就是mount的一個cgroup fs。
subsystem : 一個subsystem對一種資源進行control
cgroup : 一個hierarchy下面對程序的分組
整體的邏輯可以這樣來看:
1) 一個hierarchy和多個subsystem繫結,該hierarchy只對這幾個subsystem對應的resouce進行control
2)不同的hierarchy繫結的subsystem是互斥的
3)一個hierarchy下面可以通過mkdir方式建立不同的cgroup,這個cgroup下面可以attach一組程序,通過該cgroup指定的引數對這種程序資源使用進 行track和控制
這樣分可以使task A和task B在hierarchy A中在一個cgroup中控制CPU, 而task A和task B在hierarchy B中分別在兩個cgroup中控制memory,提供 更靈活的策略
2.2 cgroup 在核心層次和task_struct的互動
一個cgroup和一個subsystem決定了一個cgroup_subsys_state, 每個程序都以css_set(一個靜態陣列)的方式儲存了所有和它有關的cgroup_subsys_state,同樣,每個cgroup也會儲存這樣一組。 cgroup_subsys_state又會作為每個subsystem自己結構體的內部成員包含,這樣通過container_of很容易就可以找到subsystem自己的控制結構體,來進行各子系統自己的控制,於是每個task_struct都可以通過css_set找到它所屬的subsystem結構體,來進行後面的操作.
而對於每個css_set, 也會把所有使用它的task連起來,這樣cgroup匯出所有當前繫結程序也有了依據(每個Cgroup連結一組css_set,每個css_set又會串聯一組task_struct,挨個遍歷)
2.3 cgroup 當前已有的subsys
/* Add subsystem definitions of the form SUBSYS(<name>) in this
SUBSYS(cpuset)
SUBSYS(debug)
SUBSYS(ns)
SUBSYS(cpu_cgroup)
SUBSYS(cpuacct)
SUBSYS(mem_cgroup)
SUBSYS(devices)
SUBSYS(freezer)
SUBSYS(net_cls)
SUBSYS(blkio)
SUBSYS(perf)
其中ns_cgroup已經被拋棄,通過cgroup.clone_children和手動attach程序來取代
2.4 cpu_cgroup
對CPU的使用率進行控制,根本上利用了CFS中已有的task group概念,最近出來的autogroup也是 利用了這樣一個機制。核心中的排程針對的是sched_entity這樣一個結構,sched_entity本身有一個run queue,同時它也會在別人的run queue上,以此層層巢狀下來,直至最後的可執行單元(task_struct)。於是一個cgroup的cpu resource controller
便是這樣的一個sched_entity,所有attach到這個Cgroup的程序都在這個sched_entity下面 ,排程也都在這個sched_entity下面進行。
這樣的一個樹形架構下,每個sched_entity只負責進行在該sched_entity runquueu上的sched_entity進行CFS,即從紅黑樹最左端pick一個sched_entity,由該sched_entity進行下一個層次的CFS,而每次最終pick出來的task_struct的執行對所有它的父sched_entity的執行時間都有貢獻,如此實現一個全域性的CFS。並且實現task group or task group's group...
2.5 mem_cgroup
memory有全域性的per zone lru list來進行page relcaim等,有全域性的page陣列來進行實體記憶體管理. memory_cgroup在cgroup層面上實現了per_cgroup lru list, 以及page_cgroup陣列,memory_cgroup的lru操作及其他基於page的操作都是以page_cgroup為依據的,而page_cgroup和真正的page之間又會有個對映關係
mem_cgroup對於記憶體資源的控制,是對每個cgroup的使用記憶體情況進行記錄,主要方式就是在真正分配物理頁面的地方(缺頁中斷,或者分配page cache頁等)都進行了hack(可以統計哪些頁面取決於在哪些記憶體分配部分進行了分配物理頁的hack, 目前應該是絕大部分都有進行hack),通過current->cgroups->subsys[xx]找到對應的mem_cgroup控制結構,
判斷可否分配,可以就加上自己的計數,不行或達到某個limit,就會觸發基於per-cgroup lru的reclaim, 再或者就觸發cgoup內部的OOM killer等。
但是記憶體作為一個最複雜的部分,mem_cgroup的發展也是今年Linux filesystem, storage, and memory management summit討論最多的話題,
有以下幾點
1)重複的lru list,全域性記憶體緊張依然會page reclaim,不分cgroup。而且兩個lru list兩次lru也重複了
2) page_cgroup結構體可以變的沒有,現在20 bytes(32bit)/40bytes(64 bit)太大了
3) 全域性和單個cgroup的權衡
...
2.6 net_cls
主要利用了核心網路協議棧traffic control的相關東西,實現了一個cgroup的統一標記id,然後實現了一個叫cgoup的filter,這個filter就是根據當前程序所在的cgroup_subsys決定給sk_buff打上何樣的id標記,然後這個標記就會被用於匹配相應的traffic control的qdisc和class來進行具體的流量控制策略。
2.7 device
在inode_permission中hook入cgroup的檢查,對於inode->st_type為char或block型別的,會與儲存在列表中的進行讀,寫許可權匹配,根據匹配結果決定
cgoup的inode檢查返回允許否?
在vfs_mknod hook入cgroup對mknod主從裝置號的匹配檢查,決定允許否?
2.8 freezer
給cgoup中每個task_struct設定下TIF_FREEZING,然後就開始try_to_freeze, 設定個PF_FROZON的flag,程序就開始空轉了
for (;;) {
set_current_state(TASK_UNINTERRUPTIBLE);
if (!frozen(current))
break;
schedule();
}
而喚醒如上程式碼所示,就是去掉PF_FROZON這個flag的過程。
2.9 cpuset
同sched_setaffinity,但是是對於一組程序設定CPU親和性了。核心在CPU親和性邏輯跟以前沒什麼區別,無非是把這些程序只調度到對應CPU的run queue而已。
同時cpuset還提供了一個只在對應cpu間進行負載均衡的特性,就是把對應的cpu作為一個sched doamin,可以在其中負載均衡。不過要求最好各個cgoup設定互斥的cpu,否則就會取cgroup的最大互斥並集作為sched domain,這樣跨cgroup的load banlance又會導致其他的複雜因素。
3. lxc
lxc是一個使用者空間的管理工具,提供使用者友好的介面包裝了核心提供的namespace和cgroup機制.
它主要實現原理是這樣的:
0) 首先準備建立好網路裝置(netlink建立裝置),
- openpty指定數目的pty(建立對應的/dev/ptmx和/dev/pts/xx對),作為lxc_console所用,slave會被mount bind到container的/dev/ttyx,通過master fd可和container中
起來的shell通訊
並open一個/dev/tty作為console所用,這個console裝置也會被mount bind到container中,這個console fd就是用於lxc_start後獲得的那個console與container的通訊
1) 開始clone,建立新的namespace :父程序clone一個子程序,clone的時候指定新建所有的namespace,如此一個完全新的namespace就建立了。
子程序:
2) 父子同步 :子程序設定自己死了給父程序發sigkill,防止父程序空等, 同時也保證container中的init死掉同時會導致host中的lxc_start死掉.
父程序:
3) 建立對應的cgroup :父程序設定clone_children,然後建立新的cgroup,並將clone的pid attach到新建立 cgoup中。(等同於以前ns_cgroup所做的工作)
4) 挪移網路裝置: 將配置在container中的網路裝置通過傳送netlink訊息,帶上IFLA_NET_NS_PID的rtattr,觸發核心的dev_change_net_namespace,將net_device的namespace替換掉,
具體網路裝置的change namespace涉及到在一個namespace的完全停止,解註冊和在另一個namespace中的註冊等流程。
子程序:
5) 設定container utsname
6) 設定container網路裝置引數 :通過netlink訊息給本net namespace 網路裝置設定ip addr, hw addr, flags等資訊
7) 修改cgroup中resouce control引數 :由於還沒有chroot,可以修改自己cgroup中相應的resource control設定
8) 建立根檔案系統並建立自己的掛載: mount rootfs, mount fstab等等
8) 建立host和container在終端方面的通訊渠道 :
把/dev/tty mount bind到container中,這樣我們就可以:
pty slave <----> pty master <----> epoll 兩端轉發 console fd 來獲得剛開始的一個Console
把前面建立的pty的slave mount bind到container中的/dev/ttyx, 這樣我們就可以通過對應的pty master fd實現和container中的通訊:
stdin,stdou epoll 兩端轉發 <--------> pty master <------> mount bind到container中的/dev/ttyx
9) 改變根掛載點 : chroot並把從host中繼承過來的mount關係在container中不會用到的umount掉及其他一些保證系統正常運轉要乾的其他工作
10) exec 開始init: container 1號程序exec建立自己的程序樹
父程序,子程序同時運轉
11) 父 epoll迴圈 :父程序醒來 ,開始一個epoll迴圈,主要處理console和container的兩端轉發epoll, 以及接收一些外來查詢請求的unix套介面epoll
比如查詢container init在host中程序pid,這樣我們可以kill -SIGKILL pid, 就可以殺掉整個container
通過unix套接字的msgcontrol傳遞pty的master fd(也就是被mount bind到container中/dev/ttyx的peer fd),用來實現lxc_console取container tty的作用。
unix套接字繫結在一個已知的路徑下,在lxc 0.7.4中通過這個unix套介面實現有取container init pid,取tty fd,取container state及停止container的作用。
而基本所有的lxc小工具都是基於這個unix套接字來獲取一些關於container的資訊。
12) 子獨立程序樹下的運轉: 在一個單獨的namespace裡面,有自己獨立的resource control,單獨的基礎設施和使用者空間程序樹,一個隔離並資源控制的新環境,謂之container。便
運轉起來了。
1. Linux核心namespace機制
Linux Namespaces機制提供一種資源隔離方案。PID,IPC,Network等系統資源不再是全域性性的,而是屬於某個特定的Namespace。每個namespace下的資源對於其他namespace下的資源都是透明,不可見的。因此在作業系統層面上看,就會出現多個相同pid的程序。系統中可以同時存在兩個程序號為0,1,2的程序,由於屬於不同的namespace,所以它們之間並不衝突。而在使用者層面上只能看到屬於使用者自己namespace下的資源,例如使用ps命令只能列出自己namespace下的程序。這樣每個namespace看上去就像一個單獨的Linux系統。
2 . Linux核心中namespace結構體
在Linux核心中提供了多個namespace,其中包括fs (mount),uts, network, sysvipc, 等。一個程序可以屬於多個namesapce,既然namespace和程序相關,那麼在task_struct結構體中就會包含和namespace相關聯的變數。在task_struct 結構中有一個指向namespace結構體的指標nsproxy。
structtask_struct {
……..
/* namespaces */
structnsproxy *nsproxy;
…….
}
再看一下nsproxy是如何定義的,在include/linux/nsproxy.h檔案中,這裡一共定義了5個各自的名稱空間結構體,在該結構體中定義了5個指向各個型別namespace的指標,由於多個程序可以使用同一個namespace,所以nsproxy可以共享使用,count欄位是該結構的引用計數。
/* 'count' isthe number of tasks holding a reference.
* The count for each namespace, then, will bethe number
* of nsproxies pointing to it, not the numberof tasks.
* The nsproxy is shared by tasks which shareall namespaces.
* As soon as a single namespace is cloned orunshared, the
* nsproxy is copied
*/
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_for_children;
struct net *net_ns;
};
(1) UTS名稱空間包含了執行核心的名稱、版本、底層體系結構型別等資訊。UTS是UNIX Timesharing System的簡稱。
(2) 儲存在structipc_namespace中的所有與程序間通訊(IPC)有關的資訊。
(3) 已經裝載的檔案系統的檢視,在structmnt_namespace中給出。
(4) 有關程序ID的資訊,由structpid_namespace提供。
(5) struct net_ns包含所有網路相關的名稱空間引數。
系統中有一個預設的nsproxy,init_nsproxy,該結構在task初始化是也會被初始化。#defineINIT_TASK(tsk) \
{
.nsproxy = &init_nsproxy,
}
其中init_nsproxy的定義為:
static structkmem_cache *nsproxy_cachep;
struct nsproxyinit_nsproxy = {
.count =ATOMIC_INIT(1),
.uts_ns =&init_uts_ns,
#ifdefined(CONFIG_POSIX_MQUEUE) || defined(CONFIG_SYSVIPC)
.ipc_ns =&init_ipc_ns,
#endif
.mnt_ns =NULL,
.pid_ns_for_children = &init_pid_ns,
#ifdefCONFIG_NET
.net_ns =&init_net,
#endif
};
對於 .mnt_ns
沒有進行初始化,其餘的namespace都進行了系統預設初始。
3. 使用clone建立自己的Namespace
如果要建立自己的名稱空間,可以使用系統呼叫clone(),它在使用者空間的原型為
int clone(int (*fn)(void *), void*child_stack, int flags, void *arg)
這裡fn是函式指標,這個就是指向函式的指標,, child_stack是為子程序分配系統堆疊空間,flags就是標誌用來描述你需要從父程序繼承那些資源, arg就是傳給子程序的引數也就是fn指向的函式引數。下面是flags可以取的值。這裡只關心和namespace相關的引數。
CLONE_FS 子程序與父程序共享相同的檔案系統,包括root、當前目錄、umask
CLONE_NEWNS 當clone需要自己的名稱空間時設定這個標誌,不能同時設定CLONE_NEWS和CLONE_FS。
Clone()函式是在libc庫中定義的一個封裝函式,它負責建立新輕量級程序的堆疊並且呼叫對程式設計者隱藏了clone系統條用。實現clone()系統呼叫的sys_clone()服務例程並沒有fn和arg引數。封裝函式把fn指標存放在子程序堆疊的每個位置處,該位置就是該封裝函式本身返回地址存放的位置。Arg指標正好存放在子程序堆疊中的fn的下面。當封裝函式結束時,CPU從堆疊中取出返回地址,然後執行fn(arg)函式。
/* Prototype forthe glibc wrapper function */
#include
int clone(int (*fn)(void *), void*child_stack,
int flags, void *arg, ...
/* pid_t *ptid, structuser_desc *tls, pid_t *ctid*/ );
/* Prototype for the raw system call */
long clone(unsigned long flags, void *child_stack,
void *ptid, void *ctid,
struct pt_regs *regs);
我們在Linux核心中看到的實現函式,是經過libc庫進行封裝過的,在Linux核心中的fork.c檔案中,有下面的定義,最終呼叫的都是do_fork()函式。
#ifdef __ARCH_WANT_SYS_CLONE
#ifdef CONFIG_CLONE_BACKWARDS
SYSCALL_DEFINE5(clone, unsigned long,clone_flags, unsigned long, newsp,
int __user *, parent_tidptr,
int, tls_val,
int __user *, child_tidptr)
#elif defined(CONFIG_CLONE_BACKWARDS2)
SYSCALL_DEFINE5(clone, unsigned long, newsp,unsigned long, clone_flags,
int __user *, parent_tidptr,
int __user *, child_tidptr,
int, tls_val)
#elif defined(CONFIG_CLONE_BACKWARDS3)
SYSCALL_DEFINE6(clone, unsigned long,clone_flags, unsigned long, newsp,
int,stack_size,
int__user *, parent_tidptr,
int__user *, child_tidptr,
int,tls_val)
#else
SYSCALL_DEFINE5(clone, unsigned long,clone_flags, unsigned long, newsp,
int __user *, parent_tidptr,
int __user *, child_tidptr,
int, tls_val)
#endif
{
returndo_fork(clone_flags, newsp, 0, parent_tidptr, child_tidptr);
}
#endif
3.1 do_fork函式
在clone()函式中呼叫do_fork函式進行真正的處理,在do_fork函式中呼叫copy_process程序處理。
long do_fork(unsigned long clone_flags,
unsigned long stack_start,
unsigned long stack_size,
int __user *parent_tidptr,
int __user *child_tidptr)
{
structtask_struct *p;
inttrace = 0;
longnr;
/*
* Determine whether and which event to reportto ptracer. When
* called from kernel_thread or CLONE_UNTRACEDis explicitly
* requested, no event is reported; otherwise,report if the event
* for the type of forking is enabled.
*/
if(!(clone_flags & CLONE_UNTRACED)) {
if(clone_flags & CLONE_VFORK)
trace= PTRACE_EVENT_VFORK;
elseif ((clone_flags & CSIGNAL) != SIGCHLD)
trace= PTRACE_EVENT_CLONE;
else
trace= PTRACE_EVENT_FORK;
if(likely(!ptrace_event_enabled(current, trace)))
trace= 0;
}
p = copy_process(clone_flags, stack_start, stack_size,
child_tidptr, NULL, trace);
/*
* Do this prior waking up the new thread - thethread pointer
* might get invalid after that point, if thethread exits quickly.
*/
if(!IS_ERR(p)) {
structcompletion vfork;
structpid *pid;
trace_sched_process_fork(current,p);
pid= get_task_pid(p, PIDTYPE_PID);
nr= pid_vnr(pid);
if(clone_flags & CLONE_PARENT_SETTID)
put_user(nr,parent_tidptr);
if(clone_flags & CLONE_VFORK) {
p->vfork_done= &vfork;
init_completion(&vfork);
get_task_struct(p);
}
wake_up_new_task(p);
/*forking complete and child started to run, tell ptracer */
if(unlikely(trace))
ptrace_event_pid(trace,pid);
if(clone_flags & CLONE_VFORK) {
if(!wait_for_vfork_done(p, &vfork))
ptrace_event_pid(PTRACE_EVENT_VFORK_DONE,pid);
}
put_pid(pid);
}else {
nr= PTR_ERR(p);
}
returnnr;
}
3.2 copy_process函式
在copy_process函式中呼叫copy_namespaces函式。
static structtask_struct *copy_process(unsigned long clone_flags,
unsignedlong stack_start,
unsignedlong stack_size,
int__user *child_tidptr,
structpid *pid,
inttrace)
{
int retval;
struct task_struct *p;
/*下面的程式碼是對clone_flag標誌進行檢查,有部分表示是互斥的,例如CLONE_NEWNS和CLONENEW_FS*/
if ((clone_flags &(CLONE_NEWNS|CLONE_FS)) == (CLONE_NEWNS|CLONE_FS))
return ERR_PTR(-EINVAL);
if ((clone_flags &(CLONE_NEWUSER|CLONE_FS)) == (CLONE_NEWUSER|CLONE_FS))
return ERR_PTR(-EINVAL);
if ((clone_flags & CLONE_THREAD)&& !(clone_flags & CLONE_SIGHAND))
return ERR_PTR(-EINVAL);
if ((clone_flags & CLONE_SIGHAND)&& !(clone_flags & CLONE_VM))
return ERR_PTR(-EINVAL);
if ((clone_flags & CLONE_PARENT)&&
current->signal->flags& SIGNAL_UNKILLABLE)
return ERR_PTR(-EINVAL);
……
retval = copy_namespaces(clone_flags, p);
if (retval)
goto bad_fork_cleanup_mm;
retval = copy_io(clone_flags, p);
if (retval)
gotobad_fork_cleanup_namespaces;
retval = copy_thread(clone_flags,stack_start, stack_size, p);
if (retval)
goto bad_fork_cleanup_io;
/*do_fork中呼叫copy_process函式,該函式中pid引數為NULL,所以這裡的if判斷是成立的。為程序所在的namespace分配pid,在3.0的核心之前還有一個關鍵函式,就是namespace建立後和cgroup的關係,
if (current->nsproxy != p->nsproxy) {
retval = ns_cgroup_clone(p, pid);
if (retval)
goto bad_fork_free_pid;
if (pid != &init_struct_pid) {
retval = -ENOMEM;
pid =alloc_pid(p->nsproxy->pid_ns_for_children);
if (!pid)
gotobad_fork_cleanup_io;
}…..
}
3.3 copy_namespaces 函式
在kernel/nsproxy.c檔案中定義了copy_namespaces函式。
int copy_namespaces(unsigned long flags,struct task_struct *tsk)
{
structnsproxy *old_ns = tsk->nsproxy;
structuser_namespace *user_ns = task_cred_xxx(tsk, user_ns);
structnsproxy *new_ns;
/*首先檢查flag,如果flag標誌不是下面的五種之一,就會呼叫get_nsproxy對old_ns遞減引用計數,然後直接返回0*/
if(likely(!(flags & (CLONE_NEWNS | CLONE_NEWUTS | CLONE_NEWIPC |
CLONE_NEWPID | CLONE_NEWNET)))) {
get_nsproxy(old_ns);
return0;
}
/*當前程序是否有超級使用者的許可權*/
if(!ns_capable(user_ns, CAP_SYS_ADMIN))
return-EPERM;
/*
* CLONE_NEWIPC must detach from the undolist:after switching
* to a new ipc namespace, the semaphore arraysfrom the old
* namespace are unreachable. In clone parlance, CLONE_SYSVSEM
* means share undolist with parent, so we mustforbid using
* it along with CLONE_NEWIPC.
對CLONE_NEWIPC進行特殊的判斷,*/
if((flags & (CLONE_NEWIPC | CLONE_SYSVSEM)) ==
(CLONE_NEWIPC| CLONE_SYSVSEM))
return-EINVAL;
/*為程序建立新的namespace*/
new_ns= create_new_namespaces(flags, tsk, user_ns, tsk->fs);
if(IS_ERR(new_ns))
return PTR_ERR(new_ns);
tsk->nsproxy= new_ns;
return0;
}
3.4 create_new_namespaces函式
create_new_namespaces建立新的namespace
static struct nsproxy*create_new_namespaces(unsigned long flags,
structtask_struct *tsk, struct user_namespace *user_ns,
structfs_struct *new_fs)
{
structnsproxy *new_nsp;
interr;
/*為新的nsproxy分配記憶體空間,並對其引用計數設定為初始1*/
new_nsp= create_nsproxy();
if(!new_nsp)
returnERR_PTR(-ENOMEM);
/*如果Namespace中的各個標誌位進行了設定,則會呼叫相應的namespace進行建立*/
new_nsp->mnt_ns= copy_mnt_ns(flags, tsk->nsproxy->mnt_ns, user_ns, new_fs);
if(IS_ERR(new_nsp->mnt_ns)) {
err= PTR_ERR(new_nsp->mnt_ns);
gotoout_ns;
}
new_nsp->uts_ns= copy_utsname(flags, user_ns, tsk->nsproxy->uts_ns);
if(IS_ERR(new_nsp->uts_ns)) {
err= PTR_ERR(new_nsp->uts_ns);
gotoout_uts;
}
new_nsp->ipc_ns= copy_ipcs(flags, user_ns, tsk->nsproxy->ipc_ns);
if(IS_ERR(new_nsp->ipc_ns)) {
err= PTR_ERR(new_nsp->ipc_ns);
gotoout_ipc;
}
new_nsp->pid_ns_for_children=
copy_pid_ns(flags,user_ns, tsk->nsproxy->pid_ns_for_children);
if(IS_ERR(new_nsp->pid_ns_for_children)) {
err= PTR_ERR(new_nsp->pid_ns_for_children);
gotoout_pid;
}
new_nsp->net_ns= copy_net_ns(flags, user_ns, tsk->nsproxy->net_ns);
if(IS_ERR(new_nsp->net_ns)) {
err= PTR_ERR(new_nsp->net_ns);
gotoout_net;
}
returnnew_nsp;
out_net:
if(new_nsp->pid_ns_for_children)
put_pid_ns(new_nsp->pid_ns_for_children);
out_pid:
if(new_nsp->ipc_ns)
put_ipc_ns(new_nsp->ipc_ns);
out_ipc:
if(new_nsp->uts_ns)
put_uts_ns(new_nsp->uts_ns);
out_uts:
if(new_nsp->mnt_ns)
put_mnt_ns(new_nsp->mnt_ns);
out_ns:
kmem_cache_free(nsproxy_cachep,new_nsp);
returnERR_PTR(err);
}
3.4.1 create_nsproxy函式
static inline struct nsproxy*create_nsproxy(void)
{
structnsproxy *nsproxy;
nsproxy= kmem_cache_alloc(nsproxy_cachep, GFP_KERNEL);
if(nsproxy)
atomic_set(&nsproxy->count,1);
returnnsproxy;
}
例子1:namespace pid的例子
#include
#include
#include
#include
#include
#include
#include
static int fork_child(void *arg)
{
inta = (int)arg;
inti;
pid_tpid;
char*cmd = "ps -el;
printf("Inthe container, my pid is: %d\n", getpid());
/*ps命令是解析procfs的內容得到結果的,而procfs根目錄的程序pid目錄是基於mount當時的pid namespace的,這個在procfs的get_sb回撥中體現的。因此只需要重新mount一下proc, mount-t proc proc /proc*/
mount("proc","/proc", "proc", 0, "");
for(i = 0; i
pid= fork();
if(pid <0)
returnpid;
elseif (pid)
printf("pidof my child is %d\n", pid);
elseif (pid == 0) {
sleep(30);
exit(0);
}
}
execl("/bin/bash","/bin/bash","-c",cmd, NULL);
return0;
}
int main(int argc, char *argv[])
{
intcpid;
void*childstack, *stack;
intflags;
intret = 0;
intstacksize = getpagesize() * 4;
if(argc != 2) {
fprintf(stderr,"Wrong usage.\n");
return-1;
}
stack= malloc(stacksize);
if(stack== NULL)
{
return-1;
}
printf("Outof the container, my pid is: %d\n", getpid());
childstack= stack + stacksize;
flags= CLONE_NEWPID | CLONE_NEWNS;
cpid= clone(fork_child, childstack, flags, (void *)atoi(argv[1]));
printf("cpid:%d\n", cpid);
if(cpid <0) {
perror("clone");
ret= -1;
goto out;
}
fprintf(stderr,"Parent sleeping 20 seconds\n");
sleep(20);
ret= 0;
out:
free(stack);
returnret;
}
}執行結果:
[email protected]:~/c_program#./namespace 7
Out of the container, my pid is: 8684
cpid: 8685
Parent sleeping 20 seconds
In the container, my pid is: 1
pid of my child is 2
pid of my child is 3
pid of my child is 4
pid of my child is 5
pid of my child is 6
pid of my child is 7
pid of my child is 8
F S UID PID PPID CPRI NI ADDR SZ WCHAN TTY TIME CMD
4 R 0 1 0 0 80 0 - 1085 - pts/0 00:00:00 ps
1 S 0 2 1 0 80 0 - 458 hrtime pts/0 00:00:00namespace
1 S 0 3 1 0 80 0 - 458 hrtime pts/0 00:00:00namespace
1 S 0 4 1 0 80 0 - 458 hrtime pts/0 00:00:00namespace
1 S 0 5 1 0 80 0 - 458 hrtime pts/0 00:00:00namespace
1 S 0 6 1 0 80 0 - 458 hrtime pts/0 00:00:00namespace
1 S 0 7 1 0 80 0 - 458 hrtime pts/0 00:00:00namespace
1 S 0 8 1 0 80 0 - 458 hrtime pts/0 00:00:00namespace
例子2:UTS的例子
#define _GNU_SOURCE
#include
#include
#include
#include
#include
#include
#include
#define errExit(msg) do { perror(msg); exit(EXIT_FAILURE); \
} while (0)
staticint /* Start function forcloned child */
childFunc(void *arg)
{
structutsname uts;
/*Change hostname in UTS namespace of child */
if(sethostname(arg, strlen(arg)) == -1)
errExit("sethostname");
/*Retrieve and display hostname */
if(uname(&uts) == -1)
errExit("uname");
printf("uts.nodenamein child: %s\n", uts.nodename);
/*Keep the namespace open for a while, by sleeping.
* This allows some experimentation--for example, another
* process might jointhe namespace. */
sleep(200);
return0; /* Child terminates now */
}
#define STACK_SIZE (1024 * 1024) /* Stack size for cloned child */
int
main(int argc, char *argv[])
{
char*stack;