unix 标准输入输出缓存

四个月前碰到一个问题
我相信,系统为了提高程序IO效率,会先将输出流缓存起来,不会立即写到磁盘。

但感觉这个理论,不能说服我在帖子里提到了两个疑问:

  1. tail延时5-10秒,才读取到日志。(延时太长令人费解!)
  2. 部分log并不是按逻辑顺序输出。(无法接受!) 比如 PS:
     LOG("1");
     LOGERR("2");
    
     // 在日志文件里,2竟先打印
     2
     1
    

    最近从《UNIX环境高级编程》第五章-标准IO/库读到了相关内容,认为可以解答以上2个疑问。 以下大部分内容摘自抄原文。

    标准I/O提供了以下3种类型的缓冲:

    1. 全缓冲。在这种情况下,填满标准I/O缓存后才进行实际I/O操作。对于驻留在磁盘上的文件通常是由标准I/O实施全缓冲的。   缓存区可由标准I/O例程自动地冲洗(例如,当填满一个缓存区时),或者可以调用函数fflush冲洗一个流。

    2. 行缓冲。在这种情况下,当输入和输出中遇到换行符时,标准I/O库执行I/O操作。这允许我们一次输出一个字符(用标准I/O函数fputc),但只有在写了一行之后才进行实际I/O操作。当流涉及一个终端时(如标准输入和标准输出),通常使用行缓冲。

    3. 不带缓冲。标准I/O库不对字符进行缓冲存储。 例如,若用标准I/O函数fputs写15个字符到不带缓冲的流中,我们就期望这15个字符能立即输出。   标准错误流stderr通常是不带缓冲的,这就使得出错信息可以尽快显示出来,而不管它们是否含有一个换行符。

ISO C 要求下列缓冲特征

  1. 当且仅当标准输入和输出并不指向交互式设备时,它们才是全缓冲的。
  2. 标准错误决不会是全缓冲的。

根据以上说明,我们来分析问题

  1. 程序将日志输出到文本,使用系统默认的缓冲区,只有当填满缓存区时才会将文本写到磁盘文本。缓冲区越大,tail越晚显示新增的内容。 以下例子通过stat获取系统为每个文本定义的适当缓冲区长度。
include <sys/stat.h>
int stat(const char ×restrict pathname, strut stat *restrict buf);

// 读取stat.st_blksize I/O缓冲大小
struct stat st;
stat(path, &st);
bklsize_t size = st.st_blksize;

listDirblkSize遍历我机器某目录下所有文件,I/O缓存区大小都是4096,足可以存储2048个中文。导致5-10秒后才写磁盘也是有可能的。 具体关于stat.st_blksize的详细说明请读者自己查阅。

$ lsb_release -a
LSB Version:	:core-4.1-amd64:core-4.1-noarch
Distributor ID:	Fedora
Description:	Fedora release 27 (Twenty Seven)
Release:	27
Codename:	TwentySeven


$ sh listDirIoblkSize.sh 
file:./1185632884.jpg block size:4096 byte
file:./1915660170.jpg block size:4096 byte
file:./alien-8.95 block size:4096 byte
file:./alien_8.95.tar.xz block size:4096 byte
file:./centos6.9.tar block size:4096 byte
file:./code-1.22.2-1523551168.el7.x86_64.rpm block size:4096 byte
file:./Evernote_6.8.7.6387.exe block size:4096 byte
file:./Evernote.sublime-settings block size:4096 byte
file:./[FHD-1080P]MIRD-114 block size:4096 byte

  1. 我是通过nohup proc_name >> log 2>&1 &启动程序的,自定义的LOGERR,其内部实现是输出到不带缓冲的stderr,所以比stdout先写到磁盘,导致出现日志不按逻辑顺序输出。

可以使用以下两个函数中的一个,更改系统默认能分配的缓存区。

#include
void setbuf(FILE *restrict fp, char *restrict buf);
void stvbuf(FILE *restrict fp, char *restrict buf, int mode, size_t size);
  使用setbuf函数打开或关闭缓冲机制。为了缓冲进行I/O,参数buf必须指向一个长度为BUFSIZE的缓冲区。通常此之后该流就是全缓冲的,但是如果该流与一个终端设备相关,那麽某些系统也可以将其设置为行缓冲的。为了关闭缓冲,将buf设置为NULL。   使用setvbuf,我们可以精确地说明所需的缓冲类型。这是用mode参数实现的:   _IOFBF  全缓冲   _IOLBF  行缓冲   _IONBF  不带缓冲   如果指定一个不带缓冲的流,则忽略buf和size参数。   如果指定全缓冲或行缓冲,则buf和size可选择地指定一个缓冲区及其长度。   如果该流是带缓冲的,而buf是NULL,则标准I/O库将自动地为该流分配适当长度的缓冲区。



原文:
https://lizijie.github.io/2018/04/15/unix-%E6%A0%87%E5%87%86%E8%BE%93%E5%85%A5%E8%BE%93%E5%87%BA%E7%BC%93%E5%AD%98.html
作者github:
https://github.com/lizijie

PREVIOUS常见代码英文词汇缩写
NEXTzlog运行时增加format和rule规则