6

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

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

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

4 回答 4

2

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

事实证明,除非您另有说明,否则 perl 会自动关闭/使所有 fd > 2 无效。

将以下标志添加到 FD 可解决此问题(其中 READ 是此处的句柄,而不是 STDIN):

my $flags = fcntl(READ, F_GETFD, 0);
fcntl(READ, F_SETFD, $flags & ~FD_CLOEXEC);
于 2013-04-18T18:41:04.730 回答
2

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

Perl$^F控制这种行为。在调用 IO::Pipe->new之前尝试这样的操作:

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

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

于 2013-04-18T19:40:15.010 回答
0

Rodrigo,我可以告诉你,当你执行到 c 应用程序时,你的文件描述符不再有效。
请注意,我只是说它是无效的,但它仍然存在于环境变量中。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。
这就是为什么你永远不会看到“hello world”语句的原因。

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

$ENV{'MY_FD'} = 0;

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

于 2013-04-18T10:03:17.747 回答
0

您的程序失败了,因为exec 调用了另一个程序并且永远不会返回。它根本不是为与另一个进程通信而设计的。

您可能根据IO::Pipe文档编写了上述代码,其中显示“ARGS 已传递给 exec”。不过,这不是它的意思。 IO::Pipe用于 Perl 脚本中的两个进程之间的通信,它们由fork. 它们意味着新流程的执行,而不是exec在您自己的代码中调用。

编辑:对于单向通信,您只需要open一个管道

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

print {$prog} "Hello, world!";
于 2013-04-18T09:12:58.760 回答