緩沖I/O和非緩沖I/O
文件讀寫主要牽涉到了如下五個操作:打開、關閉、讀、寫、定位。在Linux系統中,提供了兩套API,一套是C標准API:fopen、fclose、fread、fwrite、fseek,另一套則是POSIX定義的系統API:open、close、read、write、seek。
其中POSIX定義的API是系統API,而C標准API是基於系統API的封裝,並且提供了額外的緩沖的功能。因此也可以把它們叫做緩沖I/O函數和非緩沖I/O函數。
除了前面介紹的這幾個緩沖IO函數外,C標准庫<stdio.h>里面還提供了一系列封裝的IO函數:如puts、putchar、printf等。
為什么要有增加緩沖區這個功能呢?主要是因為IO操作時,操作系統要從用戶態轉換為內核態的,而這個轉換過程相對來說比較慢,因此可以通過緩沖的形式減少轉換到內核態的次數。那么,緩沖IO函數又是如何工作的呢?
-
當用fopen打開文件時,除了分配文件句柄外,還額外申請了一個緩沖區。
-
讀文件時,會首先讀到緩沖區中,然后返回用戶需要的部分,多余的部分仍然放在緩沖區,下次再讀的時候可以直接從緩沖區中返回。
-
寫文件時,會先寫到緩沖區中,等緩沖區滿后再統一寫到文件中。
那么,我們該如何選擇哪一組I/O函數呢?
非緩沖I/O函數每次讀寫都要進內核,調一個系統調用比調一個用戶空間的函數要慢很多,所以在用戶空間開辟I/O緩沖區還是必要的。
-
用緩沖I/O庫函數要時刻注意I/O緩沖區和實際文件有可能不一致,在必要時需調用fflush()。
-
I/O函數也用於讀寫設備,比如終端或網絡設備。此時通常需要更快的響應,一般不使用緩沖I/O函數。
PS:嚴格來講,就算是POSIX的I/O函數,仍然是有內核I/O緩沖的,所以write也不一定是直接寫到文件的,也可能寫到內核I/O緩沖區中,至於究竟寫到了文件中還是內核緩沖區中對於進程來說是沒有太大差別的,我們不用太關注這一點。
阻塞I/O和非阻塞I/O
文件讀寫通常有阻塞和非阻塞兩種方式,其中阻塞方式是我們比較常見的一種方式,此時函數會阻塞至操作完成。例如,對於如下一個等待用戶輸入字符串,並在屏幕上輸出的例子:
#include <unistd.h>
#include <stdlib.h>
int main(void)
{
char buf[10];
int n = read(STDIN_FILENO, buf, 10);
write(STDOUT_FILENO, buf, n);
return 0;
}
執行該函數時,read函數會一直阻塞到在屏幕上輸入數據並回車(此時STDIN有數據可用)為止。
阻塞IO有一個很大的問題是:無法實現並發。當同時進行多個IO操作的時候,前面的文件數據不可用的時候(往往是Socket之類的IPC操作),后面的IO操作無法執行。
非阻塞IO則可以很好的解決這個問題,要使用非阻塞IO操作,需要在open的時候制定O_NONBLOCK標志。這樣,如果設備暫時沒有數據可讀就返回-1,調用者應該試着再讀一次(again)。這種行為方式稱為輪詢(Poll),調用者只是查詢一下,而不是阻塞在這里死等,這樣可以同時監視多個設備:
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
int main(void)
{
char buf[10];
int fd, n;
fd = open("/dev/tty", O_RDONLY|O_NONBLOCK);
while (1)
{
n = read(fd, buf, 10);
if (n >= 0)
break;
sleep(1);
}
write(STDOUT_FILENO, buf, n);
close(fd);
return 0;
}
PS:為了示例函數簡單,我這里沒有考慮異常情況(如open失敗)的處理,而這些是在實際項目中是必不可少的。
非阻塞I/O有一個缺點,如果所有設備都一直沒有數據到達,調用者則需要反復查詢,這樣會一直占着cpu不放。因此,在使用非阻塞I/O時,通常不會在一個while循環中一直不停地查詢(這稱為Tight Loop),而是每延遲等待一會兒來查詢一下,以免做太多無用功,在延遲等待的時候可以調度其它進程執行。
但是,這樣又引入了一個新的問題,可能導致數據讀取的不夠及時,就拿我前面的例子來說,我在每次循環的時候Sleep了一秒。如果剛開始Sleep的時候數據可用,但此時卻無法立即響應,需要到Sleep結束后鍾才能輸出結果。
要解圓滿解決這個問題,則需要用到select函數,它可以阻塞地同時監視多個設備,還可以設定阻塞等待的超時時間,由於select多見於socket編程場景,這里不大好舉例,后續如果會介紹socket編程的時候再詳細介紹它,要了解它的工作原理可以看一下這篇文章select,多路同步I/O模型。