新聞中心
我們接下來(lái)從下面這幾個(gè)問(wèn)題的角度來(lái)給全方面分析零拷貝這個(gè)技術(shù)點(diǎn),一邊讀不懂的同學(xué),趕緊收藏,讀多幾遍就懂了

還有還有,收藏起來(lái),等以后忘記了或者快要面試的時(shí)候,可以逃出來(lái)熟悉熟悉
畢竟,好記性不如爛筆頭的嘞
為什么要有 DMA 技術(shù)?
我們先來(lái)看一下在沒(méi)有DMA技術(shù)之前的IO過(guò)程:
1、CPU發(fā)出對(duì)應(yīng)的指令到磁盤(pán)系統(tǒng),然后返回
2、磁盤(pán)系統(tǒng)收到指令,把數(shù)據(jù)放入到磁盤(pán)系統(tǒng)的內(nèi)部緩沖區(qū)中,然后產(chǎn)生一個(gè)中斷指令
3、CPU收到中斷信號(hào),停止當(dāng)前工作,緊接著把磁盤(pán)系統(tǒng)緩沖區(qū)中的數(shù)據(jù)讀到自己的寄存器內(nèi),然后把寄存器的數(shù)據(jù)寫(xiě)入到內(nèi)存,在此數(shù)據(jù)傳輸期間CPU無(wú)法執(zhí)行其它工作
畫(huà)了一個(gè)圖幫助大家理解
聰明的小伙伴已經(jīng)發(fā)現(xiàn)其中的弊端了,就是數(shù)據(jù)傳輸期間,CPU無(wú)法執(zhí)行其它命令
我們知道CPU是中央處理器,這個(gè)東西的性能能省就省,能扣著點(diǎn)用就扣著點(diǎn)用,畢竟整個(gè)機(jī)器都要用這家伙
簡(jiǎn)單的搬運(yùn)幾個(gè)字符數(shù)據(jù)肯定沒(méi)啥問(wèn)題,但是如果傳輸大量數(shù)據(jù)的時(shí)候都需要CPU來(lái)搬運(yùn),那就很糟糕了
于是,DMA技術(shù)就誕生了,就是直接內(nèi)存訪問(wèn)技術(shù)Direct Memory Access
DMA技術(shù),就是在進(jìn)行IO設(shè)備和內(nèi)存之間數(shù)據(jù)傳輸?shù)臅r(shí)候,數(shù)據(jù)搬運(yùn)的工作全部交給DMA控制器,而CPU不再參與任何和數(shù)據(jù)搬運(yùn)相關(guān)的事情了,這樣就把CPU空出來(lái)了
具體來(lái)看一下使用DMA控制器的流程
1、用戶調(diào)用read,先操作系統(tǒng)發(fā)起IO請(qǐng)求,請(qǐng)求讀取數(shù)據(jù)到自己的內(nèi)存緩沖區(qū),然后進(jìn)入阻塞
2、操作系統(tǒng)收到請(qǐng)求,把IO請(qǐng)求發(fā)給了DMA,然后CPU執(zhí)行其它任務(wù),DMA發(fā)送給磁盤(pán)
3、磁盤(pán)收到IO請(qǐng)求,把數(shù)據(jù)放入到自己的緩沖區(qū),磁盤(pán)系統(tǒng)緩沖區(qū)滿的時(shí)候,向DMA發(fā)起中斷指令
4、DMA收到中斷指令,將磁盤(pán)緩沖區(qū)數(shù)據(jù)拷貝到內(nèi)核緩沖區(qū),不占用CPU
5、DMA讀取了足夠多數(shù)據(jù),發(fā)送中斷信號(hào)給CPU
6、CPU收到DMA信號(hào),知道數(shù)據(jù)準(zhǔn)備好了,將數(shù)據(jù)從內(nèi)核拷貝到用戶空間
看整個(gè)過(guò)程,發(fā)現(xiàn)CPU不再參與數(shù)據(jù)搬運(yùn)的工作,而是由DMA完成的,但是呢,CPU在這個(gè)過(guò)程也是必不可少,因?yàn)閭鬏斒裁?,從哪里傳輸?shù)侥睦镄枰狢PU來(lái)告訴DMA控制器
這就像創(chuàng)業(yè)公司,老板自己干活忙不過(guò)來(lái)了,就招了一個(gè)秘書(shū),但是,這個(gè)秘書(shū)操作什么,如何操作,還是得聽(tīng)老板的指揮
早期 DMA 只存在在主板上,如今由于 I/O 設(shè)備越來(lái)越多,數(shù)據(jù)傳輸?shù)男枨笠膊槐M相同,所以每個(gè) I/O 設(shè)備里面都有自己的 DMA 控制器。
傳統(tǒng)的傳輸文件
先來(lái)給大家簡(jiǎn)單說(shuō)一下用戶空間和內(nèi)核空間,比如我們部署一個(gè)Java程序到一臺(tái)Linux服務(wù)器上,我們可以認(rèn)為JVM的區(qū)域就是用戶空間,其余的空間就是內(nèi)核空間,用戶空間和內(nèi)核空間對(duì)于系統(tǒng)文件的操作權(quán)限是不一樣的
傳統(tǒng)的文件傳輸?shù)墓ぷ鞣绞剑簲?shù)據(jù)讀取和寫(xiě)入是從用戶空間和內(nèi)核空間來(lái)回復(fù)制,而內(nèi)核空間的數(shù)據(jù)是通過(guò)操作系統(tǒng)層面的IO接口從磁盤(pán)讀取或者寫(xiě)入
代碼通常如下,一般會(huì)需要兩個(gè)系統(tǒng)調(diào)用:
read(file, tmp_buf, len);
write(socket, tmp_buf, len);
看這兩行代碼做了啥
兩次系統(tǒng)調(diào)用,發(fā)生了4次用戶態(tài)和內(nèi)核態(tài)的上下文切換,每次系統(tǒng)調(diào)用都得先從用戶態(tài)切換到內(nèi)核態(tài),然后等內(nèi)核態(tài)完成任務(wù),再切換回到用戶態(tài)
一次上下文的切換耗時(shí)幾十納秒到幾微秒,時(shí)間看上去很短,但是在高并發(fā)的場(chǎng)景下,這類時(shí)間就會(huì)變得不可忽視,從而影響系統(tǒng)的性能
中間還發(fā)生了4次數(shù)據(jù)拷貝,其中兩次是DMA的拷貝,DMA技術(shù)是優(yōu)化IO設(shè)備到內(nèi)核區(qū)的,另外兩次是通過(guò)CPU拷貝用戶緩沖區(qū)的
1、第一次拷貝,磁盤(pán)上的數(shù)據(jù)通過(guò)DMA技術(shù)拷貝到操作系統(tǒng)的內(nèi)核區(qū)中
2、第二次拷貝,CPU把內(nèi)核緩沖區(qū)數(shù)據(jù)拷貝到用戶緩沖區(qū)中
3、第三次拷貝,CPU將用戶緩沖區(qū)的數(shù)據(jù)搬運(yùn)到內(nèi)核緩沖區(qū)中
4、第四次拷貝,通過(guò)DMA技術(shù)把內(nèi)核數(shù)據(jù)搬運(yùn)到網(wǎng)卡的緩沖區(qū)中
問(wèn)題:我們搬運(yùn)一次數(shù)據(jù),中間卻復(fù)制了4次,過(guò)多的上下文切換和過(guò)多的數(shù)據(jù)拷貝都會(huì)降低系統(tǒng)性能,所以,如果想提高文件傳輸?shù)男阅?,就需要減少用戶態(tài)和內(nèi)核態(tài)的上下文切換和內(nèi)容拷貝的次數(shù)
優(yōu)化思路
減少用戶態(tài)和內(nèi)核態(tài)之間的上下文切換
之所以發(fā)生上下文的切換,是因?yàn)橛脩艨臻g沒(méi)有權(quán)限操作磁盤(pán)或者網(wǎng)卡,內(nèi)核的權(quán)限最高,這些操作設(shè)備的過(guò)程都需要交給操作系統(tǒng)的內(nèi)核來(lái)完成,一次系統(tǒng)調(diào)用也就意味著必然發(fā)生2次上下文的切換,首先從用戶態(tài)切換到內(nèi)核態(tài),內(nèi)核態(tài)執(zhí)行完任務(wù)之后再切換到用戶態(tài)執(zhí)行相應(yīng)進(jìn)程的代碼指令
所以,要減少上下文切換的次數(shù),就需要減少系統(tǒng)調(diào)用的次數(shù)
減少數(shù)據(jù)拷貝的次數(shù)
數(shù)據(jù)傳輸?shù)?次拷貝,其中內(nèi)核拷貝到用戶緩沖區(qū),再?gòu)挠脩艟彌_區(qū)拷貝到內(nèi)核緩沖區(qū),這兩個(gè)過(guò)程是沒(méi)必要的,因?yàn)樵谖募鬏數(shù)膽?yīng)用場(chǎng)景中,在用戶空間我們并不會(huì)對(duì)數(shù)據(jù)再加工,所以這個(gè)數(shù)據(jù)沒(méi)必要搬運(yùn)到用戶空間
如何實(shí)現(xiàn)零拷貝?
零拷貝技術(shù)實(shí)現(xiàn)的方式通常有 2 種:
mmap + write(三次拷貝+兩次系統(tǒng)調(diào)用)
Sendfile(三次拷貝+一次系統(tǒng)調(diào)用)
下面就談一談,它們是如何減少「上下文切換」和「數(shù)據(jù)拷貝」的次數(shù)。
mmap + write
在前面我們知道,read() 系統(tǒng)調(diào)用的過(guò)程中會(huì)把內(nèi)核緩沖區(qū)的數(shù)據(jù)拷貝到用戶的緩沖區(qū)里,于是為了減少這一步開(kāi)銷,我們可以用 mmap() 替換 read() 系統(tǒng)調(diào)用函數(shù)。
buf = mmap(file, len);
write(sockfd, buf, len);
mmap() 系統(tǒng)調(diào)用函數(shù)會(huì)直接把內(nèi)核緩沖區(qū)里的數(shù)據(jù)「映射」到用戶空間,這樣,操作系統(tǒng)內(nèi)核與用戶空間就不需要再進(jìn)行任何的數(shù)據(jù)拷貝操作。
具體過(guò)程如下:
1、應(yīng)用調(diào)用了mmap(),DMA把磁盤(pán)數(shù)據(jù)拷貝到內(nèi)核緩沖區(qū),此時(shí),應(yīng)用進(jìn)程和內(nèi)核會(huì)共享這個(gè)內(nèi)核緩沖區(qū)
2、應(yīng)用系統(tǒng)調(diào)用write(),操作系統(tǒng)直接把內(nèi)核緩沖區(qū)數(shù)據(jù)拷貝到網(wǎng)絡(luò)緩沖區(qū)中,這個(gè)也是屬于內(nèi)核態(tài),內(nèi)核中的拷貝,由CPU來(lái)操作
3、第三次拷貝,通過(guò)DMA技術(shù)把網(wǎng)絡(luò)緩沖區(qū)數(shù)據(jù)拷貝到網(wǎng)卡的緩沖區(qū)中
我們可以得知,通過(guò)使用mmap()來(lái)代替 read(),可以減少一次數(shù)據(jù)拷貝的過(guò)程。
但這還不是最理想的零拷貝,因?yàn)槿匀恍枰ㄟ^(guò) CPU 把內(nèi)核緩沖區(qū)的數(shù)據(jù)拷貝到 socket 緩沖區(qū)里,而且仍然需要 4 次上下文切換,因?yàn)橄到y(tǒng)調(diào)用還是 2 次。
Sendfile
在 Linux 內(nèi)核版本 2.1 中,提供了一個(gè)專門(mén)發(fā)送文件的系統(tǒng)調(diào)用函數(shù) sendfile(),函數(shù)形式如下:
#include
ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count);
它的前兩個(gè)參數(shù)分別是目的端和源端的文件描述符,后面兩個(gè)參數(shù)是源端的偏移量和復(fù)制數(shù)據(jù)的長(zhǎng)度,返回值是實(shí)際復(fù)制數(shù)據(jù)的長(zhǎng)度。
首先,它可以替代前面的 read() 和 write() 這兩個(gè)系統(tǒng)調(diào)用,這樣就可以減少一次系統(tǒng)調(diào)用,也就減少了 2次上下文切換的開(kāi)銷。
其次,該系統(tǒng)調(diào)用,可以直接把內(nèi)核緩沖區(qū)里的數(shù)據(jù)拷貝到 socket 緩沖區(qū)里,不再拷貝到用戶態(tài),這樣就只有 2 次上下文切換,和 3 次數(shù)據(jù)拷貝。
如下圖
但是這還不是真正的零拷貝技術(shù),如果網(wǎng)卡支持 SG-DMA(The Scatter-Gather Direct Memory Access)技術(shù)(和普通的 DMA 有所不同),我們可以進(jìn)一步減少通過(guò) CPU 把內(nèi)核緩沖區(qū)里的數(shù)據(jù)拷貝到 socket 緩沖區(qū)的過(guò)程。
你可以在你的 Linux 系統(tǒng)通過(guò)下面這個(gè)命令,查看網(wǎng)卡是否支持 scatter-gather 特性:
$ ethtool -k eth0 | grep scatter-gather
scatter-gather: on
于是,從 Linux 內(nèi)核 2.4 版本開(kāi)始起,對(duì)于支持網(wǎng)卡支持 SG-DMA 技術(shù)的情況下, sendfile() 系統(tǒng)調(diào)用的過(guò)程發(fā)生了點(diǎn)變化,具體過(guò)程如下:
1、DMA直接將磁盤(pán)上的數(shù)據(jù)拷貝到內(nèi)核緩沖區(qū)中
2、緩沖區(qū)描述符和數(shù)據(jù)長(zhǎng)度傳到 socket 緩沖區(qū),這樣網(wǎng)卡的 SG-DMA 控制器就可以直接將內(nèi)核緩存中的數(shù)據(jù)拷貝到網(wǎng)卡的緩沖區(qū)里,此過(guò)程不需要將數(shù)據(jù)從操作系統(tǒng)內(nèi)核緩沖區(qū)拷貝到 socket 緩沖區(qū)中,這樣就減少了一次數(shù)據(jù)拷貝
所以,這個(gè)過(guò)程之中,只進(jìn)行了 2 次數(shù)據(jù)拷貝,如下圖:
這就是所謂的零拷貝(Zero-copy)技術(shù),因?yàn)槲覀儧](méi)有在內(nèi)存層面去拷貝數(shù)據(jù),也就是說(shuō)全程沒(méi)有通過(guò) CPU來(lái)搬運(yùn)數(shù)據(jù),所有的數(shù)據(jù)都是通過(guò) DMA 來(lái)進(jìn)行傳輸?shù)摹?/p>
CPU屬于參與了,但沒(méi)完全參與,DMA操作需要CPU指揮,描述符和數(shù)據(jù)長(zhǎng)度需要CPU發(fā)送
零拷貝技術(shù)的文件傳輸方式相比傳統(tǒng)文件傳輸?shù)姆绞剑瑴p少了 2 次上下文切換和數(shù)據(jù)拷貝次數(shù),只需要 2 次上下文切換和數(shù)據(jù)拷貝次數(shù),就可以完成文件的傳輸,而且 2 次的數(shù)據(jù)拷貝過(guò)程,都不需要通過(guò) CPU,2 次都是由 DMA 來(lái)搬運(yùn)。
我們通常說(shuō)的這個(gè)零拷貝技術(shù)中的這個(gè)零,指的是內(nèi)核態(tài)和用戶態(tài)之間的拷貝次數(shù),變成了0
所以,總體來(lái)看,零拷貝技術(shù)可以把文件傳輸?shù)男阅芴岣咧辽僖槐兑陨稀?/p>
PageCache
上面說(shuō)的第一步是先把磁盤(pán)文件數(shù)據(jù)拷貝到內(nèi)核緩沖區(qū)中,這個(gè)內(nèi)核緩沖區(qū)就是磁盤(pán)高速緩沖區(qū)PageCache,內(nèi)存速度比磁盤(pán)速度快,但是內(nèi)存空間比磁盤(pán)要小
我們需要把此時(shí)的熱點(diǎn)數(shù)據(jù)放入到緩存中,因?yàn)檫@是最近需要頻繁訪問(wèn)的,空間不足的時(shí)候淘汰掉那些訪問(wèn)頻率低的數(shù)據(jù)
緩存這些道理大家應(yīng)該都懂,零拷貝也使用了緩存技術(shù),讀取數(shù)據(jù)的時(shí)候,優(yōu)先在PageCache中找,找到直接返回,找不到去磁盤(pán)中讀取,然后緩存到PageCache中
還有一點(diǎn),讀取磁盤(pán)數(shù)據(jù)的時(shí)候,需要找到數(shù)據(jù)所在的位置,但是對(duì)于機(jī)械磁盤(pán)來(lái)說(shuō),就是通過(guò)磁頭旋轉(zhuǎn)到數(shù)據(jù)所在的扇區(qū),再開(kāi)始「順序」讀取數(shù)據(jù),但是旋轉(zhuǎn)磁頭這個(gè)物理動(dòng)作是非常耗時(shí)的,為了降低它的影響,PageCache 使用了「預(yù)讀功能」。
比如,假設(shè) read 方法每次只會(huì)讀 32 KB 的字節(jié),雖然read 剛開(kāi)始只會(huì)讀 0 ~ 32 KB 的字節(jié),但內(nèi)核會(huì)把其后面的 32~64 KB 也讀取到 PageCache,這樣后面讀取32~64 KB 的成本就很低,如果在 32~64 KB 淘汰出PageCache 前,進(jìn)程讀取到它了,收益就非常大。
所以,PageCache 的優(yōu)點(diǎn)主要是兩個(gè):
緩存最近被訪問(wèn)的數(shù)據(jù);預(yù)讀功能;
這兩個(gè)做法,將大大提高讀寫(xiě)磁盤(pán)的性能。
但是,在傳輸大文件(GB 級(jí)別的文件)的時(shí)候,PageCache 會(huì)不起作用,那就白白浪費(fèi) DMA 多做的一次數(shù)據(jù)拷貝,造成性能的降低,即使使用了PageCache 的零拷貝也會(huì)損失性能,一個(gè)大文件直接占滿,導(dǎo)致某些熱點(diǎn)小文件無(wú)法使用,性能就降低了
所以,針對(duì)大文件的傳輸,不應(yīng)該使用 PageCache,也就是說(shuō)不應(yīng)該使用零拷貝技術(shù),因?yàn)榭赡苡捎赑ageCache 被大文件占據(jù),而導(dǎo)致「熱點(diǎn)」小文件無(wú)法利用到 PageCache,這樣在高并發(fā)的環(huán)境下,會(huì)帶來(lái)嚴(yán)重的性能問(wèn)題。
對(duì)于大文件傳輸,可以通過(guò)異步IO和繞開(kāi)PageCache的IO來(lái)代替零拷貝技術(shù)
在 nginx 中,我們可以用如下配置,來(lái)根據(jù)文件的大小來(lái)使用不同的方式:
location /video/ { sendfile on; aio on; directio 1024m;}
當(dāng)文件大小大于 directio 值后,使用「異步 I/O + 直接 I/O」,否則使用「零拷貝技術(shù)」。
總結(jié)
1、早期IO,內(nèi)核數(shù)據(jù)需要IO進(jìn)行復(fù)制,2次系統(tǒng)調(diào)用,4次上下文切換,4次數(shù)據(jù)的拷貝,CPU拷貝數(shù)據(jù)期間不能執(zhí)行其它命令
2、引入DMA技術(shù),DMA可以代替CPU進(jìn)行磁盤(pán)到內(nèi)核區(qū)域數(shù)據(jù)的復(fù)制,這個(gè)期間CPU可執(zhí)行其它命令,改善了性能
3、零拷貝技術(shù):mmap+write,2次系統(tǒng)調(diào)用,4次上下文切換,3次數(shù)據(jù)的拷貝,減少了讀取期間內(nèi)核區(qū)域到用戶區(qū)域的數(shù)據(jù)復(fù)制,原因是兩者共享了內(nèi)核區(qū)域的緩沖區(qū)
4、零拷貝技術(shù):Sendfile,1次系統(tǒng)調(diào)用,2次上下文切換,3次數(shù)據(jù)的拷貝,直接指定了原文件和目標(biāo)文件,替代了原來(lái)的兩次系統(tǒng)調(diào)用,直接一次完成
5、真正的零拷貝技術(shù):網(wǎng)卡支持 SG-DMA技術(shù),數(shù)據(jù)從磁盤(pán)系統(tǒng)讀取到內(nèi)核緩沖區(qū)之后,不需要復(fù)制到相應(yīng)的socket緩沖區(qū)即可,只需要發(fā)送描述符和數(shù)據(jù)長(zhǎng)度即可,這個(gè)期間經(jīng)歷了1次系統(tǒng)調(diào)用,2次上下文切換,2次數(shù)據(jù)拷貝,沒(méi)有在內(nèi)核層面去進(jìn)行數(shù)據(jù)的拷貝
6、零拷貝技術(shù)引用PageCache緩存技術(shù),緩存技術(shù)用于加速熱點(diǎn)文件的查詢速度,但是不適用于大文件,大文件可以通過(guò)異步IO和繞開(kāi)PageCache的IO來(lái)代替零拷貝技術(shù)
參考文獻(xiàn):https://zhuanlan.zhihu.com/p/258513662
本文轉(zhuǎn)載自微信公眾號(hào)「左耳君」,可以通過(guò)以下二維碼關(guān)注。轉(zhuǎn)載本文請(qǐng)聯(lián)系左耳君公眾號(hào)。
分享標(biāo)題:包教包會(huì)的零拷貝,你會(huì)了嗎?
瀏覽路徑:http://www.5511xx.com/article/dhhiphi.html


咨詢
建站咨詢
