下面是一段类似日志记录的代码,已获取通讯的报文内容和当时的环境参数内容,就是创建一个文件,使用标准IO的fopen、fprintf进行输出记录。但是在调试中,刚开始我就傻眼了,文件创建成功了,但是实时查看竟然没有任何数据记录。经过半天的担惊受怕和反复排查,发现是被标准IO的缓冲机制摆了一道,惭愧呀。。。

  代码转自http://blog.csdn.net/mr_chenping/article/details/9166937

  下面给出一个示例程序,模拟我的项目程序:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
int main()
{
FILE* fp=NULL;
const char *filename_1="test_fprintf.log";
const char *filename_2="test_write.log";
int fd;
fp = fopen(filename_1, "wb");
if(fp == NULL)
{
printf("open %s failed, %s\n", filename_1, strerror(errno));
return -1;
}
//setbuf(fp, NULL);
//设置NULL为标准IO自动分配
//设置_IONBF为不对IO进行缓冲
//setvbuf(fp, NULL, _IONBF, 0);
fd = open(filename_2, O_WRONLY|O_CREAT|O_EXCL, 0666);
if(fd < 0)
{
printf("open %s failed, %s\n", filename_2, strerror(errno));
return -1;
}
while(1)
{
fprintf(fp, "test fprintf.\n");
fprintf(fp, "-------test fprintf.\n");
fprintf(fp, "=======test fprintf.\n");
//可以进行刷新,强制将全缓冲区数据传递到内核高速缓冲区中
//有内核完成写磁盘操作
//fflush(fp);
write(fd, "test open.\n", sizeof("test open.\n"));
write(fd, "--------test open.\n", sizeof("--------test open.\n"));
write(fd, "--------test open.\n", sizeof("--------test open.\n"));
sleep(1);
}
return 0;
}

  后台运行上面的示例程序,然后实时查看两个日志文件,会发现testfrpintf.log文件一开始一直都是空的,而testwrite.log则是不断有数据写入,如下状态: 

  我当时就是奇怪为什么文件会是空的。可以看出标准IO会缓冲4096Bytes的数据,当达到这么多数据时才会进行实际的磁盘写入,而系统调用write则是直接写入,不进行缓冲。

  标准IO库提供缓冲的目的是尽可能减少使用read和write调用的次数,降低执行IO的时间,它提供三种类型的缓冲:

  1. 全缓冲。在填满标准IO缓冲区4096Bytes后(缓冲区已满)才进行实际IO操作(通过write系统调用,将数据传递到内核高速缓冲区,最终内核将数据写入磁盘),对于磁盘文件通常就是全缓冲,上面的示例就是采用缓冲。
  2. 行缓冲。在输入和输出中遇到换行符时(缓冲区已满)进行实际的IO操作(通过write系统调用,将数据传递到内核高速缓冲区,最终内核将数据写入磁盘),当涉及到一个终端时,通常使用行缓冲。使用最频繁的printf函数就是采用行缓冲,所以感觉不出缓冲的存在。
  3. 不带缓冲。标准IO库不对字符进行缓冲存储。标准出错流stderr通常是不带缓冲的。

  ISO C要求下列缓冲特征:

  • 当且仅当标准输入和标准输出并不涉及交互式设备时,它们才是全缓冲的。
  • 标准出错决不会是全缓冲。

  很多系统默认使用下列类型的缓冲:

  • 标准出错是不带缓冲的。
  • 如若是涉及终端设备的其它流,则他们是行缓冲的;否则是全缓冲的。

  当然,对于标准IO流,我们也可以更改缓冲类型,或者是直接刷新。ISO C中提供下面两个函数以更改缓冲类型:

void setbuf(FILE *fp, char *buf); //buf为NULL,表示关闭缓冲
int setvbuf(FILE *fp, char *buf, int mode, size_t size); //成功返回0,出错返回非0值

  setvbuf函数中的mode参数可以为:_IOBUF 全缓冲, _IOLBF 行缓冲, _IONBF 不带缓冲,如果buf为NULL, 则标准IO库将自动地为该流分配适当长度(常量BUFSIZ)的缓冲区。一般而言,应由系统选择缓冲区的长度,并自动分配缓冲区,这样关闭流时,标准IO库将自动释放缓冲区。

  强制冲洗一个流,使用函数:

int fflush(FILE *fp); //成功返回0, 出错返回EOF

  项目中我是使用这个函数解决郁闷的。

 fflush(NULL); //冲洗所有输出流

  补充一下知识点:

  read()和write()系统调用在操作磁盘文件时不会直接发起磁盘请求,而是仅仅在用户空间缓冲区与内核缓冲区高速缓存之间复制数据。例如下面调用将3个字节的数据从用户空间内存传递到内核空间的缓冲区中。

  write(fd,"abc",3);

  write()随机返回。在后续某个时刻,内核会将其缓冲区中的数据写入(刷新至)磁盘。(因此,可以说系统调用与磁盘操作并不同步)

  与此同理,对输入而言,内核从磁盘中读取数据并存储到内核缓冲区中。read()调用将从该缓冲区中读取数据,直至把缓冲区中的数据读完,这时,内核会将文件的下一段内容读入缓冲区高速缓存。

  这样设计,使得read()和write()很快,不需要等待(缓慢的)磁盘操作。同时,这一设计也极为高效,因为这减少了内核必须执行的磁盘传输次数。(预读和满写)

  两句话:

  1.read()和write()负责在用户空间缓冲区和内核高速缓冲区高速缓存复制数据。

  2.内核负责从磁盘读数据到内核高速缓冲区(预读),以及当内核高速缓冲区满了,写到磁盘中去(满写)。

  总结一下:

  自上而下,首先是通过stdio库将用户数据传递到stdio缓冲区(一般是4096Bytes,或者也可以有标准IO自动分配),该缓冲区位于用户态内存区。当缓冲区满时(行缓冲遇到‘\n',全缓冲满4096Bytes),stdio库会调用write()系统调用,将数据传递到内核高速缓冲区(位于内核态内存区)。最终,内核发起磁盘操作,将数据传递到磁盘。

  使用fflush()强制刷新stdio缓冲区(通过write()调用),将数据传递到内核高速缓冲区中。

  fsync() syn()系统调用将使缓冲数据和与打开文件描述符fd相关的所有元数据都刷新到磁盘上。

  首先要明白不带缓冲的概念:所谓不带缓冲,并不是指内核不提供缓冲,而是只单纯的系统调用,不是函数库的调用。系统内核对磁盘的读写都会提供一个块缓冲,当用write函数对其写数据时,直接调用系统调用,将数据写入到块缓冲进行排队,当块缓冲达到一定的量时,才会把数据写入磁盘。因此所谓的不带缓冲的I/O是指进程不提供缓冲功能。每调用一次write或read函数,直接系统调用。
  而带缓冲的I/O是指进程对输入输出流进行了改进,提供了一个流缓冲,当用fwrite函数网磁盘写数据时,先把数据写入流缓冲区中,当达到一定条件,比如流缓冲区满了,或刷新流缓冲,这时候才会把数据一次送往内核提供的块缓冲,再经块缓冲写入磁盘。
  因此,带缓冲的I/O在往磁盘写入相同的数据量时,会比不带缓冲的I/O调用系统调用的次数要少。

  最后,以一幅图总结一下。

                    

Linux系统编程之IO_缓冲和非缓冲的更多相关文章

  1. linux系统编程之I/O内核数据结构

    文件在内核中是用三种数据结构进行表示的 (1)文件描述符表:文件描述符表是一个结构体数组,数组的下标就是open函数返回的文件描述符. 文件描述符表的每一个记录有两个字段   *文件描述符标志 * 文 ...

  2. linux系统编程之lseek帮助文档

    通过man 2 lseek可以查看linux中的系统函数lseek函数的帮助文档,为了更好的学习,我把这些重要内容翻译过来 NAME lseek - reposition read/write fil ...

  3. java 非缓冲与缓冲数据读取比较

    首先不适用缓存技术,读取数据: //非缓冲计时 package com.swust; import java.io.*; /* *功能:创建一个程序,写10000个随机双精度的数到一个文件中,同时测试 ...

  4. java 非缓冲与缓冲数据写入比较

    //非缓冲计时package com.swust; import java.io.*; /* *功能:创建一个程序,写10000个随机双精度的数到一个文件中,同时测试运用缓冲和非缓冲技术 * 进行这种 ...

  5. linux文件IO操作篇 (一) 非缓冲文件

    文件IO操作分为 2 种 非缓冲文件IO 和 缓冲文件IO 它们的接口区别是 非缓冲 open() close() read() write() 缓冲 fopen() fclose() fread() ...

  6. Linux设备驱动中的阻塞和非阻塞I/O

    [基本概念] 1.阻塞 阻塞操作是指在执行设备操作时,托不能获得资源,则挂起进程直到满足操作所需的条件后再进行操作.被挂起的进程进入休眠状态(不占用cpu资源),从调度器的运行队列转移到等待队列,直到 ...

  7. linux c编程之fcntl

    fcntl可实现对指定文件描述符的各种操作,其函数原型如下: int fcntl(int fd, int cmd, ... /* arg */ ); 其中,操作类型由cmd决定.cmd可取如下值: F ...

  8. 20個命令行工具監控 Linux 系統性能

    對於每個系統管理員或網路管理員來說,每天要監控和調試 Linux 系統性能問題都是非常困難的工作.我已經有5年 Linux 管理員的工作經歷,知道如何監控系統使其保持正常運行.為此,我們編寫了對於 L ...

  9. Linux设备驱动中的阻塞和非阻塞I/O <转载>

    Green 博客园 首页 新随笔 联系 订阅 管理 Linux设备驱动中的阻塞和非阻塞I/O   [基本概念] 1.阻塞 阻塞操作是指在执行设备操作时,托不能获得资源,则挂起进程直到满足操作所需的条件 ...

随机推荐

  1. JAVA 各种数值类型最大值和最小值 Int, short, char, long, float,&nbs

    转载地址:http://blog.sina.com.cn/s/blog_5eab3d430101fdv6.html 代码片段: fmax = Float.MAX_VALUE; fmin = Float ...

  2. MongoDB安装

    安装 1>设置MongoDB目录 cd /home/apps      附:centOS下创建目录命令  mkdir /home/apps 2>下载mongodb curl -O http ...

  3. WebService -- Java 实现之 CXF ( 添加系统预定义的拦截器)

    1. 概述 CXF允许我们在webservice的in/out位置添加拦截器.拦截器有两大分类,一类是系统预定义的:另一类是自定义拦截器. 2. 在server端添加拦截器. JaxWsServerF ...

  4. 设计模式--桥接模式Bridge(结构型)

    一.概述 在软件系统中,某些类型由于自身的逻辑,它具有两个或者多个维度的变化,如何应对这种"多维度的变化",就可以利用桥接模式. 引例: 设想如果要绘制矩形.圆形.椭圆.正方形,我 ...

  5. 关于js中window.location.href,location.href,parent.location.href,top.location.href的用法

    "window.location.href"."location.href"是本页面跳转 "parent.location.href"是上一 ...

  6. 修改git的远程仓库命令

    1. 修改命令 git remte origin set-url URL 2.先删后加 git remote rm origin git remote add origin git@github.co ...

  7. ios 项目的.gitignore

    git作为代码管理工具,.gitignore文件用来忽略哪些哪些文件不用添加到仓库管理https://www.gitignore.io/ 这个网址输入变成语言会帮你生成常用的忽略文件如:IOS项目,输 ...

  8. MVC 添加Area

    在MVC项目中经常会使用到Area来分开不同的模块让项目结构更加的清晰. 步骤如下: 项目 –> 添加 -> 区域 ( Area ) 输入 Admin 添加成功后 Area包含: 创建一个 ...

  9. js中属性节点的应用

    <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/ ...

  10. python笔记7:日期和时间

    Python 提供了一个 time 和 calendar 模块可以用于格式化日期和时间. 时间间隔是以秒为单位的浮点小数. 每个时间戳都以自从1970年1月1日午夜(历元)经过了多长时间来表示. 时间 ...