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