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


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

CPU 負(fù)責(zé)將數(shù)據(jù)從磁盤搬運(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)存與磁盤、網(wǎng)絡(luò)的數(shù)據(jù)拷貝,這將難以接受,因?yàn)榇疟P、網(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é)管理,而無需負(fù)責(zé)全程的數(shù)據(jù)拷貝;
使用 page cache 的 zero copy:
sendfile:一次代替 read/write 系統(tǒng)調(diào)用,通過使用 DMA 技術(shù)以及傳遞文件描述符,實(shí)現(xiàn)了 zero copy
mmap:僅代替 read 系統(tǒng)調(diào)用,將內(nèi)核空間地址映射為用戶空間地址,write 操作直接作用于內(nèi)核空間。通過 DMA 技術(shù)以及地址映射技術(shù),用戶空間與內(nèi)核空間無須數(shù)據(jù)拷貝,實(shí)現(xiàn)了 zero copy
不使用 page cache 的 Direct I/O:讀寫操作直接在磁盤上進(jìn)行,不使用 page cache 機(jī)制,通常結(jié)合用戶空間的用戶緩存使用。通過 DMA 技術(shù)直接與磁盤/網(wǎng)卡進(jìn)行數(shù)據(jù)交互,實(shí)現(xiàn)了 zero copy
DMA 技術(shù);
傳遞文件描述符代替數(shù)據(jù)拷貝。

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


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

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

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

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