Warning: file_get_contents(/data/phpspider/zhask/data//catemap/4/unix/3.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Unix 如何为C stdio输入流实现行缓冲?_Unix_Stdio_Buffering - Fatal编程技术网

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”,则相应的流将被行缓冲。此选项对于标准输入无效


完全缓冲和行缓冲之间的差异在输出流中变得可见,控制刷新它们的时间。

A
FILE
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;
}