大綱
- 孤兒進(jìn)程
- 僵尸進(jìn)程
-
wait
函數(shù) -
waitpid
函數(shù)
在Linux中正常情況下,子進(jìn)程是通過父進(jìn)程創(chuàng)建的悲酷,子進(jìn)程再創(chuàng)建新的進(jìn)程。子進(jìn)程的結(jié)束和父進(jìn)程的運(yùn)行是一個異步過程,也就是說,父進(jìn)程永遠(yuǎn)無法預(yù)知到子進(jìn)程會在什么時候結(jié)束肮塞。當(dāng)一個進(jìn)程完成工作終止后,它的父進(jìn)程需要調(diào)用wait()
或waitpid()
系統(tǒng)調(diào)用來獲取子進(jìn)程的終止?fàn)顟B(tài)姻锁。
子進(jìn)程有兩種比較重要的狀態(tài)枕赵,分別是孤兒進(jìn)程和僵尸進(jìn)程。
孤兒進(jìn)程
孤兒進(jìn)程是指父進(jìn)程先于子進(jìn)程結(jié)束屋摔,通俗來講也就是爹比兒子死得早烁设,此時子進(jìn)程成為孤兒進(jìn)程,此時父進(jìn)程會成為init
進(jìn)程(進(jìn)程號pid
為1的進(jìn)程)钓试,init
進(jìn)程可視為進(jìn)程孤兒院用于領(lǐng)養(yǎng)孤兒進(jìn)程。
$ ps aux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.1 0.4 225808 9512 ? Ss 00:43 0:02 /sbin/init splash
$ ps aux | grep init
root 1 0.1 0.4 225808 9512 ? Ss 00:43 0:02 /sbin/init splash
jc 3613 0.0 0.0 21536 1000 pts/0 S+ 01:18 0:00 grep --color=auto init
當(dāng)一個父進(jìn)程退出而它的子進(jìn)程還在運(yùn)行副瀑,此時這些子進(jìn)程將會成為孤兒進(jìn)程弓熏,孤兒進(jìn)程將被init
進(jìn)程收養(yǎng),并由init
進(jìn)程對它們完成狀態(tài)收集工作糠睡。
危害
孤兒進(jìn)程是沒有父進(jìn)程的子進(jìn)程挽鞠,因此孤兒進(jìn)程的管理重任會落到init
進(jìn)程身上,init
進(jìn)程好像一個民政局或孤兒所,專門負(fù)責(zé)處理孤兒進(jìn)程的善后工作信认。每當(dāng)出現(xiàn)一個孤兒進(jìn)程時內(nèi)核就會把孤兒進(jìn)程的父進(jìn)程設(shè)置為init
進(jìn)程材义,init
進(jìn)程會循環(huán)地wait
已經(jīng)退出的子進(jìn)程。這樣嫁赏,當(dāng)一個孤兒進(jìn)程的生命周期結(jié)束時其掂,init
進(jìn)程會代表黨和政府處理善后工作,因此孤兒進(jìn)程不會有什么危害潦蝇。
實(shí)例1
$ vim orphan.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(void)
{
pid_t pid;
pid = fork();//創(chuàng)建子進(jìn)程
//如果當(dāng)前進(jìn)程是init進(jìn)程則報錯退出
if(pid == 1)
{
perror("init process");
exit(1);
}
else if(pid > 0)
{
sleep(1);
printf("parent pid = %d, parent parent pid is %d\n", getpid(), getppid());
}
else if(pid == 0)
{
printf("child pid is %d, parent pid is %d\n", getpid(), getppid());
sleep(3);
printf("child pid is %d, parent pid is %d\n", getpid(), getppid());
}
return 0;
}
$ gcc orphan.c -o orphan -Wall -g
$ ./orphan
child pid is 3652, parent pid is 3651
parent pid = 3651, parent parent pid is 3633
root@junchow:/home/jc/projects/c# pachild pid is 3652, parent pid is 2700
$ ps aux | grep 2700
jc 2700 0.0 0.3 77120 8292 ? Ss 00:57 0:00 /lib/systemd/systemd --user
root 3672 0.0 0.0 21536 1052 pts/0 S+ 01:21 0:00 grep --color=auto 2700
實(shí)例2
$ vim orphan.c
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
int main(void)
{
pid_t pid;
pid = fork();
if(pid == 0)
{
while(1)
{
printf("child: parent pid=%d\n", getppid());
sleep(1);
}
}
else if(pid > 0)
{
printf("parent: pid=%d\n", getpid());
sleep(10);
printf("parent: going to die\n");
}
else
{
perror("fork");
return 1;
}
return 0;
}
$ gcc orphan.c -o orphan -Wall -g
$ ./orphan
僵尸進(jìn)程
僵尸進(jìn)程是如何產(chǎn)生的呢款熬?
當(dāng)運(yùn)行一個程序時會產(chǎn)生一個父進(jìn)程以及多個子進(jìn)程,所有子進(jìn)程都會消耗內(nèi)核分配的內(nèi)存和CPU資源攘乒。子進(jìn)程完成執(zhí)行后會發(fā)送一個exit
信號然后死掉贤牛。這個exit
型號需要被父進(jìn)程讀取。父進(jìn)程需要隨后調(diào)用wait
命令來讀取進(jìn)程的退出狀態(tài)则酝,并將子進(jìn)程從進(jìn)程表中移除殉簸。
在UNIX系統(tǒng)中一旦進(jìn)程結(jié)束,如果父進(jìn)程沒有使用wait
或waitpid
系統(tǒng)調(diào)用等待子進(jìn)程結(jié)束沽讹,又沒有顯式的忽略SIGCHILD
信號喂链,那么它將變成一個僵尸進(jìn)程并一直存在。僵尸進(jìn)程是一個早已死亡的進(jìn)程妥泉,但在進(jìn)程表中任占據(jù)著一個位置椭微。
如果子進(jìn)程的父進(jìn)程已先行結(jié)束,那么子進(jìn)程就不會變成僵尸進(jìn)程盲链,因?yàn)槊總€進(jìn)程結(jié)束的時候蝇率,系統(tǒng)會掃描當(dāng)前操作系統(tǒng)中所有運(yùn)行的進(jìn)程,檢查有沒有哪個進(jìn)程是剛剛結(jié)束的進(jìn)程的子進(jìn)程刽沾。若有則由init
進(jìn)程來接管并作為它的父進(jìn)程本慕,從而保證每個進(jìn)程都會有一個父進(jìn)程。init
進(jìn)程會自動wait
其子進(jìn)程侧漓,因此被init
接管的進(jìn)程都不會變成僵尸進(jìn)程锅尘。
僵尸進(jìn)程是指進(jìn)程終止,父進(jìn)程尚未回收布蔗,子進(jìn)程殘留資源PCB
存放于內(nèi)核kernel
中變?yōu)榻┦?code>zombie進(jìn)程藤违。
父進(jìn)程有義務(wù)將子進(jìn)程回收,如果子進(jìn)程死亡后父進(jìn)程不幫子進(jìn)程收尸纵揍,此時子進(jìn)程就會變成僵尸顿乒。
如何查找僵尸進(jìn)程呢?
$ ps -ef | grep defunct
$ ps aux | grep Z
僵尸進(jìn)程存在會有什么樣的危害呢泽谨?
正常進(jìn)程死亡以后璧榄,0到4G的進(jìn)程地址空間會主動釋放特漩,但是PCB依然會殘留在內(nèi)核中,其目的是為了讓父進(jìn)程為它報仇骨杂。如果子進(jìn)程死亡后沒有任何痕跡留下的話涂身,父進(jìn)程將無法知道子進(jìn)程是由于什么原因造成的死亡,是自殺還是他殺呢搓蚪?子進(jìn)程死亡后會在內(nèi)核中殘留PCB蛤售,父進(jìn)程通過PCB可以獲取子進(jìn)程的死亡狀態(tài)。
任何一個子進(jìn)程(init
進(jìn)程除外)在exit
退出后并非立即消失陕凹,而會留下一個稱為僵尸進(jìn)程的數(shù)據(jù)結(jié)構(gòu)悍抑,以等待父進(jìn)程處理。
UNIX提供了一種機(jī)制讓父進(jìn)程獲取子進(jìn)程結(jié)束時的狀態(tài)信息杜耙,這種機(jī)制就是在每個子進(jìn)程退出的時候搜骡,內(nèi)核會釋放該進(jìn)程所有的資源,包括打開的文件佑女、占用的內(nèi)存等记靡。但內(nèi)核仍然會保留一定的信息,如進(jìn)程號团驱、退出狀態(tài)摸吠、運(yùn)行時間等信息。直到父進(jìn)程通過wait
或waitpid
系統(tǒng)調(diào)用來獲取時才完全釋放嚎花。
這樣就導(dǎo)致了一個問題寸痢,如果進(jìn)程不調(diào)用 wait
或waitpid
,那么保留在內(nèi)核中的殘留信息將不會主動被釋放紊选,進(jìn)程號會一直被占用啼止。由于系統(tǒng)所能使用的進(jìn)程號是有限的,如果大量產(chǎn)生僵尸進(jìn)程的話兵罢,系統(tǒng)將沒有可用的進(jìn)程號献烦,從而導(dǎo)致系統(tǒng)不能產(chǎn)生新的進(jìn)程。
此時如果使用ps
命令查看會發(fā)現(xiàn)子進(jìn)程的狀態(tài)為Z
卖词,即僵尸進(jìn)程巩那。
嚴(yán)格來說,僵尸進(jìn)程并非罪魁禍?zhǔn)状蓑冢瑔栴}的根源在于產(chǎn)生大量僵尸進(jìn)程的那個父進(jìn)程即横。可以使用kill
命令發(fā)送信號SIGTERM
或SIGKILL
信號槍斃元兇進(jìn)程舶替。槍斃完成后令境,僵尸進(jìn)程將會變成了孤兒進(jìn)程,這些孤兒進(jìn)程會被ini進(jìn)程接管顾瞪。
init進(jìn)程會
wait`這些孤兒進(jìn)程舔庶,釋放他們占用的系統(tǒng)資源。
需要注意的是僵尸進(jìn)程不能使用kill
命令清除陈醒,因?yàn)?code>kill命令只是用來終止進(jìn)程惕橙,而僵尸進(jìn)程已經(jīng)是終止的。
如何避免僵尸進(jìn)程呢钉跷?
僵尸進(jìn)程的產(chǎn)生是因?yàn)楦高M(jìn)程沒有wait
子進(jìn)程弥鹦,當(dāng)系統(tǒng)中出現(xiàn)了僵尸進(jìn)程,是無法通過kill
命令來清除的爷辙,但可以殺死僵尸進(jìn)程的父進(jìn)程彬坏,讓其變成孤兒進(jìn)程,系統(tǒng)會統(tǒng)一管理和清理孤兒進(jìn)程膝晾。
有什么辦法可以清除掉僵尸進(jìn)程呢栓始?
正常情況下可以使用SIGKILL
信號來殺死進(jìn)程,由于僵尸進(jìn)程已經(jīng)早死了血当,所以不能使用kill
命令殺死已經(jīng)死掉的進(jìn)程幻赚。
$ kill -s SIGCHILD pid
可使用wait
和waitpid
回收僵尸進(jìn)程
實(shí)例
$ vim zoombie.c
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
int main(void)
{
pid_t pid;
//創(chuàng)建子進(jìn)程
pid = fork();
printf("pid=%d\n", pid);
//若子進(jìn)程為調(diào)度進(jìn)程
if(pid == 0)
{
printf("child: parent=%d\n", getppid());
printf("child: going to sleep\n");
sleep(10);
printf("child: child die\n");
}
else if(pid > 0)
{
while(1)
{
printf("parent: pid=%d child=%d\n", getpid(), pid);
sleep(1);
}
}
else
{
perror("fork error");
return 1;
}
return 0;
}
$ gcc zoombie.c -o zoombie -Wall -g
$ ./zoombie
運(yùn)行結(jié)果
pid=3654
parent: pid=3653 child=3654
pid=0
child: parent=3653
child: going to sleep
parent: pid=3653 child=3654
parent: pid=3653 child=3654
parent: pid=3653 child=3654
parent: pid=3653 child=3654
parent: pid=3653 child=3654
parent: pid=3653 child=3654
parent: pid=3653 child=3654
parent: pid=3653 child=3654
parent: pid=3653 child=3654
child: child die
parent: pid=3653 child=3654
parent: pid=3653 child=3654
parent: pid=3653 child=3654
...
注意若運(yùn)行出現(xiàn)implicit declaration of function ‘fork’ [-Wimplicit-function-declaration]
則需注意是否引入了unistd.h
頭文件。
wait函數(shù)
一個進(jìn)程在終止時會關(guān)閉所文件描述符臊旭,釋放用戶空間并分配內(nèi)存落恼,只是子進(jìn)程的PCB還保留著,內(nèi)核在其中保持努著一些信息离熏。如果是正常終止或保存退出狀態(tài)佳谦。如果是異常終止則保存著導(dǎo)致該進(jìn)程的信號,這個進(jìn)程可使用Shell中特殊滋戳。
父進(jìn)程調(diào)用wait
系統(tǒng)調(diào)用可以回收子進(jìn)程的終止信息钻蔑,然后徹底清除掉這個進(jìn)程,一個進(jìn)程的狀態(tài)可以在Shell中的特殊變量$?
來查看胧瓜。
wait
函數(shù)的三個功能分別是
- 阻塞等待子進(jìn)程退出
- 回收子進(jìn)程殘留的信息
- 獲取子進(jìn)程結(jié)束狀態(tài)與退出原因
pid_t wait(int *status)
wait
函數(shù)成功則清理掉子進(jìn)程ID矢棚,若失敗則返回-1表示沒有子進(jìn)程。
當(dāng)進(jìn)程終止時操作系統(tǒng)的隱式回收機(jī)制
- 關(guān)閉所有文件描述符
- 釋放用戶空間分配的內(nèi)存
進(jìn)程終止時內(nèi)核的PCB仍舊存在府喳,其中保存該進(jìn)程的退出狀態(tài)蒲肋,簡單來說,正常終止時返回退出值钝满,異常終止時返回終止信號兜粘。
實(shí)例:父進(jìn)程回收子進(jìn)程
$ vim wait.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
int main(void)
{
pid_t pid;//創(chuàng)建子進(jìn)程的進(jìn)程ID
pid_t wpid;//回收子進(jìn)程的返回值
//創(chuàng)建子進(jìn)程
pid = fork();
printf("pid=%d\n", pid);
//若子進(jìn)程為調(diào)度進(jìn)程
if(pid == 0)
{
//子進(jìn)程休眠10秒后死亡
printf("child: parent=%d\n", getppid());
printf("child: going to sleep\n");
sleep(10);
printf("child: child die\n");
}
else if(pid > 0)
{
//回收子進(jìn)程若失敗則提示錯誤
wpid = wait(NULL);
if(wpid == -1)
{
perror("wait error");
exit(1 );
}
while(1)
{
printf("parent: pid=%d child=%d\n", getpid(), pid);
sleep(1);
}
}
else
{
perror("fork error");
return 1;
}
return 0;
}
$ gcc wait.c -o wait -Wall -g
$ ./wait
pid=3757
pid=0
child: parent=3756
child: going to sleep
child: child die
parent: pid=3756 child=3757
parent: pid=3756 child=3757
parent: pid=3756 child=3757
...
此時若查看進(jìn)程狀態(tài)會發(fā)現(xiàn)并未出現(xiàn)僵尸進(jìn)程
$ ps aux|grep wait
jc 3756 0.0 0.0 4508 740 pts/0 S+ 22:42 0:00 ./wait
jc 3767 0.0 0.0 21536 1060 pts/1 S+ 22:42 0:00 grep --color=auto wait
子進(jìn)程死亡狀態(tài)
當(dāng)進(jìn)程終止時刻使用wait
函數(shù)傳出參數(shù)status
來保存進(jìn)程的推出狀態(tài),借助宏函數(shù)來進(jìn)一步判斷進(jìn)程終止的具體原因弯蚜。宏函數(shù)可分為三組:
- 正常退出
-
WIFEXITED(status)
:全稱wait if exited
表示判斷是否是正常退出的
若非0則表示進(jìn)程正常退出 -
WEXITSTATUS(status)
:全稱wait exit status
表示退出狀態(tài)
若WIFEXITED(status)
為真則使用宏WEXITSTATUS(status)
以獲取進(jìn)程退出的狀態(tài)孔轴,即exit
的參數(shù)。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
int main(void)
{
pid_t pid;//創(chuàng)建子進(jìn)程的進(jìn)程ID
pid_t wpid;//回收子進(jìn)程的返回值
int status;//回收子進(jìn)程返回的狀態(tài)
//創(chuàng)建子進(jìn)程
pid = fork();
printf("pid=%d\n", pid);
//若子進(jìn)程為調(diào)度進(jìn)程
if(pid == 0)
{
//子進(jìn)程休眠3秒后死亡
printf("child-%d: parent=%d\n", pid, getppid());
printf("child-%d: going to sleep 3 seconds\n", pid);
sleep(3);
printf("child-%d: child die\n", pid);
exit(76);//退出值不超過128
}
else if(pid > 0)
{
//回收子進(jìn)程
wpid = wait(&status);
//回收子進(jìn)程若失敗則提示錯誤
if(wpid == -1)
{
perror("wait error");
exit(1);
}
//若子進(jìn)程回收成功
printf("child-%d: wait if existed with %d\n", pid, WIFEXITED(status));
if(WIFEXITED(status) != 0)
{
//獲取子進(jìn)程成功退出的狀態(tài)值
printf("child-%d: wait exit status with %d\n", pid, WEXITSTATUS(status));
}
while(1)
{
printf("parent-%d: child=%d\n", getpid(), pid);
sleep(1);
}
}
else
{
perror("fork error");
return 1;
}
return 0;
}
編譯運(yùn)行程序并查看狀態(tài)
$ gcc wait.c -o wait -Wall -g
$ ./wait
pid=3934
pid=0
child-0: parent=3933
child-0: going to sleep 3 seconds
child-0: child die
child-3934: wait if existed with 1
child-3934: wait exit status with 76
parent-3933: child=3934
parent-3933: child=3934
parent-3933: child=3934
...
注意:此處使用exit(76);
也可以使用return 76;
進(jìn)行替換碎捺。
- 異常退出
-
WIFSIGNALED(status)
:全稱wait if signaled
若非0則表示進(jìn)程異常終止 -
WTERMSIG(status)
:全稱wait term signal
若WIFSIGNALED(status)
為真則使用宏WTERMSIG(status)
以獲得使進(jìn)程終止的那個信號的編號路鹰。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
int main(void)
{
pid_t pid;//創(chuàng)建子進(jìn)程的進(jìn)程ID
pid_t wpid;//回收子進(jìn)程的返回值
int status;//回收子進(jìn)程返回的狀態(tài)
//創(chuàng)建子進(jìn)程
pid = fork();
printf("pid=%d\n", pid);
//若子進(jìn)程為調(diào)度進(jìn)程
if(pid == 0)
{
//子進(jìn)程休眠后死亡
printf("child-%d: parent=%d\n", pid, getppid());
printf("child-%d: going to sleep 60 seconds\n", pid);
sleep(60);
printf("child-%d: child die\n", pid);
exit(76);//退出值不超過128
}
else if(pid > 0)
{
//回收子進(jìn)程
wpid = wait(&status);
//回收子進(jìn)程若失敗則提示錯誤
if(wpid == -1)
{
perror("wait error");
exit(1 );
}
//若子進(jìn)程正常退出
printf("child-%d: wait if existed with %d\n", pid, WIFEXITED(status));
if(WIFEXITED(status) != 0)
{
//獲取子進(jìn)程成功退出的狀態(tài)值
printf("child-%d: wait exit status with %d\n", pid, WEXITSTATUS(status));
}
//若子進(jìn)程異常退出
printf("child-%d: wait if signaled with %d\n", pid, WIFSIGNALED(status));
if(WIFSIGNALED(status) != 0)
{
printf("child-%d: wait term sig with %d\n", pid, WTERMSIG(status));
}
while(1)
{
printf("parent-%d: child=%d\n", getpid(), pid);
sleep(1);
}
}
else
{
perror("fork error");
return 1;
}
return 0;
}
編譯并運(yùn)行程序
$ gcc wait.c -o wait -Wall -g
$ ./wait
此時讓子進(jìn)程休眠60秒贷洲,為什么呢,在60秒內(nèi)新開命令行窗口并殺死子進(jìn)程和父進(jìn)程查看輸出狀態(tài)晋柱。
$ ps aux|grep wait
root 1139 0.0 0.7 194320 17560 ? Ssl 22:03 0:00 /usr/bin/python3 /usr/share/unattended-upgrades/unattended-upgrade-shutdown --wait-for-signal
jc 4030 0.0 0.0 4508 856 pts/0 S+ 23:44 0:00 ./wait
jc 4031 0.0 0.0 4508 72 pts/0 S+ 23:44 0:00 ./wait
jc 4034 0.0 0.0 21536 1148 pts/1 S+ 23:44 0:00 grep --color=auto wait
此時會發(fā)現(xiàn)有兩個進(jìn)程正在運(yùn)行优构,4030對應(yīng)的應(yīng)該是父進(jìn)程,4031對應(yīng)的應(yīng)該是子進(jìn)程雁竞,接下來分別殺死進(jìn)程钦椭。
$ kill -9 4031
$ kill -9 4030
最后查看日志輸出信息
pid=4031
pid=0
child-0: parent=4030
child-0: going to sleep 60 seconds
child-4031: wait if existed with 0
child-4031: wait if signaled with 1
child-4031: wait term sig with 9
parent-4030: child=4031
parent-4030: child=4031
parent-4030: child=4031
parent-4030: child=4031
parent-4030: child=4031
parent-4030: child=4031
parent-4030: child=4031
parent-4030: child=4031
parent-4030: child=4031
parent-4030: child=4031
已殺死
此處需注意child-4031: wait term sig with 9
中的9是由于使用kill -9 4031
,也就是向進(jìn)程發(fā)送的9號信號碑诉。表示什么意思呢彪腔?
$ kill -L
1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP
6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1
11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM
16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP
21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ
26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR
31) SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3
38) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8
43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7
58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2
63) SIGRTMAX-1 64) SIGRTMAX
根據(jù)命令可以知道9號信號表示SIGKILL
- 暫停狀態(tài)
-
WIFSTOPPED(status)
:全稱wait if stopped
進(jìn)程處于暫停狀態(tài)
若非0則表示進(jìn)程處于暫停狀態(tài) -
WSTOPSIG(status)
若WIFSTOPPED(status)
為真則使用宏WSTOPSIG(status)
以獲取使進(jìn)程暫停的那個信號的編號。 -
WIFCONTINUED(status)
若WIFCONTINUED(status)
為真則表示進(jìn)程暫停后已經(jīng)繼續(xù)運(yùn)行
子進(jìn)程死亡一般會存在兩種情況进栽,一種是正常死亡壽終正寢德挣,此時程序?qū)⒎祷?。另一種情況是異常退出泪幌,在Linux中所有的異常退出都是由于信號導(dǎo)致的盲厌,由于子進(jìn)程收到了某個特殊信號它才異常退出。所以異常終止時父進(jìn)程需要回收子進(jìn)程異常終止的信號祸泪。
waitpid函數(shù)
waitpid
函數(shù)作用于wait
函數(shù)相同吗浩,但可以指定pid
進(jìn)程清理且不阻塞。
pid_t waitpid(pid_t pid, int *status, int options)
waitpid
若成功則返回清理掉的進(jìn)程ID没隘,若失敗則返回-1表示無子進(jìn)程懂扼。
wait
函數(shù)與waitpid
的區(qū)別在于一次wait
調(diào)用只能回收一個子進(jìn)程,而waitpid
可以指定進(jìn)程ID進(jìn)行回收更加靈活右蒲。
waitpid
參數(shù)pid
返回情況
- 若
pid
小于-1表示回收指定進(jìn)程組中的任意子進(jìn)程 - 若
pid
等于0表示回收和當(dāng)前調(diào)用waitpid
一個組的所有子進(jìn)程 - 若
pid
等于-1表示回收任意子進(jìn)程阀湿,作用相當(dāng)于wait
函數(shù)。 - 若
pid
大于0表示回收指定進(jìn)程ID的子進(jìn)程
waitpid
參數(shù)options
可以設(shè)置是否阻塞瑰妄,設(shè)置非阻塞可使用WNOHANG
宏陷嘴,設(shè)置非阻塞后需要使用輪詢的方式定時查看子進(jìn)程是否回收成功。
$ vim waipid.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
int main(int argc, char *argv[])
{
int n = 5;//默認(rèn)創(chuàng)建5個子進(jìn)程
int i;//循環(huán)變量
pid_t pid;//子進(jìn)程ID
pid_t tmp;
//若命令行參數(shù)個數(shù)是2個
if(argc == 2)
{
n = atoi(argv[1]);
}
//循環(huán)創(chuàng)建子進(jìn)程
for(i=0; i<n; i++)
{
pid = fork();
//若進(jìn)程為系統(tǒng)進(jìn)程
if(pid == 0)
{
break;
}
else if(i ==3)
{
tmp = pid;
}
}
printf("tmp=%d\n", tmp);
if(n == i)
{
sleep(n);//多少個循環(huán)則休眠多少秒
printf("parent: pid=%d\n", getpid());//打印父進(jìn)程
//子進(jìn)程死亡后回收
wait(NULL);//隨機(jī)回收子進(jìn)程且只回收一個
while(1);//死循環(huán)
}
else
{
sleep(i);//休眠
printf("child%d: pid=%d\n", i+1, getpid());//打印子進(jìn)程
//while(1);//死循環(huán)
}
return 0;
}
$ gcc waitpid.c -o waitpid -Wall -g
$ ./waitpid
tmp=5043
tmp=5043
tmp=0
tmp=0
tmp=0
tmp=0
child1: pid=5040
child2: pid=5041
child3: pid=5042
child4: pid=5043
child5: pid=5044
parent: pid=5039
$ ps aux|grep waitpid
jc 5039 81.6 0.0 4508 756 pts/0 R+ 03:52 0:25 ./waitpid
jc 5041 0.0 0.0 0 0 pts/0 Z+ 03:52 0:00 [waitpid] <defunct>
jc 5042 0.0 0.0 0 0 pts/0 Z+ 03:52 0:00 [waitpid] <defunct>
jc 5043 0.0 0.0 0 0 pts/0 Z+ 03:52 0:00 [waitpid] <defunct>
jc 5044 0.0 0.0 0 0 pts/0 Z+ 03:52 0:00 [waitpid] <defunct>
可以查看到當(dāng)前存在4個僵尸進(jìn)程Z+
间坐,使用wait
回收時每次只回收了一個進(jìn)程灾挨,如果需要同時回收5個進(jìn)程,應(yīng)該怎么辦呢竹宋?
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
int main(int argc, char *argv[])
{
int n = 5;//默認(rèn)創(chuàng)建5個子進(jìn)程
int i;//循環(huán)變量
pid_t pid;//子進(jìn)程ID
pid_t tmp;
//若命令行參數(shù)個數(shù)是2個
if(argc == 2)
{
n = atoi(argv[1]);
}
//循環(huán)創(chuàng)建子進(jìn)程
for(i=0; i<n; i++)
{
pid = fork();
//若進(jìn)程為系統(tǒng)進(jìn)程
if(pid == 0)
{
break;
}
else if(i ==3)
{
tmp = pid;
}
}
printf("tmp=%d\n", tmp);
if(n == i)
{
sleep(n);//多少個循環(huán)則休眠多少秒
printf("parent: pid=%d\n", getpid());//打印父進(jìn)程
//子進(jìn)程死亡后回收
while(wait(NULL));//循環(huán)回收子進(jìn)程劳澄,每次只能回收一個。
while(1);//死循環(huán)
}
else
{
sleep(i);//休眠
printf("child%d: pid=%d\n", i+1, getpid());//打印子進(jìn)程
//while(1);//死循環(huán)
}
return 0;
}
此處會發(fā)現(xiàn)如果需要回收多個進(jìn)程蜈七,可使用while
循環(huán)的方式秒拔。
如果現(xiàn)在需要指定某個進(jìn)程進(jìn)行回收呢?應(yīng)該怎么辦飒硅?
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
int main(int argc, char *argv[])
{
int n = 5;//默認(rèn)創(chuàng)建5個子進(jìn)程
int i;//循環(huán)變量
pid_t pid;//子進(jìn)程ID
pid_t tmp;
//若命令行參數(shù)個數(shù)是2個
if(argc == 2)
{
n = atoi(argv[1]);
}
//循環(huán)創(chuàng)建子進(jìn)程
for(i=0; i<n; i++)
{
pid = fork();
//若進(jìn)程為系統(tǒng)進(jìn)程
if(pid == 0)
{
break;
}
else if(i ==3)
{
tmp = pid;//獲取第三個子進(jìn)程的pid砂缩,為了指定進(jìn)程進(jìn)行回收使用作谚。
}
}
printf("tmp=%d\n", tmp);
if(n == i)
{
sleep(n);//多少個循環(huán)則休眠多少秒
printf("parent: pid=%d\n", getpid());//打印父進(jìn)程
//子進(jìn)程死亡后回收
waitpid(tmp, NULL, 0);//指定回收某個子進(jìn)程
while(1);//死循環(huán)
}
else
{
sleep(i);//休眠
printf("child%d: pid=%d\n", i+1, getpid());//打印子進(jìn)程
//while(1);//死循環(huán)
}
return 0;
}
$ gcc waitpid.c -o waitpid -Wall -g
$ ./waitpid
tmp=5097
tmp=5097
tmp=0
tmp=0
tmp=0
tmp=0
child1: pid=5094
child2: pid=5095
child3: pid=5096
child4: pid=5097
child5: pid=5098
parent: pid=5093
$ ps aux|grep waitpid
jc 5093 54.5 0.0 4508 740 pts/0 R+ 04:05 0:06 ./waitpid
jc 5094 0.0 0.0 0 0 pts/0 Z+ 04:05 0:00 [waitpid] <defunct>
jc 5095 0.0 0.0 0 0 pts/0 Z+ 04:05 0:00 [waitpid] <defunct>
jc 5096 0.0 0.0 0 0 pts/0 Z+ 04:05 0:00 [waitpid] <defunct>
jc 5098 0.0 0.0 0 0 pts/0 Z+ 04:05 0:00 [waitpid] <defunct>
jc 5100 0.0 0.0 21536 1028 pts/1 S+ 04:05 0:00 grep --color=auto waitpid
此處會發(fā)現(xiàn)pid=5087
的子進(jìn)程被回收了
未完待續(xù)...