使用非阻塞管道 IO 的 C fork/exec

发布于 2024-09-11 18:28:27 字数 1357 浏览 12 评论 0 原文

这似乎是一件相当常见的事情,我已经成功地自学了使它工作所需的一切,除了我现在有一个问题,它无法解决我的故障排除问题。

int nonBlockingPOpen(char *const argv[]){
    int inpipe;
    pid_t pid;
    /* open both ends of pipe nonblockingly */
    pid = fork();

    switch(pid){
        case 0:         /*child*/
            sleep(1); /*child should open after parent has open for reading*/

            /*redirect stdout to opened pipe*/
            int outpipe = open("./fifo", O_WRONLY);
            /*SHOULD BLOCK UNTIL MAIN PROCESS OPENS FOR WRITING*/
            dup2(outpipe, 1);
            fcntl(1, F_SETFL, fcntl(1, F_GETFL) | O_NONBLOCK);

            printf("HELLO WORLD I AM A CHILD PROCESS\n");
            /*This seems to be written to the pipe immediately, blocking or not.*/
            execvp(*argv, argv);
            /*All output from this program, which outputs "one" sleeps for 1 second
             *outputs "two" sleeps for a second, etc, is captured only after the
             *exec'd program exits!
             */
            break;

        default:        /*parent*/
            inpipe = open("./fifo", O_RDONLY | O_NONBLOCK);
            sleep(2);
            /*no need to do anything special here*/
            break;
    }

    return inpipe;
}

为什么子进程不会在每次生成一行时将其标准输出写入管道?我在 execvp 或 dup2 的工作方式中缺少什么吗?我知道我处理这一切的方法有点奇怪,但我找不到另一种方法来以编程方式捕获闭源二进制文件的输出。

This seems to be a fairly common thing to do, and I've managed to teach myself everything that I need to make it work, except that I now have a single problem, which is defying my troubleshooting.

int nonBlockingPOpen(char *const argv[]){
    int inpipe;
    pid_t pid;
    /* open both ends of pipe nonblockingly */
    pid = fork();

    switch(pid){
        case 0:         /*child*/
            sleep(1); /*child should open after parent has open for reading*/

            /*redirect stdout to opened pipe*/
            int outpipe = open("./fifo", O_WRONLY);
            /*SHOULD BLOCK UNTIL MAIN PROCESS OPENS FOR WRITING*/
            dup2(outpipe, 1);
            fcntl(1, F_SETFL, fcntl(1, F_GETFL) | O_NONBLOCK);

            printf("HELLO WORLD I AM A CHILD PROCESS\n");
            /*This seems to be written to the pipe immediately, blocking or not.*/
            execvp(*argv, argv);
            /*All output from this program, which outputs "one" sleeps for 1 second
             *outputs "two" sleeps for a second, etc, is captured only after the
             *exec'd program exits!
             */
            break;

        default:        /*parent*/
            inpipe = open("./fifo", O_RDONLY | O_NONBLOCK);
            sleep(2);
            /*no need to do anything special here*/
            break;
    }

    return inpipe;
}

Why won't the child process write its stdout to the pipe each time a line is generated? Is there something I'm missing in the way execvp or dup2 work? I'm aware that my approach to all this is a bit strange, but I can't find another way to capture output of closed-source binaries programatically.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

帅气尐潴 2024-09-18 18:28:27

我猜你只会在退出后获得 exec'd 程序的输出,因为它不会 每条消息后刷新。如果是这样,您从外部无能为力。

我不太确定这与您问题中阻塞和非阻塞 I/O 之间的选择有何关系。非阻塞写入可能完全或部分失败:调用不会阻塞程序直到管道中有可用空间,而是立即返回并表示它无法写入应有的所有内容。非阻塞 I/O 既不会使缓冲区变大,也不会强制刷新输出,并且某些程序可能对其支持不佳。

您不能强制刷新正在执行的纯二进制程序。如果您认为非阻塞 I/O 是该问题的解决方案,那么抱歉,但恐怕它是相当正交的。

编辑:好吧,如果 exec'd 程序仅使用 libc 提供的缓冲(不实现自己的)并且是动态链接的,则可以通过将其链接到刷新每次写入的修改后的 libc 来强制它刷新。这将是一个孤注一掷的措施。仅当其他一切都失败时才尝试。

I would guess you only get the exec'd program's output after it exits because it does not flush after each message. If so, there is nothing you can do from the outside.

I am not quite sure how this is supposed to relate to the choice between blocking and nonblocking I/O in your question. A non-blocking write may fail completely or partially: instead of blocking the program until room is available in the pipe, the call returns immediately and says that it was not able to write everything it should have. Non-blocking I/O neither makes the buffer larger nor forces output to be flushed, and it may be badly supported by some programs.

You cannot force the binary-only program that you are exec'ing to flush. If you thought that non-blocking I/O was a solution to that problem, sorry, but I'm afraid it is quite orthogonal.

EDIT: Well, if the exec'd program only uses the buffering provided by libc (does not implement its own) and is dynamically linked, you could force it to flush by linking it against a modified libc that flushes every write. This would be a desperate measure. to try only if everything else failed.

荆棘i 2024-09-18 18:28:27

当进程启动时(在示例中通过 execvp() ),标准输出的行为取决于输出设备是否是终端。如果不是(并且 FIFO 不是终端),则输出将被完全缓冲,而不是行缓冲。你对此无能为力; (标准)C 库可以做到这一点。

如果您确实想让它以行缓冲方式工作,那么您必须为程序提供一个伪终端作为其标准输出。这就进入了有趣的领域——伪终端或 pty 并不是那么容易处理。有关 POSIX 函数,请参阅:

When a process is started (via execvp() in your example), the behaviour of standard output depends on whether the output device is a terminal or not. If it is not (and a FIFO is not a terminal), then the output will be fully buffered, rather than line buffered. There is nothing you can do about that; the (Standard) C library does that.

If you really want to make it work line buffered, then you will have to provide the program with a pseudo-terminal as its standard output. That gets into interesting realms - pseudo-terminals or ptys are not all that easy to handle. For the POSIX functions, see:

  • grantpt() - grant access to the slave pseudo-terminal device
  • posix_openpt() - open a pseudo-terminal device
  • ptsname() - get name of the slave pseudo-terminal device
  • unlockpt() - unlock a pseudo-terminal master/slave pair
不疑不惑不回忆 2024-09-18 18:28:27

为什么子进程不会在每次生成一行时将其标准输出写入管道?

你怎么知道的?您甚至不尝试读取 fifo 的输出。

注意,根据文件名,我假设您正在使用 fifo。或者它是一个普通文件?

子进程中的小错误:在 dup2() 后,您需要 close(outpipe) 。

fcntl(1, F_SETFL, fcntl(1, F_GETFL) | O_NONBLOCK);

根据您执行的程序(),您可能会丢失一些输出或导致程序失败,因为现在写入 stdout 可能会因 EWOULDBLOCK 而失败。

IIRC fifos 具有与管道相同的缓冲区大小。每个 POSIX 最小值为 512 字节,通常为 4K 或 8K。

您可能想解释为什么您需要它。与阻塞 IO 相比,非阻塞 IO 具有不同的语义,除非您的子进程预计您会遇到各种问题。

printf("你好世界,我是一个子进程\n");

stdout 是缓冲的,之后我就会有 fflush(stdout) 。 (找不到 exec() 本身是否会刷新标准输出的文档。)

我在 execvp 或 dup2 的工作方式中遗漏了什么吗?我知道我处理这一切的方法有点奇怪,但我找不到另一种方法来以编程方式捕获闭源二进制文件的输出。

我不会玩弄非阻塞 IO - 并让它保持阻塞模式。

我会使用 pipeline() 而不是 fifo。 Linux 的 man pipeline 有一个使用 fork() 的方便示例。

否则,这是一种很正常的做法。

Why won't the child process write its stdout to the pipe each time a line is generated?

How do you know that? You do not even try to read the output from the fifo.

N.B. by the file name I presume that you are using the fifo. Or is it a plain file?

And the minor bug in the child: after dup2(), you need to close(outpipe).

fcntl(1, F_SETFL, fcntl(1, F_GETFL) | O_NONBLOCK);

Depending on what program you exec(), you might either lose some output or cause the program to fail since write to stdout now might fail with EWOULDBLOCK.

IIRC fifos has the same buffer size as pipes. Per POSIX minimum is 512 bytes, commonly 4K or 8K.

You probably want to explain why you need that at all. Non-blocking IO has different semantics compared to blocking IO and unless your child process expects that you will run into various problems.

printf("HELLO WORLD I AM A CHILD PROCESS\n");

stdout is buffered, I would have after that fflush(stdout). (Can't find documentation whether exec() on its own would flush stdout or not.)

Is there something I'm missing in the way execvp or dup2 work? I'm aware that my approach to all this is a bit strange, but I can't find another way to capture output of closed-source binaries programatically.

I wouldn't toy with non-blocking IO - and leave it as it is in blocking mode.

And I would use pipe() instead of the fifo. Linux's man pipe has a convenient example with the fork().

Otherwise, that is a pretty normal practice.

箹锭⒈辈孓 2024-09-18 18:28:27

sleep()保证父级将首先打开管道 - 如Dummy00001 表示,您应该使用 pipe() 管道,而不是命名管道。您还应该检查 execvp()fork() 是否失败,并且您不应该将子端设置为非阻塞 - 这是子进程的决定使.

int nonBlockingPOpen(char *const argv[])
{
    int childpipe[2];
    pid_t pid;

    pipe(childpipe);
    pid = fork();

    if (pid == 0)
    {
        /*child*/

        /*redirect stdout to opened pipe*/
        dup2(childpipe[1], 1);

        /* close leftover pipe file descriptors */
        close(childpipe[0]);
        close(childpipe[1]);

        execvp(*argv, argv);

        /* Only reached if execvp fails */
        perror("execvp");
        exit(1);
    }

    /*parent*/

    /* Close leftover pipe file descriptor */
    close(childpipe[1]);

    /* Check for fork() failing */
    if (pid < 0)
    {
         close(childpipe[0]);
         return -1;
    }

    /* Set file descriptor non-blocking */
    fcntl(childpipe[0], F_SETFL, fcntl(childpipe[0], F_GETFL) | O_NONBLOCK);

    return childpipe[0];
}

The sleep()s do not guarantee that the parent will open the pipe first - as Dummy00001 says, you should be using a pipe() pipe, not a named pipe. You should also check for execvp() and fork() failing, and you shouldn't be setting the child side to non-blocking - that's a decision for the child process to make.

int nonBlockingPOpen(char *const argv[])
{
    int childpipe[2];
    pid_t pid;

    pipe(childpipe);
    pid = fork();

    if (pid == 0)
    {
        /*child*/

        /*redirect stdout to opened pipe*/
        dup2(childpipe[1], 1);

        /* close leftover pipe file descriptors */
        close(childpipe[0]);
        close(childpipe[1]);

        execvp(*argv, argv);

        /* Only reached if execvp fails */
        perror("execvp");
        exit(1);
    }

    /*parent*/

    /* Close leftover pipe file descriptor */
    close(childpipe[1]);

    /* Check for fork() failing */
    if (pid < 0)
    {
         close(childpipe[0]);
         return -1;
    }

    /* Set file descriptor non-blocking */
    fcntl(childpipe[0], F_SETFL, fcntl(childpipe[0], F_GETFL) | O_NONBLOCK);

    return childpipe[0];
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文