溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

java零拷貝的實現(xiàn)方式是什么

發(fā)布時間:2022-02-07 15:18:51 來源:億速云 閱讀:161 作者:iii 欄目:開發(fā)技術

本篇內容主要講解“java零拷貝的實現(xiàn)方式是什么”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學習“java零拷貝的實現(xiàn)方式是什么”吧!

1.什么是零拷貝

零拷貝字面上的意思包括兩個,“零”和“拷貝”:

  • “拷貝”:就是指數(shù)據(jù)從一個存儲區(qū)域轉移到另一個存儲區(qū)域。

  • “零” :表示次數(shù)為0,它表示拷貝數(shù)據(jù)的次數(shù)為0。

合起來,那零拷貝就是不需要將數(shù)據(jù)從一個存儲區(qū)域復制到另一個存儲區(qū)域咯。

零拷貝是指計算機執(zhí)行IO操作時,CPU不需要將數(shù)據(jù)從一個存儲區(qū)域復制到另一個存儲區(qū)域,從而可以減少上下文切換以及CPU的拷貝時間。它是一種I/O操作優(yōu)化技術。

2. 傳統(tǒng) IO 的執(zhí)行流程

做服務端開發(fā)的小伙伴,文件下載功能應該實現(xiàn)過不少了吧。如果你實現(xiàn)的是一個web程序,前端請求過來,服務端的任務就是:將服務端主機磁盤中的文件從已連接的socket發(fā)出去。關鍵實現(xiàn)代碼如下:

while((n = read(diskfd, buf, BUF_SIZE)) > 0)
    write(sockfd, buf , n);

傳統(tǒng)的IO流程,包括read和write的過程。

  • read:把數(shù)據(jù)從磁盤讀取到內核緩沖區(qū),再拷貝到用戶緩沖區(qū)

  • write:先把數(shù)據(jù)寫入到socket緩沖區(qū),最后寫入網(wǎng)卡設備。

流程圖如下:

java零拷貝的實現(xiàn)方式是什么

  • 用戶應用進程調用read函數(shù),向操作系統(tǒng)發(fā)起IO調用, 上下文從用戶態(tài)轉為內核態(tài)(切換1)

  • DMA控制器把數(shù)據(jù)從磁盤中,讀取到內核緩沖區(qū)。

  • CPU把內核緩沖區(qū)數(shù)據(jù),拷貝到用戶應用緩沖區(qū), 上下文從內核態(tài)轉為用戶態(tài)(切換2),read函數(shù)返回

  • 用戶應用進程通過write函數(shù),發(fā)起IO調用, 上下文從用戶態(tài)轉為內核態(tài)(切換3)

  • CPU將用戶緩沖區(qū)中的數(shù)據(jù),拷貝到socket緩沖區(qū)

  • DMA控制器把數(shù)據(jù)從socket緩沖區(qū),拷貝到網(wǎng)卡設備, 上下文從內核態(tài)切換回用戶態(tài)(切換4),write函數(shù)返回

從流程圖可以看出,傳統(tǒng)IO的讀寫流程,包括了4次上下文切換(4次用戶態(tài)和內核態(tài)的切換),4次數(shù)據(jù)拷貝(兩次CPU拷貝以及兩次的DMA拷貝),什么是DMA拷貝呢?我們一起來回顧下,零拷貝涉及的操作系統(tǒng)知識點哈。

3. 零拷貝相關的知識點回顧

3.1 內核空間和用戶空間

我們電腦上跑著的應用程序,其實是需要經(jīng)過操作系統(tǒng),才能做一些特殊操作,如磁盤文件讀寫、內存的讀寫等等。因為這些都是比較危險的操作,不可以由應用程序亂來,只能交給底層操作系統(tǒng)來。

因此,操作系統(tǒng)為每個進程都分配了內存空間,一部分是用戶空間,一部分是內核空間。內核空間是操作系統(tǒng)內核訪問的區(qū)域,是受保護的內存空間,而用戶空間是用戶應用程序訪問的內存區(qū)域。 以32位操作系統(tǒng)為例,它會為每一個進程都分配了4G(2的32次方)的內存空間。

  • 內核空間:主要提供進程調度、內存分配、連接硬件資源等功能

  • 用戶空間:提供給各個程序進程的空間,它不具有訪問內核空間資源的權限,如果應用程序需要使用到內核空間的資源,則需要通過系統(tǒng)調用來完成。進程從用戶空間切換到內核空間,完成相關操作后,再從內核空間切換回用戶空間。

3.2 什么是用戶態(tài)、內核態(tài)

  • 如果進程運行于內核空間,被稱為進程的內核態(tài)

  • 如果進程運行于用戶空間,被稱為進程的用戶態(tài)。

3.3 什么是上下文切換

  • 什么是CPU上下文?

  • CPU 寄存器,是CPU內置的容量小、但速度極快的內存。而程序計數(shù)器,則是用來存儲 CPU 正在執(zhí)行的指令位置、或者即將執(zhí)行的下一條指令位置。它們都是 CPU 在運行任何任務前,必須的依賴環(huán)境,因此叫做CPU上下文。

  • 什么是 CPU上下文切換?

  • 它是指,先把前一個任務的CPU上下文(也就是CPU寄存器和程序計數(shù)器)保存起來,然后加載新任務的上下文到這些寄存器和程序計數(shù)器,最后再跳轉到程序計數(shù)器所指的新位置,運行新任務。

一般我們說的上下文切換,就是指內核(操作系統(tǒng)的核心)在CPU上對進程或者線程進行切換。進程從用戶態(tài)到內核態(tài)的轉變,需要通過系統(tǒng)調用來完成。系統(tǒng)調用的過程,會發(fā)生CPU上下文的切換。

CPU 寄存器里原來用戶態(tài)的指令位置,需要先保存起來。接著,為了執(zhí)行內核態(tài)代碼,CPU 寄存器需要更新為內核態(tài)指令的新位置。最后才是跳轉到內核態(tài)運行內核任務。

java零拷貝的實現(xiàn)方式是什么

3.4 虛擬內存

現(xiàn)代操作系統(tǒng)使用虛擬內存,即虛擬地址取代物理地址,使用虛擬內存可以有2個好處:

  • 虛擬內存空間可以遠遠大于物理內存空間

  • 多個虛擬內存可以指向同一個物理地址

正是多個虛擬內存可以指向同一個物理地址,可以把內核空間和用戶空間的虛擬地址映射到同一個物理地址,這樣的話,就可以減少IO的數(shù)據(jù)拷貝次數(shù)啦,示意圖如下

java零拷貝的實現(xiàn)方式是什么

3.5 DMA技術

DMA,英文全稱是Direct Memory Access,即直接內存訪問。DMA本質上是一塊主板上獨立的芯片,允許外設設備和內存存儲器之間直接進行IO數(shù)據(jù)傳輸,其過程不需要CPU的參與。

我們一起來看下IO流程,DMA幫忙做了什么事情.

java零拷貝的實現(xiàn)方式是什么

  • 用戶應用進程調用read函數(shù),向操作系統(tǒng)發(fā)起IO調用,進入阻塞狀態(tài),等待數(shù)據(jù)返回。

  • CPU收到指令后,對DMA控制器發(fā)起指令調度。

  • DMA收到IO請求后,將請求發(fā)送給磁盤;

  • 磁盤將數(shù)據(jù)放入磁盤控制緩沖區(qū),并通知DMA

  • DMA將數(shù)據(jù)從磁盤控制器緩沖區(qū)拷貝到內核緩沖區(qū)。

  • DMA向CPU發(fā)出數(shù)據(jù)讀完的信號,把工作交換給CPU,由CPU負責將數(shù)據(jù)從內核緩沖區(qū)拷貝到用戶緩沖區(qū)。

  • 用戶應用進程由內核態(tài)切換回用戶態(tài),解除阻塞狀態(tài)

可以發(fā)現(xiàn),DMA做的事情很清晰啦,它主要就是幫忙CPU轉發(fā)一下IO請求,以及拷貝數(shù)據(jù)。為什么需要它的?

主要就是效率,它幫忙CPU做事情,這時候,CPU就可以閑下來去做別的事情,提高了CPU的利用效率。大白話解釋就是,CPU老哥太忙太累啦,所以他找了個小弟(名叫DMA) ,替他完成一部分的拷貝工作,這樣CPU老哥就能著手去做其他事情。

4. 零拷貝實現(xiàn)的幾種方式

零拷貝并不是沒有拷貝數(shù)據(jù),而是減少用戶態(tài)/內核態(tài)的切換次數(shù)以及CPU拷貝的次數(shù)。零拷貝實現(xiàn)有多種方式,分別是

  • mmap+write

  • sendfile

  • 帶有DMA收集拷貝功能的sendfile

4.1 mmap+write實現(xiàn)的零拷貝

mmap 的函數(shù)原型如下:

void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
  • addr:指定映射的虛擬內存地址

  • length:映射的長度

  • prot:映射內存的保護模式

  • flags:指定映射的類型

  • fd:進行映射的文件句柄

  • offset:文件偏移量

前面一小節(jié),零拷貝相關的知識點回顧,我們介紹了虛擬內存,可以把內核空間和用戶空間的虛擬地址映射到同一個物理地址,從而減少數(shù)據(jù)拷貝次數(shù)!mmap就是用了虛擬內存這個特點,它將內核中的讀緩沖區(qū)與用戶空間的緩沖區(qū)進行映射,所有的IO都在內核中完成。

mmap+write實現(xiàn)的零拷貝流程如下:

java零拷貝的實現(xiàn)方式是什么

  • 用戶進程通過 mmap方法向操作系統(tǒng)內核發(fā)起IO調用, 上下文從用戶態(tài)切換為內核態(tài)。

  • CPU利用DMA控制器,把數(shù)據(jù)從硬盤中拷貝到內核緩沖區(qū)。

  • 上下文從內核態(tài)切換回用戶態(tài),mmap方法返回。

  • 用戶進程通過 write方法向操作系統(tǒng)內核發(fā)起IO調用, 上下文從用戶態(tài)切換為內核態(tài)。

  • CPU將內核緩沖區(qū)的數(shù)據(jù)拷貝到的socket緩沖區(qū)。

  • CPU利用DMA控制器,把數(shù)據(jù)從socket緩沖區(qū)拷貝到網(wǎng)卡, 上下文從內核態(tài)切換回用戶態(tài),write調用返回。

可以發(fā)現(xiàn),mmap+write實現(xiàn)的零拷貝,I/O發(fā)生了4次用戶空間與內核空間的上下文切換,以及3次數(shù)據(jù)拷貝。其中3次數(shù)據(jù)拷貝中,包括了2次DMA拷貝和1次CPU拷貝。

mmap是將讀緩沖區(qū)的地址和用戶緩沖區(qū)的地址進行映射,內核緩沖區(qū)和應用緩沖區(qū)共享,所以節(jié)省了一次CPU拷貝‘’并且用戶進程內存是虛擬的,只是映射到內核的讀緩沖區(qū),可以節(jié)省一半的內存空間。

4.2 sendfile實現(xiàn)的零拷貝

sendfile是Linux2.1內核版本后引入的一個系統(tǒng)調用函數(shù),API如下:

ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count);
  • out_fd:為待寫入內容的文件描述符,一個socket描述符。,

  • in_fd:為待讀出內容的文件描述符,必須是真實的文件,不能是socket和管道。

  • offset:指定從讀入文件的哪個位置開始讀,如果為NULL,表示文件的默認起始位置。

  • count:指定在fdout和fdin之間傳輸?shù)淖止?jié)數(shù)。

sendfile表示在兩個文件描述符之間傳輸數(shù)據(jù),它是在操作系統(tǒng)內核中操作的,避免了數(shù)據(jù)從內核緩沖區(qū)和用戶緩沖區(qū)之間的拷貝操作,因此可以使用它來實現(xiàn)零拷貝。

sendfile實現(xiàn)的零拷貝流程如下:

java零拷貝的實現(xiàn)方式是什么

sendfile實現(xiàn)的零拷貝

  • 用戶進程發(fā)起sendfile系統(tǒng)調用, 上下文(切換1)從用戶態(tài)轉向內核態(tài)

  • DMA控制器,把數(shù)據(jù)從硬盤中拷貝到內核緩沖區(qū)。

  • CPU將讀緩沖區(qū)中數(shù)據(jù)拷貝到socket緩沖區(qū)

  • DMA控制器,異步把數(shù)據(jù)從socket緩沖區(qū)拷貝到網(wǎng)卡,

  • 上下文(切換2)從內核態(tài)切換回用戶態(tài),sendfile調用返回。

可以發(fā)現(xiàn),sendfile實現(xiàn)的零拷貝,I/O發(fā)生了2次用戶空間與內核空間的上下文切換,以及3次數(shù)據(jù)拷貝。其中3次數(shù)據(jù)拷貝中,包括了2次DMA拷貝和1次CPU拷貝。那能不能把CPU拷貝的次數(shù)減少到0次呢?有的,即帶有DMA收集拷貝功能的sendfile!

4.3 sendfile+DMA scatter/gather實現(xiàn)的零拷貝

linux 2.4版本之后,對sendfile做了優(yōu)化升級,引入SG-DMA技術,其實就是對DMA拷貝加入了scatter/gather操作,它可以直接從內核空間緩沖區(qū)中將數(shù)據(jù)讀取到網(wǎng)卡。使用這個特點搞零拷貝,即還可以多省去一次CPU拷貝。

sendfile+DMA scatter/gather實現(xiàn)的零拷貝流程如下:

java零拷貝的實現(xiàn)方式是什么

  • 用戶進程發(fā)起sendfile系統(tǒng)調用, 上下文(切換1)從用戶態(tài)轉向內核態(tài)

  • DMA控制器,把數(shù)據(jù)從硬盤中拷貝到內核緩沖區(qū)。

  • CPU把內核緩沖區(qū)中的 文件描述符信息(包括內核緩沖區(qū)的內存地址和偏移量)發(fā)送到socket緩沖區(qū)

  • DMA控制器根據(jù)文件描述符信息,直接把數(shù)據(jù)從內核緩沖區(qū)拷貝到網(wǎng)卡

  • 上下文(切換2)從內核態(tài)切換回用戶態(tài),sendfile調用返回。

可以發(fā)現(xiàn),sendfile+DMA scatter/gather實現(xiàn)的零拷貝,I/O發(fā)生了2次用戶空間與內核空間的上下文切換,以及2次數(shù)據(jù)拷貝。其中2次數(shù)據(jù)拷貝都是包DMA拷貝。這就是真正的 零拷貝(Zero-copy) 技術,全程都沒有通過CPU來搬運數(shù)據(jù),所有的數(shù)據(jù)都是通過DMA來進行傳輸?shù)摹?/p>

5. java提供的零拷貝方式

  • Java NIO對mmap的支持

  • Java NIO對sendfile的支持

5.1 Java NIO對mmap的支持

Java NIO有一個MappedByteBuffer的類,可以用來實現(xiàn)內存映射。它的底層是調用了Linux內核的mmap的API。

mmap的小demo如下:

public class MmapTest {

    public static void main(String[] args) {
        try {
            FileChannel readChannel = FileChannel.open(Paths.get("./jay.txt"), StandardOpenOption.READ);
            MappedByteBuffer data = readChannel.map(FileChannel.MapMode.READ_ONLY, 0, 1024 * 1024 * 40);
            FileChannel writeChannel = FileChannel.open(Paths.get("./siting.txt"), StandardOpenOption.WRITE, StandardOpenOption.CREATE);
            //數(shù)據(jù)傳輸
            writeChannel.write(data);
            readChannel.close();
            writeChannel.close();
        }catch (Exception e){
            System.out.println(e.getMessage());
        }
    }
}

5.2 Java NIO對sendfile的支持

FileChannel的transferTo()/transferFrom(),底層就是sendfile() 系統(tǒng)調用函數(shù)。Kafka 這個開源項目就用到它,平時面試的時候,回答面試官為什么這么快,就可以提到零拷貝sendfile這個點。

@Override
public long transferFrom(FileChannel fileChannel, long position, long count) throws IOException {
   return fileChannel.transferTo(position, count, socketChannel);
}

sendfile的小demo如下:

public class SendFileTest {
    public static void main(String[] args) {
        try {
            FileChannel readChannel = FileChannel.open(Paths.get("./jay.txt"), StandardOpenOption.READ);
            long len = readChannel.size();
            long position = readChannel.position();
            
            FileChannel writeChannel = FileChannel.open(Paths.get("./siting.txt"), StandardOpenOption.WRITE, StandardOpenOption.CREATE);
            //數(shù)據(jù)傳輸
            readChannel.transferTo(position, len, writeChannel);
            readChannel.close();
            writeChannel.close();
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
    }
}

到此,相信大家對“java零拷貝的實現(xiàn)方式是什么”有了更深的了解,不妨來實際操作一番吧!這里是億速云網(wǎng)站,更多相關內容可以進入相關頻道進行查詢,關注我們,繼續(xù)學習!

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內容(圖片、視頻和文字)以原創(chuàng)、轉載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權內容。

AI