iT邦幫忙

2022 iThome 鐵人賽

DAY 16
0

昨天我們討論了 docker exec 是怎麼利用 setns 這個 API 讓 COMMAND 所啟動的這個新的 process 「進入」container 中的,現在讓我們回頭討論 process 之間的關係。

先準備一個 contaienr:

$ docker run -it --rm alpine ash

/ # ps -eaf -o pid,ppid,comm,args
PID   PPID  COMMAND          COMMAND
    1     0 ash              ash
    7     1 ps               ps -eaf -o pid,ppid,comm,args

這邊我們都很熟悉了,在 container 中可以看到兩個 process,第一個是我們在啟動 container 時指定要執行的 ash shell,第二個 process 就是我們正在執行的這個 ps,而且其 PID 分別是 1 跟 7。藉由對於 PID namespace 的討論,我們已知啟動 container 時,在 container 中執行的 process 會被放在為這個 container 建立出來的新 namespaces 中,例如他會有一個新的 PID namespace,在這個新的 PID namespace 中的第一個 process 的 PID 會從 1 開始,而其實 PID 1 的 process 不僅僅只是因為在一個新的 PID namespace 而從頭開始這麼簡單,這個 process 是有特殊的角色的。

在繼續之前,我們再多觀察 PPID 這個欄位,這是 parent process id,在 Linux 的世界裡,如果要建立一個新的 process,會需要從一個現有的 process 去 fork 建立出來,新被建立出來的 process 就是 child process,而現有的那個 process 就是 parent process。當 child process 被 fork 出來時,會複製一份 parent process,會再透過例如 exec 家族的 API 去執行自己的程式碼。

來個測試程式 fork-test.c:

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/wait.h>

char* const argv_list[] = {
  "/bin/bash",
  NULL
};

int main(void)
{
  printf("current PID: %d\n", getpid());

  pid_t pid = fork();

  if (pid == -1) {
    // pid == -1: error occurred
    printf("can't fork, error occurred\n");
    exit(EXIT_FAILURE);
  } else if (pid == 0) {
    // pid == 0: child process created
    printf("I am child process with PID: %d\n", getpid());
    execv(argv_list[0], argv_list);
    exit(0);
  } else {
    // pid > 0: a positive number is returned for the pid of parent process
    printf("I am parent process with PID %d and my child is %d\n", getpid(), pid);
    waitpid(pid, NULL, 0);
    printf("Parent - container stopped!\n");
  }

  return 0;
}

我們可以利用 fork() 的回傳值來判斷現在自己在哪個 process 裡,如果 fork() 的回傳值是 0,那現在就是在 child process 中,如果 fork() 的回傳值大於 0,那表示是在 parent process 裡,而此時 fork() 的回傳值正是 child process 的 pid,讓我們來執行看看:

ubuntu@ip-xxx:~/fork$ gcc -o fork-test fork-test.c

ubuntu@ip-xxx:~/fork$ sudo ./fork-test
current PID: 70426
I am parent process with PID 70426 and my child is 70427
I am child process with PID: 70427
root@ip-xxx:/home/ubuntu/fork# echo $$
70427
root@ip-xxx:/home/ubuntu/fork# pstree -ps 70427
systemd(1)───sshd(679)───sshd(69971)───sshd(70074)───bash(70075)───sudo(70425)───fork-test(70426)───bash(70427)───pstree(70424)

root@ip-xxx:/home/ubuntu/fork# exit
exit
Parent - container stopped!
ubuntu@ip-xxx:~/fork$

根據結果,我們最一開始執行這個 fork-test 程式時,他的 PID 是 70426,然後 fork 出來的 child process 的 PID 是 70427,然後在 child process 裡,我們透過 execv 執行了 /bin/bash 這個程式,也因此進入了另外一個 shell 中,在這個新的 shell 裡,我們利用 echo $$ 去查看當前 process 的 PID,果然是 70427,也利用 pstree 去查看 process 之間的父子關係,可以看到 70426 是 70427 的 parent,最後用 exit 終止 child process。

child process 在執行 echo, pstree 這些指令的時間,parent process 在幹嘛呢?parent process 正在被 waitpid 給阻塞住,來看一下 waitpid手冊:

All of these system calls are used to wait for state changes in a child of the calling process, and obtain information about the child whose state has changed. A state change is considered to be: the child terminated; the child was stopped by a signal; or the child was resumed by a signal.

當 parent process 呼叫 waitpid 時,parent process 會阻塞在那邊等待 child process 的變化。我們在 child process 中執行 exit,這會使 child process 終止,但要特別注意的是 child process 僅僅只是終止,而不是釋放資源,那什麼時候才會釋放資源呢?當 child process 終止了,也就是狀態改變了,此時在 parent process 被呼叫的 wait 會立即 return,並且釋放掉 child process 的資源。

圖示起來大概是這樣:
https://ithelp.ithome.com.tw/upload/images/20221001/201518577aOmQzDClt.png


這邊就會衍生幾個問題:

  1. 如果 parent process 不呼叫 waitpid 來等待 child process 的變化並且釋放資源,那會發生什麼事呢?
  2. 如果在 child process 終止之前,parent process 就先中止了,那會怎麼樣呢?

這兩個問題,就讓我們留到明天討論吧


上一篇
Day 15: docker exec 是怎麼辦到的呢?
下一篇
Day 17: 殭屍與孤兒
系列文
那些關於 docker 你知道與不知道的事32
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言