【问题标题】:Forking and pipes: does this C program contain a race condition?分叉和管道:这个 C 程序是否包含竞争条件?
【发布时间】:2015-08-08 18:59:50
【问题描述】:

我正在学习进程间通信,并遇到了下面的示例程序。

我不明白是什么阻止了父进程在子进程完成 write 之前尝试 read(作为程序底部 else 条件的一部分) .

什么(如果有的话)限制父进程在子进程写入标准输出之前尝试从标准输入读取?

int main(void)
{
    int     fd[2], nbytes;
    pid_t   childpid;
    char    string[] = "Hello, world!\n";
    char    readbuffer[80];

    pipe(fd);

    if((childpid = fork()) == -1)
    {
            perror("fork");
            exit(1);
    }

    if(childpid == 0)
    {
            /* Child process closes up input side of pipe */
            close(fd[0]);

            /* Send "string" through the output side of pipe */
            write(fd[1], string, (strlen(string)+1));
            exit(0);
    }
    else
    {
            /* Parent process closes up output side of pipe */
            close(fd[1]);

            /* Read in a string from the pipe */
            nbytes = read(fd[0], readbuffer, sizeof(readbuffer));
            printf("Received string: %s", readbuffer);
    }

    return(0);
}

【问题讨论】:

  • 在调用系统函数'pipe()'时,始终检查返回值以确保操作成功。成功时 pipe() 返回 0 失败时 pipe() 返回 -1 并设置 'errno'
  • 关于关闭管端的cmets是向后的
  • 'read()' 函数不会将字符串终止符字节 '\0' 附加到读取的字符串,因此对 printf() 的立即调用(可能)将失败。取决于首先发生的是 read() 还是 write(),读取可能会在没有读取所有数据字节的情况下返回。读取应该在一个循环中,同时检查/累积返回的字节数。当返回的字节数为 0 时,使用累积的字节数终止字符串,(可能没有必要,因为子进程发送终止字节,但这是一个好习惯)如果返回的值曾经
  • @user3629249:read() 调用将读取write() 调用包含在管道上发送的数据中的空字节。写入的长度为strlen() + 1 以包含空字节。

标签: c process pipe fork parent-child


【解决方案1】:

没有什么可以阻止父进程在子进程向管道写入任何内容之前启动read() 调用,但父进程在子进程向管道写入数据之前不会获取任何数据(并且写入将是原子,因为它小于管道缓冲区的长度)。父级将挂起等待一些数据到达管道或管道的每个写入端关闭。

注意如果nbytes == 0在读取之后,printf()的输出是不确定的,因为readbuffer没有被初始化。

【讨论】:

  • 谢谢 - 这让我想到另一个问题,这可能源于我的另一个误解,但我还是会问 - 父母如何知道何时没有更多数据进入管道缓冲区?我正在考虑父母开始阅读的场景,然后孩子写下“Hello, world!”的“H”。 - 什么可以防止父母在读完“H”后立即假设没有什么可读的了?
  • 当没有更多数据要读取并且没有进程可以写入更多数据(因此管道的所有写入端都关闭)时,读取进程返回0字节。内核处理它;读取过程只需要注意read()函数返回的值即可。
  • @sav0h 只要一个或多个进程打开管道的写入通道,读取进程(在您的代码中,父进程)将始终假定还有其他内容要读取(并且会阻塞read(2) 如果目前没有可用的东西)。注意 Hello, world! 小于PIPE_BUF,所以写入是原子的(参见man 7 pipe);假设传递给read(2) 的缓冲区足够长,一个read(2) 调用就足够了。
最近更新 更多