unix 标准输入输出缓存

原文:https://lizijie.github.io/
github:https://github.com/lizijie


四个月前碰到一个问题
为了提高程序IO效率,会对输出流进行缓冲管理,不会立即写到磁盘。但我在帖子里提到了两个疑问:
1. tail延时5-10秒,才读取到日志
2. 部分log并不是按逻辑顺序输出。比如

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 <stdio.h>
void setbuf(FILE *restrict fp, char *restrict buf);
void stvbuf(FILE *restrict fp, char *restrict buf, int mode, size_t size);

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

阅读更多
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/MAOMAOXIAOHUO/article/details/79950686
文章标签: unix io
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

关闭
关闭
关闭