博客園的我:駿馬金龍: https://www.cnblogs.com/f-ck-need-ubash
殭屍進程:當子進程退出時,父進程尚未(使用wait或waitpid)接收其退出狀態時,子進程就成了殭屍進程
孤兒進程:當子進程還在運行時,父進程先退出了,子進程就會成爲孤兒進程被pid=1的init/systemd進程收養less
須要說明的是,殭屍進程的父進程死掉後,殭屍進程也會被pid=1的init/systemd進程收養,而init/systemd進程會按期清理其下殭屍進程,並在它的任意子進程退出時檢查它的領土下是否有殭屍進程存在,從而保證init/systemd下不會有太多殭屍進程。函數
#!/usr/bin/perl
#
use strict;
use warnings;
defined(my $pid = fork) or die "fork failed: $!";
unless($pid){
# child process
print "I am child process\n";
exit;
}
# parent process
print "I am parent process\n";
sleep(2);
system("ps -o pid,ppid,state,tty,command");
print "parent process exiting\n";
exit;
複製代碼
執行結果:ui
I am parent process
I am child process
PID PPID S TT COMMAND
22342 22340 S pts/0 -bash
22647 22342 S pts/0 perl zombie2.pl
22648 22647 Z pts/0 [perl] <defunct>
22649 22647 R pts/0 ps -o pid,ppid,state,tty,command
parent process exiting
複製代碼
#!/usr/bin/perl
use strict;
use warnings;
defined(my $pid = fork) or die "fork failed: $!";
unless($pid){
# 子進程
print "second child, ppid=",getppid(),"\n";
sleep(5);
print "second child, ppid=",getppid(),"\n";
exit 0;
}
# 父進程
sleep 1;
複製代碼
結果:spa
second child, ppid=22683
# 5秒以後輸出
second child, ppid=1
複製代碼
殭屍進程是由於沒有使用wait/waitpid接收子進程的退出狀態,只要使用wait/waitpid接收該子進程的退出狀態,父進程就會爲子進程收屍善後。code
另外,當子進程退出時,內核會當即發送SIGCHLD信號給父進程告知其該子進程退出了。blog
有幾種方式能夠應對殭屍進程:進程
這三種方式中,前兩種用的比較多,第三種比較技巧化,可是也有其用處,好比實現脫離終端的進程。get
父進程中等待全部子進程退出的方式:博客
until(wait == -1){}
until(waitpid -1, 0 == -1){}
until(waitpid -1, WNOHANG == -1){}
複製代碼
例如:
#!/usr/bin/perl
use strict;
use warnings;
use POSIX qw(WNOHANG);
# fork 5個子進程
for (1..5) {
defined(my $pid = fork) or die "fork error: $!";
unless($pid){
# 子進程
print "I am child: $_\n";
sleep 1;
exit 0;
}
}
# 每秒非阻塞wait一次
until(waitpid(-1, WNOHANG) == -1){
print "any children still exists\n";
sleep 1;
}
print "all child exits\n";
system("ps -o pid,ppid,state,tty,command");
exit 0;
複製代碼
執行結果:
I am child: 1
I am child: 2
I am child: 3
any children still exists
I am child: 5
I am child: 4
any children still exists
any children still exists
any children still exists
any children still exists
any children still exists
any children still exists
all child exits
PID PPID S TT COMMAND
22342 22340 S pts/0 -bash
24547 22342 S pts/0 perl waitallchild.pl
24553 24547 R pts/0 ps -o pid,ppid,state,tty,command
複製代碼
這裏輸出了多個"any children...",是由於waitpid對於每一個等待到的pid都返回一次,此外若是檢查的時候沒有任何退出的子進程,也會每秒返回一次。
最終的結果中顯示沒有殭屍進程的存在。
#!/usr/bin/perl
use strict;
use warnings;
use POSIX qw(WNOHANG);
sub reap_child;
# 註冊SIGCHLD信號的處理程序
$SIG{CHLD}=\&reap_child;
# fork 5個子進程
for (1..5){
defined(my $pid = fork) or die "fork failed: $!";
unless($pid){
# 子進程
print "I am child: $_\n";
sleep 1;
exit 0;
} else {
print "child $_: pid=$pid\n";
}
}
# 父進程
sleep 20;
system("ps -o pid,ppid,state,tty,command");
sub reap_child {
print "SIGCHLD triggered at:",~~localtime, "\n";
# 只要有子進程退出,就收屍
while((my $kid = waitpid -1, WNOHANG) > 0){
print "$kid reaped\n";
}
}
複製代碼
執行結果:
child 1: pid=24857
I am child: 1
child 2: pid=24858
I am child: 2
child 3: pid=24859
I am child: 3
child 4: pid=24860
I am child: 4
child 5: pid=24861
I am child: 5
SIGCHLD triggered at:Mon Feb 25 13:49:43 2019
24857 reaped
24859 reaped
24860 reaped
PID PPID S TT COMMAND
22342 22340 S pts/0 -bash
24856 22342 S pts/0 perl reap_zombie.pl
24858 24856 Z pts/0 [perl] <defunct>
24861 24856 Z pts/0 [perl] <defunct>
24862 24856 R pts/0 ps -o pid,ppid,state,tty,command
SIGCHLD triggered at:Mon Feb 25 13:49:43 2019
24858 reaped
24861 reaped
複製代碼
發現只需1-2秒程序就終止了,但父進程明明就sleep 20了,爲何?還有結果好像很奇怪?不只有兩個殭屍進程還只觸發了兩次SIGCHLD信號處理程序。
上面觸發了兩次SIGCHLD信號處理程序,由於第二次觸發的是system()打開的子進程ps命令退出時觸發的。
之因此1-2秒就結束,是由於子進程結束時,內核發送SIGCHLD信號給父進程,會中斷父進程的sleep睡眠。
只觸發兩次信號處理程序就能收走5個子進程,其中第一次觸發收走了3個子進程,第二次觸發收走了2個子進程,是由於waitpid會返回全部等待到的子進程pid,第一次等待到了3個子進程的退出,第二次等待到了2個子進程的退出。
那麼爲何system()中的ps退出時沒有被SIGCHLD信號處理程序中的waitpid收走?這是由於system()函數自身就帶有了wait阻塞函數,它本身會收走通過它fork出來的子進程,使得雖然ps的退出觸發了SIGCHLD,但ps的退出狀態值已經清空了,沒法被信號處理程序中的waitpid處理。
代碼以下:
#!/usr/bin/env perl
use strict;
use warnings;
defined(my $pid = fork) or die "fork failed: $!";
unless($pid){
# 第一個子進程
# 繼續fork一個孫子進程:第二個子進程
defined(my $kid = fork) or die "fork failed: $!";
if($kid){
# 第一個子進程5秒後退出
sleep 5;
exit 0;
}
# 孫子進程
sleep(10);
print "second child, ppid=",getppid(),"\n";
exit 0;
}
# 爲第一個子進程收屍
(waitpid $pid, 0 == $pid) or die "waitpid error: $!";
exit 0;
複製代碼
上面的代碼中,在5秒後第一個子進程退出並被父進程收屍,第二個進程將成爲孤兒進程被pid=1的進程收養。