C fork() 和 pipe():进程卡在读取管道中?

C fork() and pipe(): process get stuck reading pipe?

我正在练习 fork() 和管道,我有一个问题:如果我不关闭第一个管道(第一个子进程 - 第二个子进程),为什么第二个子进程会卡住读取管道父进程?

#include<fcntl.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<errno.h>
#include<sys/wait.h>
#include<stdlib.h>
#include<stdio.h>
#include<unistd.h>

void checkArgs(int n){
    if(n!=1){
        perror("Wrong parameters number");
        exit(-1);
    }
}

int isAConsonant(char c){
    char vowels[5]={'a','e','i','o','u'};
    int i=0,toRet=1;
    for(i=0;i<5 && toRet;i++){
        if(c==vowels[i]){
            toRet=0;
        }
    }
    return toRet;
}

int main(int argc,char** argv){
    checkArgs(argc-1);
    int pipe1[2],pipe2[2],sync[2];
    int pid1,pid2;

    if(pipe(pipe1)<0 || pipe(pipe2)<0 || pipe(sync)<0){
        perror("Error opening pipes");
        exit(-1);
    }

    if((pid1=fork())<0){
        perror("error during fork");
        exit(-1);
    }

    if(pid1==0){ //first child
        close(pipe1[0]);
        close(sync[0]);
        char buf[3];
        int fDes=open(argv[1],O_RDONLY);

        if(fDes<0){
            perror("Error opening file");
            exit(-1);
        }

        write(sync[1],"N",1);

        while(read(fDes,buf,3)==3){
            write(sync[1],"N",1);
            write(pipe1[1],buf,3);
        }

        write(sync[1],"S",1);
        close(sync[1]);
        close(fDes);
        close(pipe1[1]);
    }
    else{

        if((pid2=fork())<0){
            perror("error during fork");
            exit(-1);
        }
        if(pid2==0){ //second child
            close(pipe2[0]);
            close(pipe1[1]);
            char buf[3];

            while(read(pipe1[0],buf,3)==3){
                if(isAConsonant(buf[0])){
                    write(pipe2[1],buf,3);
                }
            }
            close(pipe2[1]);
            close(pipe1[0]);
        }
        else{   //parent
            close(pipe2[1]);
            close(sync[1]);

            //it does not work if not executed
            //close(pipe1[1]);
            //close(pipe1[0]);

            char toStart;
            read(sync[0],&toStart,1);
            while(toStart!='S'){
                read(sync[0],&toStart,1);
            }

            int fDes=open(argv[1],O_RDWR|O_APPEND,S_IRUSR|S_IWUSR);
            if(fDes<0){
                perror("Error opening file");
                exit(-1);
            }

            char buf[3];

            while(read(pipe2[0],buf,3)==3){
                write(fDes,buf,3);
                write(fDes," ",1);
            }

            close(pipe2[0]);
            close(sync[0]);
            close(fDes);
        }
    }
}

这些调用正确地终止了程序

close(pipe1[1]);
close(pipe1[0]);

输入文件:

abcdefghilmnopqrstuvz

在父进程中不使用 close(pipe1[0]) 和 close(pipe1[1]) 执行代码的 Strace 屏幕:

父进程的strace

execve("./pipe", ["./pipe", "inputF"], 0x7ffc458bc820 /* 54 vars */) = 0
brk(NULL)                               = 0x55ed3c675000
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (File o directory non esistente)
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=71886, ...}) = 0
mmap(NULL, 71886, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f7d0b895000
close(3)                                = 0
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "7ELF[=13=][=13=][=13=][=13=][=13=][=13=][=13=][=13=][=13=]>[=13=][=13=][=13=][=13=]0l[=13=][=13=][=13=][=13=][=13=]"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0755, st_size=2000480, ...}) = 0
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f7d0b893000
mmap(NULL, 2008696, PROT_READ, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f7d0b6a8000
mmap(0x7f7d0b6cd000, 1519616, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x25000) = 0x7f7d0b6cd000
mmap(0x7f7d0b840000, 299008, PROT_READ, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x198000) = 0x7f7d0b840000
mmap(0x7f7d0b889000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1e0000) = 0x7f7d0b889000
mmap(0x7f7d0b88f000, 13944, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f7d0b88f000
close(3)                                = 0
arch_prctl(ARCH_SET_FS, 0x7f7d0b894500) = 0
mprotect(0x7f7d0b889000, 12288, PROT_READ) = 0
mprotect(0x55ed3a7d0000, 4096, PROT_READ) = 0
mprotect(0x7f7d0b8d1000, 4096, PROT_READ) = 0
munmap(0x7f7d0b895000, 71886)           = 0
pipe([3, 4])                            = 0
pipe([5, 6])                            = 0
pipe([7, 8])                            = 0
clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f7d0b8947d0) = 11194
clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f7d0b8947d0) = 11195
close(6)                                = 0
close(8)                                = 0
read(7, "N", 1)                         = 1
read(7, "N", 1)                         = 1
read(7, "N", 1)                         = 1
read(7, "N", 1)                         = 1
read(7, "N", 1)                         = 1
read(7, "N", 1)                         = 1
read(7, "N", 1)                         = 1
read(7, "N", 1)                         = 1
read(7, "S", 1)                         = 1
openat(AT_FDCWD, "inputF", O_RDWR|O_APPEND) = 6
read(5, "def", 3)                       = 3
write(6, "def", 3)                      = 3
write(6, " ", 1)                        = 1
read(5, "ghi", 3)                       = 3
write(6, "ghi", 3)                      = 3
write(6, " ", 1)                        = 1
read(5, "lmn", 3)                       = 3
write(6, "lmn", 3)                      = 3
write(6, " ", 1)                        = 1
read(5, "rst", 3)                       = 3
write(6, "rst", 3)                      = 3
write(6, " ", 1)                        = 1
read(5, 0x7ffffab4a6a5, 3)              = ? ERESTARTSYS (To be restarted if SA_RESTART is set)
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=11194, si_uid=1000, si_status=0, si_utime=0, si_stime=0} ---
read(5, 0x7ffffab4a6a5, 3)              = ? ERESTARTSYS (To be restarted if SA_RESTART is set)
--- SIGINT {si_signo=SIGINT, si_code=SI_KERNEL} ---
+++ killed by SIGINT +++

第一个子进程的 strace

execve("./pipe", ["./pipe", "inputF"], 0x7ffc458bc820 /* 54 vars */) = 0
brk(NULL)                               = 0x55ed3c675000
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (File o directory non esistente)
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=71886, ...}) = 0
mmap(NULL, 71886, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f7d0b895000
close(3)                                = 0
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "7ELF[=14=][=14=][=14=][=14=][=14=][=14=][=14=][=14=][=14=]>[=14=][=14=][=14=][=14=]0l[=14=][=14=][=14=][=14=][=14=]"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0755, st_size=2000480, ...}) = 0
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f7d0b893000
mmap(NULL, 2008696, PROT_READ, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f7d0b6a8000
mmap(0x7f7d0b6cd000, 1519616, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x25000) = 0x7f7d0b6cd000
mmap(0x7f7d0b840000, 299008, PROT_READ, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x198000) = 0x7f7d0b840000
mmap(0x7f7d0b889000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1e0000) = 0x7f7d0b889000
mmap(0x7f7d0b88f000, 13944, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f7d0b88f000
close(3)                                = 0
arch_prctl(ARCH_SET_FS, 0x7f7d0b894500) = 0
mprotect(0x7f7d0b889000, 12288, PROT_READ) = 0
mprotect(0x55ed3a7d0000, 4096, PROT_READ) = 0
mprotect(0x7f7d0b8d1000, 4096, PROT_READ) = 0
munmap(0x7f7d0b895000, 71886)           = 0
pipe([3, 4])                            = 0
pipe([5, 6])                            = 0
pipe([7, 8])                            = 0
clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f7d0b8947d0) = 11194
clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f7d0b8947d0) = 11195
close(6)                                = 0
close(8)                                = 0
read(7, "N", 1)                         = 1
read(7, "N", 1)                         = 1
read(7, "N", 1)                         = 1
read(7, "N", 1)                         = 1
read(7, "N", 1)                         = 1
read(7, "N", 1)                         = 1
read(7, "N", 1)                         = 1
read(7, "N", 1)                         = 1
read(7, "S", 1)                         = 1
openat(AT_FDCWD, "inputF", O_RDWR|O_APPEND) = 6
read(5, "def", 3)                       = 3
write(6, "def", 3)                      = 3
write(6, " ", 1)                        = 1
read(5, "ghi", 3)                       = 3
write(6, "ghi", 3)                      = 3
write(6, " ", 1)                        = 1
read(5, "lmn", 3)                       = 3
write(6, "lmn", 3)                      = 3
write(6, " ", 1)                        = 1
read(5, "rst", 3)                       = 3
write(6, "rst", 3)                      = 3
write(6, " ", 1)                        = 1
read(5, 0x7ffffab4a6a5, 3)              = ? ERESTARTSYS (To be restarted if SA_RESTART is set)
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=11194, si_uid=1000, si_status=0, si_utime=0, si_stime=0} ---
read(5, 0x7ffffab4a6a5, 3)              = ? ERESTARTSYS (To be restarted if SA_RESTART is set)
--- SIGINT {si_signo=SIGINT, si_code=SI_KERNEL} ---
+++ killed by SIGINT +++
pepys@pepys:~/Scrivania/lso/C/pipe/prova strana$ ^C
pepys@pepys:~/Scrivania/lso/C/pipe/prova strana$ ls
es  es.c  inputF  pipe  pipe.c  trace.11193  trace.11194  trace.11195
pepys@pepys:~/Scrivania/lso/C/pipe/prova strana$ cat trace.11194
close(3)                                = 0
close(7)                                = 0
openat(AT_FDCWD, "inputF", O_RDONLY)    = 3
write(8, "N", 1)                        = 1
read(3, "abc", 3)                       = 3
write(8, "N", 1)                        = 1
write(4, "abc", 3)                      = 3
read(3, "def", 3)                       = 3
write(8, "N", 1)                        = 1
write(4, "def", 3)                      = 3
read(3, "ghi", 3)                       = 3
write(8, "N", 1)                        = 1
write(4, "ghi", 3)                      = 3
read(3, "lmn", 3)                       = 3
write(8, "N", 1)                        = 1
write(4, "lmn", 3)                      = 3
read(3, "opq", 3)                       = 3
write(8, "N", 1)                        = 1
write(4, "opq", 3)                      = 3
read(3, "rst", 3)                       = 3
write(8, "N", 1)                        = 1
write(4, "rst", 3)                      = 3
read(3, "uvz", 3)                       = 3
write(8, "N", 1)                        = 1
write(4, "uvz", 3)                      = 3
read(3, "", 3)                          = 0
write(8, "S", 1)                        = 1
close(8)                                = 0
close(3)                                = 0
close(4)                                = 0
exit_group(0)                           = ?
+++ exited with 0 +++

第二个子进程的strace

close(5)                                = 0
close(4)                                = 0
read(3, "abc", 3)                       = 3
read(3, "def", 3)                       = 3
write(6, "def", 3)                      = 3
read(3, "ghi", 3)                       = 3
write(6, "ghi", 3)                      = 3
read(3, "lmn", 3)                       = 3
write(6, "lmn", 3)                      = 3
read(3, "opq", 3)                       = 3
read(3, "rst", 3)                       = 3
write(6, "rst", 3)                      = 3
read(3, "uvz", 3)                       = 3
read(3, 0x7ffffab4a6a5, 3)              = ? ERESTARTSYS (To be restarted if SA_RESTART is set)
--- SIGINT {si_signo=SIGINT, si_code=SI_KERNEL} ---
+++ killed by SIGINT +++

why does the second child process get stuck reading the pipe if I don't close the first pipe (first child - second child) in the parent process?

一眼看去我不能完全理解你的代码到底做了什么,但本质上,你先创建了三个管道,然后分叉了两次。

这给您留下了三个进程和三个管道。您的每个进程作为每个管道的读取和写入端。

管道的读取端仅在所有写入器关闭时才发出 EOF 信号。我想在你的情况下这不是真的。

当有任何进程打开了管道的写入描述符时,系统不会在管道的读取描述符上报告 EOF。这包括当前流程。您必须确保管道已关闭 — 多次关闭。


经验法则:如果你 dup2() 管道的一端连接到标准输入或标准输出,同时关闭 返回的原始文件描述符 pipe() 尽早。 特别是,您应该在使用任何 exec*() 函数族。

如果您使用以下任一方式复制描述符,则该规则也适用 dup() 或者 fcntl() F_DUPFD


如果 parent 进程将不与其任何 children 通过 管道,它必须确保尽早关闭管道的两端 足够(例如,在等待之前)以便其 children 可以接收 EOF 指示读取(或获取 SIGPIPE 信号或写入错误 写),而不是无限期地阻塞。 即使 parent 使用管道而不使用 dup2(),它也应该 通常至少关闭管道的一端——这种情况极为罕见 在单个管道的两端读写的程序。

请注意 O_CLOEXEC 选项 open(), 并且 fcntl()FD_CLOEXECF_DUPFD_CLOEXEC 选项也可以考虑因素 进入这个讨论。

如果你使用 posix_spawn() 及其广泛的支持功能系列(总共 21 个功能), 您将需要查看如何在生成的进程中关闭文件描述符 (posix_spawn_file_actions_addclose(), 等)。

请注意,出于多种原因,使用 dup2(a, b) 比使用 close(b); dup(a); 更安全。 一个是如果你想强制文件描述符大于 通常的数字,dup2() 是唯一明智的方法。 另一个是如果 ab 相同(例如两者都是 0),则 dup2() 正确处理它(它在复制 a 之前不会关闭 b) 而单独的 close()dup() 则失败得很厉害。 这种情况不太可能,但并非不可能。