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


相关文章
|
1月前
|
缓存 Linux 开发者
Linux内核中的并发控制机制
本文深入探讨了Linux操作系统中用于管理多线程和进程的并发控制的关键技术,包括原子操作、锁机制、自旋锁、互斥量以及信号量。通过详细分析这些技术的原理和应用,旨在为读者提供一个关于如何有效利用Linux内核提供的并发控制工具以优化系统性能和稳定性的综合视角。
|
10天前
|
存储 编译器 Linux
动态链接的魔法:Linux下动态链接库机制探讨
本文将深入探讨Linux系统中的动态链接库机制,这其中包括但不限于全局符号介入、延迟绑定以及地址无关代码等内容。
125 17
|
4月前
|
安全 Linux vr&ar
Linux的动态库和静态库
Linux的动态库和静态库
|
18天前
|
监控 算法 Linux
Linux内核锁机制深度剖析与实践优化####
本文作为一篇技术性文章,深入探讨了Linux操作系统内核中锁机制的工作原理、类型及其在并发控制中的应用,旨在为开发者提供关于如何有效利用这些工具来提升系统性能和稳定性的见解。不同于常规摘要的概述性质,本文将直接通过具体案例分析,展示在不同场景下选择合适的锁策略对于解决竞争条件、死锁问题的重要性,以及如何根据实际需求调整锁的粒度以达到最佳效果,为读者呈现一份实用性强的实践指南。 ####
|
22天前
|
消息中间件 安全 Linux
深入探索Linux操作系统的内核机制
本文旨在为读者提供一个关于Linux操作系统内核机制的全面解析。通过探讨Linux内核的设计哲学、核心组件、以及其如何高效地管理硬件资源和系统操作,本文揭示了Linux之所以成为众多开发者和组织首选操作系统的原因。不同于常规摘要,此处我们不涉及具体代码或技术细节,而是从宏观的角度审视Linux内核的架构和功能,为对Linux感兴趣的读者提供一个高层次的理解框架。
|
29天前
|
算法 Linux 开发者
Linux内核中的锁机制:保障并发控制的艺术####
本文深入探讨了Linux操作系统内核中实现的多种锁机制,包括自旋锁、互斥锁、读写锁等,旨在揭示这些同步原语如何高效地解决资源竞争问题,保证系统的稳定性和性能。通过分析不同锁机制的工作原理及应用场景,本文为开发者提供了在高并发环境下进行有效并发控制的实用指南。 ####
|
1月前
|
缓存 Linux 开发者
Linux内核中的并发控制机制:深入理解与应用####
【10月更文挑战第21天】 本文旨在为读者提供一个全面的指南,探讨Linux操作系统中用于实现多线程和进程间同步的关键技术——并发控制机制。通过剖析互斥锁、自旋锁、读写锁等核心概念及其在实际场景中的应用,本文将帮助开发者更好地理解和运用这些工具来构建高效且稳定的应用程序。 ####
41 5
|
1月前
|
Linux 数据库
Linux内核中的锁机制:保障并发操作的数据一致性####
【10月更文挑战第29天】 在多线程编程中,确保数据一致性和防止竞争条件是至关重要的。本文将深入探讨Linux操作系统中实现的几种关键锁机制,包括自旋锁、互斥锁和读写锁等。通过分析这些锁的设计原理和使用场景,帮助读者理解如何在实际应用中选择合适的锁机制以优化系统性能和稳定性。 ####
60 6
|
1月前
|
消息中间件 存储 Linux
|
1月前
|
安全 Linux 数据安全/隐私保护
深入探索Linux操作系统的多用户管理机制
【10月更文挑战第21天】 本文将详细解析Linux操作系统中的多用户管理机制,包括用户账户的创建与管理、权限控制以及用户组的概念和应用。通过具体实例和命令操作,帮助读者理解并掌握Linux在多用户环境下如何实现有效的资源分配和安全管理。