在C中,当没有东西要读取时,读取功能如何“知道”?

时间:2015-08-08 22:22:01

标签: c pipe fork parent-child

在下面的程序中(不是我的程序,而是我修改过的程序),子进程对管道进行两次写入。当我运行程序时,我得到以下输出:

Received string: Hello, world!
This is the child process.

父进程执行的读取如何从管道缓冲区中捕获这两个字符串?什么(如果有的话)阻止父进程在读取第一个字符串(或第一个字符串的第一个字符串)之后假设没有其他内容可以从缓冲区中读取并退出?

有问题的节目:

int main(void)
{
    int     fd[2], nbytes;
    pid_t   childpid;
    char    string[] = "Hello, world!\n";
    char    string2[] = "This is the child process.\n";
    char    readbuffer[80];

    pipe(fd);

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

    if(childpid == 0)
    {
            /* Child process closes pipe's input file descriptor */
            close(fd[0]);

            /* Send "string" through the output side of pipe */
            write(fd[1], string, (strlen(string)));
            write(fd[1], string2, (strlen(string2)+1));
            return 1;
    }
    else
    {
            /* Parent process closes pipe's output file descriptor */
            close(fd[1]);

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

    return 0;
}

5 个答案:

答案 0 :(得分:1)

此处的关键是read()缓冲区大小80

通常read() 阻止正在调用它的进程(设置为处于休眠状态),直到某些条件不发生为止,例如:

  • 请求的缓冲区已满
  • 数据少于请求数据(EOF)或发生错误
  • 一个信号唤醒进程(如^C),这可能是您的情况,子进程退出,系统发送损坏的管道信号到父进程(进程唤醒和read()获取整个缓冲区)

请注意,这些条件取决于您正在阅读的子系统,例如pipe。子系统可能具有不同的属性,如缓冲区大小。一个荒谬的例子:如果内核端上的管道缓冲区大小小于或等于第一次写入,读取进程将在之前唤醒,返回截断的缓冲区。

答案 1 :(得分:0)

父级将读取最多80个字节。因此,第一个write系统调用(以及第二个)将把第一个字符串放在相对于管道的内核缓冲区中。 read系统调用最多会读取80个字节,因此会向内核请求数据,内核将返回缓冲区中前80个字节的数据(因此不可能只读取字符串的第一个字符,单个写入整个字符串,单个阻塞读取80个字节)。

如你所说,孩子和父母之间没有同步。 事实上,在我的linux系统上,你的程序有时只会打印第一个字符串,有时会打印它们。

问题是经典的种族条件问题,有时候孩子只会写第一个字符串,然后调度父级,它会读取第一个字符串,然后程序将结束(或者控件将在read之后返回给子节点,子节点会将第二个字符串写入管道缓冲区,但实际上没有人会读取数据。)

您应该在程序中放置一些同步功能,例如可以在close(fd[1]);

之后等待孩子
[...]
else
{
      /* Parent process closes up output side of pipe */
      close(fd[1]);

      /* Synchronize parent and child */
      wait(NULL);

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

然后,只有当两个字符串正确放入缓冲区时,父级才会读取数据。

答案 2 :(得分:0)

这是如何写入管道的示例。

此示例用于父级写入子级,但逻辑非常相似,无论数据流方向如何。

           close(pipefd[0]);          /* Close unused read end */
           write(pipefd[1], argv[1], strlen(argv[1]));
           close(pipefd[1]);          /* Reader will see EOF */
           wait(NULL);                /* Wait for child to exit */
           exit(EXIT_SUCCESS);

除此之外,这意味着当从管道中读取所有内容时,read()的调用者将获得返回值0。

这也意味着读者在循环中读取,直到返回错误条件指示或返回0。

答案 3 :(得分:0)

  

...当没有什么东西可以阅读时,读取功能如何“知道”?

成功read()会返回基础对象中的可用内容,但不会超过请求。

来自man 3 read (POSIX)

  

成功完成后,当nbyte大于0时,read()将标记更新文件的st_atime字段,并返回读取的字节数。这个数字永远不会更大          比nbyte。如果文件中剩余的字节数小于nbyte,如果read()请求被信号中断,或者文件是管道或FIFO,则返回的值可能小于nbyte          或特殊文件,并且可以立即读取少于nbyte的字节。

答案 4 :(得分:0)

我喜欢这个问题。它让我记得过去的时光......

元答案:只知道流是否已关闭

答案:您的流程会读取它可以/必须读取的所有内容,而不会在'\ n'

处停止

但我想,你想写/读记录。

尽管其他人我尝试回答而没有区分类型的流。我在这里做了一个免责声明:

这一切都取决于操作系统和流类型 - 以及两个(!)方面的选项

基本:

让我们说一个进程写一个其他的读取 - 很容易。

不,不是。

想象一下 - 总是;-) - 他们将字符(字节)放入流中,并且可以 - 慢慢地 - 快速 - 逐个或全部缓冲。

你读它 - 不是 - 不是你而不是你的程序 - 介于两者之间 - 逐字节或块或者它们之间的层如何。

所以如果你想要一个记录,那么只有三种可能性:

  1. 制作一个结束字符(字节) - 块分隔符,
  2. 使记录固定长度,
  3. 旧的两字节长度然后阅读其余的
  4. 再次回答:

    在你的情况下,孩子将所有字符放在父母读取它们的流中 - 为什么其中一个要休息?

    解决方案:

    根据操作系统和语言以及库,您有时可以告诉操作系统创建/接受结束字符(例如\ n) - 有时您的写入读取功能会为您执行此操作