C语言 由' pipe '返回的两个文件描述符使用哪种类型的缓冲?



根据man 2 pipe的手册,上面说[强调我的]:

pipe()创建一个管道,一个单向数据通道,可用于进程间通信。数组pipefd用于返回两个文件描述符指的是管道的两端。Pipefd[0]表示管道的读端。Pipefd[1]表示管道的写端。写入管道的写端数据由内核进行缓冲,直到从管道的读端读取为止。有关详细信息,请参见管道(7)

但是上面的引用和man 7 pipe并没有提到pipe返回的两个文件描述符使用哪种类型的缓冲?

根据文档,它说有三种类型的缓冲[强调我]:

标准I/O库缓冲studio库缓冲数据的目的是尽量减少对read()和write()系统调用的调用次数。使用了三种不同类型的缓冲:

完全(块)缓冲. 当字符被写入流时,它们将被缓冲,直到缓冲区满为止。在这个阶段,数据被写入流引用的文件。类似地,如果可能的话,读取将导致读取整个缓冲区的数据。

行缓冲. 当字符被写入流时,它们将被缓冲,直到写入换行符。此时,包含换行符的数据行被写入流引用的文件。类似地,对于读取,字符被读取到找到换行符的位置。

无缓冲的. 当输出流未被缓冲时,写入流的任何数据都将立即写入与该流相关联的文件。

ANSI C标准规定标准输入和输出应该被完全缓冲,而标准错误应该不被缓冲。通常,标准输入和输出设置为终端设备行缓冲,否则为完全缓冲。

我对Ubuntu16.04做了一个简单的测试,下面是代码片段:

#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <string>
#include <thread>
#include <array>
#include <iostream>
int
main(int argc, char *argv[])
{
int pipefd[2];
pid_t cpid;
std::array<char, 1024> buf;

if (pipe(pipefd) == -1) {
perror("pipe");
exit(EXIT_FAILURE);
}
cpid = fork();
if (cpid == -1) {
perror("fork");
exit(EXIT_FAILURE);
}
if (cpid == 0) {    /* Child reads from pipe */
close(pipefd[1]);          /* Close unused write end */
int size;
while ((size = read(pipefd[0], buf.data(), buf.size())) > 0)
std::cout << size << std::endl;
write(STDOUT_FILENO, "n", 1);
close(pipefd[0]);
_exit(EXIT_SUCCESS);
} else {            /* Parent writes argv[1] to pipe */
close(pipefd[0]);          /* Close unused read end */
std::string str{"hello world"};
for(int i=0; i<3; i++)
{
write(pipefd[1], str.c_str(), str.size());
std::this_thread::sleep_for(std::chrono::seconds(3));
}
close(pipefd[1]);          /* Reader will see EOF */
wait(NULL);                /* Wait for child */
exit(EXIT_SUCCESS);
}
}
以下是上述代码片段的输出:
11
//about three seconds later
11
//about three seconds later

您可以看到,写端写入管道的内容不包含n,而读端可以每三秒钟读取一个完整的字符串。所以我认为pipe返回的两个文件描述符既不是block buffered也不是line buffered。去掉两个选项后,就只剩下unbuffered buffering了。

但是man 7 pipe的手册也说:

PIPE_BUFPOSIX.1-2001规定小于PIPE_BUF字节的write(2)s必须是原子的:输出数据作为连续序列写入管道。写更多PIPE_BUF字节可以是非原子的:内核可以将这些数据与其他进程写入的数据交叉。POSIX.1-2001要求PIPE_BUF至少为512字节。(在Linux上,PIPE_BUF是4096字节)

根据上面的引语,确实存在两个文件描述符的缓冲区。

所以我真的很困惑pipe返回的两个文件描述符使用哪种类型的缓冲。既然每个文件描述符都有缓冲区,我怎么能按时收到字符串呢?除了三根弦连在一起以外,每三秒一根弦?

有谁能说明一下这件事吗?

缓冲在两个不同的地方,重要的是不要混淆它们。

你引用的文档是关于"标准I/O库缓冲"的,它指的是标准C库(libc)。它适用于像fprintffwrite这样的libc函数。这种缓冲发生在用户空间,即在程序的进程中。在底层,当这个缓冲区被刷新时,libc调用write将数据发送到底层文件描述符。

然而,pipe是对内核的直接系统调用,它与libc中的缓冲无关。您可以看出区别,因为它适用于文件描述符,而不是FILE*s。你代码中使用的readwrite函数也是系统调用。

管道在内核空间中仍然是缓冲的,但是在那里谈论"line buffered"是没有意义的。或者"block buffered"因为没有自动冲洗。如果缓冲区已满,任何write调用都会阻塞,直到再次有空间。内核中占用缓冲区的唯一方法是通过read调用。


¹通过C库中的瘦包装器,但这不是重点。

最新更新