现在的位置: 首页 > 综合 > 正文

IO编程 之 缓冲篇

2018年02月17日 ⁄ 综合 ⁄ 共 2148字 ⁄ 字号 评论关闭

涉及linux中IO编程的时候,我们总是不可避免地要跟各种各样的缓冲机制打交道,学好linux IO编程不是简单地掌握几个函数的用法就万事大吉,正确地运用IO需要对其缓冲机制全面的理解,切不可依葫芦画瓢,蒙混过关。


切入正题之前,我们先看一个简单的程序:


#include <stdio.h>
#include <errno.h>

#include <unistd.h> 


#define BUFSIZE 5*1024

int
main(void){
    FILE *fp1, *fp2;
    char buf[BUFSIZE];

    fp1 = fopen("src", "r");
    fp2 = fopen("des", "w");

    fgets(buf, BUFSIZE, fp1);
    fputs(buf, fp2);

    sleep(30);
    return 0;
}


其中src是一个只有一行的文本文件,这一行有4500个字符(即占用4.5k存储空间)。


注意到程序倒数第二行睡眠本分钟,我们在此期间查看两个相关文件,如下:

sleep结束之前src和des的大小

 

 

 

 

figure1. sleep函数结束之前两个文件src和des的大小 


程序运行结束后,我们再次查看两个相关文件,如下:

sleep结束之后src和des的大小

figure2. sleep函数结束之后两个文件src和des的大小


从这个图1可以清晰地看到,fputs函数在既没有遇到换行符,也没有被强制冲洗的情况下输出了4k大小的字符,原因在于标准IO库函数自动分配的缓存区满了,自动进行了缓存区冲洗,从而导致了真正的IO操作。记住,这4k数据是由于缓冲区满了之后通过fputs函数被刷新 出去的,而剩下的字符则跟fputs没有任何关系了,它们是由于程序最后调用了return 语句,退出进程的时候系统自动刷新所有的IO流以及关闭所有打开的文件句柄的时候被刷新至内核的。

 

举着个例子的原因在于,很多人不明白UNIX中所谓的行缓冲、全缓冲以及无缓冲本质是什么,总是很容易跟在程序中调用的fgets函数、fread函数的读取方式相混淆,甚至跟自定义的缓冲区(如上面例子中提到的buf)相混淆。

 

我们要搞清楚上面例子中的那些关系,必须着重强调的一点是:用户空间中实际开辟了两个缓冲区,一个是看得见那个buf,就是我们自己手工创建的,另一个是库函数中为我们创建的4k缓冲区。

 

这个缓冲区是怎么来的呢?注意到这句定义文件指针的语句没有:FILE *fp;其中FILE就是一个结构体,我们打开一个文件之后就利用句柄fp与实际内存中与此文件对应的那个结构体关联起来,这个结构体大致如下(在库函数源代码文件中的libio/libio.h中定义):

 


 

struct _IO_FILE {
  int _flags;        /* High-order word is _IO_MAGIC; rest is flags. */
#define _IO_file_flags _flags

  /* The following pointers correspond to the C++ streambuf protocol. */
  /* Note:  Tk uses the _IO_read_ptr and _IO_read_end fields directly. */
  char* _IO_read_ptr;    /* Current read pointer */
  char* _IO_read_end;    /* End of get area. */
  char* _IO_read_base;    /* Start of putback+get area. */
  char* _IO_write_base;    /* Start of put area. */
  char* _IO_write_ptr;    /* Current put pointer. */
  char* _IO_write_end;    /* End of put area. */
  char* _IO_buf_base;    /* Start of reserve area. */
  char* _IO_buf_end;    /* End of reserve area. */ 
  /* The following fields are used to support backing up and undo. */
  char *_IO_save_base; /* Pointer to start of non-current get area. */
  char *_IO_backup_base;  /* Pointer to first valid character of backup area */
  char *_IO_save_end; /* Pointer to end of non-current get area. */
  ... ... ...

  ... ... ...

};


我们只关心红色标识的那两行,就是这两个字符指针,指向了一个4k大小的内存缓冲区,库函数把这个缓冲区作为与内核交互数据的场所,所谓的行缓冲、全缓冲、无缓冲都是相对这块内存区域而言的,绝非我们自己定义的那个buf,也跟fgets的按行读取模式没有什么关系。
它们的关系可以简单地用一下这幅图来表示:

 

实际上像fgets函数这样按行读取的函数,它的按行读取指的是把数据填充到自定义的那个buf时一行一行地读,最终是通过标准IO库里面的缓冲区跟具体文件交互的。

【上篇】
【下篇】

抱歉!评论已关闭.