Unix 如何为C stdio输入流实现行缓冲?
我知道,对于可能大于应用程序所需的数据块,可以通过发出单个Unix 如何为C stdio输入流实现行缓冲?,unix,stdio,buffering,Unix,Stdio,Buffering,我知道,对于可能大于应用程序所需的数据块,可以通过发出单个readsyscall来实现完全缓冲输入。但我不明白,如果没有内核的支持,行缓冲是如何应用于输入的。我想象一个人必须读取一块数据,然后寻找换行符,但是如果是这样的话,完全缓冲有什么区别 更具体地说: 假设我在中有一个输入流文件*。关于stdio库如何从操作系统检索字节以填充其缓冲区,以下两者之间是否有任何区别 行缓冲:setvbuf(in,NULL,_IOLBF,BUFSIZ) 完全缓冲:setvbuf(in,NULL,_IOFBF,
read
syscall来实现完全缓冲输入。但我不明白,如果没有内核的支持,行缓冲是如何应用于输入的。我想象一个人必须读取一块数据,然后寻找换行符,但是如果是这样的话,完全缓冲有什么区别
更具体地说: 假设我在中有一个输入流
文件*。关于stdio
库如何从操作系统检索字节以填充其缓冲区,以下两者之间是否有任何区别
- 行缓冲:
setvbuf(in,NULL,_IOLBF,BUFSIZ)
- 完全缓冲:
setvbuf(in,NULL,_IOFBF,BUFSIZ)
如果是这样的话,区别是什么?你说得对,就STDIN而言,行缓冲和完全缓冲没有区别,因为libc仍然需要读取更大的块才能在其中找到换行符。不支持从内核管道缓冲区读取单独的行。考虑下面的例子:
printf "a\nb\nc\n" | (sed 1q ; sed 1q ; sed 1q)
a
如您所见,第一个sed
实例在尝试读取一行数据时获取了所有数据。无论STDIN是完全缓冲还是行缓冲,结果都是相同的。例如,检查手册页:
如果模式为“L”,则相应的流将被行缓冲。此选项对于标准输入无效
完全缓冲和行缓冲之间的差异在输出流中变得可见,控制刷新它们的时间。AFILE
struct具有默认的内部缓冲。在fopen
之后,在fread
、fgets
等上,缓冲区由read(2)
调用的stdio层填充
当您执行fgets
操作时,它会将数据复制到您的缓冲区,并从内部缓冲区中提取数据[直到找到换行符]。如果未找到换行符,则流内部缓冲区将用另一个read(2)
调用进行补充。然后,继续扫描换行符并填充缓冲区
这可能会重复多次[如果您正在执行fread
,尤其如此]。剩余的内容可用于下一个流读取操作(例如,fread
,fgets
,fgetc
)
您可以使用setlinebuf
设置流缓冲区的大小。为了提高效率,典型的默认大小是机器页面大小[IIRC]
因此,可以说,流缓冲区“比您领先一步”。它的操作非常类似于环形队列(实际上,如果不是实际的话)
当然不知道,但行缓冲[或任何缓冲模式]通常用于输出文件(例如,默认设置为stdout)。它说,如果你看到一个新行,做一个隐含的fflush
。完全缓冲意味着在缓冲区已满时执行fflush
。无缓冲意味着对每个字符都执行fflush
如果您打开一个输出日志文件,您将获得完全缓冲[最有效],因此如果您的程序崩溃,您可能无法获得最后N行输出(即,它们仍在缓冲区中挂起)。您可以设置行缓冲,以便在程序崩溃后获得最后一个跟踪行
在输入时,行缓冲对文件[AFAICT]没有任何意义。它只是尽可能使用最有效的大小(例如,流缓冲区大小)
我认为重要的一点是,在输入时,你不知道换行符在哪里,所以\u IOLBF
像其他模式一样运行——因为它必须这样。(即)您确实读取了(2)到流buf大小(或完成未完成的fread
所需的金额)。换句话说,唯一重要的是fread
的内部缓冲区大小和大小/计数参数,而不是缓冲模式
对于TTY设备(例如stdin),流将等待换行[除非您在底层fildes(例如0)上使用TIOC*ioctl来设置char-at-a-time aka-raw模式],而不考虑流模式。这是因为[内核中]的TTY设备规范处理层将阻止读取(例如,这就是为什么您可以键入backspace等,而应用程序不必处理它)
但是,在TTY设备/流上执行fgets
将在内部得到特殊处理(例如),它将执行select/poll并获取挂起字符的数量,并仅读取该数量,因此不会阻止读取。然后它将查找换行符,如果没有找到换行符,则重新发出select/poll。但是,如果找到换行符,它将从fgets
返回。换句话说,它将做任何必要的事情来允许stdin上的预期行为。如果用户输入10个字符+换行符,则无法阻止4096字节的读取
更新:
回答您的第二轮后续问题
我认为tty子系统和进程中运行的stdio代码是完全独立的。它们接口的唯一方式是由进程发出read系统调用;这些可能会阻塞或不阻塞,这取决于tty设置
通常情况下,这是正确的。大多数应用程序不会尝试调整TTY层设置。但是,如果应用程序愿意,它可以这样做,但不能通过任何流/stdio功能
但该过程完全不知道这些设置,无法更改它们
同样,这通常是正确的。但是,这一过程同样可以改变它们
如果我们在同一页上,您所说的意味着setvbuf调用将改变tty设备的缓冲策略,我发现这与我对Unix I/O的理解很难调和
否setvbuf
仅设置流缓冲区大小和策略。它与内核没有任何关系。内核只看到read(2)
,不知道应用程序是否读了
// getkey -- wait for user input
#include <stdio.h>
#include <fcntl.h>
#include <termios.h>
#include <stdlib.h>
#include <unistd.h>
#include <time.h>
#include <string.h>
#include <errno.h>
#define sysfault(_fmt...) \
do { \
printf(_fmt); \
exit(1); \
} while (0)
int
main(int argc,char **argv)
{
int fd;
int remain;
int err;
int oflag;
int stdflg;
char *cp;
struct termios tiold;
struct termios tinew;
int len;
int flag;
char buf[1];
int code;
--argc;
++argv;
stdflg = 0;
for (; argc > 0; --argc, ++argv) {
cp = *argv;
if (*cp != '-')
break;
switch (cp[1]) {
case 's':
stdflg = 1;
break;
}
}
printf("using %s\n",stdflg ? "fgetc" : "read");
fd = fileno(stdin);
oflag = fcntl(fd,F_GETFL);
fcntl(fd,F_SETFL,oflag | O_NONBLOCK);
err = tcgetattr(fd,&tiold);
if (err < 0)
sysfault("getkey: tcgetattr failure -- %s\n",strerror(errno));
tinew = tiold;
#if 1
tinew.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP |
INLCR | IGNCR | ICRNL | IXON);
tinew.c_oflag &= ~OPOST;
tinew.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
tinew.c_cflag &= ~(CSIZE | PARENB);
tinew.c_cflag |= CS8;
#else
cfmakeraw(&tinew);
#endif
#if 0
tinew.c_cc[VMIN] = 0;
tinew.c_cc[VTIME] = 0;
#endif
err = tcsetattr(fd,TCSAFLUSH,&tinew);
if (err < 0)
sysfault("getkey: tcsetattr failure -- %s\n",strerror(errno));
for (remain = 9; remain > 0; --remain) {
printf("\rHit any key within %d seconds to abort ...",remain);
fflush(stdout);
sleep(1);
if (stdflg) {
len = fgetc(stdin);
if (len != EOF)
break;
}
else {
len = read(fd,buf,sizeof(buf));
if (len > 0)
break;
}
}
tcsetattr(fd,TCSAFLUSH,&tiold);
fcntl(fd,F_SETFL,oflag);
code = (remain > 0);
printf("\n");
printf("%s (%d remaining) ...\n",code ? "abort" : "normal",remain);
return code;
}