按照正常的邏輯,應該講講 vfork 的(專為 exec 而定制)。不過鑒于 vfork 現在已經很少使用了,而且現在的 fork 也完全可以替代 vfork,是以講 vfork 有點重複的意思。當然了不排除面試或者考試會有人問到 vfork,這裡稍微提兩筆。vfork 采用了類似讀時共享的機制,但是其不保證寫時複制,它産生的子程序和父程序共享程序空間,是以,如果在 vfork 後沒有使用 exec 或者 _exit 函數,其行為将是未定義的。
好了,點到為止,而且實際開發中,也不希望使用 vfork(如果你對vfork感興趣,請自行 man 或者查閱 《apue》)。下面來造僵屍吧 ^_^。
1. 僵屍程序
如果你對父子程序還不了解,趕緊先跳到前面的章節。
“如果子程序運作結束了而父程序還沒有,就會産生僵屍程序。”
這很容易了解,比如你 fork 了一個子程序後,父程序由于耗時任務還沒結束,子程序卻未老先衰,父程序還對它視而不見,它就死不瞑目。
1.1 造 5 個僵小魚
下面的代碼示範了僵屍程序的制造方法。其原理很簡單,就是保證“白發人送黑發人”這一條件就OK。
- 代碼
// mywait.c
#include <unistd.h>
#include <stdio.h>
#include <string.h>
int main() {
printf("before fork\n");
pid_t pid, n = 5;
// 父程序生出 5 個子程序
while(n--) {
pid = fork();
if (pid == 0) break;
else if (pid < 0) {
perror("fork");
return 1;
}
}
// 子程序列印一句話就死了。
if (pid == 0) {
printf("hello, I'm child %d; my father is %d\n", getpid(), getppid());
return 0;
}
// 父程序永遠在這列印。
while(1) {
sleep(3);
printf("hello, I'm father %d\n", getpid());
}
return 0;
}
- 編譯
$ gcc mywait.c -o mywait
- 運作
$ ./mywait
- 結果
before fork
hello, I'm child 7923; my father is 7918
hello, I'm child 7922; my father is 7918
hello, I'm child 7921; my father is 7918
hello, I'm child 7920; my father is 7918
hello, I'm child 7919; my father is 7918
hello, I'm father 7918
hello, I'm father 7918
hello, I'm father 7918
hello, I'm father 7918
1.2 檢視僵屍
再開啟一個終端,使用
ps -af
指令,結果如下:
UID PID PPID C STIME TTY TIME CMD
……
allen 7918 4565 0 14:19 pts/1 00:00:00 ./mywait
allen 7919 7918 0 14:19 pts/1 00:00:00 [mywait] <defunct>
allen 7920 7918 0 14:19 pts/1 00:00:00 [mywait] <defunct>
allen 7921 7918 0 14:19 pts/1 00:00:00 [mywait] <defunct>
allen 7922 7918 0 14:19 pts/1 00:00:00 [mywait] <defunct>
allen 7923 7918 0 14:19 pts/1 00:00:00 [mywait] <defunct>
可以看到,這裡有 5 個程序,名字被加了方括号,後面還跟着
<defunct>
字樣。可是子程序明明已經結束了呢?為什麼還死不瞑目?
實際上,子程序在死的時候,通知了它父親:父親我要死了,快來給我收屍吧!(發送 SIGCHILD信号給父程序)
可是前面的代碼父親除了一直在喊:我是父親 7918, 我是父親 7918,……好像沒有幹其它任何事情。子程序未等到父親的回複,是以在那死不瞑目。除非子程序的父親也死了,這時候會有 init 程序來替代原來的父親替這些子程序收屍。
2. wait 函數——讓逝者安息
弄明白了僵屍程序的由來,我們就能想出對策來清理這些僵屍程序。有人說,不清理不行嗎?這樣說吧,如果僵屍程序的數量非常少,其實對系統造不成什麼威脅,如果僵屍程序越來越多,最後就會造成資源耗盡。是以,代碼的健壯性很重要!!!特别是伺服器開發領域中,通常一運作都上數月甚至好幾年,如果代碼不健壯,就會造成系統中的僵屍程序越來越多,最後癱瘓(雖然重新開機也可以解決,但是這治标不治本)。
前面講到,子程序在死的時候會通知父親(給父程序發送 SIGCHILD 信号,信号的概念後面才會講到,這裡隻要知道就行了),是以父程序隻要妥尚處理好子程序的通知就行了,需要用到的函數就是 wait.
wait 函數原型如下:
// 參數儲存子程序退出通知碼,傳回 -1 表示沒有子程序或者錯誤。否則傳回子程序的程序 id 号。
pid_t wait(int *status);
前面的代碼改成如下的樣子:
- 代碼
// wipeoutzombie.c
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <sys/wait.h>
#include <sys/types.h>
int main() {
printf("before fork\n");
pid_t pid, n = 5;
while(n--) {
pid = fork();
if (pid == 0) break;
else if (pid < 0) {
perror("fork");
return 1;
}
}
if (pid == 0) {
printf("hello, I'm child %d; my father is %d\n", getpid(), getppid());
return 0;
}
while(1) {
sleep(3);
pid = wait(NULL); // 忽略子程序通知碼
if (pid == -1) {
perror("wait");
sleep(10);
printf("I'm father %d; I have wiped out all zombies\n", getpid());
return 1;
}
printf("Hello, I'm father %d; child %d, getpid(), pid);
}
return 0;
}
before fork
hello, I'm child 8092; my father is 8087
hello, I'm child 8091; my father is 8087
hello, I'm child 8090; my father is 8087
hello, I'm child 8089; my father is 8087
hello, I'm child 8088; my father is 8087
Hello, I'm father 8087; child 8088 exit
Hello, I'm father 8087; child 8089 exit
Hello, I'm father 8087; child 8090 exit
Hello, I'm father 8087; child 8091 exit
Hello, I'm father 8087; child 8092 exit
wait: No child processes
I'm father 8087; I have wiped out all
3. 總結
- 了解什麼是僵屍程序
- 知道子程序退出時會給父程序發送 SIGCHILD 信号
- 學會使用 wait 函數來清理僵屍程序