簡 述: 這篇繼承上一篇,先要自己梳理清楚一下進程相關的知識, 上上一篇的虛擬地址空間和進程控制塊 PCB
,以及上一篇的進程相關知識,帶着思考來學習 孤兒進程、殭屍進程、 以及進程回收 wait()
, waitpid()
相關的概念。
編程環境:
💻: uos20
📎 gcc/g++ 8.3
📎 gdb8.0
💻: MacOS 10.14
📎 gcc/g++ 9.2
📎 gdb8.3
孤兒進程:
-
父進程創建子進程
-
父進程死了,子進程還活着,該進程叫孤兒進程(Orphan Process)
-
在傳統的 Linux 系統中,孤兒進程被 init 進程領養,init 進程成爲孤兒進程的父親
- init 進程這樣做,主要是爲了釋放子進程佔用的系統資源。
- 因爲進程結束之後,能夠釋放用戶控空間;但是釋放不了系統空間的 PCB,這塊資源必須由父進程釋放。
-
寫一個代碼例子:
#include <stdio.h> #include <unistd.h> int main(int argc, char *argv[]) { pid_t pid = fork(); //創建子進程 if (pid == 0) { //存活時間大於 1s(此時父進程已經執行完成銷燬), 爲孤兒子進程 sleep(1); printf("this is a child process, pid = %d ppid = %d\n", getpid(), getppid()); } else if (pid > 0) { //父進程 printf("this is a parent process, pid = %d ppid = %d\n", getpid(), getppid()); //執行完後,父進程就結束了 } return 0; }
-
代碼分析:
當父進程創建子進程之後,很快的就執行完後就結束銷燬自己(小於 1s 內);因爲子進程的生存時間大於 1s,故此時它爲孤兒進程(下圖可證明:此時其父進程號是 1 而非 17441),然後被系統的 init 進程領養,幫助銷燬子進程的 PCB;
-
運行結果:
殭屍進程:
-
父進程創建子進程
-
子進程死了,父進程還活着,且不去釋放子進程的 PCB,該子進程就變成了殭屍進程(Zombie Process)。
-
殭屍進程是一個已經死掉的進程。
-
寫一個代碼例子:
#include <stdio.h> #include <unistd.h> int main(int argc, char *argv[]) { pid_t pid = fork(); //創建子進程 if (pid == 0) { //(此時子進程已經執行完成銷燬), 而父進程沒有時間去銷燬這個子進程的 pcb,故爲殭屍進程 printf("this is a child process, pid = %d ppid = %d\n", getpid(), getppid()); } else if (pid > 0) { //父進程 while (true) { sleep(1); printf("this is a parent process, pid = %d ppid = %d\n", getpid(), getppid()); //執行完後,父進程就結束了 } } return 0; }
-
代碼分析:
父進程創建子進程之後,子進程在極短的時間內執行完所有代碼後,其用戶區的資源被釋放,其系統區的 pcb 等待其父進程來釋放;但是父進程一直在忙其它的時間,沒有做處理,釋放自己成的 PCB,在下圖左側還沒有運行
ctrl c
的時候,使用ps
在管道里面查詢該子進程的號 5563,可以發現其已經被標註爲死亡的進程(其 Z+ 中的是單詞殭屍 Zombie 的首字母)。 -
運行結果:
進程回收:
wait():
pid_t wait(int *stat_loc);
-
作用:
- 阻塞並且等待子進程退出
- 回收子進程殘留的資源
- 獲取子進程結束的狀態(退出原因)
- 每次只能夠回收一個進程,調用一次 wait() 也只能回收一個進程
-
參數:
WIFEXITED(status)
: 爲非 0 --> 進程正常退出WEXITSTATUS(status)
: 如果這個宏爲真,使用此宏 --> 獲取進程退出狀態(exit / return) 的參數
WIFSIGNALED(status)
: 爲非 0 --> 進程異常終止WTERMSIG(status)
: 如果這個宏爲真,使用此宏 --> 獲取使進程退出的那個信號的編號。
-
返回值:
- 成功:清理掉的子進程 ID
- 失敗:-1(沒有子進程)
-
寫一個代碼例子驗證:
#include <stdio.h> #include <unistd.h> #include <sys/wait.h> int main(int argc, char *argv[]) { printf("-----開始-----\n"); pid_t pid = fork(); //創建子進程 if (pid == 0) { //(此時子進程已經執行完成銷燬), 而父進程沒有時間去銷燬這個子進程的 pcb,故爲殭屍進程 while (true) { sleep(2); printf("this is a child process, pid = %d ppid = %d\n", getpid(), getppid()); } } else if (pid > 0) { //父進程 //sleep(1); int status = 0; pid_t wpid = wait(&status); //回收子進程 if (WIFEXITED(status)) //判斷是否正常退出 printf("退出值是 val = %d\n", WEXITSTATUS(status)); if (WIFSIGNALED(status)) //判斷是被信號殺死 printf("通過信號退出 val = %d\n", WTERMSIG(status)); printf("this is a parent process, pid = %d ppid = %d\n", getpid(), getppid()); //執行完後,父進程就結束了 } printf("+++++結束+++++\n"); return 9; //若是註釋掉 12 和 15 行代碼,會顯示父父進程的 退出值是 9 }
-
代碼分析:
代碼是在 MacOS(Unix) 系統上面運行的;
若是註釋掉 12 和 15 行代碼,則會運行第一個宏,可以看到進程結束的返回值是 9 (人爲隨機指定的一個數值,return 9);運行結果見下面第一個圖。
而不註釋掉 12 和 15 行代碼;運行程序後,執行
kill 進程號
殺死子進程, 則會執行第二個宏,顯示是被信號(15)所殺「若果是 Linux 運行,這裏會顯示信號是 9」;運行結果見下面第二個圖。 -
運行截圖:
-
waitpid():
pid_t waitpid(pid_t pid, int *stat_loc, int options);
-
作用:
同 wait(),但是可以指定要回收的進程 pid 號,且能夠指定是阻塞還是非阻塞模式。
-
參數:
-
pid:
pid == -1;回收所有的子進程。與 wait 等效
pid > 0; 回收指定 pid 的進程
pid == 0; 回收當前組的所有子進程
pid < -1; 回收進程的 pid 取反(加減號)
-
status:
子進程的退出狀態,用法同 wait()
-
options:
設置爲 WNOHANG,函數非阻塞,設置爲 0,函數阻塞
-
-
返回值:
> 0 //返回清理掉的子進程 ID -1 //無子進程 =0 //參數 3 爲 WHOHANG,且子進程正在運行
下載地址:
歡迎 star 和 fork 這個系列的 linux 學習,附學習由淺入深的目錄。