注:本文絕大部分內容來自LinuxNamespaces實踐part4 ,原文系列文章詳細描述了
Linux Namespace
相關內容,英語過關的建議閱讀原文,本文內容主要用來記錄學習內容,如有不當之處還請評論區指正。
PID Namespace
一個新創建的PID Namespace中的第一個進程的ID爲1,這個進程同時也是該Namespace中的初始化(init)進程,需要承擔回收孤兒進程等任務。
一個例子
ns_child_exec.c
在新的Namespace中執行simple_init.c
simple_init.c
新Namespace中的init
進程,回收孤兒進程orphan.c
一個孤兒進程示例
上述三個程序組成了這個示例,在ns_child_exec.c
程序中調用clone
函數創建子進程並設置Namespace標誌。子進程運行simple_init.c
程序,該程序會模擬一個終端,調用execvp
命令執行輸入的程序。在這個“終端”中執行orphan
程序,可以看到orphan
的父進程結束後,子進程成爲孤兒進程並且被simple_init
回收。代碼ns_child_exec.c, simple_init.c, orphan.c,代碼中的英文註釋解釋了處理SIGTTOU信號、設置終端前臺進程組等的原因。
上述程序的執行結果如下:
root@jeffrey: /Coding/namespace# ./ns_child_exec -pmv ./simple_init -v
./ns_child_exec: PID of child created by clone() is 545
init: my PID is 1
init$ ./orphan
init: created child 2
Parent (PID=2) created child with PID 3
Parent (PID=2; PPID=1) terminating
init: SIGCHLD handler: PID 2 terminated
init$
Child (PID=3) now an orphan (parent PID=1)
Child (PID=3) terminating
init: SIGCHLD handler: PID 3 terminated
init$ ps
init: created child 4
PID TTY TIME CMD
240 tty2 00:00:00 init
526 tty2 00:00:00 sudo
527 tty2 00:00:00 su
528 tty2 00:00:00 bash
544 tty2 00:00:00 ns_child_exec
545 tty2 00:00:00 simple_init
548 tty2 00:00:00 ps
init: SIGCHLD handler: PID 4 terminated
init$ mount -t proc proc /proc
init: created child 5
init: SIGCHLD handler: PID 5 terminated
init$ ps
init: created child 6
PID TTY TIME CMD
1 tty2 00:00:00 simple_init
6 tty2 00:00:00 ps
init: SIGCHLD handler: PID 6 terminated
init$ ^C./ns_child_exec: terminating
root@jeffrey: /Coding/namespace#
上述shell命令及輸出結果展示了該程序的使用
-pmv
參數表示創建新的PID Namespace
與Mount Namespace
,並打印日誌信息- 日誌信息
init: my PID is 1
表明simple_init
程序成爲了新PID Namespace
的init
進程 - 執行
orphan
程序後simple_init
打印了兩條SIGCHLD
信號處理日誌,表明孤兒進程也被它回收了 ps
命令依賴/proc
中的信息,故若要在執行ps
顯示當前PID Namespace
的程序,需要顯式掛載它- 創建
Namespace
時需指定創建新Mount Namespace
及PID Namespace
,如-pmv
參數 - 可以通過
mount -t proc proc /proc
命令掛載 - 或通過
mount("proc", "/proc", "proc", 0, NULL)
函數掛載
- 創建
init進程與信號
linux中的init
進程:
- 只處理設置了處理程序的信號
- 不會被其它程序或用戶(包括superuser)kill
PID Namespace
中的init
進程:
- 類似的,在該
Namespace
中只會收到設置了處理程序的信號 - 內核產生的信號依然會發送給
init
進程,如硬件異常等 - 對於來自祖先
PID Namespace
的信號,init
進程會收到設置了處理程序的信號+SIGKILL
+SIGSTOP
- 被
SIGKILL
(來自祖先)kill後,所有子進程都會被kill
一般來講,一個PID Namespace
的所有進程都被終止後,這個PID Namespace
也會被銷燬,然而有一個特例:若/proc/PID/ns/pid
文件處於bind mounted
或打開狀態,則該PID Namespace
不會被銷燬。不過,這種情況下無法再在該PID Namespace
中創建新的進程(通過setns()+fork()
),實際上當執行fork
時會失敗併產生ENOMEM
錯誤。
unshare() 與 setns()
unshare(CLONE_NEWPID);
fd = open("/proc/PID/ns/pid", O_RDONLY);
setns(fd, 0);
通過在unshare(int flags)
的參數中指定CLONE_NEWPID
可以創建一個新PID Namespace
,調用unshare
的進程本身不會加入到新PID Namespace
中,但由該進程創建的子進程都會被加入其中。其原因在於,幾乎Linux程序包括GNU等都認爲進程標識PID
是不變的,若改變當前進程的PID
,則許多依賴於此的函數都將無法使用了。
setns(int fd, int nstype)
同樣支持PID Namespace
,fd
參數爲一個PID Namespace
的文件描述符,該PID Namespace
應爲調用進程的PID Namespace
的後代。相應文件描述符fd
可以通過打開/proc/PID/ns/pid
文件獲得。與unshare
類似,setns
同樣不會講調用進程移動到新的PID Namespace
,而是將其子進程放入其中。
又一個例子
本示例展示了setns
的使用,使用上一個例子創建一個新的PID Namespace
並運行simple_init
模擬終端,之後我們在另一個終端中運行ns_run
程序,設置子進程的PID Namespace
與simple_init
的相同,然後創建一個子進程。示例程序,如下爲終端的運行結果。
terminal a:
root@jeffrey: /Coding/namespace# clear
root@jeffrey: /Coding/namespace# ./ns_child_exec -p ./simple_init -v
init: my PID is 1
init$ init: SIGCHLD handler: PID 4 terminated
terminal b:
root@jeffrey: /Coding/namespace# ps -C simple_init
PID TTY TIME CMD
601 tty3 00:00:00 simple_init
root@jeffrey: /Coding/namespace# ./ns_run -f -n /proc/601/ns/pid ./orphan
Parent (PID=3) created child with PID 4
Parent (PID=3; PPID=0) terminating
root@jeffrey: /Coding/namespace#
Child (PID=4) now an orphan (parent PID=1)
Child (PID=4) terminating
root@jeffrey: /Coding/namespace#
首先在終端a中執行./ns_child_exec -p ./simple_init -v
,之後再終端b中執行./ns_run -f -n /proc/601/ns/pid ./orphan
,其中601爲simple_init
程序的PID
。可以看到,orphan
程序創建了孤兒進程PID=4
,該進程被simple_init
回收了,同時orphan
的輸出Parent (PID=3; PPID=0)
也表明它在與simple_init
相同的Namespace
中執行了,因而無法看到位於ns_run
的Namespace
中的父進程表示PPID=0
。下圖表明瞭上述代碼的關係:
小結
本文主要講述了PID Namespace
中init
進程的角色、unshare
和setns
等內容,感興趣的同學可以閱讀原文獲得更多相關內容。