Linux高级编程--05.文件读写
緩沖I/O和非緩沖I/O
文件讀寫主要牽涉到了如下五個操作:打開、關(guān)閉、讀、寫、定位。在Linux系統(tǒng)中,提供了兩套API,
- 一套是C標(biāo)準(zhǔn)API:fopen、fclose、fread、fwrite、fseek,
- 另一套則是POSIX定義的系統(tǒng)API:open、close、read、write、seek。
其中POSIX定義的API是系統(tǒng)API,而C標(biāo)準(zhǔn)API是基于系統(tǒng)API的封裝,并且提供了額外的緩沖的功能。因此也可以把它們叫做緩沖I/O函數(shù)和非緩沖I/O函數(shù)。
除了前面介紹的這幾個緩沖IO函數(shù)外,C標(biāo)準(zhǔn)庫里面還提供了一系列封裝的IO函數(shù):如puts、putchar、printf等。
為什么要有增加緩沖區(qū)這個功能呢?主要是因?yàn)镮O操作時,操作系統(tǒng)要從用戶態(tài)轉(zhuǎn)換為內(nèi)核態(tài)的,而這個轉(zhuǎn)換過程相對來說比較慢,因此可以通過緩沖的形式減少轉(zhuǎn)換到內(nèi)核態(tài)的次數(shù)。
那么,緩沖IO函數(shù)又是如何工作的呢?
- 當(dāng)用fopen打開文件時,除了分配文件句柄外,還額外申請了一個緩沖區(qū)。
- 讀文件時,會首先讀到緩沖區(qū)中,然后返回用戶需要的部分,多余的部分仍然放在緩沖區(qū),下次再讀的時候可以直接從緩沖區(qū)中返回。
- 寫文件時,會先寫到緩沖區(qū)中,等緩沖區(qū)滿后再統(tǒng)一寫到文件中。
那么,我們該如何選擇哪一組I/O函數(shù)呢?
- 非緩沖I/O函數(shù)每次讀寫都要進(jìn)內(nèi)核,調(diào)一個系統(tǒng)調(diào)用比調(diào)一個用戶空間的函數(shù)要慢很多,所以在用戶空間開辟I/O緩沖區(qū)還是必要的。
- 用緩沖I/O庫函數(shù)要時刻注意I/O緩沖區(qū)和實(shí)際文件有可能不一致,在必要時需調(diào)用fflush()。
I/O函數(shù)也用于讀寫設(shè)備,比如終端或網(wǎng)絡(luò)設(shè)備。此時通常需要更快的響應(yīng),一般不使用緩沖I/O函數(shù)。
PS:嚴(yán)格來講,就算是POSIX的I/O函數(shù),仍然是有內(nèi)核I/O緩沖的,所以write也不一定是直接寫到文件的,也可能寫到內(nèi)核I/O緩沖區(qū)中,至于究竟寫到了文件中還是內(nèi)核緩沖區(qū)中對于進(jìn)程來說是沒有太大差別的,我們不用太關(guān)注這一點(diǎn)。
阻塞I/O和非阻塞I/O
文件讀寫通常有阻塞和非阻塞兩種方式,其中阻塞方式是我們比較常見的一種方式,此時函數(shù)會阻塞至操作完成。例如,對于如下一個等待用戶輸入字符串,并在屏幕上輸出的例子:
#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; }執(zhí)行該函數(shù)時,read函數(shù)會一直阻塞到在屏幕上輸入數(shù)據(jù)并回車(此時STDIN有數(shù)據(jù)可用)為止。
阻塞IO有一個很大的問題是:無法實(shí)現(xiàn)并發(fā)。當(dāng)同時進(jìn)行多個IO操作的時候,前面的文件數(shù)據(jù)不可用的時候(往往是Socket之類的IPC操作),后面的IO操作無法執(zhí)行。
非阻塞IO則可以很好的解決這個問題,要使用非阻塞IO操作,需要在open的時候制定O_NONBLOCK標(biāo)志。這樣,如果設(shè)備暫時沒有數(shù)據(jù)可讀就返回-1,調(diào)用者應(yīng)該試著再讀一次(again)。這種行為方式稱為輪詢(Poll),調(diào)用者只是查詢一下,而不是阻塞在這里死等,這樣可以同時監(jiān)視多個設(shè)備:
PS:為了示例函數(shù)簡單,我這里沒有考慮異常情況(如open失敗)的處理,而這些是在實(shí)際項(xiàng)目中是必不可少的。
非阻塞I/O有一個缺點(diǎn),如果所有設(shè)備都一直沒有數(shù)據(jù)到達(dá),調(diào)用者則需要反復(fù)查詢,這樣會一直占著cpu不放。因此,在使用非阻塞I/O時,通常不會在一個while循環(huán)中一直不停地查詢(這稱為Tight Loop),而是每延遲等待一會兒來查詢一下,以免做太多無用功,在延遲等待的時候可以調(diào)度其它進(jìn)程執(zhí)行。
但是,這樣又引入了一個新的問題,可能導(dǎo)致數(shù)據(jù)讀取的不夠及時,就拿我前面的例子來說,我在每次循環(huán)的時候Sleep了一秒。如果剛開始Sleep的時候數(shù)據(jù)可用,但此時卻無法立即響應(yīng),需要到Sleep結(jié)束后鐘才能輸出結(jié)果。
要解圓滿解決這個問題,則需要用到select函數(shù),它可以阻塞地同時監(jiān)視多個設(shè)備,還可以設(shè)定阻塞等待的超時時間,由于select多見于socket編程場景,這里不大好舉例,后續(xù)如果會介紹socket編程的時候再詳細(xì)介紹它,要了解它的工作原理可以看一下這篇文章select,多路同步I/O模型。
來自為知筆記(Wiz)
轉(zhuǎn)載于:https://www.cnblogs.com/linzhenjie/p/5485628.html
總結(jié)
以上是生活随笔為你收集整理的Linux高级编程--05.文件读写的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 个人征信有问题怎么处理 怎么处理个人征信
- 下一篇: linux查看端口占用程序