使用 open 创建子进程时杀死子进程及其子进程

发布于 2024-08-10 05:23:01 字数 1109 浏览 4 评论 0原文

这是我的代码,为了清楚起见,删除了错误处理和其他内容:

sub launch_and_monitor {  

    my ($script, $timeout) = @_;

    sub REAPER {
        while ((my $child = waitpid(-1, &WNOHANG)) > 0) {}
        $SIG{CHLD} = \&REAPER;
    }
    $SIG{CHLD} = \&REAPER;

    my $pid = fork;
    if (defined $pid) {
        if ($pid == 0) {
            # in child
            monitor($timeout);
        }
        else {
            launch($script);
        }
    }
}

启动子程序执行一个 shell 脚本,该脚本又启动其他进程,如下所示:

sub launch($) {

    my ($script) = @_;

    my $pid = open(PIPE, "$script|");

    # write pid to pidfile
    
    if ($pid != 0) {
        while(<PIPE>) {
            # do stuff with output
        }
        close(PIPE) or die $!;
    }
}

监视器子程序基本上只是等待指定的时间段,然后尝试终止 shell脚本。

sub monitor($) {

    my ($timeout) = @_;

    sleep $timeout;

    # check if script is still running and if so get pid from pidfile
    if (...) {
        my $pid = getpid(...);        
        kill 9, $pid;
    }
}

这会杀死脚本,但是,它不会杀死其任何子进程。如何修复它?

Here's my code, with error handling and other stuff removed for clarity:

sub launch_and_monitor {  

    my ($script, $timeout) = @_;

    sub REAPER {
        while ((my $child = waitpid(-1, &WNOHANG)) > 0) {}
        $SIG{CHLD} = \&REAPER;
    }
    $SIG{CHLD} = \&REAPER;

    my $pid = fork;
    if (defined $pid) {
        if ($pid == 0) {
            # in child
            monitor($timeout);
        }
        else {
            launch($script);
        }
    }
}

The launch sub executes a shell script which in turn launches other processes, like so:

sub launch($) {

    my ($script) = @_;

    my $pid = open(PIPE, "$script|");

    # write pid to pidfile
    
    if ($pid != 0) {
        while(<PIPE>) {
            # do stuff with output
        }
        close(PIPE) or die $!;
    }
}

The monitor sub basically just waits for a specified period of time and then attempts to kill the shell script.

sub monitor($) {

    my ($timeout) = @_;

    sleep $timeout;

    # check if script is still running and if so get pid from pidfile
    if (...) {
        my $pid = getpid(...);        
        kill 9, $pid;
    }
}

This kills the script, however, it does not kill any of its subprocesses. How to fix it?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

枫林﹌晚霞¤ 2024-08-17 05:23:01

如果您的操作系统支持,您可以使用进程组来执行此操作。你需要让脚本进程成为进程组的领导者。它运行的子进程将从其父进程继承进程组。然后,您可以使用kill同时向组中的每个进程发送信号。

launch() 中,您需要将 open 行替换为 fork 行。然后在子进程中,您将在执行命令之前调用 setpgrp() 。类似下面的内容应该可以工作:

my $pid = open(PIPE, "-|");
if (0 == $pid) {
    setpgrp(0, 0);
    exec $script;
    die "exec failed: $!\n";
}
else {
    while(<PIPE>) {
        # do stuff with output
    }
    close(PIPE) or die $!;
}

稍后,要终止脚本进程及其子进程,请否定您发出信号的进程 ID:

kill 9, -$pid;

You can do this with process groups, if your operating system supports them. You need to make the script process become a process group leader. The child processes that it runs will inherit the process group from their parent. You can then use kill to send a signal to each process in the group at the same time.

In launch(), you will need to replace the open line with one that forks. Then in the child, you would call setpgrp() before exec'ing the command. Something like the following should work:

my $pid = open(PIPE, "-|");
if (0 == $pid) {
    setpgrp(0, 0);
    exec $script;
    die "exec failed: $!\n";
}
else {
    while(<PIPE>) {
        # do stuff with output
    }
    close(PIPE) or die $!;
}

Later, to kill the script process and its children, negate the process ID that you're signalling:

kill 9, -$pid;
断桥再见 2024-08-17 05:23:01

一般来说,我认为您不能期望信号传播到所有子进程中;这不是 Perl 特有的。

也就是说,您也许可以使用 Perl 内置的进程信号功能

...如果 SIGNAL 为负数,它会杀死进程组而不是进程...

您可能需要使用 setpgrp() 在你的(直接)子进程上,然后将你的kill调用更改为:

kill -9, $pgrp;

In general, I don't think you can expect signals to be propagated into all child processes; this isn't specific to perl.

That said, you might be able to use the process group signal feature built into perl kill():

...if SIGNAL is negative, it kills process groups instead of processes...

You probably need to use setpgrp() on your (direct) child process, then change your kill call to something like:

kill -9, $pgrp;
剩一世无双 2024-08-17 05:23:01

尝试

use POSIX qw(setsid);
setsid;

launch_and_monitor 函数顶部 添加:这会将您的进程置于单独的会话中,并导致会话领导者(即主进程)退出时退出。

Try adding:

use POSIX qw(setsid);
setsid;

at the top of your launch_and_monitor function. This will put your processes in a separate session, and cause things to exit when the session leader (i.e. the master) exits.

夏九 2024-08-17 05:23:01

杀死一个进程组是可行的,但不要忘记父进程也可以被单独杀死。假设子进程有一个事件循环,它们可以在执行 fork() 之前检查在套接字对中创建的父套接字的有效性。事实上,当父套接字消失时,select()会干净地退出,所需要做的就是检查套接字。

例如:

use strict; use warnings;
use Socket;

$SIG{CHLD} = sub {};

socketpair(my $p, my $c, AF_UNIX, SOCK_STREAM, PF_UNSPEC) or die $!;

print "parent $, fork 2 kids\n";
for (0..1){
    my $kid = fork();
    unless($kid){
        child_loop($p, $c);
        exit; 
    }
    print "parent $, forked kid $kid\n";
}

print "parent $, waiting 5s\n";
sleep 5;
print "parent $ exit, closing sockets\n";

sub child_loop {
    my ($p_s, $c_s) = @_;
    print "kid: $\n";
    close($c_s);
    my $rin = '';
    vec($rin, fileno($p_s), 1) = 1;
    while(1){
        select my $rout = $rin, undef, undef, undef;
        if(vec($rout, fileno($p_s), 1)){
            print "kid: $, parent gone, exiting\n";
            last;
        }
    }
}

运行如下:

tim@mint:~$ perl ~/abc.pl
parent 5638, fork 2 kids
parent 5638, forked kid 5639
kid: 5639
parent 5638, forked kid 5640
parent 5638, waiting 5s
kid: 5640
parent 5638 exit, closing sockets
kid: 5640, parent gone, exiting
kid: 5639, parent gone, exiting
tim@mint:~$ 

Killing a processgroup works, but don't forget the parent can be killed alone too. Assuming child processes have an event loop, they can check the parent socket that was created in a socketpair prior doing the fork() for validness. In fact, select() cleanly exits when the parent socket is gone, all that needs to be done is to check the socket.

E.g.:

use strict; use warnings;
use Socket;

$SIG{CHLD} = sub {};

socketpair(my $p, my $c, AF_UNIX, SOCK_STREAM, PF_UNSPEC) or die $!;

print "parent $, fork 2 kids\n";
for (0..1){
    my $kid = fork();
    unless($kid){
        child_loop($p, $c);
        exit; 
    }
    print "parent $, forked kid $kid\n";
}

print "parent $, waiting 5s\n";
sleep 5;
print "parent $ exit, closing sockets\n";

sub child_loop {
    my ($p_s, $c_s) = @_;
    print "kid: $\n";
    close($c_s);
    my $rin = '';
    vec($rin, fileno($p_s), 1) = 1;
    while(1){
        select my $rout = $rin, undef, undef, undef;
        if(vec($rout, fileno($p_s), 1)){
            print "kid: $, parent gone, exiting\n";
            last;
        }
    }
}

Runs like this:

tim@mint:~$ perl ~/abc.pl
parent 5638, fork 2 kids
parent 5638, forked kid 5639
kid: 5639
parent 5638, forked kid 5640
parent 5638, waiting 5s
kid: 5640
parent 5638 exit, closing sockets
kid: 5640, parent gone, exiting
kid: 5639, parent gone, exiting
tim@mint:~$ 
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文