深入理解零拷貝技術(shù)
點(diǎn)擊下方“IT牧場(chǎng)”,選擇“設(shè)為星標(biāo)”


File.read(file, buf, len);
Socket.send(socket, buf, len);

CPU 負(fù)責(zé)將數(shù)據(jù)從磁盤(pán)搬運(yùn)到內(nèi)核空間的 Page Cache 中;
CPU 負(fù)責(zé)將數(shù)據(jù)從內(nèi)核空間的 Socket 緩沖區(qū)搬運(yùn)到的網(wǎng)絡(luò)中;
CPU 負(fù)責(zé)將數(shù)據(jù)從內(nèi)核空間的 Page Cache 搬運(yùn)到用戶空間的緩沖區(qū);
CPU 負(fù)責(zé)將數(shù)據(jù)從用戶空間的緩沖區(qū)搬運(yùn)到內(nèi)核空間的 Socket 緩沖區(qū)中。
read 系統(tǒng)調(diào)用時(shí):用戶態(tài)切換到內(nèi)核態(tài);
read 系統(tǒng)調(diào)用完畢:內(nèi)核態(tài)切換回用戶態(tài);
write 系統(tǒng)調(diào)用時(shí):用戶態(tài)切換到內(nèi)核態(tài);
write 系統(tǒng)調(diào)用完畢:內(nèi)核態(tài)切換回用戶態(tài)。
CPU 全程負(fù)責(zé)內(nèi)存內(nèi)的數(shù)據(jù)拷貝還可以接受,因?yàn)樾蔬€算可以接受,但是如果要全程負(fù)責(zé)內(nèi)存與磁盤(pán)、網(wǎng)絡(luò)的數(shù)據(jù)拷貝,這將難以接受,因?yàn)榇疟P(pán)、網(wǎng)卡的速度遠(yuǎn)小于內(nèi)存,內(nèi)存又遠(yuǎn)遠(yuǎn)小于 CPU;
4 次 copy 太多了,4 次上下文切換也太頻繁了。





sendfile
mmap
splice
直接 Direct I/O
DMA 技術(shù)回顧:DMA 負(fù)責(zé)內(nèi)存與其他組件之間的數(shù)據(jù)拷貝,CPU 僅需負(fù)責(zé)管理,而無(wú)需負(fù)責(zé)全程的數(shù)據(jù)拷貝;
使用 page cache 的 zero copy:
sendfile:一次代替 read/write 系統(tǒng)調(diào)用,通過(guò)使用 DMA 技術(shù)以及傳遞文件描述符,實(shí)現(xiàn)了 zero copy
mmap:僅代替 read 系統(tǒng)調(diào)用,將內(nèi)核空間地址映射為用戶空間地址,write 操作直接作用于內(nèi)核空間。通過(guò) DMA 技術(shù)以及地址映射技術(shù),用戶空間與內(nèi)核空間無(wú)須數(shù)據(jù)拷貝,實(shí)現(xiàn)了 zero copy
不使用 page cache 的 Direct I/O:讀寫(xiě)操作直接在磁盤(pán)上進(jìn)行,不使用 page cache 機(jī)制,通常結(jié)合用戶空間的用戶緩存使用。通過(guò) DMA 技術(shù)直接與磁盤(pán)/網(wǎng)卡進(jìn)行數(shù)據(jù)交互,實(shí)現(xiàn)了 zero copy
DMA 技術(shù);
傳遞文件描述符代替數(shù)據(jù)拷貝。

page cache 以及 socket buffer 都在內(nèi)核空間中;
數(shù)據(jù)傳輸過(guò)程前后沒(méi)有任何寫(xiě)操作。


緩存文件 I/O:用戶空間要讀寫(xiě)一個(gè)文件并不直接與磁盤(pán)交互,而是中間夾了一層緩存,即 page cache;
直接文件 I/O:用戶空間讀取的文件直接與磁盤(pán)交互,沒(méi)有中間 page cache 層。

Write 操作:由于其不使用 page cache,所以其進(jìn)行寫(xiě)文件,如果返回成功,數(shù)據(jù)就真的落盤(pán)了(不考慮磁盤(pán)自帶的緩存);
Read 操作:由于其不使用 page cache,每次讀操作是真的從磁盤(pán)中讀取,不會(huì)從文件系統(tǒng)的緩存中讀取。
Linux 中的直接 I/O 技術(shù)省略掉緩存 I/O 技術(shù)中操作系統(tǒng)內(nèi)核緩沖區(qū)的使用,數(shù)據(jù)直接在應(yīng)用程序地址空間和磁盤(pán)之間進(jìn)行傳輸,從而使得自緩存應(yīng)用程序可以省略掉復(fù)雜的系統(tǒng)級(jí)別的緩存結(jié)構(gòu),而執(zhí)行程序自己定義的數(shù)據(jù)讀寫(xiě)管理,從而降低系統(tǒng)級(jí)別的管理對(duì)應(yīng)用程序訪問(wèn)數(shù)據(jù)的影響。
與其他零拷貝技術(shù)一樣,避免了內(nèi)核空間到用戶空間的數(shù)據(jù)拷貝,如果要傳輸?shù)臄?shù)據(jù)量很大,使用直接 I/O 的方式進(jìn)行數(shù)據(jù)傳輸,而不需要操作系統(tǒng)內(nèi)核地址空間拷貝數(shù)據(jù)操作的參與,這將會(huì)大大提高性能。
由于設(shè)備之間的數(shù)據(jù)傳輸是通過(guò) DMA 完成的,因此用戶空間的數(shù)據(jù)緩沖區(qū)內(nèi)存頁(yè)必須進(jìn)行 page pinning(頁(yè)鎖定),這是為了防止其物理頁(yè)框地址被交換到磁盤(pán)或者被移動(dòng)到新的地址而導(dǎo)致 DMA 去拷貝數(shù)據(jù)的時(shí)候在指定的地址找不到內(nèi)存頁(yè)從而引發(fā)缺頁(yè)錯(cuò)誤,而頁(yè)鎖定的開(kāi)銷(xiāo)并不比 CPU 拷貝小,所以為了避免頻繁的頁(yè)鎖定系統(tǒng)調(diào)用,應(yīng)用程序必須分配和注冊(cè)一個(gè)持久的內(nèi)存池,用于數(shù)據(jù)緩沖。
如果訪問(wèn)的數(shù)據(jù)不在應(yīng)用程序緩存中,那么每次數(shù)據(jù)都會(huì)直接從磁盤(pán)進(jìn)行加載,這種直接加載會(huì)非常緩慢。
在應(yīng)用層引入直接 I/O 需要應(yīng)用層自己管理,這帶來(lái)了額外的系統(tǒng)復(fù)雜性。
對(duì)于某些應(yīng)用程序來(lái)說(shuō),它會(huì)有它自己的數(shù)據(jù)緩存機(jī)制,比如,它會(huì)將數(shù)據(jù)緩存在應(yīng)用程序地址空間,這類應(yīng)用程序完全不需要使用操作系統(tǒng)內(nèi)核中的高速緩沖存儲(chǔ)器,這類應(yīng)用程序就被稱作是自緩存應(yīng)用程序( self-caching applications )。
例如,應(yīng)用內(nèi)部維護(hù)一個(gè)緩存空間,當(dāng)有讀操作時(shí),首先讀取應(yīng)用層的緩存數(shù)據(jù),如果沒(méi)有,那么就通過(guò) Direct I/O 直接通過(guò)磁盤(pán) I/O 來(lái)讀取數(shù)據(jù)。緩存仍然在應(yīng)用,只不過(guò)應(yīng)用覺(jué)得自己實(shí)現(xiàn)一個(gè)緩存比操作系統(tǒng)的緩存更高效。
數(shù)據(jù)庫(kù)管理系統(tǒng)是這類應(yīng)用程序的一個(gè)代表。自緩存應(yīng)用程序傾向于使用數(shù)據(jù)的邏輯表達(dá)方式,而非物理表達(dá)方式;當(dāng)系統(tǒng)內(nèi)存較低的時(shí)候,自緩存應(yīng)用程序會(huì)讓這種數(shù)據(jù)的邏輯緩存被換出,而并非是磁盤(pán)上實(shí)際的數(shù)據(jù)被換出。自緩存應(yīng)用程序?qū)σ僮鞯臄?shù)據(jù)的語(yǔ)義了如指掌,所以它可以采用更加高效的緩存替換算法。自緩存應(yīng)用程序有可能會(huì)在多臺(tái)主機(jī)之間共享一塊內(nèi)存,那么自緩存應(yīng)用程序就需要提供一種能夠有效地將用戶地址空間的緩存數(shù)據(jù)置為無(wú)效的機(jī)制,從而確保應(yīng)用程序地址空間緩存數(shù)據(jù)的一致性。

Provider 向 Kakfa 發(fā)送消息,Kakfa 負(fù)責(zé)將消息以日志的方式持久化落盤(pán);
Consumer 向 Kakfa 進(jìn)行拉取消息,Kafka 負(fù)責(zé)從磁盤(pán)中讀取一批日志消息,然后再通過(guò)網(wǎng)卡發(fā)送。
sendfile 避免了內(nèi)核空間到用戶空間的 CPU 全程負(fù)責(zé)的數(shù)據(jù)移動(dòng);
sendfile 基于 Page Cache 實(shí)現(xiàn),因此如果有多個(gè) Consumer 在同時(shí)消費(fèi)一個(gè)主題的消息,那么由于消息一直在 page cache 中進(jìn)行了緩存,因此只需一次磁盤(pán) I/O,就可以服務(wù)于多個(gè) Consumer。
MySQL 的具體實(shí)現(xiàn)比 Kakfa 復(fù)雜很多,這是因?yàn)橹С?SQL 查詢的數(shù)據(jù)庫(kù)本身比消息隊(duì)列對(duì)復(fù)雜很多。

減少甚至避免用戶空間和內(nèi)核空間之間的數(shù)據(jù)拷貝:在一些場(chǎng)景下,用戶進(jìn)程在數(shù)據(jù)傳輸過(guò)程中并不需要對(duì)數(shù)據(jù)進(jìn)行訪問(wèn)和處理,那么數(shù)據(jù)在 Linux 的 Page Cache 和用戶進(jìn)程的緩沖區(qū)之間的傳輸就完全可以避免,讓數(shù)據(jù)拷貝完全在內(nèi)核里進(jìn)行,甚至可以通過(guò)更巧妙的方式避免在內(nèi)核里的數(shù)據(jù)拷貝。這一類實(shí)現(xiàn)一般是是通過(guò)增加新的系統(tǒng)調(diào)用來(lái)完成的,比如 Linux 中的 mmap(),sendfile() 以及 splice() 等。
繞過(guò)內(nèi)核的直接 I/O:允許在用戶態(tài)進(jìn)程繞過(guò)內(nèi)核直接和硬件進(jìn)行數(shù)據(jù)傳輸,內(nèi)核在傳輸過(guò)程中只負(fù)責(zé)一些管理和輔助的工作。這種方式其實(shí)和第一種有點(diǎn)類似,也是試圖避免用戶空間和內(nèi)核空間之間的數(shù)據(jù)傳輸,只是第一種方式是把數(shù)據(jù)傳輸過(guò)程放在內(nèi)核態(tài)完成,而這種方式則是直接繞過(guò)內(nèi)核和硬件通信,效果類似但原理完全不同。
內(nèi)核緩沖區(qū)和用戶緩沖區(qū)之間的傳輸優(yōu)化:這種方式側(cè)重于在用戶進(jìn)程的緩沖區(qū)和操作系統(tǒng)的頁(yè)緩存之間的 CPU 拷貝的優(yōu)化。這種方法延續(xù)了以往那種傳統(tǒng)的通信方式,但更靈活。
https://spongecaptain.cool/SimpleClearFileIO/3.%20mmap.html
https://www.ibm.com/developerworks/cn/linux/l-cn-directio/
https://spongecaptain.cool/zerocopyofmysql
作者:Spongecaptain
來(lái)源:https://github.com/Spongecaptain/SimpleClearFileIO/blob/main/2.%20DMA%20與零拷貝技術(shù).md
版權(quán)申明:內(nèi)容來(lái)源網(wǎng)絡(luò),僅供分享學(xué)習(xí),版權(quán)歸原創(chuàng)者所有。除非無(wú)法確認(rèn),我們都會(huì)標(biāo)明作者及出處,如有侵權(quán)煩請(qǐng)告知,我們會(huì)立即刪除并表示歉意。謝謝!
干貨分享
最近將個(gè)人學(xué)習(xí)筆記整理成冊(cè),使用PDF分享。關(guān)注我,回復(fù)如下代碼,即可獲得百度盤(pán)地址,無(wú)套路領(lǐng)取!
?001:《Java并發(fā)與高并發(fā)解決方案》學(xué)習(xí)筆記;?002:《深入JVM內(nèi)核——原理、診斷與優(yōu)化》學(xué)習(xí)筆記;?003:《Java面試寶典》?004:《Docker開(kāi)源書(shū)》?005:《Kubernetes開(kāi)源書(shū)》?006:《DDD速成(領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)速成)》?007:全部?008:加技術(shù)群討論
加個(gè)關(guān)注不迷路
喜歡就點(diǎn)個(gè)"在看"唄^_^
