【问题标题】:C fork() and pipe(): process get stuck reading pipe?C fork() 和 pipe():进程卡在读取管道?
【发布时间】:2020-05-07 18:33:12
【问题描述】:

我正在练习 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]);

inputF 文件:

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, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\200l\2\0\0\0\0\0"..., 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, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\200l\2\0\0\0\0\0"..., 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 +++

【问题讨论】:

  • 请将控制台文本粘贴为文本(在代码块中),而不是图像。

标签: c pipe fork low-level


【解决方案1】:

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


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

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


如果父进程不会通过以下方式与其任何子进程通信 管,它必须确保它及早关闭管道的两端 足够(例如,在等待之前),以便它的孩子可以收到 读取时的 EOF 指示(或获取 SIGPIPE 信号或写入错误) write),而不是无限期地阻塞。 即使父级使用管道而不使用dup2(),它也应该 通常至少关闭管道的一端——这是非常罕见的 在单个管道的两端进行读写的程序。

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

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

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

【讨论】:

  • 感谢您的回答。那么在这种情况下我是否必须关闭每个进程中的所有管道?
  • 您需要关闭所有未使用的管道末端。我不完全清楚同步管道的用途。我还没有编译或调试代码。我不完全确定它应该做什么。我观察到孩子 1 连续向同步管道发送了两条 N 消息——一条在循环之前,一条在循环期间。我不确定这是否重要。感觉太多了,但感觉并不可靠。如果父级根本没有使用pipe1,它必须关闭两端以便子级使用它来检测EOF——你的cmets 表明你已经发现了。
  • 我建议绘制进程和所有管道和标准 I/O 以及任何其他文件描述符。确保你知道谁对每个人做了什么。那是您需要追踪的至少 9 个,可能是 10 个,甚至更多的文件描述符。 (9 个来自 3 个管道加上 3 个标准 I/O 通道。)。如果您无法弄清楚所有这些都发生了什么,则需要研究代码,直到可以为止。您最终应该在每个子级和父级中有 6 个管道关闭 close() 调用。当然,如果进程退出,它们就会关闭,但是您遇到了进程未退出的问题;你需要知道。
  • 第一个孩子既不读取也不写入pipe2;它应该在它几乎做任何事情之前关闭pipe2 的两端。它确实正确地关闭了它写入的管道的读取端。
  • 第二个孩子既不读取也不写入sync管道;它应该在它几乎做任何事情之前关闭sync 的两端。它确实正确地关闭了它读取和写入的管道的未使用端。
【解决方案2】:

如果我不关闭父进程中的第一个管道(第一个子 - 第二个子),为什么第二个子进程会卡在读取管道?

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

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

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

【讨论】:

  • 第一个子进程从 inputF 读取 3 个字符,并将它们传递给第二个子进程,检查第一个字符是否为辅音。如果为真,第二个子进程将字符传递给父进程,父进程将它们写入 inputF。
  • 好的,所以我在父进程中关闭 pipe1。如果我不关闭第二个子进程中的“同步”管道,为什么程序会结束?
猜你喜欢
  • 1970-01-01
  • 2014-05-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-05-28
  • 1970-01-01
  • 2014-05-12
  • 2011-07-02
相关资源
最近更新 更多