代码之家  ›  专栏  ›  技术社区  ›  Rahn

linux管道:捕获分叉子级的输出

  •  0
  • Rahn  · 技术社区  · 3 年前

    我有一个简单的程序,可以将数字0到2发送到管道中,然后函数 fork_child 从该管道接收编号,并打印出它接收的第一个编号,然后将以下编号发送到另一个管道中,该管道连接到由创建的下一个子管道 fork_child :

    #include <stdio.h>
    #include <stdlib.h>
    #include <sys/wait.h>
    #include <unistd.h>
    
    void fork_child(int port_in, int generation) {
        int number;
        int receive_first_number = 0;
        int pid = -1;
        int fd[2];
    
        while (read(port_in, &number, sizeof(number)) != 0) {
            if (receive_first_number == 0) {
                receive_first_number = 1;
                printf("child-%d (pid %d) receives: %d\n", generation, getpid(), number);
                continue;
            }
    
            if (pid == -1) {
                // execute only once to create child
                pipe(fd);
                pid = fork();
                if (pid == 0) {
                    // child process
                    close(fd[1]);
                    fork_child(fd[0], generation + 1);
                    close(fd[0]);
                    exit(0);
                } else {
                    // parent process
                    close(fd[0]);
                }
            }
            // send number to next child
            write(fd[1], &number, sizeof(number));
        }
    
        close(fd[1]);
        wait(&pid);
    }
    
    int main() {
        int fd[2];
        pipe(fd);
    
        int pid = fork();
        if (pid == 0) {
            // child process
            close(fd[1]);
            fork_child(fd[0], 0);
            close(fd[0]);
            exit(0);
        } else {
            // parent process
            close(fd[0]);
            for (int number = 0; number < 3; number++) {
                write(fd[1], &number, sizeof(number));
            }
            close(fd[1]);
            wait(&pid);
        }
    
        return 0;
    }
    

    它在运行时按预期工作:

    $ ./pipe
    child-0 (pid 149437) receives: 0
    child-1 (pid 149438) receives: 1
    child-2 (pid 149439) receives: 2
    

    execept捕获输出并重定向到文件中:

    $ ./pipe > out
    $ cat out
    child-0 (pid 151246) receives: 0
    child-1 (pid 151247) receives: 1
    child-2 (pid 151248) receives: 2
    child-0 (pid 151246) receives: 0
    child-1 (pid 151247) receives: 1
    child-0 (pid 151246) receives: 0
    

    为什么捕获的输出与没有管道和重定向的情况不同?

    0 回复  |  直到 3 年前
        1
  •  0
  •   root    3 年前

    这是一个依赖于缓冲的并发错误:

    $ stdbuf -o 0 ./pipe 
    child-0 (pid 864370) receives: 0
    child-1 (pid 864371) receives: 1
    child-2 (pid 864372) receives: 2
    $ stdbuf -o 0 ./pipe > out && cat out
    child-0 (pid 864355) receives: 0
    child-1 (pid 864356) receives: 1
    child-2 (pid 864357) receives: 2
    $ stdbuf -o L ./pipe 
    child-0 (pid 866493) receives: 0
    child-1 (pid 866494) receives: 1
    child-2 (pid 866495) receives: 2
    $ stdbuf -o L ./pipe > out && cat out
    child-0 (pid 866501) receives: 0
    child-1 (pid 866502) receives: 1
    child-2 (pid 866503) receives: 2
    $ stdbuf -o 4096 ./pipe 
    child-0 (pid 864399) receives: 0
    child-1 (pid 864400) receives: 1
    child-2 (pid 864401) receives: 2
    child-0 (pid 864399) receives: 0
    child-1 (pid 864400) receives: 1
    child-0 (pid 864399) receives: 0
    $ stdbuf -o 4096 ./pipe > out && cat out
    child-0 (pid 864385) receives: 0
    child-1 (pid 864386) receives: 1
    child-2 (pid 864387) receives: 2
    child-0 (pid 864385) receives: 0
    child-1 (pid 864386) receives: 1
    child-0 (pid 864385) receives: 0
    

    请注意,这种类型的缓冲只影响 printf() read() write()

    当没有缓冲时( stdbuf -o 0 ),或者当每行都进行缓冲时( stdbuf -o L ), 打印() 将在打印后立即刷新其写入缓冲区。

    重定向到文件时,缓冲区要大得多(例如4k字节),并且 打印() 不会刷新该缓冲区。
    然后你 fork() 但是 打印() 的缓冲区是用户空间的东西,内核并不知道,所以在 分叉() 父级和子级都有缓冲区的副本。
    在2个这样的叉之后,缓冲区包含所有3个打印,第三个子将打印这些打印。
    但第二个孩子没有意识到第三个孩子已经打印了所有内容,它将打印缓冲区,其中包含前两个打印。 类似地,第一个子将打印其缓冲区,该缓冲区合法地仅包含其自己的打印。
    这就是你得到0、1、2(来自第三个孩子)、0、1(来自第二个孩子)和0(来自第一个孩子)的方式。

    你可以通过 strace (1) 通过检查写入的字节数:

    $ strace -o /tmp/trace -f ./x && grep 'write.*child-' /tmp/trace
    child-0 (pid 867548) receives: 0
    child-1 (pid 867549) receives: 1
    child-2 (pid 867550) receives: 2
    867548 write(1, "child-0 (pid 867548) receives: 0"..., 33) = 33
    867549 write(1, "child-1 (pid 867549) receives: 1"..., 33) = 33
    867550 write(1, "child-2 (pid 867550) receives: 2"..., 33) = 33
    $ strace -o /tmp/trace -f ./x > out && cat out && grep 'write.*child-' /tmp/trace
    child-0 (pid 867515) receives: 0
    child-1 (pid 867516) receives: 1
    child-2 (pid 867517) receives: 2
    child-0 (pid 867515) receives: 0
    child-1 (pid 867516) receives: 1
    child-0 (pid 867515) receives: 0
    867517 write(1, "child-0 (pid 867515) receives: 0"..., 99) = 99
    867516 write(1, "child-0 (pid 867515) receives: 0"..., 66) = 66
    867515 write(1, "child-0 (pid 867515) receives: 0"..., 33) = 33
    

    可能的解决方案:

    另请参阅 this question