【问题标题】:C fork/exec with non-blocking pipe IO具有非阻塞管道 IO 的 C fork/exec
【发布时间】:2011-03-20 14:18:25
【问题描述】:

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

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 的工作方式有什么我遗漏的吗?我知道我处理这一切的方法有点奇怪,但我找不到另一种方法来以编程方式捕获闭源二进制文件的输出。

【问题讨论】:

  • 我刚刚找到homepage.ntlworld.com/jonathan.deboynepollard/FGA/…这个页面,这表明我不应该尝试向子进程传递非阻塞描述符,但没有列出其中的原因似乎适用于我正在做的事情。我确实意识到,如果监听进程与管道断开连接(在子进程中生成 EAGAIN),我正在运行的程序可能会崩溃,但这并不困扰我。

标签: c pipe fork stdout nonblocking


【解决方案1】:

我猜你只会在执行程序退出后得到它的输出,因为它不会在每条消息之后flush。如果是这样,你就无法从外面做任何事情。

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

您不能强制您正在执行的纯二进制程序刷新。如果您认为非阻塞 I/O 可以解决该问题,对不起,但恐怕它是完全正交的。

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

【讨论】:

  • 我接受您的解决方案,并用我自己的解决方案进行扩充。我显然没有受过在这种情况下运行的缓冲区的全部数量和类型的教育。 pixelbeat.org/programming/stdio_buffering 让我非常满意。您说失败的原因是在程序终止之前没有刷新行缓冲区是正确的,但是链接页面详细说明了许多可能的解决方案来更改 stdio 使用的缓冲形式。我现在使用的方法只是在我的命令之前调用 unbuffer 。我不确定它的效率如何,但它确实有效!
【解决方案2】:

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

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

【讨论】:

    【解决方案3】:

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

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

    注意通过文件名,我认为您使用的是fifo。还是普通文件?

    还有孩子的小bug:dup2()之后,你需要close(outpipe)

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

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

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

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

    printf("HELLO WORLD 我是一个子进程\n");

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

    在 execvp 或 dup2 的工作方式中我是否缺少某些东西?我知道我处理这一切的方法有点奇怪,但我找不到另一种方法来以编程方式捕获闭源二进制文件的输出。

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

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

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

    【讨论】:

    • 对不起 - 我没有在这里包含整个程序,只是打开管道进行读取的功能。该文件确实是一个名为 fifo 的文件。问题不在于该代码中的输出,而是 exec 块将启动代码(服务器),该代码将不确定地运行,仅产生偶尔的输出(如果一切顺利,不足以填充缓冲区)。你也是对的,似乎在 all 之后,如果孩子的 IO 阻塞是完全可以的,就此而言,甚至可能是父母的。谢谢。
    • @conartist6:非阻塞 IO 标志对文件描述符的影响完全不同。但同样具有程序逻辑破坏性。或根本没有效果。取决于程序。 linux.die.net/man/2/fcntl - 并寻找 O_NONBLOCK。只需将其删除。
    【解决方案4】:

    sleep()s 确实保证父级将首先打开管道 - 正如 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];
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2013-05-06
      • 2011-12-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多