Perl进程:僵尸进程和孤儿进程

僵尸进程:当子进程退出时,父进程还没有(使用wait或waitpid)接收其退出状态时,子进程就成了僵尸进程
孤儿进程:当子进程还在运行时,父进程先退出了,子进程就会成为孤儿进程被pid=1的init/systemd进程收养

需要说明的是,僵尸进程的父进程死掉后,僵尸进程也会被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;

执行结果:

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;

结果:

second child, ppid=22683 # 5秒之后输出 second child, ppid=1 解决僵尸进程的方式

僵尸进程是因为没有使用wait/waitpid接收子进程的退出状态,只要使用wait/waitpid接收该子进程的退出状态,父进程就会为子进程收尸善后。

另外,当子进程退出时,内核会立即发送SIGCHLD信号给父进程告知其该子进程退出了。

有几种方式可以应对僵尸进程:

直接在父进程中使用wait/waitpid等待所有子进程退出(不能留下任一个子进程)

父进程中定义SIGCHLD信号的处理程序,并在该信号处理程序中调用wait/waitpid为每个退出的子进程收尸

连续fork两次,在第二次fork中执行主代码,第一次fork的子进程立即退出并在父进程中被收尸。这使得第一个退出的子进程不会成为僵尸进程,也使得第二个子进程立即成为孤儿进程被pid=1的init/systemd收养,从而保证其不会成为僵尸进程

这三种方式中,前两种用的比较多,第三种比较技巧化,但是也有其用处。

等待所有子进程退出

父进程中等待所有子进程退出的方式:

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都返回一次,此外如果检查的时候没有任何退出的子进程,也会每秒返回一次。

最终的结果中显示没有僵尸进程的存在。

SIGCHLD处理程序收掉僵尸进程 #!/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睡眠。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zysddx.html