昨天我們用 unshare
建立了新的 namespace,今天我們換個方式,改用另外一個可以用來建立 namespace 的 API clone
,我在 github 上找到了一個範例(連結),並且基於這個範例加上了一點資訊:
#define _GNU_SOURCE
#include <sys/mount.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <sched.h>
#include <signal.h>
#include <unistd.h>
#define STACK_SIZE (1024 * 1024)
static char container_stack[STACK_SIZE];
char* const container_args[] = {
"/bin/bash",
NULL
};
int container_main(void* arg)
{
printf("Container - inside the container!\n");
printf("Container - child id: %d\n", getpid());
printf("Container - parent id: %d\n", getppid());
execv(container_args[0], container_args);
printf("Something's wrong!\n");
return 1;
}
int main()
{
printf("Parent - start a container!\n");
int container_pid = clone(container_main, container_stack + STACK_SIZE, CLONE_NEWPID | SIGCHLD, NULL);
printf("Parent - parent id: %d\n", getpid());
printf("Parent - child id: %d\n", container_pid);
waitpid(container_pid, NULL, 0);
printf("Parent - container stopped!\n");
return 0;
}
在執行前,我們先來看一下 clone
這個 API,其相關的 man 文件在這裏,有興趣的朋友可以點開來參考一下。clone
函數結構如下:
int clone(int (*fn)(void *), void *stack, int flags, void *arg, ...
/* pid_t *parent_tid, void *tls, pid_t *child_tid */ );
第一個參數 fn 是一個函數指針,第二個參數 child_stack 則是給 child process 使用的 stack 空間。第三個參數是我們的重點了,我們這邊設定了 CLONE_NEWPID | SIGCHLD
,這裡的 CLONE_NEWPID
就是用來告訴 clone
,要在一個新的 PID namespace 中建立這個新的 process。除了 CLONE_NEWPID
外,還可以設定的 flag 有 CLONE_NEWCGROUP
, CLONE_NEWIPC
, CLONE_NEWNET
, CLONE_NEWNS
, CLONE_NEWUSER
, CLONE_NEWUTS
等,跟我們在 Day 10 列出來的 namespace 種類是不是很一致呢。
現在讓我們來編譯並且執行看看:
ubuntu@ip-xxx:~/pid-ns-test$ gcc -o ns ns.c
ubuntu@ip-xxx:~/pid-ns-test$ sudo ./ns
Parent - start a container!
Parent - parent id: 48236
Parent - child id: 48237
Container - inside the container!
Container - child id: 1
Container - parent id: 0
root@ip-xxx:/home/ubuntu/pid-ns-test# exit
exit
Parent - container stopped!
ubuntu@ip-xxx:~/pid-ns-test$
這邊可以看到,當我們執行了 ns
後,我們會先印出當前這個 process 的 PID 為 48236,利用 clone
這個 API 去建立了一個新的 process,為當前 process 的 child process(當前 process 則為這個新 process 的 parent process),其 PID 是 48237,但在這個新的 process 中,去印出 parent PID 時會是 0,而印自己的 PID 時會是 1,是不是覺得很熟悉呢?
這裡我們可以知道,這是因為新的這個 process 是被執行在一個新的 PID namespace 中,所以他的 PID 會從 1 開始。接著畫面會停在顯示為 root
使用者的輸入命令列,這時候如果輸入 exit,就會回到以 ubuntu 為使用者的命令列中。
這裡我們來做另外一個實驗,把上面那段程式的 clone
中的 CLONE_NEWPID
拿掉,看看這樣會發生什麼事:
ubuntu@ip-xxx:~/pid-ns-test$ sudo ./no-ns
Parent - start a container!
Parent - parent id: 49109
Parent - child id: 49110
Container - inside the container!
Container - child id: 49110
Container - parent id: 49109
root@ip-xxx:/home/ubuntu/pid-ns-test# exit
exit
Parent - container stopped!
是不是看到一個很大的差別呢?如果沒有設定 CLONE_NEWPID
的話,被建立的這個 process 會跟當前 process (也就是 parent process) 在同一個 PID namespace 中。
在執行 ns
程式時,我在新的這個 process 中時,執行了 ps -ef
,我以為會像用 unshare
做出來的結果一樣,只看到新的這個 PID namespace 中少少幾個 processes,沒想到事情沒有這麼單純,我還是太天真了...大家可以動手試試看,當執行 ps -ef
的時候,會出現一大堆 processes,就跟在 Host 中看到的沒兩樣!當下是覺得有點被顛覆了,難到我過去的理解都不正確了嗎?後來才發現,我在用 unshare
來建立新的 PID namespace 時,有加上一個參數 --mount-proc
,看一下 man page 中對這個參數的解釋:
--mount-proc[=mountpoint]
Just before running the program, mount the proc filesystem at mountpoint (default is /proc). This is useful when creating a new PID namespace. It also implies creating a new mount namespace since the /proc mount would otherwise mess up existing programs on the system. The new proc filesystem is explicitly mounted as private (with MS_PRIVATE|MS_REC).
他的意思是他會掛載一個 proc 檔案系統給掛載點,還是不太懂什麼意思對吧?(還是只有我不懂!?)立刻手動做個實驗:
ubuntu@ip-xxx:~$ sudo unshare --pid --fork /bin/bash
root@ip-xxx:/home/ubuntu# ps -ef | wc -l
117
果然,只要拿掉 --mount-proc
這個參數,用 ps -ef
查看,會有 117 列資料,跟用上面提供的程式的結果一樣,會看到所有 Host 上的 processes,欸,這又是為什麼呢?