僵屍進程
當一個子進程先於父進程結束運行時,它與其父進程之間的關聯還會保持到父進程也正常地結束運行,或者父進程調用了wait才告終止。
子進程退出時,內核將子進程置為僵屍狀態,這個進程稱為僵屍進程,它只保留最小的一些內核數據結構,以便父進程查詢子進程的退出狀態。
進程表中代表子進程的數據項是不會立刻釋放的,雖然不再活躍了,可子進程還停留在系統里,因為它的退出碼還需要保存起來以備父進程中后續的wait調用使用。它將稱為一個“僵進程”。
如何避免僵屍進程
- 調用wait或者waitpid函數查詢子進程退出狀態,此方法父進程會被掛起。
- 如果不想讓父進程掛起,可以在父進程中加入一條語句: signal(SIGCHLD,SIG_IGN);表示父進程忽略SIGCHLD信號,該信號是子進程退出的時候向父進程發送的
- 注冊信號處理函數,在信號處理函數總調用
wait
函數。
SIGCHLD 信號
當子進程退出的時候,內核會向父進程發送SIGCHLD信號,子進程的退出是個異步事件(子進程可以在父進程運行的任何時刻終止)
如果不想讓子進程編程僵屍進程可在父進程中加入:signal(SIGCHLD,SIG_IGN)
;
如果將此信號的處理方式設為忽略,可讓內核把僵屍子進程轉交給init進程去處理,省去了大量僵屍進程占用系統資源。(父進程忽略了 SIGCHLD
信號之后,會將僵屍子進程轉交給init
進程給處理,這樣子就不保存父子關系了嗎?)。
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <stdlib.h>
int main(void)
{
pid_t pid;
if(signal(SIGCHLD,SIG_IGN) == SIG_ERR)
{
perror("signal error");
exit(EXIT_FAILURE);
}
pid = fork();
if(pid == -1)
{
perror("fork error");
exit(EXIT_FAILURE);
}
if(pid == 0)
{
printf("this is child process\n");
exit(0);
}
if(pid > 0)
{
sleep(100);
printf("this is parent process\n");
}
return 0;
}
結果是:
可以看到,雖然子進程先退出了,但是進程表中已經不存在子進程的僵屍狀態了。(因為被 init 進程處理掉了)
wait() 函數
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *status);
進程一旦調用了wait,就立即阻塞自己,由wait自動分析是否當前進程的某個子進程已經退出,如果讓它找到了這樣一個已經變成僵屍的子進程,wait就會收集這個子進程的信息,並把它徹底銷毀后返回;如果沒有找到這樣一個子進程,wait就會一直阻塞在這里,直到有一個出現為止。
參數status用來保存被收集進程退出時的一些狀態,它是一個指向int類型的指針。但如果我們對這個子進程是如何死掉的毫不在意,只想把這個僵屍進程消滅掉,(事實上絕大多數情況下,我們都會這樣想),我們就可以設定這個參數為NULL,就象下面這樣:
`pid = wait(NULL)`
如果成功,wait會返回被收集的子進程的進程ID,如果調用進程沒有子進程,調用就會失敗,此時wait返回-1,同時errno被置為ECHILD
- wait系統調用會使父進程暫停執行,直到它的一個子進程結束為止。
- 返回的是子進程的PID,它通常是結束的子進程
- 狀態信息允許父進程判定子進程的退出狀態,即從子進程的main函數返回的值或子進程中exit語句的退出碼。
- 如果status不是一個空指針,狀態信息將被寫入它指向的位置
可以用一些宏判斷子進程的退出情況:
示例程序如下:
#include <stdio.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <unistd.h>
int main(void)
{
pid_t pid;
pid = fork();
if(pid < 0){
perror("fork error");
exit(EXIT_FAILURE);
}
if(pid == 0){
printf("this is child process\n");
exit(100);
}
int status;
pid_t ret;
ret = wait(&status);
if(ret <0){
perror("wait error");
exit(EXIT_FAILURE);
}
printf("ret = %d pid = %d\n", ret, pid);
if (WIFEXITED(status))
printf("child exited normal exit status=%d\n", WEXITSTATUS(status));
else if (WIFSIGNALED(status))
printf("child exited abnormal signal number=%d\n", WTERMSIG(status));
else if (WIFSTOPPED(status))
printf("child stoped signal number=%d\n", WSTOPSIG(status));
return 0;
}
上述程序正常退出exit(100)
,程序返回值為100。
當子進程正常退出的時候, wait
返回子進程的 pid, 並且 WIFEXITED(status)
為真, WEXITSTATUS(status)
獲得返回碼。
實例2:
#include <stdio.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <unistd.h>
int main(void)
{
pid_t pid;
pid = fork();
if(pid < 0){
perror("fork error");
exit(EXIT_FAILURE);
}
if(pid == 0){
printf("this is child process\n");
//exit(100);
abort();
}
int status;
pid_t ret;
ret = wait(&status);
if(ret <0){
perror("wait error");
exit(EXIT_FAILURE);
}
printf("ret = %d pid = %d\n", ret, pid);
if (WIFEXITED(status))
printf("child exited normal exit status=%d\n", WEXITSTATUS(status));
else if (WIFSIGNALED(status))
printf("child exited abnormal signal number=%d\n", WTERMSIG(status));
else if (WIFSTOPPED(status))
printf("child stoped signal number=%d\n", WSTOPSIG(status));
return 0;
}
上述代碼中,程序通過 abort()
系統調用返回,發送 SIGABRT
信號,對此信號的默認動作是終止進程。 並且系統終止的時候,不會 without destroying any objecty and without calling any of the function passed to atexit or at_quick_exit
。
結果如下:
當程序異常退出的時候, WIFSIGNALED(status)
為真,可用 WTERMSIG(status)
返回相應的信號代碼。
(另外,WIFSTOPPED
,子進程被暫停也會被父進程捕捉到,但是並不代表子進程已經是處於退出狀態吧?? 不太確定)
waitpid() 函數
waitpid()
是一個非常有用的函數,不單單可以等待子進程。 還可以等待進程組中的任意一個進程。 可以看到 wait()
函數實際上是 waitpid()
函數的特例。
#include <sys/types.h>
#include <sys/wait.h>
pid_t waitpid(pid_t pid, int *status, int options);
參數:
status:如果不是空,會把狀態信息寫到它指向的位置,與wait一樣
options:允許改變waitpid的行為,最有用的一個選項是WNOHANG,它的作用是防止waitpid把調用者的執行掛起 (也就是不阻塞父進程)
對於waitpid的p i d參數的解釋與其值有關:
pid == -1 等待任一子進程。於是在這一功能方面waitpid與wait等效。
pid > 0 等待其進程I D與p i d相等的子進程。
pid == 0 等待其組I D等於調用進程的組I D的任一子進程。換句話說是與調用者進程同在一個組的進程。
pid < -1 等待其組I D等於p i d的絕對值的任一子進程
wait與waitpid區別:
在一個子進程終止前, wait 使其調用者阻塞,而waitpid 有一選擇項,可使調用者不阻塞。
waitpid並不等待第一個終止的子進程—它有若干個選擇項,可以控制它所等待的特定進程。
實際上wait函數是waitpid函數的一個特例。waitpid(-1, &status, 0);
代碼如下:
#include <stdio.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <unistd.h>
int main(void)
{
pid_t pid;
pid = fork();
if(pid < 0){
perror("fork error");
exit(EXIT_FAILURE);
}
if(pid == 0){
printf("this is child process\n");
sleep(5);
exit(100);
}
int status;
pid_t ret;
ret = waitpid(pid,&status,WNOHANG);
if(ret <0){
perror("wait error");
exit(EXIT_FAILURE);
}
printf("ret = %d pid = %d\n", ret, pid);
if (WIFEXITED(status))
printf("child exited normal exit status=%d\n", WEXITSTATUS(status));
else if (WIFSIGNALED(status))
printf("child exited abnormal signal number=%d\n", WTERMSIG(status));
else if (WIFSTOPPED(status))
printf("child stoped signal number=%d\n", WSTOPSIG(status));
return 0;
}
結果如下:
這里可以看到,首先 option 設置為了 WNOHANG
之后,父進程不會等待子進程的退出,也就是不會阻塞,如果沒有子進程的退出立即返回-1。
上述中的代碼是有問題的: 首先 ret = 0, 是什么意思? 如果沒有子進程的退出了,那么上述代碼中檢測 WIFEXITED(status)
就失去了意義,所以還需要增加一個判斷條件,判斷是否有子進程退出。也就是ret > 0
的選項。
關於父子進程之間還有很多話題可以講述,以后慢慢的一一道來。