Perl,如何为我的exec'd孩子创建一个管道?

时间:2013-04-18 08:42:22

标签: c perl unix pipe

我正在尝试使用管道(单向)将数据从我的perl脚本传递到我的c程序。 我需要找到一种方法来做到这一点,而不会弄乱子程序STDIN或STDOUT,所以我尝试创建一个新句柄并传递fd。

我创建了2个IO :: Handles并创建了一个管道。我写到管道的一端,并尝试将管道另一端的文件描述符传递给正在执行的子程序。我通过设置ENV变量来传递文件描述符。为什么这不起作用? (它没有打印出“你好世界”)。据我所知,文件描述符和管道在执行时由子进行继承。

Perl脚本:

#!/opt/local/bin/perl
use IO::Pipe;
use IO::Handle;

my $reader = IO::Handle->new();
my $writer = IO::Handle->new();
$reader->autoflush(1);
$writer->autoflush(1);
my $pipe = IO::Pipe->new($reader, $writer);
print $writer "hello world";
my $fh = $reader->fileno;
$ENV{'MY_FD'} = $fh;
exec('./child') or print "error opening app\n";
# No more code after this since exec replaces the current process

C程序,app.c(编译为gcc app.c -o child):

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

int main(int argc, char ** argv) {
  int fd = atoi(getenv("MY_FD"));
  char buf[12];
  read(fd, buf, 11);
  buf[11] = '\0';
  printf("fd: %d\n", fd);
  printf("message: %s\n", buf);
}

输出:

fd: 3
message:

消息永远不会通过管道传递给C程序。有什么建议吗?

4 个答案:

答案 0 :(得分:2)

您的管道文件描述符设置为FD_CLOEXEC,因此在exec()时关闭。

Perl的$^F控制此行为。尝试这样的事情,之前你调用IO :: Pipe-&gt; new:

$^F = 10;  # Assumes we don't already have a zillion FDs open

或者,您可以在创建管道后自己Fcntl清除FD_CLOEXEC标志。

答案 1 :(得分:1)

我找到了解决方案。有些人说exec是不可能的,它不会看到管道或文件描述符,但这不正确。

原来perl关闭/无效所有fd&gt;除非你另有说明,否则自动2。

将以下标志添加到FD修复此问题(其中READ是此处的句柄,NOT STDIN):

my $flags = fcntl(READ, F_GETFD, 0);
fcntl(READ, F_SETFD, $flags & ~FD_CLOEXEC);

答案 2 :(得分:0)

您的计划失败,因为exec calls another program and never returns。它不是为与其他进程的通信而设计的。

您可能基于IO::Pipe documentation编写了上述代码,其中说“ARGS传递给exec”。但这并不意味着什么。 IO::Pipe用于在Perl脚本中的两个进程之间进行通信,这两个进程由fork创建。它们意味着执行新流程,而不是在您自己的代码中调用exec

用于单向通信的

修改all you need is open with a pipe

open my $prog, '|-', './child' or die "can't run program: $!";

print {$prog} "Hello, world!";

答案 3 :(得分:0)

罗德里戈,我可以告诉你,当你执行c应用程序时,你的文件描述符不再有效 请注意,我只是说它是INVALID,但它仍然存在于环境变量中。 FD = 3将继续存在,直到整个过程结束 你可以通过fcntl查看fd。代码列在下面

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>

int main(int argc, char ** argv) {
  int fd = atoi(getenv("MY_FD"));
  char buf[12];
  read(fd, buf, 11);
  buf[11] = '\0';
  printf("fd: %d, if fd still valid: %d\n", fd, fcntl(fd, F_GETFD));
  printf("strlen %d\n", (int)strlen(buf));
  printf("message: %s\n", buf);
}

你可以看到MY_FD = 3总是在ENV中,因为进程不会破坏它自己,所以你可以得到fd为3.但是,这个文件描述符无效。所以fcntl(fd,F_GETFD)的结果为-1,从fd读取的长度为0。 这就是为什么你永远不会看到“你好世界”的句子。

还有一件事,@ dan1111是对的,但您不需要打开新管道,因为您已经这样做了。
您只需设置MY_FD = 0,就像

一样
$ENV{'MY_FD'} = 0;

STDIN / OUT是另一个始终存在的独立进程,因此当你的perl应用程序执行到c app时管道不会崩溃。这就是为什么你可以阅读你在app中输入的内容。
如果您的要求是从另一个文件hanle写入,请尝试使该文件处理一个独立的进程并始终存在,就像STDIN一样。