从子进程到控制台的fork()和STDOUT / STDERR

时间:2010-10-06 20:15:33

标签: linux perl console fork

我正在编写一个程序,它会分叉多个子进程,我希望所有这些子进程能够将行写入STDERRSTDOUT,而输出不会出现乱码。我没有做任何花哨的事情,只是发出以新行结束的行(至少在我的理解中,这将是Linux的原子操作)。来自perlfaq,它说:

  

主进程和后台进程(“子进程”)共​​享相同的STDIN,STDOUT和STDERR文件句柄。如果两者都试图立即访问它们,可能会发生奇怪的事情。您可能想为孩子关闭或重新打开这些。您可以通过打开管道来解决这个问题(请参阅打开),但在某些系统上,这意味着子进程不能超过父进程。

它说我应该为孩子“关闭或重新打开”这些文件句柄。关闭很简单,但“重新打开”是什么意思?我在我的子进程中尝试了类似这样的东西并且它不起作用(输出仍然出现乱码):

open(SAVED_STDERR, '>&', \*STDERR) or die "Could not create copy of STDERR: $!";
close(STDERR);

# re-open STDERR
open(STDERR, '>&SAVED_STDERR') or die "Could not re-open STDERR: $!";

那么,我做错了什么?它所暗示的管道示例是什么样的?有没有更好的方法将多个进程的输出协调到控制台?

2 个答案:

答案 0 :(得分:9)

对于STDOUT和STDIN,写入文件句柄 NOT 原子。像fifos这样的事情有特殊情况,但这不是你目前的情况。

当它说重新打开STDOUT意味着“创建一个新的STDOUT实例”这个新实例与父实例不同。这是你可以在你的系统上打开多个终端而不是让所有STDOUT到达同一个地方的方法。

管道解决方案会通过管道(如shell中的|)将子节点连接到父节点,并且您需要将父节点从管道中读出并复用输出本身。父级将负责从管道读取并确保它不会同时交错管道的输出和指定给父级STDOUT的输出。有一个示例和管道here

一个snippit:

use IO::Handle;

pipe(PARENTREAD, PARENTWRITE);
pipe(CHILDREAD, CHILDWRITE);

PARENTWRITE->autoflush(1);
CHILDWRITE->autoflush(1);

if ($child = fork) { # Parent code
   chomp($result = <PARENTREAD>);
   print "Got a value of $result from child\n";
   waitpid($child,0);
} else {
   print PARENTWRITE "FROM CHILD\n";
   exit;
}

查看子进程如何不写入stdout,而是使用管道向父进程发送消息,父进程使用stdout进行编写。一定要看看,因为我省略了诸如关闭不需要的文件句柄之类的东西。

答案 1 :(得分:1)

虽然这对你的粗暴无济于事,但我花了很长时间才找到启动子进程的方法,该进程可以由父进程写入,并将子进程的stderr和stdout直接发送给屏幕(这解决了在尝试从两个不同的FD读取时可能遇到的令人讨厌的阻塞问题,而不使用像select这样的东西。)

一旦我弄明白,解决方案就是微不足道的

my $pid = open3(*CHLD_IN, ">&STDERR", ">&STDOUT", 'some child program');
# write to child
print CHLD_IN "some message";
close(CHLD_IN);
waitpid($pid, 0);

“some child program”中的所有内容都会被发送到stdout / stderr,你可以通过写入CHLD_IN来简单地抽取数据,并相信如果孩子的缓冲区填满它会阻止它。对于父程序的调用者,它只是看起来像stderr / stdout。