2017-08-09 67 views
3

我从主控主机到从属主机执行ZFS远程复制,其中我有一个在主控主机上运行的Perl脚本。如何避免<defunct>进程?

对于每个文件系统,ssh到远程主机并在监听模式下启动mbuffer,然后脚本继续并发送数据。成功时,mbuffer应该自行退出。

问题

这是相当困难的开始mbuffer通过ssh远程主机上,然后可以继续在脚本。我最终做了你可以在下面看到的内容。

问题是,在脚本退出之前,它会为每个文件系统处理一个文件系统。

问题

有可能避免在<defunct>流程?

sub mbuffer { 
    my ($id, $zfsPath) = @_; 

    my $m = join(' ', $mbuffer, '-I', $::c{port}); 
    my $z = join(' ', $zfs, 'receive', , $zfsPath); 
    my $c = shellQuote($ssh, $::c{slaves}{$id}, join('|', $m, $z)); 

    my $pm = Parallel::ForkManager->new(1); 
    my $pid = $pm->start; 
    if (!$pid) { 
     no warnings; # fixes "exec" not working 
     exec($c); 
     $pm->finish; 
    } 

    sleep 3; # wait for mbuffer to listen 

    return $pid; 
} 
+5

父进程必须始终在其子进程上调用“wait”(或其一个变体),以便让内核知道终止的子进程可以清理。 [这个问题](https://stackoverflow.com/questions/9164316/c-fork-without-wait-defuncts-execl)有一些答案,可能会指出你在正确的方向。 – Thomas

+3

最快的解决方法是设置'$ SIG {CHLD} ='IGNORE''。见['perldoc -f fork'](http://metacpan.org/pod/perlfunc#fork) – mob

+2

一个不存在的进程或僵尸进程是一个终止进程,没有它的父母调用'wait'就可以了。因此,内核保留终止的子进程的条目,所以当父进程调用“wait”时,它会返回所需的信息。为了避免僵尸,父进程需要等待其子进程。 – direprobs

回答

3

当你创建一个进程时,它会一直存在,直到它的父节点获得它为止。 (如果其父母先退出,它将自动获得。)一个过程可以使用waitwaitpid收获其子女。在创建孩子之前,它还可以通过使用local $SIG{CHLD} = 'IGNORE';自动获得孩子的收入。


请注意Parallel :: ForkManager不是启动一个孩子的工作的正确工具。这不是它产生一个工人的目的。

use String::ShellQuote qw(shell_quote); 

sub mbuffer { 
    my ($id, $zfsPath) = @_; 

    my $mbuffer_cmd = shell_quote($mbuffer, '-I', $::c{port}); 
    my $zfs_cmd  = shell_quote($zfs, 'receive', $zfsPath); 
    my $remote_cmd = "$mbuffer_cmd | $zfs_cmd"; 
    my $local_cmd = shell_quote($ssh, $::c{slaves}{$id}, $remote_cmd); 

    # open3 will close this handle. 
    # open3 doesn't deal well with lexical handles. 
    open(local *CHILD_STDIN, '<', '/dev/null') or die $!;  

    return open3('<&CHILD_STDIN', '>&STDOUT', '>&STDERR', $local_cmd); 
} 

IPC :: Open3是相当低的水平,但它是最接近你现有的代码。启动进程的更好方式包括IPC :: Run3和IPC :: Run。

1

其中之一,没有理由使用P::FM与一个进程。此外,由于您放弃了对流程管理的更好控制,因此它在这里是不利的。

但是这里的直接错误是在使用exec;这篇文章仅解决这个问题。

exec调用将替换与另一个程序中的进程和永不返回。因此exec之后的子代码不会运行(请参阅文档)。因此$pm->finish被挂起,子进程永远不会被获得,操作系统将它的信息保存在进程表中,所以有一个不存在的/僵尸。

下面是使用exec直接

my $cmd = '...'; 

my $pid = fork // die "Can't fork: $!"; 

if ($pid == 0) { 
    exec $cmd; 
    die "exec shouldn't return: $!"; 
} 
my $gone = waitpid $pid, 0; 

if ($gone > 0) { say "Child $gone exited with $?" } 
elsif ($gone < 0) { say "No $pid process ($gone), reaped already?" } 
else    { say "Process $pid still running?" } 

这里的孩子继承父标准流断火另一个程序的基本途径。此外,在某些情况下,错误报告很粗糙(不精确),请参阅ikegami的评论。

一个更详细和忠实的替代品是你在ikegami的answer

+0

当'exec'失败时,这看起来像是孩子成功启动了。这就是为什么我推荐'open3'而不是(或更好的)。 – ikegami

+0

@ikegami这是我没有得到 - 这样我看到它是,如果'叉'失败有一个消息,而如果我得到'死',那么孩子_was_成功创建。或者你的意思是'waitpid'不会揭示问题出在哪里?我怀疑我可能会在这里失去东西。 (顺便说一句,我不是故意说这是一个完整而强大的方法,我希望帖子明确)。谢谢你的评论。 – zdim

+0

你的方式无法区分无法启动'ssh'和'ssh'做'exit(2)'。这不是Perl的“系统”工作原理。这不是C的“系统”工程。这不是'bash'的工作方式。在所有这些情况下,'exec'失败和准备'exec'的错误被认为是启动程序失败,而不是启动程序返回的错误。 (对于Perl的'system',它会返回'$?= -1'并设置'$!'。) – ikegami