淺談帶緩沖I/O 和不帶緩沖I/O的區(qū)別與聯(lián)系
這里搜集從網(wǎng)上看到的一些言論,自認(rèn)為還是比較靠譜的,有些不靠譜的根據(jù)自己的理解進(jìn)行了修正。
首先要明白不帶緩沖的概念:所謂不帶緩沖,并不是指內(nèi)核不提供緩沖,而是只單純的系統(tǒng)調(diào)用,不是函數(shù)庫的調(diào)用。系統(tǒng)內(nèi)核對磁盤的讀寫都會提供一個塊緩沖(在有些地方也被稱為內(nèi)核高速緩存),當(dāng)用write函數(shù)對其寫數(shù)據(jù)時,直接調(diào)用系統(tǒng)調(diào)用,將數(shù)據(jù)寫入到塊緩沖進(jìn)行排隊,當(dāng)塊緩沖達(dá)到一定的量時,才會把數(shù)據(jù)寫入磁盤。因此所謂的不帶緩沖的I/O是指進(jìn)程不提供緩沖功能(但內(nèi)核還是提供緩沖的)。每調(diào)用一次write或read函數(shù),直接系統(tǒng)調(diào)用。
而帶緩沖的I/O是指進(jìn)程對輸入輸出流進(jìn)行了改進(jìn),提供了一個流緩沖,當(dāng)用fwrite函數(shù)網(wǎng)磁盤寫數(shù)據(jù)時,先把數(shù)據(jù)寫入流緩沖區(qū)中,當(dāng)達(dá)到一定條件,比如流緩沖區(qū)滿了,或刷新流緩沖,這時候才會把數(shù)據(jù)一次送往內(nèi)核提供的塊緩沖,再經(jīng)塊緩沖寫入磁盤。(雙重緩沖)
因此,帶緩沖的I/O在往磁盤寫入相同的數(shù)據(jù)量時,會比不帶緩沖的I/O調(diào)用系統(tǒng)調(diào)用的次數(shù)要少。
看正常情況下,和磁盤交互的讀寫文件是怎么個流程!
當(dāng)應(yīng)用程序嘗試讀取某塊數(shù)據(jù)的時候,如果這塊數(shù)據(jù)已經(jīng)存放在頁緩存中,那么這塊數(shù)據(jù)就可以立即返回給應(yīng)用程序,而不需要經(jīng)過實際的物理讀盤操作。當(dāng)然,如果數(shù)據(jù)在應(yīng)用程序讀取之前并未被存放在頁緩存中(也就是上面提到的內(nèi)核高速緩存),那么就需要先將數(shù)據(jù)從磁盤讀到頁緩存中去。對于寫操作來說,應(yīng)用程序也會將數(shù)據(jù)先寫到頁緩存中去(這里所說的寫到頁緩存中,如果是調(diào)用標(biāo)準(zhǔn)庫I/O進(jìn)行寫,那么首先是寫到標(biāo)準(zhǔn)庫的緩沖區(qū)內(nèi),如果標(biāo)準(zhǔn)庫的緩沖區(qū)寫滿以后,在寫到頁緩沖內(nèi);如果是系統(tǒng)調(diào)用,那么直接寫到頁緩沖內(nèi)),數(shù)據(jù)是否被立即寫到磁盤上去取決于應(yīng)用程序所采用的寫操作機(jī)制:如果用戶采用的是同步寫機(jī)制,那么數(shù)據(jù)會立即被寫回到磁盤上,應(yīng)用程序會一直等到數(shù)據(jù)被寫完為止;如果用戶采用的是延遲寫機(jī)制,那么應(yīng)用程序就完全不需要等到數(shù)據(jù)全部被 寫回到磁盤,數(shù)據(jù)只要被寫到頁緩存中去就可以了。在延遲寫機(jī)制的情況下,操作系統(tǒng)會定期地將放在頁緩存中的數(shù)據(jù)刷到磁盤上。與異步寫機(jī)制不同的是,延遲寫機(jī)制在數(shù)據(jù)完全寫到磁盤上得時候不會通知應(yīng)用程序,而異步寫機(jī)制在數(shù)據(jù)完全寫到磁盤上得時候是會返回給應(yīng)用程序的。所以延遲寫機(jī)制本省是存在數(shù)據(jù)丟失的風(fēng)險的,而異步寫機(jī)制則不會有這方面的擔(dān)心。
下面聊聊不帶緩沖的I/O
不帶緩存,不是直接對磁盤文件進(jìn)行讀取操作,像read()和write()函數(shù),它們都屬于系統(tǒng)調(diào)用,只不過在用戶層沒有緩存,所以叫做無緩存IO,但對于內(nèi)核來說,還是進(jìn)行了緩存,只是用戶層看不到罷了。
帶不帶緩存是相對來說的,如果你要寫入數(shù)據(jù)到文件上時(就是寫入磁盤上),內(nèi)核先將數(shù)據(jù)寫入到內(nèi)核中所設(shè)的緩沖儲存器,假如這個緩沖儲存器的長度是100個字節(jié),你調(diào)用系統(tǒng)函:
ssize_t write (int fd,const void * buf,size_t count);
寫操作時,設(shè)每次寫入長度count=10個字節(jié),那么你幾要調(diào)用10次這個函數(shù)才能把這個緩沖區(qū)寫滿,此時數(shù)據(jù)還是在緩沖區(qū),并沒有寫入到磁盤,緩沖區(qū)滿時才進(jìn)行實際上的IO操作,把數(shù)據(jù)寫入到磁盤上,所以上面說的“不帶緩存""不是沒有緩存而是沒有直寫進(jìn)磁盤就是這個意思(既然沒有寫入磁盤,調(diào)用系統(tǒng)調(diào)用為何可以在文件中看到寫入的內(nèi)容呢,因為內(nèi)核控件是共享的)
那么,既然不帶緩存的操作實際在內(nèi)核是有緩存器的,那帶緩存的IO操作又是怎么回事呢?
帶緩存IO也叫標(biāo)準(zhǔn)IO,符合ANSI C 的標(biāo)準(zhǔn)IO處理,不依賴系統(tǒng)內(nèi)核,所以移植性強(qiáng),我們使用標(biāo)準(zhǔn)IO操作很多時候是為了減少對read()和write()的系統(tǒng)調(diào)用次數(shù),帶緩存IO其實就是在用戶層再建立一個緩存區(qū),這個緩存區(qū)的分配和優(yōu)化長度等細(xì)節(jié)都是標(biāo)準(zhǔn)IO庫代你處理好了,不用去操心,還是用上面那個例子說明這個操作過程:
上面說要寫數(shù)據(jù)到文件上,內(nèi)核緩存(注意這個不是用戶層緩存區(qū))區(qū)長度是100字節(jié),我們調(diào)用不帶緩存的IO函數(shù)write()就要調(diào)用10次,這樣系統(tǒng)效率低,現(xiàn)在我們在用戶層建立另一個緩存區(qū)(用戶層緩存區(qū)或者叫流緩存),假設(shè)流緩存的長度是50字節(jié),我們用標(biāo)準(zhǔn)C庫函數(shù)的fwrite()將數(shù)據(jù)寫入到這個流緩存區(qū)里面,流緩存區(qū)滿50字節(jié)后在進(jìn)入內(nèi)核緩存區(qū),再調(diào)用系統(tǒng)函數(shù)write()將數(shù)據(jù)寫入到內(nèi)核緩沖內(nèi),如果內(nèi)核緩沖也被填滿,或者內(nèi)核進(jìn)行fflush操作,那么內(nèi)核緩沖區(qū)內(nèi)數(shù)據(jù)就別寫入到文件(實質(zhì)是磁盤)上,看到這里,你用該明白一點,標(biāo)準(zhǔn)IO操作fwrite()最后還是要掉用無緩存IO操作write,這里進(jìn)行了兩次調(diào)用fwrite()寫100字節(jié)也就是進(jìn)行兩次系統(tǒng)調(diào)用write()。
如果看到這里還沒有一點眉目的話,那就比較麻煩了,希望下面兩條總結(jié)能夠幫上忙:
無緩存IO操作數(shù)據(jù)流向路徑:數(shù)據(jù)——內(nèi)核緩存區(qū)——磁盤
標(biāo)準(zhǔn)IO操作數(shù)據(jù)流向路徑:數(shù)據(jù)——流緩存區(qū)——內(nèi)核緩存區(qū)——磁盤
下面是一個網(wǎng)友的見解,以供參考:
不帶緩存的I/O對文件描述符操作,下面帶緩存的I/O是針對流的。
標(biāo)準(zhǔn)I/O庫就是帶緩存的I/O,它由ANSI C標(biāo)準(zhǔn)說明。當(dāng)然,標(biāo)準(zhǔn)I/O最終都會調(diào)用上面的I/O例程。標(biāo)準(zhǔn)I/O庫代替用戶處理很多細(xì)節(jié),比如緩存分配、以優(yōu)化長度執(zhí)行I/O等。
標(biāo)準(zhǔn)I/O提供緩存的目的就是減少調(diào)用read和write的次數(shù),它對每個I/O流自動進(jìn)行緩存管理(標(biāo)準(zhǔn)I/O函數(shù)通常調(diào)用malloc來分配緩存)。
下面的東西是我從網(wǎng)上查到的對這兩者的理解,我覺得還是很到位的:
以下主要討論關(guān)于open,write等基本系統(tǒng)IO的帶緩沖與不帶緩沖的差別
帶緩存的文件操作是標(biāo)準(zhǔn)C庫的實現(xiàn),第一次調(diào)用帶緩存的文件操作函數(shù)時標(biāo)準(zhǔn)庫會自動分配內(nèi)存并且讀出一段固定大小的內(nèi)容存儲在緩存中。所以以后每次的讀寫操作并不是針對硬盤上的文件直接進(jìn)行的,而是針對內(nèi)存中的緩存的。何時從硬盤中讀取文件或者向硬盤中寫入文件有標(biāo)準(zhǔn)庫的機(jī)制控制。不帶緩存的文件操作通常都是系統(tǒng)提供的系統(tǒng)調(diào)用, 更加低級,直接從硬盤中讀取和寫入文件,由于IO瓶頸的原因,速度并不如意,而且原子操作需要程序員自己保證,但使用得當(dāng)?shù)脑捫什⒉徊睢?span style="color: #0000ff">另外標(biāo)準(zhǔn)庫中的帶緩存文件IO 是調(diào)用系統(tǒng)提供的不帶緩存IO實現(xiàn)的。
“術(shù)語不帶緩沖指的是每個read和write都調(diào)用內(nèi)核中的一個系統(tǒng)調(diào)用。所有的磁盤I/O都要經(jīng)過內(nèi)核的塊緩沖(也稱內(nèi)核的緩沖區(qū)高速緩存),唯一例外的是對原始磁盤設(shè)備的I/O。既然read或write的數(shù)據(jù)都要被內(nèi)核緩沖,那么術(shù)語“不帶緩沖的I/O“指的是在用戶的進(jìn)程中對這兩個函數(shù)不會自動緩沖,每次read或write就要進(jìn)行一次系統(tǒng)調(diào)用?!?-------摘自<unix環(huán)境編程>
程序中用open和write打開創(chuàng)建并把“hello world“寫入文件test.txt,相應(yīng)用fopen和fwrite操作文件test2.txt。程序執(zhí)行到open和fopen之后,sleep 15秒,這時用ls查看生成了文件沒,這時用open打開的test.txt出現(xiàn)了,用fopen打開的的test2.txt也出現(xiàn)了;當(dāng)程序執(zhí)行完write和 fwrite之后,在15秒睡眠期間,用cat查看test.txt,其內(nèi)容是“hello,world”;但是此時用cat查看test2.txt,其內(nèi)容為空。睡眠結(jié)束后,執(zhí)行了close(fd),此時再用cat查看test2.txt,發(fā)現(xiàn)其內(nèi)容也有了:“hello,world”。該例子證明了open和write是不帶緩沖的,即程序一執(zhí)行其io操作也立即執(zhí)行,不會停留在系統(tǒng)提供的緩沖里,不需等到close操作完才執(zhí)行。與之相比的fopen和fwrite則是帶緩沖的,(一般)要等到fclose操作完后才會執(zhí)行。
相關(guān)的源碼示例如下:
#include <unistd.h> #include <iostream> #include <fcntl.h> #include <string> #include <sys/types.h> #include <sys/stat.h> using namespace std; int main(){ int fd; FILE *file; char *s="hello,world\n"; if((fd=open("test.txt",O_WRONLY|O_CREAT,S_IRUSR|S_IWUSR))==-1){ cout<<"Error open file"<<endl; return -1; } if((file=fopen("test2.txt","w"))==NULL){ cout<<"Error Open File."<<endl; return -1; } cout<<"File has been Opened."<<endl; sleep(15); if(write(fd,s,strlen(s))<strlen(s)){ cout<<"Write Error"<<endl; return -1; } if(fwrite(s,sizeof(char),strlen(s),file)<strlen(s)){ cout<<"Write Error in 2."<<endl; return -1; } cout<<"After write"<<endl; sleep(15); cout<<"After sleep."<<endl; close(fd); return 0; }
以 ssize_t write(int filedes, const void *buff, size_t nbytes)和size_t fwrite(const void *ptr, size_t size, size_t nobj, FILE *fp)來講講自己對unix系統(tǒng)下帶緩存的I/O和不帶緩存的I/O的區(qū)別。
首先要清楚一個概念,所謂的帶緩存并不是指上面兩個函數(shù)的buff參數(shù)。
當(dāng)將數(shù)據(jù)寫到文件上時,內(nèi)核先將該數(shù)據(jù)寫到緩存,如果該緩存未滿,則并不將其排入輸出隊列,直到緩存寫滿或者內(nèi)核再次需要重新使用此緩存時才將其排入輸入隊列,待其到達(dá)隊首,再進(jìn)行實際的I/O操作,也就是此時才把數(shù)據(jù)真正寫到磁盤,這種技術(shù)叫延遲寫。
現(xiàn)在假設(shè)內(nèi)核所設(shè)的緩存是100個字節(jié),如果你使用write,且buff的size為10,當(dāng)你要把9個同樣的buff寫到文件時,你需要調(diào)用9次write,也就是9次系統(tǒng)調(diào)用,此時也并沒有寫到硬盤,如果想立即寫到硬盤,調(diào)用fsync,可以進(jìn)行實際的I/O操作。
標(biāo)準(zhǔn)I/O,也就是帶緩存的I/O采用 FILE*,F(xiàn)ILE實際上包含了為管理流所需要的所有信息:實際I/O的文件描述符,指向流緩存的指針(標(biāo)準(zhǔn)I/O緩存,由malloc分配,又稱為用戶態(tài)進(jìn)程空間的緩存,區(qū)別于內(nèi)核所設(shè)的緩存),緩存長度,當(dāng)前在緩存中的字節(jié)數(shù),出錯標(biāo)志等,假設(shè)流緩存的長度為50字節(jié),把以上的數(shù)據(jù)寫到文件,則只需要2次系統(tǒng)調(diào)用(fwrite調(diào)用write系統(tǒng)調(diào)用),因為先把數(shù)據(jù)寫到流緩存,當(dāng)其滿以后或者調(diào)用fflush時才填入內(nèi)核緩存,所以進(jìn)行了2次的系統(tǒng)調(diào)用write。
fflush將流所有未寫的數(shù)據(jù)送入(刷新)到內(nèi)核(內(nèi)核緩沖區(qū)),fsync將所有內(nèi)核緩沖區(qū)的數(shù)據(jù)寫到文件(磁盤)。至于究竟寫到了文件中還是內(nèi)核緩沖區(qū)中對于進(jìn)程來說是沒有差別 的,如果進(jìn)程A和進(jìn)程B打開同一文件,進(jìn)程A寫到內(nèi)核I/O緩沖區(qū)中的數(shù)據(jù)從進(jìn)程B也能讀到,因為內(nèi)核空間是進(jìn)程共享的,
而c標(biāo)準(zhǔn)庫的I/O緩沖區(qū)則不具有這一特性,因為進(jìn)程的用戶空間是完全獨立的.(個人覺得這句話非常重要)
不帶緩存的read和write是相對于 fread/fwrite等流函數(shù)來說明的,因為fread和fwrite是用戶函數(shù)(3),所以他們會在用戶層進(jìn)行一次數(shù)據(jù)的緩存,而read/write是系統(tǒng)調(diào)用(2)所以他們在用戶層是沒有緩存的,所以稱read和write是無緩存的IO,其實對于內(nèi)核來說還是進(jìn)行了緩存,不過用戶層看不到罷了。
上面的內(nèi)容介紹了庫緩沖機(jī)制,其中也提到了內(nèi)核緩沖區(qū)這個概念,到底內(nèi)核緩沖存在的價值是很么呢:
為什么總是需要將數(shù)據(jù)由內(nèi)核緩沖區(qū)換到用戶緩沖區(qū)或者相反呢?
答:用戶進(jìn)程是運行在用戶空間的,不能直接操作內(nèi)核緩沖區(qū)的數(shù)據(jù)。 用戶進(jìn)程進(jìn)行系統(tǒng)調(diào)用的時候,會由用戶態(tài)切換到內(nèi)核態(tài),待內(nèi)核處理完之后再返回用戶態(tài)
應(yīng)用緩沖技術(shù)能很明顯的提高系統(tǒng)效率。內(nèi)核與外圍設(shè)備的數(shù)據(jù)交換,內(nèi)核與用戶空間的數(shù)據(jù)交換都是比較費時的,使用緩沖區(qū)就是為了優(yōu)化這些費時的操作。其實核心到用戶空間的操作本身是不buffer的,是由I/O庫用buffer來優(yōu)化了這個操作。比如read本來從內(nèi)核讀取數(shù)據(jù)時是比較費時的,所以一次取出一塊,以避免多次陷入內(nèi)核。
應(yīng)用內(nèi)核緩沖區(qū)的 主要思想就是一次讀入大量的數(shù)據(jù)放在緩沖區(qū),需要的時候從緩沖區(qū)取得數(shù)據(jù)。
管理員模式和用戶模式之間的切換需要消耗時間,但相比之下,磁盤的I/O操作消耗的時間更多,為了提高效率,內(nèi)核也使用緩沖區(qū)技術(shù)來提高對磁盤的訪問速度。磁盤是數(shù)據(jù)塊 的集合,內(nèi)核會對磁盤上的數(shù)據(jù)塊做緩沖。內(nèi)核將磁盤上的數(shù)據(jù)塊復(fù)制到內(nèi)核緩沖區(qū)中,當(dāng)一個用戶空間中的進(jìn)程要從磁盤上讀數(shù)據(jù)時,內(nèi)核一般不直接讀磁盤,而 是將內(nèi)核緩沖區(qū)中的數(shù)據(jù)復(fù)制到進(jìn)程的緩沖區(qū)中。當(dāng)進(jìn)程所要求的數(shù)據(jù)塊不在內(nèi)核緩沖區(qū)時,內(nèi)核會把相應(yīng)的數(shù)據(jù)塊加入到請求隊列,然后把該進(jìn)程掛起,接著為其 他進(jìn)程服務(wù)。一段時間之后(其實很短的時間),內(nèi)核把相應(yīng)的數(shù)據(jù)塊從磁盤讀到內(nèi)核緩沖區(qū),然后再把數(shù)據(jù)復(fù)制到進(jìn)程的緩沖區(qū)中,最后喚醒被掛起的進(jìn)程。
注:理解內(nèi)核緩沖區(qū)技術(shù)的原理有助于更好的掌握系統(tǒng)調(diào)用read&write,read把數(shù)據(jù)從內(nèi)核緩沖區(qū)復(fù)制到進(jìn)程緩沖區(qū),write把數(shù)據(jù)從進(jìn)程緩沖區(qū)復(fù)制到內(nèi)核緩沖區(qū),它們不等價于數(shù)據(jù)在內(nèi)核緩沖區(qū)和磁盤之間的交換。
從理論上講,內(nèi)核可以在任何時候?qū)懘疟P,但并不是所有的write操作都會導(dǎo)致內(nèi)核的寫動作。內(nèi)核會把要寫的數(shù)據(jù)暫時存在緩沖區(qū)中,積累到一定數(shù)量后再一 次寫入。有時會導(dǎo)致意外情況,比如斷電,內(nèi)核還來不及把內(nèi)核緩沖區(qū)中的數(shù)據(jù)寫道磁盤上,這些更新的數(shù)據(jù)就會丟失。
應(yīng)用內(nèi)核緩沖技術(shù)導(dǎo)致的結(jié)果是:提高了磁盤的I/O效率;優(yōu)化了磁盤的寫操作;需要及時的將緩沖數(shù)據(jù)寫到磁盤。
以上這篇淺談帶緩沖I/O 和不帶緩沖I/O的區(qū)別與聯(lián)系就是小編分享給大家的全部內(nèi)容了,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
C語言入門篇--學(xué)習(xí)選擇,if,switch語句以及代碼塊
本篇文章是基礎(chǔ)篇,適合c語言剛?cè)腴T的朋友,本文主要帶大家學(xué)習(xí)一下C語言的選擇,if,switch語句及代碼塊,幫助大家快速入門c語言的世界,更好的理解c語言2021-08-08解析C++編程中如何使用設(shè)計模式中的狀態(tài)模式結(jié)構(gòu)
這篇文章主要介紹了如何在C++編程中適用設(shè)計模式中的狀態(tài)模式結(jié)構(gòu),狀態(tài)模式強(qiáng)調(diào)將特定狀態(tài)相關(guān)的邏輯分散到一些類的狀態(tài)類中,需要的朋友可以參考下2016-03-03通過stringstream實現(xiàn)常用的類型轉(zhuǎn)換實例代碼
在本篇文章里小編給大家分享了關(guān)于通過stringstream實現(xiàn)常用的類型轉(zhuǎn)換實例代碼內(nèi)容,需要的朋友們可以參考下。2020-04-04