简体   繁体   English

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

[英]Perl, how do I create a pipe to my exec'd child?

I am trying to pass data from my perl script to my c program using a pipe (uni-directional). 我试图使用管道(单向)将数据从我的perl脚本传递到我的c程序。 I need to find a way to to do this without messing with the child programs STDIN or STDOUT, so I try creating a new handle and passing the fd. 我需要找到一种方法来做到这一点,而不会弄乱子程序STDIN或STDOUT,所以我尝试创建一个新句柄并传递fd。

I create 2 IO::Handles and create a pipe. 我创建了2个IO :: Handles并创建了一个管道。 I write to one end of the pipe and attempt to pass the File descriptor of the other end of the pipe to my child program that is being execed. 我写到管道的一端,并尝试将管道另一端的文件描述符传递给正在执行的子程序。 I pass the file descriptor by setting an ENV variable. 我通过设置ENV变量来传递文件描述符。 Why does this not work? 为什么这不起作用? (It does not print out 'hello world'). (它没有打印出“你好世界”)。 As far as I know, file descriptors and pipes are inherited by the child when exec'd. 据我所知,文件描述符和管道在执行时由子进程继承。

Perl script: 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 Program, app.c (Compiled with gcc app.c -o child ): 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);
}

Output: 输出:

fd: 3
message:

The message is never passed through the pipe to the C program. 消息永远不会通过管道传递给C程序。 Any suggestions? 有什么建议?

Your pipe file descriptors are set FD_CLOEXEC, and so are closed upon exec(). 您的管道文件描述符设置为FD_CLOEXEC,因此在exec()时关闭。

Perl's $^F controls this behavior. Perl的$^F控制此行为。 Try something like this, before you call IO::Pipe->new: 调用IO :: Pipe-> new 之前尝试这样的事情:

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

Alternatively, you can with Fcntl clear the FD_CLOEXEC flag yourself after creating the pipe. 或者,您可以在创建管道后使用Fcntl自行清除FD_CLOEXEC标志。

I found the solution. 我找到了解决方案。 Some people said that it was not possible with exec, that it would not see pipes or file descriptors, but that was not correct. 有些人说exec是不可能的,它不会看到管道或文件描述符,但这是不正确的。

Turns out that perl closes/invalidates all fd > 2 automatically unless you say otherwise. 事实证明perl会自动关闭/无效所有fd> 2,除非你另有说明。

Adding the following flags to the FD fixes this problem (where READ is the handle here, NOT STDIN): 将以下标志添加到FD修复此问题(其中READ是此处的句柄,NOT STDIN):

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

Your program is failing because exec calls another program and never returns . 您的程序失败,因为exec 调用另一个程序并且永远不会返回 It isn't designed for communication with another process at all. 它不是为与其他进程的通信而设计的。

You probably wrote the above code based on the IO::Pipe documentation , which says "ARGS are passed to exec". 您可能基于IO::Pipe文档编写了上述代码,其中说“ARGS传递给exec”。 That isn't what it means, though. 但这并不意味着什么。 IO::Pipe is for communication between two processes within your Perl script, which are created by fork . IO::Pipe用于Perl脚本中的两个进程之间的通信,这两个进程由fork创建。 They mean the execution of the new process, rather than a call to exec in your own code. 它们意味着执行新进程,而不是在您自己的代码中调用exec

Edit: for one-directional communication, all you need is open with a pipe : 编辑:对于单向通信, 您只需open一个管道

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

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

Rodrigo, I can tell you that your file descriptor is no longer valid when you exec into the c app. 罗德里戈,我可以告诉你,当你执行c应用程序时,你的文件描述符不再有效。
Please be aware that I just say it is INVALID, but it still exists in the environment variables. 请注意,我只是说它是INVALID,但它仍然存在于环境变量中。 The FD=3 will continue existing until the whole process ends. FD = 3将继续存在,直到整个过程结束。
You can check the fd by fcntl. 你可以通过fcntl查看fd。 The code is listing below 代码列在下面

#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);
}

You can see that MY_FD=3 will always in ENV as the process doesn't destroy itself, so you can get fd as 3. But, this file descriptor has been invalid. 您可以看到MY_FD = 3将始终在ENV中,因为进程不会自行销毁,因此您可以将fd设置为3.但是,此文件描述符无效。 so the result of fcntl(fd, F_GETFD) will be -1, and the length you read from fd will be 0. 所以fcntl(fd,F_GETFD)的结果为-1,从fd读取的长度为0。
That's why you will never see the "hello world" sentence. 这就是为什么你永远不会看到“你好世界”的句子。

One more thing, @dan1111 is right, but you don't need to open a new pipe, as you have already done so. 还有一件事,@ dan1111是对的,但你不需要打开一个新的管道,因为你已经这样做了。
All you need to is just set MY_FD=0, like 你需要的只是设置MY_FD = 0,就像

$ENV{'MY_FD'} = 0;

The STDIN/OUT is another independent process that always exists, so the pipe will not broken down when your perl app exec into c app. STDIN / OUT是另一个始终存在的独立进程,因此当你的perl应用程序执行到c app时管道不会崩溃。 That's why you can read from what you input in app. 这就是为什么你可以阅读你在应用程序中输入的内容。
If your requirement is writing from another file hanle, please try to make that file handle an independent process and always exist, just like STDIN. 如果您的要求是从另一个文件写入,请尝试使该文件处理一个独立的进程并始终存在,就像STDIN一样。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM