【问题标题】:About the unbuffered I/O in UNIX Systems关于 UNIX 系统中的无缓冲 I/O
【发布时间】:2014-03-15 15:26:10
【问题描述】:

我最近在阅读 APUE 时出现了一个基本问题。 我的代码如下所示


#include <apue.h>
#define BUFFSIZE 4096
int main()
{
    int n;
    char buf[BUFFSIZE];
    while((n = read(STDIN_FILENO, buf, BUFFSIZE)) > 0)
    {
        printf("n is %d\n", n);          //this line is added by me for testing
        if(write(STDOUT_FILENO, buf, n) != n)
            err_sys("write error");  //functions defined by the book to print error message
    }

    if(n < 0)
        err_sys("read error");
    exit(0);
}

编译后,当我运行程序时如下图所示

> $ ./mycat
123456[enter]
n is 7
123456
1234[enter]
n is 5
1234

它似乎根据我的代码结构工作。而且我不太了解 'enter' 的功能。每次我按下 'enter' 时,读取函数都会终止,并将包括由 'enter' 生成的 '\n' 在内的字符传递给写入函数。所以它进入循环内部,首先打印读取的字符数。


但是,下面的测试似乎与上述和代码结构背道而驰。

> $ ./mycat > data
123456[enter]
1234[enter]
^D
> $ cat data
123456
1234
n is 7
n is 5

似乎程序首先将所有字符写入文件然后打印'n'的值,但根据我的理解,它应该首先打印如下

n is 7
123456
n is 5
1234

我想了又想,就是想不通。你能帮帮我吗?

【问题讨论】:

    标签: c file-io eol


    【解决方案1】:

    write()缓冲的。 printf()ing 到 stdout 缓冲,但在某种程度上取决于输出的去向。

    如果 stdout 的输出进入控制台,则它是行缓冲的,如果不是,它是 fully 缓冲的,在您的第二个示例中,这会导致在程序结束时被刷新,而来自打电话给write() 马上出去。

    来自man stdio

    [...] 标准输入和输出流完全 当且仅当流不引用交互时才缓冲 设备。

    引用终端设备的输出流总是行 默认缓冲;

    【讨论】:

      【解决方案2】:

      先一个解决办法,把readwrite改成fread and fwrite

      #include <apue.h>
      #define BUFFSIZE 4096
      int main()
      {
          int n;
          char buf[BUFFSIZE];
          while((n = fread(buf, 1, BUFFSIZE, stdin)) > 0)
          {
              printf("n is %d\n", n);          //this line is added by me for testing
              if(fwrite(buf, 1, n, stdout) != n) {
                  // note: if err_sys depend on errno, it may print wrong error
                  err_sys("write error");
              }
          }
      
          if(ferror(stdin)) {
              // note: if err_sys depend on errno, it may print wrong error
              err_sys("read error");
          }
          exit(0);
      }
      

      代码注意事项:

      • 此处使用fread 是可选的,因为否则您不会从stdio 读取。
      • fread and fwrite 采用元素大小和元素数量来确定应该写入多少。部分元素将不会被读取,因此 元素大小 1(不是计数 1)通常是文本所需要的。
      • 错误处理和返回值存在差异,stdio 函数对errno 的设置不是很明确,更多信息请参见here

      最后简短解释:stdio 输入和输出被缓冲。较低级别的文件描述符 IO(openclosereadwrite 等)没有缓冲并完全绕过 stdio 缓冲。 这些不应该在同一个文件中混合,因为即使你尝试这样做,它也很容易混淆缓冲细节,以便它“应该”工作。即使你让它在你的操作系统上工作,当为不同的操作系统和库编译时它也可能会中断。所以不要这样做,而是对同一个打开的文件使用其中一个或另一个。

      【讨论】:

        【解决方案3】:

        有几种缓冲方式。程序的输入由伪终端设备的行缓冲规程进行缓冲。在输出端,有一个文件系统缓存(操作系统中用于整个文件的缓冲区),以及在打印到FILE * 类型时在 C 程序中的额外缓冲。但是readwrite 绕过FILE * 缓冲,或多或少地直接将数据移入/移出文件系统缓存。

        因此,当所有输出都进入终端时,您的stdout 缓冲区似乎会自动刷新,但在重定向到文件时不会。所以我建议添加对

        的调用
        fflush(stdout);
        

        printf 调用之后。这应该显式刷新缓冲区(并强制执行您想要的输出排序)。

        需要注意的重要一点是,当您使用 FILE *s 时,它是由库函数(如 fopen)操作的 C 级结构,以及当您使用原始文件描述符(只是一个整数,但指的是底层操作系统文件)。 FILE 数据类型是围绕这个较低级别的 Unix 实现细节的包装器。 FILE 函数实现了额外的缓冲层,因此较低级别可以对更大的块进行操作,并且您可以高效地执行逐字节处理,而无需进行大量 I/O 握手。

        【讨论】:

        • 所以输入的字符是行缓冲的,一旦按下'enter'就会被传输?
        • 是的,完全正确。这发生在伪终端设备中并且不受(直接)程序控制,因此readscanf 都被缓冲(或无缓冲,如果你使用类似system("stty -raw") 的东西)。
        猜你喜欢
        • 1970-01-01
        • 2012-01-14
        • 2015-07-05
        • 2010-10-16
        • 1970-01-01
        • 2010-09-21
        • 1970-01-01
        • 1970-01-01
        • 2011-10-06
        相关资源
        最近更新 更多