天天看點

IO之标準C庫buffer

IO之标準C庫buffer

在論述這個主題之前,先介紹一下标準C庫和linux系統調用以及windows API之間的關系。

拿寫檔案來舉個例子

linux下寫檔案用write()

windows下寫檔案用WriteFile()

這說明不同作業系統實作同樣的系統功能的接口應該是不一樣的。造成這種現狀是作業系統發展的曆史原因造成的,無法在作業系統的層面統一系統函數接口。同樣功能的程式在linux上寫一套,windows上又得寫另外一套,毫無移植性可言。如果要開發一個既能在linux跑,又能在windows上跑的程式,開發成本飙升!

為了解決這個移植性的問題,标準C庫利用了封裝技術,扮演了一個重要的角色,統一了部分基本功能接口。

标準C規定的寫檔案的函數是fwrite(),就是不管在linux還是在windows上,各自都有一個标準C庫,庫函數封裝的下層細節不一樣,但是接口完全一樣,提供的功能完全一樣。

這是怎麼做到的?猜一猜大緻實作就知道了

在linux上,标準C接口fwrite()的實作僞代碼

size_t fwrite(constvoid* buffer, size_t size, size_t count, FILE* stream){

   ...

   ...

   return write(stream->fd,buffer,count);

}

在windows上,标準C接口fwrite()的實作僞代碼

size_t fwrite(constvoid* buffer, size_t size, size_t count, FILE* stream){

#defineOUT 

   BOOL ret = false;

   OUT int optnum;

   ...

   ...

    ret = WriteFile(stream->filehandle, buffer, count,&optnum,...);

    if( ret == true)

       return optnum;

   else

       return -1;

}

内部實作不一緻,沒關系,接口一樣就可以,不管在linux還是windows上,寫檔案都用fwrite(),分别在各自平台上編譯就可以了。

标準C(即應用層标準C語言函數庫)就是這樣一個處于系統層面之上的應用層标準函數庫,為了統一各個作業系統上的函數接口而生。

回到我們的主題----IO之應用層buffer

什麼是應用層buffer?

回想一下我之前介紹的《IO之核心buffer"buffer cache"》,既然write()能把需要寫檔案的資料推送到一個核心buffer來偷工減料欺騙應用層(為了加速I/O),說“我已經寫完檔案并傳回了”。那應用層的标準C庫的fwrite()按道理也可以為了加速,在真正調用write()之前,把資料放到(FILE*)stream->buffer中,等到多次調用fwrite(),直至(FILE*)stream->buffer中積攢的資料量達到(FILE*)stream->bufferlen這麼多的時候,一次性的把這些資料全部送入write()接口,寫入核心,這是多麼美妙啊。。。

實際上,标準C庫就是這麼做的!

把fwrite()的linux實作再細緻一下

過程其實仍然很粗糙,為了突出buffer的重點,計算stream->buffer是否滿,拷貝多少,填充多少這樣的細節和主題無關的東西我略去了

size_t fwrite(constvoid* buffer, size_t size, size_t count, FILE* stream){

   ...

   if( stream->buffer滿 ){

       write(stream->fd,stream->buffer,stream->bufferlen);

   } else{

       拷貝buffer内容至stream->buffer//應用層的C庫裡的buffer指的就是這裡的stream->buffer

   }

   ...

    return count;

   //過程很粗糙,為了突出buffer的重點,計算stream->buffer是否滿,拷貝多少,填充多少這樣的細節和主題無關的東西我略去了

}

fwrite()在windows平台的實作也基本上是這樣的,也有buffer。

值得一說的是,fread()也有一個讀cache來完成預讀。

setvbuf()和setbuf()都是控制這個标準C庫的buffer的。

還有fflush()是C庫用于flush(應用層buffer,即這個标準C庫裡的buffer)資料的函數。

以上三個函數,如果大家有興趣,可以去看看linux上對應的man文檔。

重點是要知道不僅系統的核心有buffer,應用層的C庫同樣也有buffer。這些buffer的唯一作用就是為了加速應用,不讓應用老是卡在和磁盤互動上。

說個題外話,

實際上對于磁盤、RAID卡、盤陣這樣的外存媒體而言,他們各自在硬體上也都有一層前端的buffer,有時也叫cache,用來緩沖讀寫加速。cache越多,價格越貴,性能越好。大型儲存設備一般擁有多層cache,用的是昂貴的SSD。

需要分享的一點經驗是,不管是标準C庫的buffer也好,核心的"buffer cache"也罷,我們終究對它們的控制力度是有限的。我們在做伺服器程式的時候,如果業務上涉及太大的I/O量,需要做服務整體加速的時候,我們一般自己在業務層做一層自己的"buffer",把業務資料buffer住,攢成以檔案系統或者磁盤的block塊機關的大塊資料,然後集中寫,然後集中寫又有集中寫的政策。。。

再引申一點内容,做高性能大流量的大站的架構,其中最重要幾個架構角色之一就是cache。前端CDN、後端memcache、redis、mysql内部cache等等,都是cache的應用場景,可以說"buffer cache"在伺服器領域從軟體實作到硬體加速再到架構,真的是無處不在。