您當前的位置:首頁 > 攝影

看一遍就能理解的:零複製技術

作者:由 玩轉Linux核心 發表于 攝影時間:2022-05-05

1.什麼是零複製

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

“複製”:就是指資料從一個儲存區域轉移到另一個儲存區域。

“零” :表示次數為0,它表示複製資料的次數為0。

2022年嵌入式開發想進網際網路大廠,你技術過硬嗎?

從事十年嵌入式轉核心開發(23K到45K),給兄弟們的一些建議

騰訊首發Linux核心原始碼《嵌入式開發進階筆記》差距差的不止一點點哦

合起來,那

零複製

就是不需要將資料從一個儲存區域複製到另一個儲存區域咯。

零複製

是指計算機執行IO操作時,CPU不需要將資料從一個儲存區域複製到另一個儲存區域,從而可以減少上下文切換以及CPU的複製時間。它是一種

I/O

操作最佳化技術。

2. 傳統 IO 的執行流程

做服務端開發的小夥伴,檔案下載功能應該實現過不少了吧。如果你實現的是一個

web程式

,前端請求過來,服務端的任務就是:將服務端主機磁碟中的檔案從已連線的socket發出去。關鍵實現程式碼如下:

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

write(sockfd, buf , n);

傳統的IO流程,包括read和write的過程。

read

:把資料從磁碟讀取到核心緩衝區,再複製到使用者緩衝區

write

:先把資料寫入到socket緩衝區,最後寫入網絡卡裝置。

流程圖如下:

看一遍就能理解的:零複製技術

使用者應用程序呼叫read函式,向作業系統發起IO呼叫,

上下文從使用者態轉為核心態(切換1)

DMA控制器把資料從磁碟中,讀取到核心緩衝區。

CPU把核心緩衝區資料,複製到使用者應用緩衝區,

上下文從核心態轉為使用者態(切換2)

,read函式返回

使用者應用程序透過write函式,發起IO呼叫,

上下文從使用者態轉為核心態(切換3)

CPU將使用者緩衝區中的資料,複製到socket緩衝區

DMA控制器把資料從socket緩衝區,複製到網絡卡裝置,

上下文從核心態切換回使用者態(切換4)

,write函式返回

從流程圖可以看出,

傳統IO的讀寫流程

,包括了4次上下文切換(4次使用者態和核心態的切換),4次資料複製(

兩次CPU複製以及兩次的DMA複製

),什麼是DMA複製呢?我們一起來回顧下,零複製涉及的

作業系統知識點

哈。

【文章福利

】小編推薦自己的Linux核心技術交流群:【

865977150

】整理了一些個人覺得比較好的學習書籍、影片資料共享在群檔案裡面,有需要的可以自行新增哦!!!前100名進群領取,額外贈送一份價值

699的核心資料包

(含影片教程、電子書、實戰專案及程式碼)

看一遍就能理解的:零複製技術

學習直通車:

Linux核心原始碼/記憶體調優/檔案系統/程序管理/裝置驅動/網路協議棧-學習影片教程-騰訊課堂​ke。qq。com/course/4032547?flowToken=1040236​ke。qq。com/course/4032547?flowToken=1040236

核心資料直通車:

Linux核心原始碼技術學習路線+影片教程程式碼資料​docs。qq。com/doc/DTkZRWXRFcWx1bWVx​docs。qq。com/doc/DTkZRWXRFcWx1bWVx

3. 零複製相關的知識點回顧

3.1 核心空間和使用者空間

我們電腦上跑著的應用程式,其實是需要經過

作業系統

,才能做一些特殊操作,如磁碟檔案讀寫、記憶體的讀寫等等。因為這些都是比較危險的操作,

不可以由應用程式亂來

,只能交給底層作業系統來。

因此,作業系統為每個程序都分配了記憶體空間,一部分是使用者空間,一部分是核心空間。

核心空間是作業系統核心訪問的區域,是受保護的記憶體空間,而使用者空間是使用者應用程式訪問的記憶體區域。

以32位作業系統為例,它會為每一個程序都分配了

4G

(2的32次方)的記憶體空間。

核心空間:主要提供程序排程、記憶體分配、連線硬體資源等功能

使用者空間:提供給各個程式程序的空間,它不具有訪問核心空間資源的許可權,如果應用程式需要使用到核心空間的資源,則需要透過系統呼叫來完成。程序從使用者空間切換到核心空間,完成相關操作後,再從核心空間切換回使用者空間。

3.2 什麼是使用者態、核心態

如果程序運行於核心空間,被稱為程序的核心態

如果程序運行於使用者空間,被稱為程序的使用者態。

3.3 什麼是上下文切換

什麼是CPU上下文?

CPU 暫存器,是CPU內建的容量小、但速度極快的記憶體。而程式計數器,則是用來儲存 CPU 正在執行的指令位置、或者即將執行的下一條指令位置。它們都是 CPU 在執行任何任務前,必須的依賴環境,因此叫做CPU上下文。

什麼是

CPU上下文切換

它是指,先把前一個任務的CPU上下文(也就是CPU暫存器和程式計數器)儲存起來,然後載入新任務的上下文到這些暫存器和程式計數器,最後再跳轉到程式計數器所指的新位置,執行新任務。

一般我們說的

上下文切換

,就是指核心(作業系統的核心)在CPU上對程序或者執行緒進行切換。程序從使用者態到核心態的轉變,需要透過

系統呼叫

來完成。系統呼叫的過程,會發生

CPU上下文的切換

CPU 暫存器裡原來使用者態的指令位置,需要先儲存起來。接著,為了執行核心態程式碼,CPU 暫存器需要更新為核心態指令的新位置。最後才是跳轉到核心態執行核心任務。

看一遍就能理解的:零複製技術

3.4 虛擬記憶體

現代作業系統使用虛擬記憶體,即虛擬地址取代物理地址,使用虛擬記憶體可以有2個好處:

虛擬記憶體空間可以遠遠大於物理記憶體空間

多個虛擬記憶體可以指向同一個物理地址

正是

多個虛擬記憶體可以指向同一個物理地址

,可以把核心空間和使用者空間的虛擬地址對映到同一個物理地址,這樣的話,就可以減少IO的資料複製次數啦,示意圖如下

看一遍就能理解的:零複製技術

3.5 DMA技術

DMA,英文全稱是

Direct Memory Access

,即直接記憶體訪問。

DMA

本質上是一塊主機板上獨立的晶片,允許外設裝置和記憶體儲存器之間直接進行IO資料傳輸,其過程

不需要CPU的參與

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

看一遍就能理解的:零複製技術

使用者應用程序呼叫read函式,向作業系統發起IO呼叫,進入阻塞狀態,等待資料返回。

CPU收到指令後,對DMA控制器發起指令排程。

DMA收到IO請求後,將請求傳送給磁碟;

磁碟將資料放入磁碟控制緩衝區,並通知DMA

DMA將資料從磁碟控制器緩衝區複製到核心緩衝區。

DMA向CPU發出資料讀完的訊號,把工作交換給CPU,由CPU負責將資料從核心緩衝區複製到使用者緩衝區。

使用者應用程序由核心態切換回使用者態,解除阻塞狀態

可以發現,DMA做的事情很清晰啦,它主要就是

幫忙CPU轉發一下IO請求,以及複製資料

。為什麼需要它的?

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

4. 零複製實現的幾種方式

零複製並不是沒有複製資料,而是減少使用者態/核心態的切換次數以及CPU複製的次數。零複製實現有多種方式,分別是

mmap+write

sendfile

帶有DMA收集複製功能的sendfile

4.1 mmap+write實現的零複製

mmap 的函式原型如下:

void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);

addr:指定對映的虛擬記憶體地址

length:對映的長度

prot:對映記憶體的保護模式

flags:指定對映的型別

fd:進行對映的檔案控制代碼

offset:檔案偏移量

前面一小節,零複製相關的知識點回顧,我們介紹了

虛擬記憶體

,可以把核心空間和使用者空間的虛擬地址對映到同一個物理地址,從而減少資料複製次數!mmap就是用了虛擬記憶體這個特點,它將核心中的讀緩衝區與使用者空間的緩衝區進行對映,所有的IO都在核心中完成。

mmap+write

實現的零複製流程如下:

看一遍就能理解的:零複製技術

使用者程序透過

mmap方法

向作業系統核心發起IO呼叫,

上下文從使用者態切換為核心態

CPU利用DMA控制器,把資料從硬碟中複製到核心緩衝區。

上下文從核心態切換回使用者態

,mmap方法返回。

使用者程序透過

write

方法向作業系統核心發起IO呼叫,

上下文從使用者態切換為核心態

CPU將核心緩衝區的資料複製到的socket緩衝區。

CPU利用DMA控制器,把資料從socket緩衝區複製到網絡卡,

上下文從核心態切換回使用者態

,write呼叫返回。

可以發現,

mmap+write

實現的零複製,I/O發生了

4

次使用者空間與核心空間的上下文切換,以及3次資料複製。其中3次資料複製中,包括了

2次DMA複製和1次CPU複製

mmap

是將讀緩衝區的地址和使用者緩衝區的地址進行對映,核心緩衝區和應用緩衝區共享,所以節省了一次CPU複製‘’並且使用者程序記憶體是

虛擬的

,只是

對映

到核心的讀緩衝區,可以節省一半的記憶體空間。

4.2 sendfile實現的零複製

sendfile

是Linux2。1核心版本後引入的一個系統呼叫函式,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之間傳輸的位元組數。

sendfile表示在兩個檔案描述符之間傳輸資料,它是在

作業系統核心

中操作的,

避免了資料從核心緩衝區和使用者緩衝區之間的複製操作

,因此可以使用它來實現零複製。

sendfile實現的零複製流程如下:

看一遍就能理解的:零複製技術

使用者程序發起sendfile系統呼叫,

上下文(切換1)從使用者態轉向核心態

DMA控制器,把資料從硬碟中複製到核心緩衝區。

CPU將讀緩衝區中資料複製到socket緩衝區

DMA控制器,非同步把資料從socket緩衝區複製到網絡卡,

上下文(切換2)從核心態切換回使用者態

,sendfile呼叫返回。

可以發現,

sendfile

實現的零複製,I/O發生了

2

次使用者空間與核心空間的上下文切換,以及3次資料複製。其中3次資料複製中,包括了

2次DMA複製和1次CPU複製

。那能不能把CPU複製的次數減少到0次呢?有的,即

帶有DMA收集複製功能的sendfile

4.3 sendfile+DMA scatter/gather實現的零複製

linux 2。4版本之後,對

sendfile

做了最佳化升級,引入SG-DMA技術,其實就是對DMA複製加入了

scatter/gather

操作,它可以直接從核心空間緩衝區中將資料讀取到網絡卡。使用這個特點搞零複製,即還可以多省去

一次CPU複製

sendfile+DMA scatter/gather實現的零複製流程如下:

看一遍就能理解的:零複製技術

使用者程序發起sendfile系統呼叫,

上下文(切換1)從使用者態轉向核心態

DMA控制器,把資料從硬碟中複製到核心緩衝區。

CPU把核心緩衝區中的

檔案描述符資訊

(包括核心緩衝區的記憶體地址和偏移量)傳送到socket緩衝區

DMA控制器根據檔案描述符資訊,直接把資料從核心緩衝區複製到網絡卡

上下文(切換2)從核心態切換回使用者態

,sendfile呼叫返回。

可以發現,

sendfile+DMA scatter/gather

實現的零複製,I/O發生了

2

次使用者空間與核心空間的上下文切換,以及2次資料複製。其中2次資料複製都是包

DMA複製

。這就是真正的

零複製(Zero-copy)

技術,全程都沒有透過CPU來搬運資料,所有的資料都是透過DMA來進行傳輸的。

5. java提供的零複製方式

Java NIO對mmap的支援

Java NIO對sendfile的支援

5.1 Java NIO對mmap的支援

Java NIO有一個

MappedByteBuffer

的類,可以用來實現記憶體對映。它的底層是呼叫了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);

//資料傳輸

writeChannel。write(data);

readChannel。close();

writeChannel。close();

}catch (Exception e){

System。out。println(e。getMessage());

}

}

}

5.2 Java NIO對sendfile的支援

FileChannel的

transferTo()/transferFrom()

,底層就是sendfile() 系統呼叫函式。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);

//資料傳輸

readChannel。transferTo(position, len, writeChannel);

readChannel。close();

writeChannel。close();

} catch (Exception e) {

System。out。println(e。getMessage());

}

}

}

看一遍就能理解的:零複製技術

標簽: 核心  複製  CPU  緩衝區  DMA