大白話解釋蹦骑,零拷貝就是沒有把數(shù)據(jù)從一個(gè)存儲區(qū)域拷貝到另一個(gè)存儲區(qū)域曼库。但是沒有數(shù)據(jù)的復(fù)制区岗,怎么可能實(shí)現(xiàn)數(shù)據(jù)的傳輸呢?其實(shí)我們在java NIO毁枯、netty慈缔、kafka遇到的零拷貝,并不是不復(fù)制數(shù)據(jù)种玛,而是減少不必要的數(shù)據(jù)拷貝次數(shù)藐鹤,從而提升代碼性能
零拷貝的好處
內(nèi)核空間和用戶空間
緩沖區(qū)和虛擬內(nèi)存
傳統(tǒng)的 I/O
mmap+write 實(shí)現(xiàn)的零拷貝
sendfile 實(shí)現(xiàn)的零拷貝
帶有DMA收集拷貝功能的sendfile實(shí)現(xiàn)的零拷貝
java提供的零拷貝方式
零拷貝的好處
減少或避免不必要的CPU數(shù)據(jù)拷貝,從而釋放CPU去執(zhí)行其他任務(wù)
零拷貝機(jī)制能減少用戶空間和操作系統(tǒng)內(nèi)核空間的上下文切換
減少內(nèi)存的占用
內(nèi)核空間和用戶空間
內(nèi)核空間:Linux自身使用的空間赂韵;主要提供進(jìn)程調(diào)度娱节、內(nèi)存分配、連接硬件資源等功能
用戶空間:提供給各個(gè)程序進(jìn)程的空間祭示;用戶空間不具有訪問內(nèi)核空間資源的權(quán)限肄满,如果應(yīng)用程序需要使用到內(nèi)核空間的資源,則需要通過系統(tǒng)調(diào)用來完成:從用戶空間切換到內(nèi)核空間质涛,完成相關(guān)操作后再從內(nèi)核空間切換回用戶空間
緩沖區(qū)和虛擬內(nèi)存
直接內(nèi)存訪問(Direct Memory Access)(DMA)
直接內(nèi)存訪問:DMA允許外設(shè)設(shè)備和內(nèi)存存儲器之間直接進(jìn)行IO數(shù)據(jù)傳輸稠歉,其過程不需要CPU的參與
緩沖區(qū) 是所有I/O的基礎(chǔ),I/O 無非就是把數(shù)據(jù)移進(jìn)或移出緩沖區(qū)
進(jìn)程發(fā)起read請求汇陆,內(nèi)核先檢查內(nèi)核空間緩沖區(qū)是否存在進(jìn)程所需數(shù)據(jù)怒炸,如果已經(jīng)存在,則直接copy數(shù)據(jù)到進(jìn)程的內(nèi)存區(qū)毡代。如果沒有阅羹,系統(tǒng)則向磁盤請求數(shù)據(jù)勺疼,通過DMA寫入內(nèi)核的read緩沖沖區(qū),接著再將內(nèi)核緩沖區(qū)數(shù)據(jù)copy到進(jìn)程的內(nèi)存區(qū)
進(jìn)程發(fā)起write請求捏鱼,則是把進(jìn)程的內(nèi)存區(qū)數(shù)據(jù)copy到內(nèi)核的write緩沖區(qū)恢口,然后再通過DMA把內(nèi)核緩沖區(qū)數(shù)據(jù)刷回磁盤或者網(wǎng)卡中
虛擬內(nèi)存:現(xiàn)代操作系統(tǒng)都使用虛擬內(nèi)存,有如下兩個(gè)好處
一個(gè)以上的虛擬地址可以指向同一個(gè)物理內(nèi)存地址
虛擬內(nèi)存空間可大于實(shí)際可用的物理地址
利用第一點(diǎn)特性可以把內(nèi)核空間地址和用戶空間的虛擬地址映射到同一個(gè)物理地址穷躁,這樣DMA就可以填充(讀寫)對內(nèi)核和用戶空間進(jìn)程同時(shí)可見的緩沖區(qū)了;大致如下
傳統(tǒng)的 I/O
#include<unistd>ssize_twrite(intfiledes,void*buf,size_tnbytes);ssize_tread(intfiledes,void*buf,size_tnbytes);
如java在linux系統(tǒng)上因妇,讀取一個(gè)磁盤文件问潭,并發(fā)送到遠(yuǎn)程端的服務(wù)
1)發(fā)出read系統(tǒng)調(diào)用,會導(dǎo)致用戶空間到內(nèi)核空間的上下文切換婚被,然后再通過DMA將文件中的數(shù)據(jù)從磁盤上讀取到內(nèi)核空間緩沖區(qū)
2)接著將內(nèi)核空間緩沖區(qū)的數(shù)據(jù)拷貝到用戶空間進(jìn)程內(nèi)存狡忙,然后read系統(tǒng)調(diào)用返回。而系統(tǒng)調(diào)用的返回又會導(dǎo)致一次內(nèi)核空間到用戶空間的上下文切換
3)write系統(tǒng)調(diào)用址芯,則再次導(dǎo)致用戶空間到內(nèi)核空間的上下文切換灾茁,將用戶空間的進(jìn)程里的內(nèi)存數(shù)據(jù)復(fù)制到內(nèi)核空間的socket緩沖區(qū)(也是內(nèi)核緩沖區(qū),不過是給socket使用的)谷炸,然后write系統(tǒng)調(diào)用返回北专,再次觸發(fā)上下文切換
4)至于socket緩沖區(qū)到網(wǎng)卡的數(shù)據(jù)傳輸則是獨(dú)立異步的過程,也就是說write系統(tǒng)調(diào)用的返回并不保證數(shù)據(jù)被傳輸?shù)骄W(wǎng)卡
「一共有四次用戶空間與內(nèi)核空間的上下文切換旬陡。四次數(shù)據(jù)copy拓颓,分別是兩次CPU數(shù)據(jù)復(fù)制,兩次DMA數(shù)據(jù)復(fù)制」
mmap+write實(shí)現(xiàn)的零拷貝
#include<sys/mman.h>void*mmap(void*start,size_tlength,intprot,intflags,intfd,off_toffset)
1)發(fā)出mmap系統(tǒng)調(diào)用描孟,導(dǎo)致用戶空間到內(nèi)核空間的上下文切換驶睦。然后通過DMA引擎將磁盤文件中的數(shù)據(jù)復(fù)制到內(nèi)核空間緩沖區(qū)
2)mmap系統(tǒng)調(diào)用返回,導(dǎo)致內(nèi)核空間到用戶空間的上下文切換
3)這里不需要將數(shù)據(jù)從內(nèi)核空間復(fù)制到用戶空間匿醒,因?yàn)橛脩艨臻g和內(nèi)核空間共享了這個(gè)緩沖區(qū)
4)發(fā)出write系統(tǒng)調(diào)用场航,導(dǎo)致用戶空間到內(nèi)核空間的上下文切換。將數(shù)據(jù)從內(nèi)核空間緩沖區(qū)復(fù)制到內(nèi)核空間socket緩沖區(qū)廉羔;write系統(tǒng)調(diào)用返回溉痢,導(dǎo)致內(nèi)核空間到用戶空間的上下文切換
5)異步,DMA引擎將socket緩沖區(qū)中的數(shù)據(jù)copy到網(wǎng)卡
「通過mmap實(shí)現(xiàn)的零拷貝I/O進(jìn)行了4次用戶空間與內(nèi)核空間的上下文切換蜜另,以及3次數(shù)據(jù)拷貝适室;其中3次數(shù)據(jù)拷貝中包括了2次DMA拷貝和1次CPU拷貝」
sendfile實(shí)現(xiàn)的零拷貝
#include <sys/sendfile.h>
ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count);
1)發(fā)出sendfile系統(tǒng)調(diào)用,導(dǎo)致用戶空間到內(nèi)核空間的上下文切換举瑰,然后通過DMA引擎將磁盤文件中的內(nèi)容復(fù)制到內(nèi)核空間緩沖區(qū)中捣辆,接著再將數(shù)據(jù)從內(nèi)核空間緩沖區(qū)復(fù)制到socket相關(guān)的緩沖區(qū)
2)sendfile系統(tǒng)調(diào)用返回,導(dǎo)致內(nèi)核空間到用戶空間的上下文切換此迅。DMA異步將內(nèi)核空間socket緩沖區(qū)中的數(shù)據(jù)傳遞到網(wǎng)卡
「通過sendfile實(shí)現(xiàn)的零拷貝I/O使用了2次用戶空間與內(nèi)核空間的上下文切換汽畴,以及3次數(shù)據(jù)的拷貝旧巾。其中3次數(shù)據(jù)拷貝中包括了2次DMA拷貝和1次CPU拷貝」
帶有DMA收集拷貝功能的sendfile實(shí)現(xiàn)的零拷貝
從Linux 2.4版本開始,操作系統(tǒng)提供scatter和gather的SG-DMA方式忍些,直接從內(nèi)核空間緩沖區(qū)中將數(shù)據(jù)讀取到網(wǎng)卡鲁猩,無需將內(nèi)核空間緩沖區(qū)的數(shù)據(jù)再復(fù)制一份到socket緩沖區(qū)
1)發(fā)出sendfile系統(tǒng)調(diào)用,導(dǎo)致用戶空間到內(nèi)核空間的上下文切換罢坝。通過DMA引擎將磁盤文件中的內(nèi)容復(fù)制到內(nèi)核空間緩沖區(qū)
2)這里沒把數(shù)據(jù)復(fù)制到socket緩沖區(qū)廓握;取而代之的是,相應(yīng)的描述符信息被復(fù)制到socket緩沖區(qū)嘁酿。該描述符包含了兩種的信息:A)內(nèi)核緩沖區(qū)的內(nèi)存地址隙券、B)內(nèi)核緩沖區(qū)的偏移量
3)sendfile系統(tǒng)調(diào)用返回,導(dǎo)致內(nèi)核空間到用戶空間的上下文切換闹司。DMA根據(jù)socket緩沖區(qū)的描述符提供的地址和偏移量直接將內(nèi)核緩沖區(qū)中的數(shù)據(jù)復(fù)制到網(wǎng)卡
「帶有DMA收集拷貝功能的sendfile實(shí)現(xiàn)的I/O使用了2次用戶空間與內(nèi)核空間的上下文切換娱仔,以及2次數(shù)據(jù)的拷貝,而且這2次的數(shù)據(jù)拷貝都是非CPU拷貝游桩。這樣一來我們就實(shí)現(xiàn)了最理想的零拷貝I/O傳輸了牲迫,不需要任何一次的CPU拷貝,以及最少的上下文切換」
java提供的零拷貝方式
java NIO的零拷貝實(shí)現(xiàn)是基于mmap+write方式
FileChannel的map方法產(chǎn)生的MappedByteBuffer FileChannel提供了map()方法借卧,該方法可以在一個(gè)打開的文件和MappedByteBuffer之間建立一個(gè)虛擬內(nèi)存映射盹憎,MappedByteBuffer繼承于ByteBuffer;該緩沖器的內(nèi)存是一個(gè)文件的內(nèi)存映射區(qū)域谓娃。map方法底層是通過mmap實(shí)現(xiàn)的脚乡,因此將文件內(nèi)存從磁盤讀取到內(nèi)核緩沖區(qū)后,用戶空間和內(nèi)核空間共享該緩沖區(qū)滨达。用法如下
FileChannel的transferTo奶稠、transferFrom 如果操作系統(tǒng)底層支持的話,transferTo捡遍、transferFrom也會使用相關(guān)的零拷貝技術(shù)來實(shí)現(xiàn)數(shù)據(jù)的傳輸锌订。用法如下
作者:cscw
來源:掘金