zoukankan      html  css  js  c++  java
  • 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
    ";
        exit;
    }
    
    # parent process
    print "I am parent process
    ";
    sleep(2);
    system("ps -o pid,ppid,state,tty,command");
    print "parent process exiting
    ";
    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(),"
    ";
        sleep(5);
        print "second child, ppid=",getppid(),"
    ";
        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收养,从而保证其不会成为僵尸进程。这样,需要想要成为孤儿的已经孤儿了,父进程却可以继续执行父进程的代码。如果只fork一次,想要子进程孤儿,父进程继续执行代码是不可能的,因为只有父进程退出,子进程才会孤儿

    这三种方式中,前两种用的比较多,第三种比较技巧化,但是也有其用处,比如实现脱离终端的进程。

    等待所有子进程退出

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

    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: $_
    ";
            sleep 1;
            exit 0;
        }
    }
    
    # 每秒非阻塞wait一次
    until(waitpid(-1, WNOHANG) == -1){
        print "any children still exists
    ";
        sleep 1;
    }
    
    print "all child exits
    ";
    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: $_
    ";
            sleep 1;
            exit 0;
        } else {
            print "child $_: pid=$pid
    ";
        }
    }
    
    # 父进程
    sleep 20;
    system("ps -o pid,ppid,state,tty,command");
    
    sub reap_child {
        print "SIGCHLD triggered at:",~~localtime, "
    ";
        # 只要有子进程退出,就收尸
        while((my $kid = waitpid -1, WNOHANG) > 0){
            print "$kid reaped
    ";
        }
    }
    

    执行结果:

    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处理。

    fork两次收掉僵尸进程

    代码如下:

    #!/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(),"
    ";
        exit 0;
    }
    
    # 为第一个子进程收尸
    (waitpid $pid, 0 == $pid) or die "waitpid error: $!";
    
    exit 0;
    

    上面的代码中,在5秒后第一个子进程退出并被父进程收尸,第二个进程将成为孤儿进程被pid=1的进程收养。

  • 相关阅读:
    背景图
    PKUWC 2019~2020 游记
    前置内容2:复杂度分析
    前置内容1:算法与数据结构
    莫比乌斯反演学习笔记2
    莫比乌斯反演学习笔记1
    CSP-J&S-2019 游记
    最近面试的一些感触
    算法学习-整数反转
    入行九年,入园8年,突然想写一点东西.
  • 原文地址:https://www.cnblogs.com/f-ck-need-u/p/10508297.html
Copyright © 2011-2022 走看看