在《unix環(huán)境高級(jí)編程》中有提到僵尸進(jìn)程
和孤兒進(jìn)程
。不少同學(xué)對(duì)這兩個(gè)概念會(huì)混淆,這篇文章總結(jié)一下。
在 unix/linux 系統(tǒng)中狂秘,大多情況下,子進(jìn)程是通過(guò)父進(jìn)程 fork 創(chuàng)建的躯肌,注:系統(tǒng)調(diào)用 fork者春,是一個(gè)比較有意思系統(tǒng)調(diào)用,它調(diào)用一次清女,返回兩個(gè)值钱烟,失敗返回 -1,成功時(shí)在子進(jìn)程返回 0,父進(jìn)程返回所創(chuàng)建子進(jìn)程的 pid。
子進(jìn)程創(chuàng)建后拴袭,子進(jìn)程的結(jié)束和父進(jìn)程的運(yùn)行是一個(gè)異步過(guò)程,也就是說(shuō)父進(jìn)程沒(méi)辦法預(yù)測(cè)子進(jìn)程什么時(shí)候結(jié)束读第。 當(dāng)一個(gè)子進(jìn)程完成它的工作終止之后,其父進(jìn)程需要調(diào)用 wait() 或 waitpid() 去獲取子進(jìn)程的終止?fàn)顟B(tài)拥刻。
孤兒進(jìn)程
所謂孤兒進(jìn)程怜瞒,顧名思義,和現(xiàn)實(shí)生活中的孤兒有點(diǎn)類似般哼,當(dāng)一個(gè)進(jìn)程的父進(jìn)程結(jié)束時(shí)吴汪,但是它自己還沒(méi)有結(jié)束,那么這個(gè)進(jìn)程將會(huì)成為孤兒進(jìn)程蒸眠。最后孤兒進(jìn)程將會(huì)被 init 進(jìn)程(進(jìn)程號(hào)為1)的進(jìn)程收養(yǎng)漾橙,當(dāng)然在子進(jìn)程結(jié)束時(shí)也會(huì)由 init 進(jìn)程完成對(duì)它的狀態(tài)收集工作,因此一般來(lái)說(shuō)黔宛,孤兒進(jìn)程并不會(huì)有什么危害近刘。
下面看一個(gè)關(guān)于孤兒進(jìn)程的例子:先打印父進(jìn)程 id擒贸,然后創(chuàng)建子進(jìn)程臀晃,讓父進(jìn)程睡眠 5s,讓子進(jìn)程先運(yùn)行循環(huán)打印出其進(jìn)程 id(pid)介劫;隨后子進(jìn)程睡眠 1-3s(此運(yùn)行過(guò)程父進(jìn)程會(huì)結(jié)束)徽惋,目的是讓父進(jìn)程先于子進(jìn)程結(jié)束,讓子進(jìn)程有個(gè)孤兒的狀態(tài)座韵;最后子進(jìn)程再打印出其進(jìn)程 id(pid) 以及父進(jìn)程 id(ppid) 险绘;觀察兩次打印 其父進(jìn)程 id(ppid) 的區(qū)別。
示例代碼:
<?php
// 獲取當(dāng)前進(jìn)程ID
$parentPid = posix_getpid();
echo "parent progress pid:{$parentPid}\n";
$childList = array();
// 創(chuàng)建子進(jìn)程
$pid = pcntl_fork();
if ( $pid == -1) {
// 創(chuàng)建失敗
exit("fork progress error!\n");
} else if ($pid == 0) {
$repeatNum = 10;
for ( $i = 1; $i <= $repeatNum; $i++) {
// 子進(jìn)程執(zhí)行程序
$ppid = posix_getppid();
$pid = posix_getpid();
echo "PPID:{$ppid} , PID:{$pid} child progress is running! {$i} \n";
$rand = rand(1,3);
sleep($rand);
}
exit("({$pid})child progress end!\n");
} else {
// 父進(jìn)程執(zhí)行程序
$childList[$pid] = 1;
}
// 延遲5秒誉碴,父進(jìn)程退出
sleep(5);
echo "({$parentPid})main progress end!\n";
執(zhí)行過(guò)程輸出:
可以看到宦棺,當(dāng)父進(jìn)程退出后,子進(jìn)程被 init 進(jìn)程收養(yǎng)黔帕。子進(jìn)程執(zhí)行完畢代咸,init 進(jìn)程完成對(duì)它的狀態(tài)收集工作。
僵尸進(jìn)程
一個(gè)進(jìn)程使用 fork 創(chuàng)建子進(jìn)程成黄,如果子進(jìn)程退出呐芥,而父進(jìn)程并沒(méi)有調(diào)用 wait 或 waitpid 獲取子進(jìn)程的狀態(tài)信息,那么子進(jìn)程的某些信息如進(jìn)程描述符仍然保存在系統(tǒng)中奋岁。這種進(jìn)程稱之為僵死進(jìn)程思瘟。
下面是1個(gè)關(guān)于僵尸進(jìn)程的例子,創(chuàng)建子進(jìn)程闻伶,然后讓父進(jìn)程睡眠 90s滨攻,讓子進(jìn)程先終止(注意和孤兒進(jìn)程例子的區(qū)別);這里子進(jìn)程結(jié)束后父進(jìn)程沒(méi)有調(diào)用 wait/waitpid 函數(shù)獲取其狀態(tài),用ps查看進(jìn)程狀態(tài)可以看出子進(jìn)程為僵尸狀態(tài)光绕。
示例代碼:
// 獲取當(dāng)前進(jìn)程ID
$parentPid = posix_getpid();
echo "parent progress pid:{$parentPid}\n";
$childList = array();
// 創(chuàng)建子進(jìn)程
$pid = pcntl_fork();
if ( $pid == -1) {
// 創(chuàng)建失敗
exit("fork progress error!\n");
} else if ($pid == 0) {
$repeatNum = 10;
for ( $i = 1; $i <= $repeatNum; $i++) {
// 子進(jìn)程執(zhí)行程序
$ppid = posix_getppid();
$pid = posix_getpid();
echo date('Y-m-d H:i:s') , ' ';
echo "PPID:{$ppid} , PID:{$pid} child progress is running! {$i} \n";
$rand = rand(1,3);
sleep($rand);
}
echo date('Y-m-d H:i:s') , ' ';
exit("({$pid})child progress end!\n");
} else {
// 父進(jìn)程執(zhí)行程序
$childList[$pid] = 1;
}
// 延遲90秒更鲁,父進(jìn)程退出
sleep(90);
echo date('Y-m-d H:i:s') , ' ';
echo "({$parentPid})main progress end!\n";
執(zhí)行過(guò)程輸出:
僵尸進(jìn)程狀態(tài):
任何一個(gè)子進(jìn)程 (init 除外) 在 exit() 之后,并非馬上就消失掉奇钞,而是留下一個(gè)稱為僵尸進(jìn)程 (Zombie) 的數(shù)據(jù)結(jié)構(gòu)澡为,等待父進(jìn)程處理。這是每個(gè)子進(jìn)程在結(jié)束時(shí)都要經(jīng)過(guò)的階段景埃。如果子進(jìn)程在 exit() 之后媒至,父進(jìn)程沒(méi)有來(lái)得及處理,這時(shí)用 ps 命令就能看到子進(jìn)程的狀態(tài)是“Z”谷徙。如果父進(jìn)程能及時(shí) 處理拒啰,可能用 ps 命令就來(lái)不及看到子進(jìn)程的僵尸狀態(tài),但這并不等于子進(jìn)程不經(jīng)過(guò)僵尸狀態(tài)完慧。 如果父進(jìn)程在子進(jìn)程結(jié)束之前退出谋旦,則子進(jìn)程將由 init 接管。init 將會(huì)以父進(jìn)程的身份對(duì)僵尸狀態(tài)的子進(jìn)程進(jìn)行處理屈尼。
僵尸進(jìn)程的危害:
僵尸進(jìn)程會(huì)在系統(tǒng)中保留其某些信息如進(jìn)程描述符册着、進(jìn)程 id 等等。以進(jìn)程 id 為例脾歧,系統(tǒng)中可用的進(jìn)程 id 是有限的甲捏,如果由于系統(tǒng)中大量的僵尸進(jìn)程占用進(jìn)程 id,就會(huì)導(dǎo)致因?yàn)闆](méi)有可用的進(jìn)程 id 系統(tǒng)不能產(chǎn)生新的進(jìn)程鞭执,這種問(wèn)題可就大了司顿,這就是僵尸進(jìn)程帶來(lái)的危害。因此大部分情況下兄纺,我們都應(yīng)當(dāng)避免僵尸進(jìn)程的產(chǎn)生大溜。
如何消除僵尸進(jìn)程:
嚴(yán)格地來(lái)說(shuō),僵死進(jìn)程并不是問(wèn)題的根源估脆,罪魁禍?zhǔn)资钱a(chǎn)生出大量僵死進(jìn)程的那個(gè)父進(jìn)程钦奋。因此,當(dāng)我們尋求如何消滅系統(tǒng)中大量的僵死進(jìn)程時(shí)旁蔼,答案就是把產(chǎn)生大量僵死進(jìn)程的那個(gè)元兇槍斃掉(也就是通過(guò) kill 發(fā)送 SIGTERM 或者 SIGKILL 信號(hào))锨苏。
槍斃了元兇進(jìn)程之后,它產(chǎn)生的僵死進(jìn)程就變成了孤兒進(jìn)程棺聊,這些孤兒進(jìn)程會(huì)被 init 進(jìn)程接管伞租,init 進(jìn)程會(huì) wait() 這些孤兒進(jìn)程,釋放它們占用的系統(tǒng)進(jìn)程表中的資源限佩。這樣葵诈,這些已經(jīng)僵死的孤兒進(jìn)程就能瞑目而去了裸弦。