1. Linux 百萬并發(fā)「零拷貝」實現(xiàn)原理

        共 2965字,需瀏覽 6分鐘

         ·

        2021-02-13 10:57




        傳統(tǒng)的I/O操作讀取文件并通過Socket發(fā)送,需要經(jīng)過4次上下文切換、2次CPU數(shù)據(jù)拷貝和2次DMA控制器數(shù)據(jù)拷貝,如下圖:


        從中也可以看得出提高性能可以從減少數(shù)據(jù)拷貝和上下文切換的次數(shù)著手,在Linux操作系統(tǒng)層面上有4種實現(xiàn)方案:內(nèi)存映射mmap、sendfile、splice、tee,這些實現(xiàn)中或多多少的減少數(shù)據(jù)拷貝次數(shù)或減少上下文切換次數(shù)。

        操作系統(tǒng)層面的減少數(shù)據(jù)拷貝次數(shù)主要是指用戶空間和內(nèi)核空間的數(shù)據(jù)拷貝,因為只有他們的拷貝是大量消耗CPU時間片的,而DMA控制器拷貝數(shù)據(jù)CPU參與的工作較少,只是輔助作用。


        現(xiàn)實中對零拷貝的概念有廣義和狹義之分,廣義上是指只要減少了數(shù)據(jù)拷貝的次數(shù)就稱之為零拷貝;狹義上是指真正的零拷貝,比如上例中避免2和3的CPU拷貝。


        下面我們逐一看看他們的設(shè)計思想和實現(xiàn)方案


        mmap內(nèi)存映射
        既然是內(nèi)存映射,首先來了解解下虛擬內(nèi)存和物理內(nèi)存的映射關(guān)系,虛擬內(nèi)存是操作系統(tǒng)為了方便操作而對物理內(nèi)存做的抽象,他們之間是靠頁表(Page Table)進(jìn)行關(guān)聯(lián)的,關(guān)系如下

        每個進(jìn)程都有自己的PageTable,進(jìn)程的虛擬內(nèi)存地址通過PageTable對應(yīng)于物理內(nèi)存,內(nèi)存分配具有惰性,它的過程一般是這樣的:進(jìn)程創(chuàng)建后新建與進(jìn)程對應(yīng)的PageTable,當(dāng)進(jìn)程需要內(nèi)存時會通過PageTable尋找物理內(nèi)存,如果沒有找到對應(yīng)的頁幀就會發(fā)生缺頁中斷,從而創(chuàng)建PageTable與物理內(nèi)存的對應(yīng)關(guān)系。虛擬內(nèi)存不僅可以對物理內(nèi)存進(jìn)行擴(kuò)展,還可以更方便地靈活分配,并對編程提供更友好的操作。


        內(nèi)存映射(mmap)是指用戶空間和內(nèi)核空間的虛擬內(nèi)存地址同時映射到同一塊物理內(nèi)存,用戶態(tài)進(jìn)程可以直接操作物理內(nèi)存,避免用戶空間和內(nèi)核空間之間的數(shù)據(jù)拷貝。

        它的具體執(zhí)行流程是這樣的

        1. 用戶進(jìn)程通過系統(tǒng)調(diào)用mmap函數(shù)進(jìn)入內(nèi)核態(tài),發(fā)生第1次上下文切換,并建立內(nèi)核緩沖區(qū);

        2. 發(fā)生缺頁中斷,CPU通知DMA讀取數(shù)據(jù);

        3. DMA拷貝數(shù)據(jù)到物理內(nèi)存,并建立內(nèi)核緩沖區(qū)和物理內(nèi)存的映射關(guān)系;

        4. 建立用戶空間的進(jìn)程緩沖區(qū)和同一塊物理內(nèi)存的映射關(guān)系,由內(nèi)核態(tài)轉(zhuǎn)變?yōu)橛脩魬B(tài),發(fā)生第2次上下文切換;

        5. 用戶進(jìn)程進(jìn)行邏輯處理后,通過系統(tǒng)調(diào)用Socket send,用戶態(tài)進(jìn)入內(nèi)核態(tài),發(fā)生第3次上下文切換;

        6. 系統(tǒng)調(diào)用Send創(chuàng)建網(wǎng)絡(luò)緩沖區(qū),并拷貝內(nèi)核讀緩沖區(qū)數(shù)據(jù);

        7. DMA控制器將網(wǎng)絡(luò)緩沖區(qū)的數(shù)據(jù)發(fā)送網(wǎng)卡,并返回,由內(nèi)核態(tài)進(jìn)入用戶態(tài),發(fā)生第4次上下文切換;

        總結(jié)
        1. 避免了內(nèi)核空間和用戶空間的2次CPU拷貝,但增加了1次內(nèi)核空間的CPU拷貝,整體上相當(dāng)于只減少了1次CPU拷貝;

        2. 針對大文件比較適合mmap,小文件則會造成較多的內(nèi)存碎片,得不償失;

        3. 當(dāng)mmap一個文件時,如果文件被另一個進(jìn)程截獲可能會因為非法訪問導(dǎo)致進(jìn)程被SIGBUS 信號終止;


        sendfile
        sendfile是在linux2.1引入的,它只需要2次上下文切換和1次內(nèi)核CPU拷貝、2次DMA拷貝,函數(shù)原型
        ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count);

        out_fd為文件描述符,in_fd為網(wǎng)絡(luò)緩沖區(qū)描述符,offset偏移量(默認(rèn)NULL),count文件大小。

        它的內(nèi)部執(zhí)行流程是這樣的

        1. 用戶進(jìn)程系統(tǒng)調(diào)用senfile,由用戶態(tài)進(jìn)入內(nèi)核態(tài),發(fā)生第1次上下文切換;

        2. CPU通知DMA控制器把文件數(shù)據(jù)拷貝到內(nèi)核緩沖區(qū);

        3. 內(nèi)核空間自動調(diào)用網(wǎng)絡(luò)發(fā)送功能并拷貝數(shù)據(jù)到網(wǎng)絡(luò)緩沖區(qū);

        4. CPU通知DMA控制器發(fā)送數(shù)據(jù);

        5. sendfile系統(tǒng)調(diào)用結(jié)束并返回,進(jìn)程由內(nèi)核態(tài)進(jìn)入用戶態(tài),發(fā)生第2次上下文切換;

        總結(jié)


        1. 數(shù)據(jù)處理完全是由內(nèi)核操作,減少了2次上下文切換,整個過程2次上下文切換、1次CPU拷貝,2次DMA拷貝;

        2. 雖然可以設(shè)置偏移量,但不能對數(shù)據(jù)進(jìn)行任何的修改;


        sendfile+DMA gather
        Linux2.4對sendfile進(jìn)行了優(yōu)化,為DMA控制器引入了gather功能,就是在不拷貝數(shù)據(jù)到網(wǎng)絡(luò)緩沖區(qū),而是將待發(fā)送數(shù)據(jù)的內(nèi)存地址和偏移量等描述信息存在網(wǎng)絡(luò)緩沖區(qū),DMA根據(jù)描述信息從內(nèi)核的讀緩沖區(qū)截取數(shù)據(jù)并發(fā)送。它的流程是如下

        1. 用戶進(jìn)程系統(tǒng)調(diào)用senfile,由用戶態(tài)進(jìn)入內(nèi)核態(tài),發(fā)生第1次上下文切換;

        2. CPU通知DMA控制器把文件數(shù)據(jù)拷貝到內(nèi)核緩沖區(qū);

        3. 把內(nèi)核緩沖區(qū)地址和sendfile的相關(guān)參數(shù)作為數(shù)據(jù)描述信息存在網(wǎng)絡(luò)緩沖區(qū)中;

        4. CPU通知DMA控制器,DMA根據(jù)網(wǎng)絡(luò)緩沖區(qū)中的數(shù)據(jù)描述截取數(shù)據(jù)并發(fā)送;

        5. sendfile系統(tǒng)調(diào)用結(jié)束并返回,進(jìn)程由內(nèi)核態(tài)進(jìn)入用戶態(tài),發(fā)生第2次上下文切換;

        總結(jié)
        1. 需要硬件支持,如DMA;

        2. 整個過程2次上下文切換,0次CPU拷貝,2次DMA拷貝,實現(xiàn)真正意義上的零拷貝;

        3. 依然不能修改數(shù)據(jù);

        但那時的sendfile有個致命的缺陷,如果你查看Sendfild手冊,你會發(fā)現(xiàn)如下描述

        in_fd不僅僅不能是socket,而且在2.6.33之前Sendfile的out_fd必須是socket,因此sendfile幾乎成了專為網(wǎng)絡(luò)傳輸而設(shè)計的,限制了其使用范圍比較狹窄。2.6.33之后out_fd才可以是任何file,于是乎出現(xiàn)了splice。



        splice
        鑒于Sendfile的缺點,在Linux2.6.17中引入了Splice,它在讀緩沖區(qū)和網(wǎng)絡(luò)操作緩沖區(qū)之間建立管道避免CPU拷貝:先將文件讀入到內(nèi)核緩沖區(qū),然后再與內(nèi)核網(wǎng)絡(luò)緩沖區(qū)建立管道。它的函數(shù)原型
        ssize_t splice(int fd_in, loff_t *off_in, int fd_out, loff_t *off_out, size_t len, unsigned int flags);

        它的執(zhí)行流程如下

        1. 用戶進(jìn)程系統(tǒng)調(diào)用splice,由用戶態(tài)進(jìn)入內(nèi)核態(tài),發(fā)生第1次上下文切換;

        2. CPU通知DMA控制器把文件數(shù)據(jù)拷貝到內(nèi)核緩沖區(qū);

        3. 建立內(nèi)核緩沖區(qū)和網(wǎng)絡(luò)緩沖區(qū)的管道;

        4. CPU通知DMA控制器,DMA從管道讀取數(shù)據(jù)并發(fā)送;

        5. splice系統(tǒng)調(diào)用結(jié)束并返回,進(jìn)程由內(nèi)核態(tài)進(jìn)入用戶態(tài),發(fā)生第2次上下文切換;

        總結(jié)
        1. 整個過程2次上下文切換,0次CPU拷貝,2次DMA拷貝,實現(xiàn)真正意義上的零拷貝;

        2. 依然不能修改數(shù)據(jù);

        3. fd_in和fd_out必須有一個是管道;


        tee
        tee與splice類同,但fd_in和fd_out都必須是管道。

        寫在最后


        各種I/O方案總結(jié)對比如上。

        良許個人微信


        添加良許個人微信即送3套程序員必讀資料


        → 精選技術(shù)資料共享

        → 高手如云交流社群





        本公眾號全部博文已整理成一個目錄,請在公眾號里回復(fù)「m」獲??!

        推薦閱讀:

        VS Code 真的會一統(tǒng)江湖嗎?

        從 "?" 到 "錕斤拷",這都是些啥玩意?

        推薦一款,比 Navicat 還要好用,功能還很強(qiáng)大的 工具!


        5T技術(shù)資源大放送!包括但不限于:C/C++,Linux,Python,Java,PHP,人工智能,單片機(jī),樹莓派,等等。在公眾號內(nèi)回復(fù)「1024」,即可免費(fèi)獲?。?!


        瀏覽 76
        點贊
        評論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報
        評論
        圖片
        表情
        推薦
        點贊
        評論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報
          
          

            1. 性一级录像片片视频免费看 | 日本色色网站 | 99在线观看免费视频 | 五月天乱伦电影 | a级大片免费观看 |