標準緩衝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。


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