Linux Chapter 13 - 檔案IO緩衝區
檔案I/O緩衝區
The Linux programming interface chapter 13
核心緩衝區概述
system call read() & write()在操作磁碟檔案時不會直接對磁碟存取,而是單純在使用者空間緩衝區與核心緩衝區快取(kernel buffer cache)之間複製資料。
例如: write(fd, "abc",3)
在後續的某個時間點,核心會將其緩衝區的資料寫入磁碟
如果在此期間,另外一個process試圖讀取檔案的這幾個bytes,則核心將自動從緩衝區提供資料,而不是從檔案讀取。同理於read()期間有write() process的情況
緩衝區大小對效能的影響

- system cpu time:主要是測量使用者空間與核心空間之間資料傳輸所消耗的時間
- 包含CPU的處理, 等待I/O I/O的處理,作業系統處理提供一些服務額外的時間,以及系統閒置的時間
- Elapsed time : 完整的程式在這個系統裡面執行的時間
當緩衝區大小為1byte,需要呼叫大約一億次的system call,而緩衝區大小為4096bytes時,system call次數約為24000,當超過此設定值,效能的改善就開始不明顯,是因為在使用者空間和核心空間之間複製資料以及執行實際I/O所需的時間相較之下,read()和write()的system call 成本就顯得微不足道。
* 結論: 若我們正對檔案進行大量的資料讀寫,則以大區塊緩衝資料來減少system call 即可提升系統效能
stdio函式庫的緩衝
操作磁碟檔案時,對大型區塊資料使用緩衝可以降低system call。
設定一個stdio stream的緩衝模式
|
|
在開啟串流之後,必須在對串流進行任何其他的stdio函式之前先呼叫setbuf() 因為setbuf()將影響後續的stdio操作對串流的行為
- 參數
- stream:可以識別要修改的檔案stream緩衝區
- buf & size 指定使用的緩衝區
- 若buf不為NULL則以其指向的size大小區塊作為stream的緩衝區,因為stdio接著會使用buf指向的緩衝區,所以應該以靜態配置或使用malloc()於heap做動態配置,不應該配置為stack裡面的區域變數,否則當函式返回時會釋放stack frame,導致混亂
- 若buf為NULL,stdio會自動配置一個緩衝區提供stream使用(除非我們選擇無緩衝的I/O)。在SUSv3允許但不要求實作時使用size來決定緩衝區的大小
- mode指定緩衝區的類型

範例
|
|
*setvbuf()出錯時會傳回非0值
setbuf()位於setvbuf上層
|
|
- 呼叫
setbuf(fp,buf)等同於setvbuf(fp, buf, (buf != NULL) ? _IOFBF: _IONBF, BUFSIZ); - 參數
- buf可以指定為NULL表示無緩衝,或指向由呼叫者配置的BUFSIZ大小的緩衝區(BUFSIZ定義在<stdio.h>通常是8192)
setbuffer()與setbuf()相似,但允許呼叫者指定buf緩衝區的大小
|
|
- SUSv3並未規範此函式,但多數UNIX有提供
刷新stdio緩衝區
我們可以用fflush()函式強制寫入stdio的輸出串流資料(如透過write()刷新寫入核心緩衝區)。此函式會刷新指定stream的輸出緩衝區
|
|
- 參數
- 若stream為NULL,則fflush將刷新與output stream相關的每個stdio緩衝區
- 也能應用於輸入串流,這將捨棄緩衝區裡面的所有輸入資料(當程序下次嘗試從串流讀資料,將重頭開始寫入緩衝區)
- 當關閉對應的串流時,會自動刷新stdio的緩衝區
- 在許多c函式庫實作中,若stdin stdout是參考到terminal,則從stdin讀取輸入時,都會進行一次
fflush(stdout)呼叫,效果是刷新寫入stdout的任何提示,但不包含結尾的換行符號 範例1:
|
|
常用於立刻將輸入值印出來。
範例2:
|
|
此範例如果輸入非int類型的值,則輸入值會放入緩衝區,而導致你下個迴圈的輸入無法被讀到,因為scanf會一直去緩衝區讀取,而不會理會使用者輸入。可以在scanf()前面加上,fflush(stdin),但不是每個系統都有支援fflush(stdin)。這時候可以利用一些特殊的函數(內部本身就包含fflush()),來解決這種問題。
控制檔案I/O的核心緩衝
可以強制將核心緩衝區刷新到輸出檔,例如資料庫的日誌行程,要確保在繼續操作前將結果真正的寫入到硬碟(或硬碟的快取)
同步I/O資料完整性與同步I/O檔案完整性
SUSv3將同步I/O完成(synchronized IO completion) 定義為: 一種I/O操作,若非已成功傳輸(到硬碟),及診斷為同步失敗
有兩種不同類型的同步I/O完成
-
synchronized I/O data integrity completion(資料完整性)
- 對於read操作,意思是所需的檔案已經(從硬碟)傳輸給process。若有任何未完成的write操作並且會影響資料的read時,會在進行read前,將資料寫入磁碟
- 對於write操作,意思是在write請求的指定資料已經傳輸(到硬碟)完成,而且用以取得資料所需的全部檔案中的中繼資料也已經全部傳輸(到硬碟)完成。
- 關鍵在於不需要將全部的已修改檔案中的中繼資料屬性傳輸完成,就可以檢索檔案資料
- 需要傳輸的其中一個中繼資料屬性是檔案的大小
-
synchronized I/O file integrity compliction(檔案完整性)
- 與資料完整性的差異在於,在檔案更新期間,會將全部已更新的檔案中繼資料傳輸到硬碟,即使後續的檔案read操作不需要檔案中繼資料
中繼資料(metadata):檔案擁有者與所屬群組 檔案權限,檔案大小,檔案的(hard)連結數量,檔案最後的存取時間,最後修改時間,中繼資料發生變化的時間,檔案資料區塊指標
用於控制檔案I/O核心緩衝的系統呼叫
|
|
- 將緩衝資料與開啟檔案的fd相關的metadate刷新到磁碟
- 會強制檔案處於I/O檔案完整性狀態
- 會在完成磁碟裝置(或快取記憶體)的傳輸之後才返回
|
|
- 會強制檔案處於I/O資料完整性狀態
- 操作與fsync()類似
- 可能會降低磁碟操作的次數
|
|
- 會將包含更新檔案資訊的全部核心緩衝區(資料區塊,指標區塊,中繼資料等)刷新到磁碟
- 在已將全部資料傳輸到磁碟裝置(或者是快取)之後返回
- 在SUSv3中,實作時只是單純對I/O傳遞進行排班,因此可以在未完成之前返回
同步全部的寫入檔案
在呼叫open()時指定O_SYNC flag可以讓全部的後續輸出同步
|
|
每次write()檔案都會自動將檔案資料與中繼資料刷新到硬碟(依照檔案完整性進行寫入操作)
關於O_SYNC對效能的影響可以參考 書籍p.265
I/O緩衝摘要
繞過緩衝區快取:直接I/O
在進行磁碟I/O時,繞過緩衝區快取,可以直接從使用者空間將資料傳輸到檔案或磁碟裝置。 direct I/O有時反而會大幅降低效能,因為核心為了改善效能,進行的許多優化。因此只適用於有特定I/O需求的應用,例如資料庫系統有自己的快取和I/O優化,所以不需要核心消耗CPU的時間與記憶體去進行同樣的工作。
- 可以對個別的檔案或block device進行直接I/O,可以在open()指定O_DIRECT flag。
direct I/O的對齊限制
- 傳虛用途的資料緩衝區,必須對齊符合block size倍數的記憶體邊界
- 資料傳輸的開始點,即檔案或裝置的偏移量,必須是block size的倍數
- 待傳輸資料的長度必須是block sizek的倍數
為檔案I/O混搭函式庫函式與system call
|
|
對同一個檔案進行I/O操作時,可以混搭system call與標準C函式庫的函式
- fdopen()是fileno()的反函式,此函式會建立一個與此檔案描述符對應的stream給I/O使用
- fdopen()對非正規檔案的描述符特別有用,例如建立通訊(socket)和 pipe的system call