fra*_*gio 4 unix stdio buffering
据我所知,完全缓冲的输入可以通过read为可能大于应用程序所需的数据块发出单个系统调用来实现.但我不明白在没有内核支持的情况下如何将行缓冲应用于输入.我想有人必须读取一个数据块,然后查找换行符,但如果是这样,那么完全缓冲有什么区别?
更具体:
假设我有一个输入流FILE* in.关于stdio库如何从操作系统中检索字节以填充其缓冲区,以下是否有任何区别?
setvbuf(in, NULL, _IOLBF, BUFSIZ)setvbuf(in, NULL, _IOFBF, BUFSIZ) 如果是这样,那有什么区别?
甲FILEstruct包含默认内部缓冲器.后fopen,和上fread,fgets等,缓冲通过从标准输入输出层填充read(2)呼叫.
当你这样做fgets,将数据复制到您的缓冲区,从内部缓冲区拉动[直到换行被发现.如果未找到换行符,则使用另一个read(2)调用补充流内部缓冲区.然后,继续扫描换行符和填充缓冲区.
这可以重复多次[特别是如果你正在做的话fread].无论剩下的是可用于下一流中读取操作(例如fread,fgets,fgetc).
您可以使用设置流缓冲区的大小setlinebuffer.为了提高效率,典型的默认大小是机器页面大小[IIRC].
因此,流缓冲区"比你先行一步",可以这么说.它的运行方式就像一个环队列[实际上,如果不是现实].
Dunno肯定,但行缓冲[或任何缓冲模式 ]通常用于输出文件(例如默认设置为stdout).它说,如果你看到换行符,请做一个暗示fflush.完全缓冲意味着fflush在缓冲区满时执行.无缓冲意味着fflush对每个角色都做.
如果打开输出日志文件,则会获得完全缓冲[效率最高],因此如果程序崩溃,则可能无法获得最后N行输出(即它们在缓冲区中仍未处理).您可以设置行缓冲,以便在程序崩溃后获得最后一条跟踪线.
在输入时,行缓冲对文件[AFAICT]没有任何意义.它只是尝试使用尽可能最有效的大小(例如流缓冲区大小).
我认为重要的一点是,在输入,你不知道在哪里的换行符是事前,所以_IOLBF运行像任何其他模式-因为它有到.(即)你读(2)直到流buf大小(或实现未完成所需的金额fread).换言之,每个重要唯一的东西是内部缓冲器的大小和的大小/计数参数fread和不缓冲模式.
对于TTY设备(例如stdin),流将等待换行符[除非您在底层fildes(例如0)上使用TIOC*ioctl来设置一次性char(即原始模式),而不管流模式如何.那是因为TTY设备规范处理层[在内核中]将阻止读取(例如,这就是为什么你可以键入退格等,而不需要应用程序处理它).
但是,fgets在TTY设备/流上执行将在内部得到特殊处理(例如)它将执行select/poll并获取待处理字符的数量并且只读取该数量,因此它不会阻止读取.然后它会查找换行符,如果找不到换行符,则重新发出select/poll.但是,如果找到换行符,它将从fgets.换句话说,它将执行任何必要的操作以允许stdin上的预期行为.如果用户输入10个字符+换行符,它就不会阻止4096字节的读取.
更新:
回答你的第二轮后续问题
我看到tty子系统和在进程中运行的stdio代码是完全独立的.它们接口的唯一方法是通过发出read syscalls的进程; 这些可能会阻止或不阻止,这取决于tty设置.
通常,这是事实.大多数应用程序不会尝试调整TTY图层设置.但是,应用程序可以这样做,如果它希望,但没有通过任何流/标准输入输出功能.
但是这个过程完全没有意识到这些设置,也无法改变它们.
再次,通常是真的.但是,再一次,这个过程可以改变它们.
如果我们在同一页面上,你所说的意味着setvbuf调用将改变tty设备的缓冲策略,我发现很难与我对Unix I/O的理解相协调.
没有 setvbuf只设置流缓存大小和策略.它根本与内核无关.内核只看到read(2)并且不知道应用程序是否是原始的,或者流是否通过fread[或fgets]来完成.它不以任何方式影响了TTY层.
在循环开启fgetc且用户输入的普通应用程序中abcdef\n,fgetc将阻止[在驱动程序中] 直到输入换行符.这是执行此操作的TTY规范处理层.然后,进入了新行的情况下,read(2)通过完成fgetc将与价值回归7.第一个fgetc将返回,其余六个将快速发生,从流的内部缓冲区实现.
但是......
更复杂的应用程序可能会改变TTY层策略ioctl(fileno(stdin),TIOC*,...).流不会意识到这一点.因此,在这样做时,必须要小心.因此,如果进程需要,它可以完全控制文件单元后面的TTY层,但必须通过ioctl
使用ioctl修改[甚至禁用] TTY规范处理[aka"TTY原始模式"]可以由需要真正的一次性char输入的应用程序使用.例如,vim,emacs,getkey,等.
虽然应用程序可以混合原始模式和stdio流[并且有效地进行],但正常的用法是在正常模式/使用中使用流或 完全绕过 stdio层,ioctl(0,TIOC*,...)然后read(2) 直接执行.
这是一个示例getkey程序:
// 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;
}
Run Code Online (Sandbox Code Playgroud)