在文件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系統調用函數將這些字符立即寫至關聯的打開的文件上。對任何一個給定的流,我們可以通過以下兩個函數更改緩衝類型。
- #include <stdio.h>
- void setbuf(FILE* restrict fp, char* restrict buf);
- int setvbuf(FILE* restrict fp, char* restrict buf, int mode, size_t size); //如果成功返回0,出錯則返回非0.
_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。