系統編程-回收子進程-孤兒和殭屍進程,wait和waitpid方法
1. 孤兒進程
孤兒進程: 父進程先於子進程結束,則子進程成爲孤兒進程,子進程的父進程成爲init進程,稱爲init進程領養孤兒進程。
危害:沒什麼危害…
產生孤兒進程的舉例:
#include <stdio.h>
#include <unistd.h>
int main(){
pid_t pid = fork();
if(pid == 0){// child
while(1){//強制不讓子進程退出,變爲孤兒進程
printf("I am child,PID:%d,PPID:%d\n",pid,getpid(),getppid());
sleep(1);
}
}else if(pid>0){// parent
printf("I am parent,PID:%d,PPID:%d\n",pid,getpid(),getppid());
sleep(5);
printf("I am parent,I will die!\n");
}
return 0;
}
2. 殭屍進程
殭屍進程: 子進程終止,父進程尚未回收,子進程殘留資源(PCB)存放於內核中,此子進程便成爲殭屍(zombie)進程。
注意:殭屍進程是不能使用kill命令清除掉的。因爲kill命令只是用來終止進程的,而殭屍進程已經終止。
產生殭屍進程的舉例:
#include <stdio.h>
#include <unistd.h>
int main(){
pid_t pid = fork();
if(pid == 0){// child
printf("I am child,PID:%d,PPID:%d\n",pid,getpid(),getppid());
sleep(2);
printf("I am child,I will die!\n");
}
}else if(pid>0){// parent
while(1){//強制不讓父進程退出,子進程變爲殭屍進程
printf("I am parent,PID:%d,PPID:%d\n",pid,getpid(),getppid());
sleep(5);
}
}
return 0;
}
危害:殭屍進程會佔用系統資源,如果很多,則會嚴重影響服務器的性能;
如何解決殭屍進程:
①殺死他的父進程使其變成孤兒進程,進而被系統處理。
②wait函數
3. 子進程回收
一個進程在終止時會關閉所有文件描述符,釋放在用戶空間分配的內存,但它的PCB還保留着,內核在其中保存了一些信息:如果是正常終止則保存着退出狀態,如果是異常終止則保存着導致該進程終止的信號是哪個。這個進程的父進程可以調用wait
或waitpid
獲取這些信息,然後徹底清除掉這個進程。我們知道一個進程的退出狀態可以在Shell中用特殊變量$?
查看,因爲Shell是它的父進程,當它終止時Shell調用wait
或waitpid
得到它的退出狀態同時徹底清除掉這個進程。
3.1 wait
父進程調用wait
函數可以回收子進程終止信息。該函數有三個功能:
① 阻塞等待子進程退出
② 回收子進程殘留資源
③ 獲取子進程結束狀態(退出原因)。
wait
函數的原型是:
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *status);
進程一旦調用了wait
,就立即阻塞自己,由wait自動分析是否當前進程的某個子進程已經退出,如果讓它找到了這樣一個已經變成殭屍進程的子進程,wait
就會收集這個子進程的信息,並把它徹底銷燬後返回;如果沒有找到這樣一個子進程,wait
就會一直阻塞在這裏,直到有一個這樣的進程出現爲止。
參數status
用來保存被回收進程退出時的一些狀態,如果我們不想知道這個子進程是如何死掉的,只想把它消滅掉的話,那麼我們可以設定這個參數爲NULL
,就像下面這樣:
pid_t pid = wait(NULL);
如果成功,wait
會返回被回收子進程的進程ID
,如果調用進程沒有子進程,調用就會失敗,此時wait
返回-1
,同時errno
被置爲ECHILD
。
返回的status
只是一個整型變量,不能很精確的描述出狀態,因此需要藉助宏函數來進一步判斷進程終止的具體原因。經常用到的宏函數爲如下兩組:
//正常死亡WIFEXITED
if(WIFEXITED(status)){
pfintf("%d:"WEXITSTATUS(status));
}
//非正常死亡
if(WIFSIGNALED(status)){
pfintf("%d:"WTERMSIG(status));
}
WIFEXITED(status)
爲非0 → 進程正常結束
WEXITSTATUS(status)
如上宏爲真,使用此宏 → 獲取進程退出狀態 (return/exit
的參數)
WIFSIGNALED(status)
爲非0 → 進程異常終止
WTERMSIG(status)
如上宏爲真,使用此宏 → 取得使進程終止的那個信號的編號。
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>
int main(){
pid_t pid = fork();
if(pid==0){
printf("I am child,will die!\n");
sleep(2);
//while(1){
printf("I am child,Come and hit me!!\n");
sleep(1);
//}
return 101;
}
else if(pid>0){
printf("I am parent,wait for child die\n");
int status;
pid_t wpid = wait(&status);
printf("wait ok,wpid = %d,pid = %d\n",wpid,pid);
//正常死亡WIFEXITED
if(WIFEXITED(status)){
printf("child exit with %d\n:",WEXITSTATUS(status));
}
//非正常死亡
if(WIFSIGNALED(status)){
printf("child killed by %d:",WTERMSIG(status));
}
}
}
子進程正常死亡運行結果:
子進程一直運行時(while(1)
),kill
掉之後運行結果:
3.2 waitpid
作用同wait
,但可指定pid進程清理,可以不阻塞。
waitpid
函數的原型是:
#include <sys/types.h>
#include <sys/wait.h>
pid_t waitpid(pid_t pid, int *status, int options);
返回值:
成功:返回清理掉的子進程ID
;
失敗:-1
(出錯,無子進程);
返回0
值,參3傳WNOHANG
,且子進程正在運行。
pid
:
從參數的名字pid
和類型pid_t
就可以看出,這裏需要的是一個進程ID。當pid
取不同的值時,在這裏有不同的意義。
- pid >0時,回收指定ID的子進程,只等待進程ID等於
pid
的子進程,不管其他已經有多少個子進程運行結束退出了,只要指定的子進程還沒結束,waitpid
就會一直等下去; - pid = -1時,回收任意子進程(相當於
wait
),等待任何一個子進程退出,沒有 任何限制; - pid = 0時,回收和當前調用waitpid一個組的所有子進程,如果 子進程已經加入了別的進程組,
waitpid
不會對它做任何理睬; - pid < -1時,回收指定進程組內的任意子進程,這個進程組的ID等於
pid
的絕對值;
options
:
- 0:(相當於wait)阻塞回收
WNOHANG
:非阻塞回收,用輪詢結構回收。
3. wait和waitpid的區別
- 在一個子進程終止前,
wait
使其調用者阻塞,而waitpid
則提供了非阻塞版本; waitpid
等待一個指定的子進程,而wait
等待第一個終止的子進程;waitpid
支持作業控制(以WUNTRACED
選項,由pid
指定的任一子進程狀態,且其狀態自暫停以來還未報告過,則返回其狀態);
注意:一次wait
或waitpid
調用只能清理一個子進程,清理多個子進程應使用循環。
wait
循環回收:
運行結果:
waitpid循環回收:
運行結果: