标准缓冲I/0(及其可能遇到的错误)

在文件I/O中所有函数都是针对文件描述符的,对于标准I/O库,他们的操作则是围绕流进行的。当用标准I/O库打开或创建一个文件时,我们使一个流与一个文件相关联。当打开一个流时,标准I/O函数fopen返回一个指向FILE对象的指针。该对象通常是一个结构,它包含了标准I/O库为管理该流所需要的所有信息,包括:用于实际I/O的文件描述符、指向用于该流缓冲区的指针、缓冲区的长度、当前在缓冲区中的字符数以及出错标志等等。在文件I/O中已经介绍标准I/O是提供缓冲区的。标准I/O库提供缓冲的目的是尽可能减少使用read和write调用的次数。标准I/O库处理很多细节,例如缓冲区分配,以优化长度执行I/O等。

标准I/O提供了三种类型的缓冲:

1)全缓冲。这种情况下,在填满标准I/O缓冲区后才进行实际I/O操作。对于驻留在磁盘上的文件通常是由标准I/O库实施全缓冲的。在一个流上执行第一次I/O操作时,相关标准I/O函数通常调用malloc获得需使用的缓冲区。

2)行缓冲。在这种情况下,当在输入和输出中遇到换行符时,标准I/O库执行I/O操作。这允许我们一次输出一个字符(标准I/O fputc函数),但只有在写了一行之后才进行实际I/O操作。当流涉及一个终端时(例如标准输入和标准输出),通常使用行缓冲。对于行缓冲有两个限制。第一,因为标准I/O库用来收集每一行的缓冲区的长度是固定的、所以只要填满了缓冲区,那么即使还没有写一个换行符,也进行I/O操作。第二,任何时候只要通过标准I/O库要求从(a)一个不带缓冲的流,或者(b)一个行缓冲的流(它要求从内核得到数据)得到输入数据,那么就会造成冲洗所有行缓冲输出流。

3)不带缓冲。标准I/O库不对字符进行缓冲存储。例如,如果用标准I/O函数fputs写15个字符到不带缓冲的流中,则该函数很可能用上面讲述的write系统调用函数将这些字符立即写至关联的打开的文件上。对任何一个给定的流,我们可以通过以下两个函数更改缓冲类型。

  1. #include <stdio.h>  
  2. void setbuf(FILE* restrict fp, char* restrict buf);  
  3. int setvbuf(FILE* restrict fp, char* restrict buf, int mode, size_t size); //如果成功返回0,出错则返回非0.  
可以使用setbuf函数打开或关闭缓冲机制,为了带缓冲进行IO,参数buf必须制定一个长度为BUFSIZ的缓冲区(这就是为什么没有在setbuf函数的参数中指定buf的长度),通常在此之后该流就是全缓冲的,但是如果该流与一个终端相关,那么某些系统也可以将其设置为行缓冲。为了关闭缓冲,将buf设置为NULL。使用setbuf,可以精确地指定所需的缓冲类型。mode的取值及其代表的含义如下:

  _IOFBF 全部缓冲
  _IOLBF 行缓冲
  _IONBF 不缓冲

注意:

如果指定一个不带缓冲的流,则忽略buf和size参数。

如果指定全缓冲和行缓冲,则buf和size可选择地指定一个缓冲区及其长度。

如果该流是带缓冲的,而buff是NULL,则标准IO库将自动地为该流分配适当长度的缓冲区(长度为BUFSIZ指定的值)。


1)关于I/O缓存区问题

当标准I/O函数遇到fork函数(多进程)时可能会因为其缓冲问题得到不同的结果

看下面的代码:


在fork函数之前,我们执行一次write函数和printf(“before fork”)。按道理,我们的结果应该只会得到一次输出。

但是运行的结果却是如下:

  

从结果中我们看到 before fork 执行了两次。这是为什么呢?

这是由于缓冲区的缘故.write函数时不带缓冲区的,所以在调用write函数之后会立刻执行I/O操作,将结果输出到标准输出上。然而printf函数(标准I/O函数)是带缓冲的,在此处是行缓冲(交互式程序中是行缓冲),当执行完printf("before fork")函数之后,系统会将结果存放在缓冲区中(因为没有遇到换行,同时也没有超过行缓冲的大小)。接下来执行fork函数之后,子进程会将上面的缓冲区的内容也复制到自己的缓冲区中,所以在父进程和子进程的缓冲区中都存在。在程序退出时,系统会将缓冲区的内容刷新到标准输出上。


2)关于流的问题

   首先来看看父子进程中文件描述符的共享情况。


从图中可以看出来,在fork函数执行之后,相当于执行了dup2复制函数。文件描述符执行同一个文件表项。可以同时操作写同一文件,当一个进程关闭文件描述符以后,并不影响另一进程通过文件描述符操作文件。

 接着看看父子进程中流的共享。

在标准I/O库中,我们操控文件方式是采用的是流的方式。我们将一个流与一个文件想关联。在fork函数之后,父子进程都会同时通过这一个流文件来操作文件,比如修改操作。与文件描述符不同的是,当某一个进程关闭流后,其他进程也不能使用该流。

    从上面可以看出:在操作文件描述符的时候,父子进程之间共享的是同一个文件表项。在操作流的时候,父子进程之间则是共享同一个流。

看下面代码运行的不同。

             

查看其运行结构:


                    

从结果可以看出关闭流以后,父进程则不能使用标准输出流。而关闭文件描述符(在此处关闭流会关闭其描述符),父进程仍然能使用描述符STDOUT_FILENO。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章