zoukankan      html  css  js  c++  java
  • Linux系统编程之IO_缓冲和非缓冲

      下面是一段类似日志记录的代码,已获取通讯的报文内容和当时的环境参数内容,就是创建一个文件,使用标准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
    ", 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
    ", filename_2, strerror(errno));
                    return -1;
            }
            while(1)
            {
                    fprintf(fp, "test fprintf.
    ");
                    fprintf(fp, "-------test fprintf.
    ");
                    fprintf(fp, "=======test fprintf.
    ");
                    //可以进行刷新,强制将全缓冲区数据传递到内核高速缓冲区中
                    //有内核完成写磁盘操作
                    //fflush(fp);
                    write(fd, "test open.
    ", sizeof("test open.
    "));
                    write(fd, "--------test open.
    ", sizeof("--------test open.
    "));
                    write(fd, "--------test open.
    ", sizeof("--------test open.
    "));
                    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自动分配),该缓冲区位于用户态内存区。当缓冲区满时(行缓冲遇到‘ ',全缓冲满4096Bytes),stdio库会调用write()系统调用,将数据传递到内核高速缓冲区(位于内核态内存区)。最终,内核发起磁盘操作,将数据传递到磁盘。

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

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

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

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

                                  

  • 相关阅读:
    HDU 1010 Tempter of the Bone
    HDU 4421 Bit Magic(奇葩式解法)
    HDU 2614 Beat 深搜DFS
    HDU 1495 非常可乐 BFS 搜索
    Road to Cinema
    Sea Battle
    Interview with Oleg
    Spotlights
    Substring
    Dominating Patterns
  • 原文地址:https://www.cnblogs.com/xieweichong/p/4241029.html
Copyright © 2011-2022 走看看