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


相关文章
|
4月前
|
安全 Linux vr&ar
Linux的动态库和静态库
Linux的动态库和静态库
|
18天前
|
缓存 Linux 开发者
Linux内核中的并发控制机制:深入理解与应用####
【10月更文挑战第21天】 本文旨在为读者提供一个全面的指南,探讨Linux操作系统中用于实现多线程和进程间同步的关键技术——并发控制机制。通过剖析互斥锁、自旋锁、读写锁等核心概念及其在实际场景中的应用,本文将帮助开发者更好地理解和运用这些工具来构建高效且稳定的应用程序。 ####
35 5
|
21天前
|
Linux 数据库
Linux内核中的锁机制:保障并发操作的数据一致性####
【10月更文挑战第29天】 在多线程编程中,确保数据一致性和防止竞争条件是至关重要的。本文将深入探讨Linux操作系统中实现的几种关键锁机制,包括自旋锁、互斥锁和读写锁等。通过分析这些锁的设计原理和使用场景,帮助读者理解如何在实际应用中选择合适的锁机制以优化系统性能和稳定性。 ####
42 6
|
28天前
|
消息中间件 存储 Linux
|
2月前
|
Linux API 开发工具
FFmpeg开发笔记(五十九)Linux编译ijkplayer的Android平台so库
ijkplayer是由B站研发的移动端播放器,基于FFmpeg 3.4,支持Android和iOS。其源码托管于GitHub,截至2024年9月15日,获得了3.24万星标和0.81万分支,尽管已停止更新6年。本文档介绍了如何在Linux环境下编译ijkplayer的so库,以便在较新的开发环境中使用。首先需安装编译工具并调整/tmp分区大小,接着下载并安装Android SDK和NDK,最后下载ijkplayer源码并编译。详细步骤包括环境准备、工具安装及库编译等。更多FFmpeg开发知识可参考相关书籍。
98 0
FFmpeg开发笔记(五十九)Linux编译ijkplayer的Android平台so库
|
4月前
|
存储 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占用最高的进程,构建复杂数据处理流程。
50 9
|
4月前
|
监控 Linux
在Linux中,如何监控磁盘I/O性能?
在Linux中,如何监控磁盘I/O性能?
|
4月前
|
Linux API
在Linux中,程序产生了库日志虽然删除了,但磁盘空间未更新是什么原因?
在Linux中,程序产生了库日志虽然删除了,但磁盘空间未更新是什么原因?
|
4月前
|
存储 缓存 编译器
Linux源码阅读笔记06-RCU机制和内存优化屏障
Linux源码阅读笔记06-RCU机制和内存优化屏障
|
4月前
|
Linux
Linux的I/O操作
Linux的I/O操作