Perl:当子/管道的文件句柄被别名时,关闭子进程失败

Posted

技术标签:

【中文标题】Perl:当子/管道的文件句柄被别名时,关闭子进程失败【英文标题】:Perl: closing child process fails when file handle to child/pipe is aliased 【发布时间】:2020-07-29 22:54:32 【问题描述】:

我已经阅读了有关 open、close 和 IPC 的所有 perl 文档,并阅读了许多相关的论坛主题,但仍然无法弄清楚我做错了什么。首先,我创建了这个简单的分叉进程来证明我正确地进行了打开处理:

#!/usr/bin/perl
use IO::Handle;
my $pid = open(CHILD, "|-");
if ($pid == 0) 
    # child:
    while (<STDIN>)  # keeps reading until parent closes it's end of the pipe
        chomp $_;
        print "child: $_\n";
    

else 
    # parent:
    for my $line (1..3) 
        print CHILD "$line\n";
    
    my $success = close CHILD;
    print "parent: success: $success, \$!: $!, \$?: $?\n";

正如预期的那样,输出是:

child: 1
child: 2
child: 3
parent: success: 1, $!: Illegal seek, $?: 0

成功值为真;所以我只是忽略了“非法寻求”,因为它不相关,据说。但是,当我将 STDOUT 别名为管道(以避免需要显式打印到 CHILD)然后将 STDOUT 恢复为其原始值时,如下所示,关闭的 CHILD 不再起作用(即使 STDOUT 不再指向它) :

#!/usr/bin/perl
use IO::Handle;
my $pid = open(CHILD, "|-");
if ($pid == 0) 
    # child:
    while (<STDIN>)  # keeps reading until parent closes it's end of the pipe
        chomp $_;
        print "child: $_\n";
    

else 
    # parent:
    open(ORIG_STDOUT, ">&STDOUT") or die "Unable to dup STDOUT: $!";
    open(STDOUT, ">&=CHILD")      or die "Unable to alias CHILD: $!";
    for my $line (1..3) 
        print "$line\n";
    
    open(STDOUT, ">&=ORIG_STDOUT") or die "Unable to alias ORIG_STDOUT: $!";
    my $success = close CHILD;
    print "parent: success: $success, \$!: $!, \$?: $?\n";

输出是这样的:

child: 1
child: 2
child: 3
parent: success: , $!: Illegal seek, $?: -1

success 值不再为 true,这意味着关闭管道时出错,或者 CHILD 返回了一个非零值。 $ 的 -1 值?表示等待系统调用由于某种原因失败,我不确定“非法搜索”是什么意思,但事实是 $!不为零,进一步表明不仅仅是 CHILD 进程返回一个非零代码使关闭 CHILD 返回错误,而是关闭过程中存在某种错误。我在这里错过了什么?

【问题讨论】:

CHILD 是特殊的,因为它还充当孩子的句柄。使用&gt;&amp;= 似乎把事情搞砸了。复制句柄 (&gt;&amp;) 而不是复制其 fd (&gt;&amp;=) 作品 【参考方案1】:

至少有两个问题。首先,您对 open 的调用实际上在两个程序中都失败了,或者更确切地说,如果您在第一次调用之后附加了 check 子句,它就会失败。

#!/usr/bin/perl
use IO::Handle;

my $pid = open(CHILD, "|-") or die("A descriptive message");

程序在执行时立即崩溃。然而,由于开放模式参数中的管道意味着对fork 的调用,这意味着应该有两个独立的进程相当于总输出。可以理解的是,您假设调用成功,因为在那之后的代码正在执行。

然而,对open 的调用失败的原因是多方面的,而且很有趣。你传入了一个无效的文件描述符,但是如果子进程可以存活足够长的时间来尝试复制文件描述符,就会发生一些非常有趣的事情。在对open 的调用中使用"&gt;&amp;=" 而不是简单的"&gt;&amp;" 将对内核的底层调用从dup 更改为fdopen;第一个返回一个文件descriptor,第二个返回一个文件handle。 (在 C 中:intFILE*

TL;DR:您没有复制文件描述符,因为您说您不想复制文件描述符,并且查找错误是尝试读取 1 + n 字节超过 0 的逻辑结果您从调用open 收到的空句柄的总字节数。

【讨论】:

以上是关于Perl:当子/管道的文件句柄被别名时,关闭子进程失败的主要内容,如果未能解决你的问题,请参考以下文章

当子进程和父进程在 Perl 中写入同一个日志文件时进程卡住(在 Windows 中)

关闭其它进程占用的文件句柄

Perl 缓冲区刷新

Perl:关闭信号处理程序中的子进程管道挂起?

管道被 Perl 中的子进程阻塞

Perl基础命令---文件句柄基础