Linux系統IO中write原型為 ssize_t write(int filedes, const void * buff, size_t nbytes) ;
當調用write寫數據的時候,調用完成后write直接返回,但是磁盤是個慢速設備,操作系統會將數據保存在內核中的緩沖區中,並負責異步地將數據寫至磁盤。當然如果此時系統宕機了則會丟失數據。write是系統調用,每次調用都會陷入內核,所以選取一個合適的塊長度buffsize,並盡量減少它的調用可以優化效率。在ANSI C的標准IO中我們調用printf/fprintf/fputs等會以流的方式進行處理,我們只需要寫入流中,而不用像write一樣選擇一個buffsize,因為標准IO庫幫我們處理了很多細節,例如緩沖區分配,以優化長度執行IO等。這樣的話就會減少wirte/read系統調用的數量,提高效率。但是與此同時會引入另外一個問題:數據拷貝,例如當使用函數fgets和fputs時,通常需要經過兩次緩沖區:一次是標准IO緩沖區,還有一次是調用read和write的內核緩沖區。但是總的來說使用標准IO相對於系統IO來說接口簡單,且效率相當。
標准IO提供了三種類型的緩沖區:全緩存,行緩存和不帶緩存,全緩存只有在緩沖區滿時才會主動flush,通常用在對一個磁盤文件IO。行緩存在緩沖區中遇到換行符就會flush,還有一種情況是需要從標准輸入輸出得到輸入數據時也會flush緩沖區,行緩存一般用在交互的終端中。不帶緩存則相當於直接 write系統調用輸出,標准出錯流stderr通常是不帶緩存的,這就使得出錯信息可以盡快顯示出來。除了默認的flush條件外,顯式調用fflush函數和程序正常終止時也會flush緩沖區。我們可以使用setbuf/setvbuf來更改默認的緩沖區長度,參見APUE 5.4節。
在使用標准IO的程序中,當我們將一個標准輸出重新定向到一個文件時,會將行緩存變為全緩存,在某些情況下可能會導致一些非預期錯誤,比如調用printf(“*****\n”)時,當以交互方式運行該程序時,會正常輸出。但是當將標准輸出重新定向到一個文件時,緩沖區區變為全緩存,printf就不會正常輸出,該行數據仍在緩沖區中。如果此時再fork一個子進程,數據空間被復制到子進程中時,該緩沖區數據也被復制到子進程中。接着在子進程中如果輸出則會刷新之前在緩沖區的內容,產生一些非預期的輸出。
在網絡編程中,應該直接使用系統IO,標准IO為提升性能而引入緩沖機制增加了網絡應用程序的復雜性。並且,某種意義上說標准IO流是全雙工的,能同時執行輸入和輸出,然而對流的限制和對套接字的限制,有時候會互相沖突。(參見CSAPP P611)
某些高級的網絡庫中(比如說muduo庫)在使用系統IO的基礎上會創建自己的緩沖區,幫助用戶屏蔽系統IO的某些不便,例如調用write發送大量數據的時候,發送緩沖區滿時需要應用層等待,read接收數據的時候粘包和數據接受的緩慢。當增加應用層緩沖區后,由網絡庫處理這些實現細節,簡化用戶操作。
Linux還提供了零拷貝技術來減少內存拷貝,進而提升效率,我們知道利用read/write從磁盤發送數據到網卡會經過四次拷貝操作:當應用程序需要訪問某塊數據的時候,操作系統內核會先檢查這塊數據是不是因為前一次對相同文件的訪問而已經被存放在操作系統內核地址空間的緩沖區內,如果在內核緩沖區中找不到這塊數據,Linux 操作系統內核會先將這塊數據從磁盤讀出來放到操作系統內核的緩沖區里。如果這個數據讀取操作是由 DMA 完成的,那么在 DMA 進行數據讀取的這一過程中,CPU 只需要進行緩沖區管理,以及創建和處理 DMA ,除此之外,CPU 不需要再做更多的事情,DMA 執行完數據讀取操作之后,會通知操作系統做進一步的處理。Linux 操作系統會根據 read系統調用指定的應用程序地址空間的地址,把這塊數據存放到請求這塊數據的應用程序的地址空間中去,待用戶對數據完成操作后,操作系統需要將數據再一次從用戶應用程序地址空間的緩沖區拷貝到與網絡堆棧相關的內核緩沖區中去,這個過程也是需要占用 CPU 的。數據拷貝操作結束以后,數據會被打包,然后發送到網絡接口卡上去。從上面的描述可以看出,在這種傳統的數據傳輸過程中,數據至少發生了四次拷貝操作,即便是使用了 DMA 來進行與硬件的通訊,CPU 仍然需要訪問數據兩次。
(ps:記得之前看過一個面試題說是printf輸出過程經過幾次緩沖區,現在大家明白了吧!)
使用零拷貝技術可以避免數據在系統內核地址空間的緩沖區和用戶應用程序地址空間的緩沖區進行拷貝。有時候,應用程序在數據傳輸的過程中不需要對數據進行訪問,傳輸的數據可以不用復制到用戶應用區,直接通過內核發送到網卡就可以,這樣可以提高性能,而此時就需要零拷貝技術。linux下可以用mmap,sendfile,splice實現零拷貝。具體參見 linux 中的零拷貝技術 第1部分 第2部分