四種緩沖I/O(緩沖I/O息堂,直接I/O嚷狞,內(nèi)存映射,零拷貝)

https://byvoid.com/zhs/blog/fast-readfile/
https://www.cnblogs.com/sumuyi/p/12813787.html
https://blog.csdn.net/weixin_37782390/article/details/103833306【Java零拷貝】


緩沖I/O和直接I/O

  • 應用程序內(nèi)存(用戶空間):你malloc荣堰、new的內(nèi)存
  • 用戶緩沖區(qū)(用戶空間):C語言里面的FILE*里面的buffer
typedef struct
{
  short bsize;
  ....
  unsigned char* buffer;
}
  • 內(nèi)核緩沖區(qū)(內(nèi)核空間床未,內(nèi)存):Linux的Page Cache,為了加快磁盤IO振坚,將磁盤上的page(一個page一般4K)加載到內(nèi)存中的內(nèi)核緩沖區(qū)
緩沖I/O:讀寫都是三次數(shù)據(jù)拷貝薇搁,方向相反

磁盤<->內(nèi)核緩沖區(qū)<->用戶緩沖區(qū)<->應用程序內(nèi)存

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<iostream>
int main(){
    char *charFilePath="1.txt";
    FILE *pfile=fopen(charFilePath,"rb");//打開文件,返回文件操作符
    char *pread;
    size_t result;
    if(pfile)//打開文件一定要判斷是否成功
    {
        fseek(pfile,0,SEEK_END);//將文件內(nèi)部的指針指向文件末尾
        long lsize=ftell(pfile);//獲取文件長度渡八,(得到文件位置指針當前位置相對于文件首的偏移字節(jié)數(shù))
        rewind(pfile);//將文件內(nèi)部的指針重新指向一個流的開頭
        pread=(char *) malloc(lsize*sizeof(char)+1);//申請內(nèi)存空間啃洋,lsize*sizeof(char)是為了更嚴謹,16位上char占一個字符屎鳍,其他機器上可能變化

        //用malloc申請的內(nèi)存是沒有初始值的宏娄,如果不賦值會導致寫入的時候找不到結(jié)束標志符而出現(xiàn)內(nèi)存比實際申請值大,寫入數(shù)據(jù)后面跟隨亂碼的情況
        memset(pread,0,lsize*sizeof(char)+1);//將內(nèi)存空間都賦值為‘\0’

        result=fread(pread,1,lsize,pfile);//將pfile中內(nèi)容讀入pread指向內(nèi)存中
    }
    std::cout<<pread<<std::endl;
    fclose(pfile);//關掉文件操作符逮壁,和句柄一樣孵坚,有open就一定有close
    free(pread);//釋放內(nèi)存
    pread=NULL;//指針不再使用,一定要“刪除”窥淆,防止產(chǎn)生野指針
}
直接I/O:讀寫都是兩次數(shù)據(jù)拷貝卖宠,方向各自相反。

磁盤<->內(nèi)核緩沖區(qū)<->應用程序內(nèi)存

#include <sys/types.h>    
#include <sys/stat.h>    
#include <fcntl.h>
int main(){
    int flag;
    int in=open("1.txt",O_RDONLY,S_IRUSR);
    char buffer[1024];
    if(in==-1){
        return -1;
    }
    int out=open("2.txt",O_WRONLY|O_CREAT);
    if(out==-1){
        return -1;
    }  
    while ((flag = read(in, buffer, 1024)) > 0)  
    {     
        write(out, buffer, flag);  
    }
    close(in);  
    close(out);    
    return 0;      
}
緩沖I/O與直接I/O
  • read和fread有什么區(qū)別忧饭?

fread會自動分配緩存FILE buffer扛伍,fread返回的是一個FILE結(jié)構(gòu)指針。
而read返回的是一個int的文件號词裤。從測試結(jié)果看兩者速度差別不是很大刺洒,但還是推薦用read汁咏。

  • fflush和fsync的區(qū)別:

fflush用于把C標準緩沖的數(shù)據(jù)寫到內(nèi)核緩沖,而fsync及其其他類似的函數(shù)用于將數(shù)據(jù)從內(nèi)核緩沖寫進磁盤作媚。如果在寫數(shù)據(jù)后不調(diào)用fsync攘滩,斷電的時候最新的部分數(shù)據(jù)會丟失在內(nèi)存中。


內(nèi)存映射文件與零拷貝

內(nèi)存映射

磁盤<->應用內(nèi)存(跳過內(nèi)核緩沖區(qū))
對變長文件不適合

mmap是一種內(nèi)存映射文件的方法纸泡,即將一個文件或者其它對象映射到進程的地址空間

mmap示意圖
image.png
void *mmap(void *start, size_t length, int prot, int flags, int fd, 
off_t offset);

mmap是一種內(nèi)存映射文件的方法漂问,即將一個文件或者其它對象映射到進程的地址空間,實現(xiàn)文件磁盤地址和進程虛擬地址空間中一段虛擬地址的一一對映關系女揭。(初始化的時候無需拷貝文件到用戶空間)實現(xiàn)這樣的映射關系后蚤假,進程就可以采用指針的方式讀寫操作這一段內(nèi)存(發(fā)生缺頁中斷然后調(diào)頁過程先在交換緩存空間(swap cache)中尋找需要訪問的內(nèi)存頁,如果沒有則調(diào)用nopage函數(shù)把所缺的頁從磁盤裝入到主存中)吧兔,而系統(tǒng)會自動回寫臟頁面到對應的文件磁盤上磷仰,即完成了對文件的操作而不必再調(diào)用read,write等系統(tǒng)調(diào)用函數(shù)。相反境蔼,內(nèi)核空間對這段區(qū)域的修改也直接反映用戶空間灶平,從而可以實現(xiàn)不同進程間的文件共享。

內(nèi)存中的內(nèi)容并不會立即更新到文件中箍土,而是有一段時間的延遲逢享,你可以調(diào)用msync()來顯式同步一下。

總結(jié):常規(guī)文件操作為了提高讀寫效率和保護磁盤吴藻,使用了頁緩存機制瞒爬。這樣造成讀文件時需要先將文件頁從磁盤拷貝到頁緩存中,由于頁緩存處在內(nèi)核空間沟堡,不能被用戶進程直接尋址侧但,所以還需要將頁緩存中數(shù)據(jù)頁再次拷貝到內(nèi)存對應的用戶空間中。這樣航罗,通過了兩次數(shù)據(jù)拷貝過程禀横,才能完成進程對文件內(nèi)容的獲取任務。寫操作也是一樣伤哺,待寫入的buffer在內(nèi)核空間不能直接訪問燕侠,必須要先拷貝至內(nèi)核空間對應的主存,再寫回磁盤中(延遲寫回)立莉,也是需要兩次數(shù)據(jù)拷貝绢彤。

而使用mmap操作文件中,創(chuàng)建新的虛擬內(nèi)存區(qū)域和建立文件磁盤地址和虛擬內(nèi)存區(qū)域映射這兩步蜓耻,沒有任何文件拷貝操作茫舶。而之后訪問數(shù)據(jù)時發(fā)現(xiàn)內(nèi)存中并無數(shù)據(jù)而發(fā)起的缺頁異常過程,可以通過已經(jīng)建立好的映射關系刹淌,只使用一次數(shù)據(jù)拷貝饶氏,就從磁盤中將數(shù)據(jù)傳入內(nèi)存的用戶空間中讥耗,供進程使用。

總而言之疹启,常規(guī)文件操作需要從磁盤到頁緩存再到用戶主存的兩次數(shù)據(jù)拷貝古程。而mmap操控文件,只需要從磁盤到用戶主存的一次數(shù)據(jù)拷貝過程喊崖。說白了挣磨,mmap的關鍵點是實現(xiàn)了用戶空間和內(nèi)核空間的數(shù)據(jù)直接交互而省去了空間不同數(shù)據(jù)不通的繁瑣過程。因此mmap效率更高荤懂。

  • 直接對該段內(nèi)存寫時不會寫入超過當前文件大小的內(nèi)容

  • mmap的缺點: 文件小于4k的頁的大小會造成空間浪費茁裙。且無法擴展空間。

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <iostream>
using namespace std;
int main(){
    int fd;
    void *start;
    struct stat sb;
    fd = open("/etc/passwd", O_RDONLY); 
    /*打開/etc/passwd */
    cout<<fd<<endl;
    fstat(fd, &sb); /* 取得文件大小 */

    start = mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
    if(start == MAP_FAILED) /* 判斷是否映射成功 */
        return;
    cout<<start<<endl;

    munma(start, sb.st_size); /* 解除映射 */
    closed(fd);
}
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <stdio.h>
#include <memory.h>
#include <iostream>
using namespace std;
int main(){
    // mmap讀文件

    // 打開文件
    int fd = open("1.txt", O_RDONLY);  
    // 讀取文件長度
    int len = lseek(fd,0,SEEK_END);  
    // 建立內(nèi)存映射
    char *addr = (char *) mmap(NULL, len, PROT_READ, MAP_PRIVATE,fd, 0);      
    close(fd);
    // data用于保存讀取的數(shù)據(jù)
    char* data=new char(len+1);
    // 復制過來
    memcpy(data, addr, len);
    // 解除映射
    // cout<<data;
    munmap(addr, len);

    // mmap寫文件

    len=strlen(data);
    // 打開文件
    fd=open("output.txt", O_RDWR|O_CREAT, 00777);
    // lseek將文件指針往后移動file_size-1位
    lseek(fd,len-1,SEEK_END);  
    // 從指針處寫入一個空字符节仿;mmap不能擴展文件長度晤锥,這里相當于預先給文件長度,準備一個空架子
    write(fd, "", 1);
    // 使用mmap函數(shù)建立內(nèi)存映射
    addr = (char*)mmap(NULL, len, PROT_READ|PROT_WRITE,MAP_SHARED, fd, 0);
    // 內(nèi)存映射建立好了廊宪,此時可以關閉文件了
    close(fd);
    // 把data復制到addr里
    memcpy(addr, data, len);
    // 解除映射
    munmap(addr, len);
}
image.png

零拷貝(一般用于磁盤->socket)

跳過Socket緩沖區(qū)矾瘾,把內(nèi)核緩沖區(qū)當socket緩沖區(qū)用。
mmap 適合小數(shù)據(jù)量讀寫挤忙,sendFile 適合大文件傳輸霜威。

image.png
sendfile linux老版本2.1-2.3:2次用戶態(tài)和內(nèi)核態(tài)的上下文切換和3次拷貝
sendfile+DMA Scatter/Gather linux新版本2.4:2次用戶態(tài)和內(nèi)核態(tài)的上下文切換和2次拷貝

新版本sendfile沒有CPU拷貝谈喳,但是會傳輸文件描述符和數(shù)據(jù)長度給socket緩沖區(qū)册烈,一般面試主要還是問新版本。

1. 用戶進程通過sendfile()方法向操作系統(tǒng)發(fā)起調(diào)用婿禽,上下文從用戶態(tài)轉(zhuǎn)向內(nèi)核態(tài)
2. DMA控制器利用scatter把數(shù)據(jù)從硬盤中拷貝到讀緩沖區(qū)離散存儲
3. CPU把讀緩沖區(qū)中的文件描述符和數(shù)據(jù)長度發(fā)送到socket緩沖區(qū)
4. DMA控制器根據(jù)文件描述符和數(shù)據(jù)長度赏僧,使用scatter/gather把數(shù)據(jù)從內(nèi)核緩沖區(qū)拷貝到網(wǎng)卡
5. sendfile()調(diào)用返回,上下文從內(nèi)核態(tài)切換回用戶態(tài)
#include <sys/sendfile.h>  
size_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count); 
  • sendfile() 局限于基于文件服務的網(wǎng)絡應用程序扭倾,比如 web 服務器淀零。據(jù)說,在 Linux 內(nèi)核中實現(xiàn) sendfile() 只是為了在其他平臺上使用 sendfile() 的 Apache 程序
  • 由于網(wǎng)絡傳輸具有異步性膛壹,很難在 sendfile () 系統(tǒng)調(diào)用的接收端進行配對的實現(xiàn)方式驾中,所以數(shù)據(jù)傳輸?shù)慕邮斩艘话銢]有用到這種技術。
  • sendfile方法IO數(shù)據(jù)對用戶空間完全不可見模聋,所以只能適用于完全不需要用戶空間處理的情況肩民,比如靜態(tài)文件服務器。
  • 由于CPU和IO速度的差異問題链方,產(chǎn)生了DMA技術持痰,通過DMA搬運來減少CPU的等待時間。DMA(Direct Memory Access)直接內(nèi)存訪問技術本質(zhì)上來說就是一塊主板上獨立的芯片祟蚀,通過它來進行內(nèi)存和IO設備的數(shù)據(jù)傳輸工窍,從而減少CPU的等待時間割卖。

DMA Scatter/Gather 分散/收集功能:

cpu將讀緩沖區(qū)中的數(shù)據(jù)描述信息--內(nèi)存地址和偏移量記錄到socket緩沖區(qū),由 DMA 根據(jù)這些將數(shù)據(jù)從讀緩沖區(qū)拷貝到網(wǎng)卡患雏,相比之前版本減少了一次CPU拷貝的過程鹏溯。但是還是需要占用數(shù)據(jù)總線。


應用場景:

對于文章開頭說的兩個場景:RocketMQ和Kafka都使用到了零拷貝的技術淹仑。

對于MQ而言剿涮,無非就是生產(chǎn)者發(fā)送數(shù)據(jù)到MQ然后持久化到磁盤,之后消費者從MQ讀取數(shù)據(jù)攻人。

對于RocketMQ來說這兩個步驟使用的是mmap+write取试,而Kafka則是使用mmap+write持久化數(shù)據(jù),發(fā)送數(shù)據(jù)使用sendfile怀吻。

總結(jié):

以網(wǎng)卡到磁盤為例:

開銷類型 直接I/O 內(nèi)存映射 零拷貝
數(shù)據(jù)拷貝次數(shù) 4 3 2
內(nèi)存拷貝次數(shù) 2 (內(nèi)核往返用戶) 1 (內(nèi)核到socket) 0 (內(nèi)存與IO設備間不算內(nèi)存拷貝)
上下文切換次數(shù) 4 4 2
最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末瞬浓,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子蓬坡,更是在濱河造成了極大的恐慌猿棉,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,941評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件屑咳,死亡現(xiàn)場離奇詭異萨赁,居然都是意外死亡,警方通過查閱死者的電腦和手機兆龙,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評論 3 395
  • 文/潘曉璐 我一進店門杖爽,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人紫皇,你說我怎么就攤上這事慰安。” “怎么了聪铺?”我有些...
    開封第一講書人閱讀 165,345評論 0 356
  • 文/不壞的土叔 我叫張陵化焕,是天一觀的道長。 經(jīng)常有香客問我铃剔,道長撒桨,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,851評論 1 295
  • 正文 為了忘掉前任键兜,我火速辦了婚禮凤类,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘蝶押。我一直安慰自己踱蠢,他們只是感情好,可當我...
    茶點故事閱讀 67,868評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著茎截,像睡著了一般苇侵。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上企锌,一...
    開封第一講書人閱讀 51,688評論 1 305
  • 那天榆浓,我揣著相機與錄音,去河邊找鬼撕攒。 笑死陡鹃,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的抖坪。 我是一名探鬼主播萍鲸,決...
    沈念sama閱讀 40,414評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼擦俐!你這毒婦竟也來了脊阴?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,319評論 0 276
  • 序言:老撾萬榮一對情侶失蹤蚯瞧,失蹤者是張志新(化名)和其女友劉穎嘿期,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體埋合,經(jīng)...
    沈念sama閱讀 45,775評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡备徐,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了甚颂。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蜜猾。...
    茶點故事閱讀 40,096評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖西设,靈堂內(nèi)的尸體忽然破棺而出瓣铣,到底是詐尸還是另有隱情,我是刑警寧澤贷揽,帶...
    沈念sama閱讀 35,789評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站梦碗,受9級特大地震影響禽绪,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜洪规,卻給世界環(huán)境...
    茶點故事閱讀 41,437評論 3 331
  • 文/蒙蒙 一印屁、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧斩例,春花似錦雄人、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,993評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽恰力。三九已至,卻和暖如春旗吁,著一層夾襖步出監(jiān)牢的瞬間踩萎,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,107評論 1 271
  • 我被黑心中介騙來泰國打工很钓, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留香府,地道東北人。 一個月前我還...
    沈念sama閱讀 48,308評論 3 372
  • 正文 我出身青樓码倦,卻偏偏與公主長得像企孩,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子袁稽,可洞房花燭夜當晚...
    茶點故事閱讀 45,037評論 2 355