gpt4 book ai didi

unix - C stdio 输入流如何实现行缓冲?

转载 作者:行者123 更新时间:2023-12-04 22:58:19 42 4
gpt4 key购买 nike

我知道可以通过发出单个 read 来实现完全缓冲的输入。系统调用可能大于应用程序所需的数据块。但我不明白如何在没有内核支持的情况下将行缓冲应用于输入。我想人们必须读取一个数据块然后寻找换行符,但如果是这样,那么完全缓冲有什么区别?

更具体:

假设我有一个输入流 FILE* in .以下有什么区别,关于stdio库将从操作系统中检索字节以填充其缓冲区?

  • 行缓冲:​​setvbuf(in, NULL, _IOLBF, BUFSIZ)
  • 全缓冲:setvbuf(in, NULL, _IOFBF, BUFSIZ)

  • 如果是这样,那有什么区别?

    最佳答案

    一个 FILE struct 有一个默认的内部缓冲区。后 fopen ,并在 fread 上, fgets等,缓冲区由来自 read(2) 的 stdio 层填充。称呼。
    当您这样做时fgets ,它将数据复制到您的缓冲区,从内部缓冲区中提取数据 [直到找到换行符]。如果没有找到换行符,流内部缓冲区会被另一个 read(2) 补充。称呼。然后,继续扫描换行符并填充缓冲区。
    这可以重复多次[尤其是如果你正在做 fread ]。剩下的任何内容都可用于下一个流读取操作(例如 freadfgetsfgetc )。
    您可以使用 setlinebuf 设置流缓冲区的大小.为了效率,典型的默认大小是机器页面大小 [IIRC]。
    因此,流缓冲区“比你领先一步”,可以这么说。它的操作很像一个环形队列[实际上,如果不是现实的话]。

    不知道,但行缓冲 [或任何缓冲模式] 通常用于输出文件(例如,默认设置为 stdout)。它说,如果你看到一个换行符,做一个隐含的 fflush .全缓冲意味着做 fflush当缓冲区已满时。无缓冲意味着做 fflush在每个字符上。
    如果你打开一个输出日志文件,你会得到完整的缓冲[最有效],所以如果你的程序崩溃,你可能不会得到最后 N 行的输出(即它们仍然在缓冲区中待处理)。您可以设置行缓冲,以便在程序崩溃后获得最后一行。
    在输入时,行缓冲对文件 [AFAICT] 没有任何意义。它只是尝试使用最有效的大小(例如流缓冲区大小)。
    我认为重要的一点是,在输入时,您事先不知道换行符在哪里,所以 _IOLBF像任何其他模式一样运作——因为它必须如此。 (即)您将 read(2) 读到流 buf 大小(或完成未完成的 fread 所需的数量)。换句话说,唯一重要的是内部缓冲区大小和 fread 的大小/计数参数。而不是缓冲模式。

    对于 TTY 设备(例如 stdin),流将等待换行符 [除非您在底层 fildes(例如 0)上使用 TIOC* ioctl 来设置一次字符又名原始模式],无论流模式如何。那是因为 TTY 设备规范处理层 [在内核中] 将阻止读取(例如,这就是为什么您可以键入退格等而无需应用程序处理它)。
    然而,做fgets在 TTY 设备/流上将在内部得到特殊处理(例如)它将执行选择/轮询并获取待处理字符的数量并仅读取该数量,因此它不会阻止读取。然后它将寻找换行符,如果没有找到换行符,则重新发出选择/轮询。但是,如果找到换行符,它将从 fgets 返回.换句话说,它会做任何必要的事情来允许标准输入上的预期行为。如果用户输入 10 个字符 + 换行符,它不会阻止读取 4096 字节。

    更新:
    回答您的第二轮跟进问题

    I see the tty subsystem and the stdio code running in the process as completely independent. The only way they interface is by the process issuing read syscalls; these may block or not, and this is what depends on the tty settings.


    通常情况下,这是真的。大多数应用程序不会尝试调整 TTY 层设置。但是,如果应用程序愿意,它可以这样做,但不能通过任何流/stdio 函数。

    But the process is completely unaware of those settings and can't change them.


    同样,通常是正确的。但是,同样,过程可以改变它们。

    If we're on the same page, what you're saying implies that a setvbuf call will change the buffering policy of the tty device, and I find that hard to reconcile with my understanding of Unix I/O.


    setvbuf只设置流缓冲区大小和策略。它与内核完全无关。内核只看到 read(2)并且不知道应用程序是原始的还是流通过 fread 来的[或 fgets ]。它不会以任何方式影响 TTY 层。
    fgetc 上循环的普通应用程序中用户输入 abcdef\n , fgetc将阻塞 [in the driver] 直到输入换行符。这是执行此操作的 TTY 规范处理层。然后,当输入换行符时, read(2)fgetc 完成将返回值 7 .第一 fgetc将返回,其余六个将快速发生,从流的内部缓冲区中完成。

    However ...


    更复杂的应用程序可能会通过 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;
    }

    关于unix - C stdio 输入流如何实现行缓冲?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34806490/

    42 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com