FILE 结构具有默认的内部缓冲区。在fopen 和fread、fgets 等之后,缓冲区由来自read(2) 调用的stdio 层填充。
当您执行fgets 时,它会将数据复制到您的 缓冲区,并从内部缓冲区中拉出数据[直到找到换行符]。如果没有找到换行符,则使用另一个read(2) 调用来补充流内部缓冲区。然后,继续扫描换行符并填充缓冲区。
这可以重复多次[如果你正在做fread,尤其如此]。剩下的任何内容都可用于下一个流读取操作(例如fread、fgets、fgetc)。
您可以使用setlinebuf 设置流缓冲区的大小。为了提高效率,典型的默认大小是机器页面大小 [IIRC]。
因此,可以这么说,流缓冲区“比您领先一步”。它的运作方式很像一个环形队列[实际上,如果不是现实的话]。
不知道,但行缓冲 [或 any 缓冲 mode] 通常用于输出文件(例如,默认设置为标准输出)。它说,如果您看到换行符,请执行隐含的fflush。完全缓冲意味着当缓冲区已满时执行fflush。无缓冲意味着对每个字符执行fflush。
如果您打开一个输出日志文件,您将获得完整的缓冲 [最有效],因此如果您的程序崩溃,您可能无法获得最后 N 行输出(即它们仍在缓冲区中等待处理)。您可以设置行缓冲,以便在程序崩溃后获得最后一条跟踪行。
在输入时,行缓冲对文件 [AFAICT] 没有任何意义。它只是尝试使用最有效的大小(例如流缓冲区大小)。
我认为重要的一点是,在输入时,您事先不知道换行符在哪里,所以_IOLBF 的操作方式与任何其他模式一样——因为它有 到。 (即)您确实读取(2)到流 buf 大小(或完成未完成的 fread 所需的数量)。换句话说,唯一重要的是内部缓冲区大小和fread 的大小/计数参数,而不是缓冲模式。
对于 TTY 设备(例如 stdin),流将等待换行符 [除非您在底层文件(例如 0)上使用 TIOC* ioctl 来设置 char-at-a-time aka raw 模式],无论流模式。这是因为 [内核中] 的 TTY 设备规范处理层会阻止读取(例如,这就是为什么您可以键入退格键等,而无需应用程序处理)。
但是,在 TTY 设备/流上执行 fgets 将在内部得到特殊处理(例如)它将执行选择/轮询并获取待处理字符的数量并仅读取该数量,因此它不会阻塞读。然后它将寻找换行符,如果没有找到换行符,则重新发出选择/轮询。但是,如果找到换行符,它会从fgets 返回。换句话说,它将做任何必要的事情来允许标准输入上的预期行为。如果用户输入 10 个字符 + 换行符,它不会阻止 4096 字节的读取。
更新:
回答您的第二轮后续问题
我认为 tty 子系统和进程中运行的 stdio 代码完全独立。它们接口的唯一方式是通过进程发出读取系统调用;这些可能会阻塞或不会阻塞,这取决于 tty 设置。
通常情况下是这样。大多数应用程序不会尝试调整 TTY 层设置。但是,如果应用愿意,可以这样做,但不能通过任何流/stdio 函数。
但该过程完全不知道这些设置,也无法更改它们。
同样,通常是正确的。但是,同样,流程可以改变它们。
如果我们在同一页面上,您所说的意味着 setvbuf 调用将更改 tty 设备的缓冲策略,我发现这与我对 Unix I/O 的理解不一致。
否 setvbuf 仅设置流 缓冲区大小和策略。它根本与内核无关。内核只看到read(2),并且不知道应用程序是直接执行的还是流是通过fread [或fgets] 执行的。它确实不会以任何方式影响 TTY 层。
在循环使用fgetc 并且用户输入abcdef\n 的普通应用中,fgetc 将阻塞[在驱动程序中]直到输入换行符。这是执行此操作的 TTY 规范处理层。然后,当输入换行符时,fgetc 完成的read(2) 将返回7 的值。第一个 fgetc 将返回,其余六个将迅速发生,从 流的 内部缓冲区中完成。
但是...
更复杂的应用可能会通过ioctl(fileno(stdin),TIOC*,...) 更改 TTY 层策略。流不会意识到这一点。因此,在这样做时,必须小心。因此,如果一个进程想要,它可以完全控制文件单元后面的TTY层,但必须通过ioctl手动完成
使用ioctl 修改[甚至禁用] TTY 规范处理 [又名“TTY 原始模式”] 可以由需要真正的一次字符输入的应用程序使用。例如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;
}