GNU/Linux C 库I/O缓冲机制

简介: GNU/Linux C 库I/O缓冲机制

0.引子


对于使用C语言开发者着而言,I/O库可能是最为经常的使用的程序库,当你需要调试的时候可能最为经常使用的就是printf打印了。可是不知道大家,是不是遇到过这样一个问题,那就是我明明打印了,为什么终端里没有输出呢?为什么日志没有存到文件里?我也遇到过这样的问题,当时也没有搞清楚问题的原因。今天,在读UNIX高级环境编程的fork一节时,意外的收获了对于该问题最终的解释,一切的原因都是因为I/O标准库的缓冲机制,下面就来详细介绍一下,I/O标准库的缓冲机制。


1.细节


在Linux系统下,write函数是无缓冲的,I/O标准库是带缓冲的,并且,I/O在不同的情境下,缓冲方式也是不同。首先通过一个例子说明一下,I/O标准库的缓冲机制。


#include "apue.h"
#if defined(MACOS)
#define _IO_UNBUFFERED  __SNBF
#define _IO_LINE_BUF  __SLBF
#define _IO_file_flags  _flags
#define BUFFERSZ(fp)  (fp)->_bf._size
#else
#define BUFFERSZ(fp)  ((fp)->_IO_buf_end - (fp)->_IO_buf_base)
#endif
void  pr_stdio(const char *, FILE *);
int
main(void)
{
  FILE  *fp;
  fputs("enter any character\n", stdout);
  if (getchar() == EOF)
    err_sys("getchar error");
  fputs("one line to standard error\n", stderr);
  pr_stdio("stdin",  stdin);
  pr_stdio("stdout", stdout);
  pr_stdio("stderr", stderr);
  if ((fp = fopen("/etc/passwd", "r")) == NULL)
    err_sys("fopen error");
  if (getc(fp) == EOF)
    err_sys("getc error");
  pr_stdio("/etc/passwd", fp);
  exit(0);
}
void
pr_stdio(const char *name, FILE *fp)
{
  printf("stream = %s, ", name);
  /*
   * The following is nonportable.
   */
  if (fp->_IO_file_flags & _IO_UNBUFFERED)
    printf("unbuffered");
  else if (fp->_IO_file_flags & _IO_LINE_BUF)
    printf("line buffered");
  else /* if neither of above */
    printf("fully buffered");
  printf(", buffer size = %d\n", BUFFERSZ(fp));
}


==*注意,==*在打印缓冲区状态之前,先对每个流执行I/O操作,第一个I/O操作通常造成为该流分配缓冲。结构成员_IO_file_flags、_IO_buf_base、_IO_buf_end和常量_IO_UNBUFFERED、_IO_LINE_BUFFERD是Linux中GNU标准I/O库定。应当了解,其他UNIX系统可能会有不同的标准I/O库实现。 如果运行两次上面的示例程序,一次使三个标准流与终端相连接,另一次使它们重定向到普通文件,则所得结果是:


$./buf              stdin、stdout、stderr都连至终端
  enter any character
  one line to standard error
  stream = stdin, line buffered, buffer size = 1024
  stream = stdout, line buffered, buffer size = 1024
  stream = stderr, unbuffered, buffer size = 1
  stream = /etc/passwd, fully buffered, buffer size = 4096
  $./buf < ./buf.c > std.out 2> std.err     三个流都重定向,再次运行该程序
  enter any character
  stream = stdin, fully buffered, buffer size = 4096
  stream = stdout, fully buffered, buffer size = 4096
  stream = stderr, unbuffered, buffer size = 1
  stream = /etc/passwd, fully buffered, buffer size = 4096


从中可以见,该系统默认的情况是:


  1. 当标准输入、输出连至终端时,它们时行缓冲的。航缓冲区长度是1024字节。(行缓冲区的输出条件是:缓冲区满或者遇到换行符,这就是我们为什么在printf的末尾需要添加'\n'的原因了)。注意,这并没有将输出入、输出的长度限制为1024字节,这只是缓冲区的长度。如果要将2048字节的行写到标准输出,则要进行两次write系统调用


  1. 当将这两个流重定向到普通文件时,它们就会变成是全缓冲的,其缓冲区长度是该文件系统优先选用的I/O长度(从stat结构中得到的st_blksize,也就是文件系统块的大小)。


  1. 标准出错在任何情况下都是无缓冲的,而对于普通文件按照系统默认是全缓冲的。


  1. 对于全缓冲而言,缓冲区清空或者说冲刷的时机是缓冲区满或者手动调用I/O标准库的fflush函数,该函数会将标准I/O库所使用的缓冲区的内容全部输出,但是并不代表这些内容已经写到文件系统中了,为了确保内容全部保存到物理文件中,需要再次调用sync或者fsync函数。


  1. 文章开头说write系统调用是无缓冲的,为什么这么设计呢?其实,这是Unix系统的设计精髓”机制与策略分离原则“,Unix提供了write这种与文件系统或者设备通信的机制,而标准I/O库就是基于该机制实现的带缓冲的策略。


  1. 通过上面的分析文章开头所讲的那些奇怪的问题相信大家都应该理解是什么原因了,所以看似简单的问题,其实背后隐藏了太多太多的学问了,以后不能轻视任何一个看似简单的"小问题”了,以小见大。


相关文章
|
2天前
|
存储 Unix Linux
Linux I/O 重定向与管道
【8月更文挑战第17天】重定向在Linux中改变命令I/O流向,默认有&quot;&gt;&quot;覆盖输出至文件及&quot;&gt;&gt;&quot;追加输出至文件末尾,便于保存结果;使用&quot;&lt;&quot;从文件读取输入而非键盘,高效处理数据。文件描述符如0(stdin)、1(stdout)、2(stderr)标识I/O资源,支持读写操作。管道以&quot;|&quot;连接命令,使前一命令输出成为后一命令输入,如排序用户或找出CPU占用最高的进程,构建复杂数据处理流程。
16 9
|
4天前
|
存储 缓存 编译器
Linux源码阅读笔记06-RCU机制和内存优化屏障
Linux源码阅读笔记06-RCU机制和内存优化屏障
|
5天前
|
存储 Unix Linux
Linux I/O 重定向与管道
【8月更文挑战第14天】输出重定向可将命令结果存入文件,如`&gt;`覆盖写入或`&gt;&gt;`追加写入。输入重定向从文件读取数据,如`&lt;`代替键盘输入。这些操作利用文件描述符(如0:stdin, 1:stdout, 2:stderr)管理I/O。管道`|`连接命令,使前一命令输出作为后一命令输入,便于数据处理,如排序用户`sort -t: -k3 -n /etc/passwd | head -3`或查找CPU占用高的进程`ps aux --sort=-%cpu | head -6`。
17 4
|
5天前
|
Unix Linux Shell
Linux I/O 重定向简介
Linux I/O 重定向简介
15 2
|
1月前
|
Oracle 关系型数据库 Linux
讲解linux下的Qt如何编译oracle的驱动库libqsqloci.so
通过这一连串的步骤,可以专业且有效地在Linux下为Qt编译Oracle驱动库 `libqsqloci.so`,使得Qt应用能够通过OCI与Oracle数据库进行交互。这些步骤适用于具备一定Linux和Qt经验的开发者,并且能够为需要使用Qt开发数据库应用的专业人士提供指导。
49 1
讲解linux下的Qt如何编译oracle的驱动库libqsqloci.so
|
18天前
|
Linux 开发者
深入理解Linux I/O模型:同步、异步、阻塞与非阻塞
【8月更文挑战第1天】在探索操作系统的奥秘中,I/O模型作为影响性能的关键因素之一,常常让开发者们感到困惑。本文将通过浅显易懂的语言和实际代码示例,揭示Linux下同步与异步、阻塞与非阻塞的概念及其区别,并指导如何在实际应用中选择合适的I/O模型以优化程序性能。
16 1
|
1月前
|
缓存 监控 关系型数据库
深入理解Linux操作系统的内存管理机制
【7月更文挑战第11天】在数字时代的浪潮中,Linux操作系统凭借其强大的功能和灵活性,成为了服务器、云计算以及嵌入式系统等领域的首选平台。内存管理作为操作系统的核心组成部分,对于系统的性能和稳定性有着至关重要的影响。本文将深入探讨Linux内存管理的基本原理、关键技术以及性能优化策略,旨在为读者提供一个全面而深入的理解视角,帮助开发者和系统管理员更好地优化和管理Linux系统。
|
1月前
|
小程序 Linux
【编程小实验】利用Linux fork()与文件I/O:父进程与子进程协同实现高效cp命令(前半文件与后半文件并行复制)
这个小程序是在文件IO的基础上去结合父子进程的一个使用,利用父子进程相互独立的特点实现对数据不同的操作
|
1月前
|
缓存 网络协议 算法
【Linux系统编程】深入剖析:四大IO模型机制与应用(阻塞、非阻塞、多路复用、信号驱动IO 全解读)
在Linux环境下,主要存在四种IO模型,它们分别是阻塞IO(Blocking IO)、非阻塞IO(Non-blocking IO)、IO多路复用(I/O Multiplexing)和异步IO(Asynchronous IO)。下面我将逐一介绍这些模型的定义:
|
2月前
|
存储 缓存 Linux
Linux VFS机制详解
Linux VFS机制详解
48 1