殭屍進程:當子進程退出時,父進程尚未(使用wait或waitpid)接收其退出狀態時,子進程就成了殭屍進程
孤兒進程:當子進程還在運行時,父進程先退出了,子進程就會成爲孤兒進程被pid=1的init/systemd進程收養bash
須要說明的是,殭屍進程的父進程死掉後,殭屍進程也會被pid=1的init/systemd進程收養,而init/systemd進程會按期清理其下殭屍進程,並在它的任意子進程退出時檢查它的領土下是否有殭屍進程存在,從而保證init/systemd下不會有太多殭屍進程。less
#!/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;
執行結果:函數
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;
結果:code
second child, ppid=22683 # 5秒以後輸出 second child, ppid=1
殭屍進程是由於沒有使用wait/waitpid接收子進程的退出狀態,只要使用wait/waitpid接收該子進程的退出狀態,父進程就會爲子進程收屍善後。進程
另外,當子進程退出時,內核會當即發送SIGCHLD信號給父進程告知其該子進程退出了。get
有幾種方式能夠應對殭屍進程:it
這三種方式中,前兩種用的比較多,第三種比較技巧化,可是也有其用處,好比實現脫離終端的進程。perl
父進程中等待全部子進程退出的方式:終端
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的進程收養。