前言
在了解零拷貝之前淑蔚,我們先來(lái)看看標(biāo)準(zhǔn)的的I/O操作..
1.傳統(tǒng)IO的原理
標(biāo)準(zhǔn) I/O又被稱作緩存 I/O 灵迫,大多數(shù)文件系統(tǒng)的默認(rèn) I/O 操作都是緩存 I/O书幕。在 Linux 的緩存 I/O 機(jī)制中象迎,操作系統(tǒng)會(huì)將 I/O 的數(shù)據(jù)先被拷貝到操作系統(tǒng)內(nèi)核的緩沖區(qū)中香伴,然后才會(huì)從操作系統(tǒng)內(nèi)核的緩沖區(qū)拷貝到應(yīng)用程序的地址空間慰枕。
緩存 I/O 有以下這些優(yōu)點(diǎn):
- 緩存 I/O 使用了操作系統(tǒng)內(nèi)核緩沖區(qū),在一定程度上分離了應(yīng)用程序空間和實(shí)際的物理設(shè)備即纲。
- 緩存 I/O 可以減少讀盤的次數(shù)具帮,從而提高性能。
當(dāng)應(yīng)用程序嘗試讀取某塊數(shù)據(jù)的時(shí)候,如果這塊數(shù)據(jù)已經(jīng)存放在了內(nèi)核的緩沖區(qū)中蜂厅,那么這塊數(shù)據(jù)就可以立即返回給應(yīng)用程序匪凡,而不需要經(jīng)過(guò)實(shí)際的物理讀盤操作。當(dāng)然掘猿,如果數(shù)據(jù)在應(yīng)用程序讀取之前并未被存放在內(nèi)核的緩沖區(qū)中病游,那么就需要先將數(shù)據(jù)從磁盤讀到頁(yè)緩存中去。
對(duì)于寫操作來(lái)說(shuō)稠通,應(yīng)用程序也會(huì)將數(shù)據(jù)先寫到內(nèi)核的緩沖區(qū)中去衬衬,數(shù)據(jù)是否被立即寫到磁盤上去取決于應(yīng)用程序所采用的寫操作機(jī)制:
如果用戶采用的是同步寫機(jī)制( synchronous writes ), 那么數(shù)據(jù)會(huì)立即被寫回到磁盤上,應(yīng)用程序會(huì)一直等到數(shù)據(jù)被寫完為止改橘;
如果用戶采用的是延遲寫機(jī)制( deferred writes )滋尉,那么應(yīng)用程序就完全不需要等到數(shù)據(jù)全部被寫回到磁盤,數(shù)據(jù)只要被寫到頁(yè)緩存中(內(nèi)核的緩沖區(qū))去就可以了飞主。在延遲寫機(jī)制的情況下狮惜,操作系統(tǒng)會(huì)定期地將放在頁(yè)緩存中的數(shù)據(jù)刷到磁盤上。與異步寫機(jī)制( asynchronous writes )不同的是碌识,延遲寫機(jī)制在數(shù)據(jù)完全寫到磁盤上的時(shí)候不會(huì)通知應(yīng)用程序碾篡,而異步寫機(jī)制在數(shù)據(jù)完全寫到磁盤上的時(shí)候是會(huì)返回給應(yīng)用程序的。所以延遲寫機(jī)制本身是存在數(shù)據(jù)丟失的風(fēng)險(xiǎn)的丸冕,而異步寫機(jī)制則不會(huì)有這方面的擔(dān)心耽梅。
2.傳統(tǒng)IO的缺點(diǎn)
在緩存 I/O 機(jī)制中,DMA 方式可以將數(shù)據(jù)直接從磁盤讀到頁(yè)緩存中胖烛,或者將數(shù)據(jù)從頁(yè)緩存直接寫回到磁盤上,而不能直接在應(yīng)用程序地址空間和磁盤之間進(jìn)行數(shù)據(jù)傳輸诅迷,這樣的話佩番,數(shù)據(jù)在傳輸過(guò)程中需要在應(yīng)用程序地址空間和頁(yè)緩存之間進(jìn)行多次數(shù)據(jù)拷貝操作,這些數(shù)據(jù)拷貝操作所帶來(lái)的 CPU 以及內(nèi)存開(kāi)銷是非常大的罢杉。
當(dāng)然也可以采用直接 I/O 技術(shù)來(lái)滿足自緩存應(yīng)用程序( self-caching applications)的需求趟畏。
具體描述可以看這篇博客:Linux 中直接 I/O 機(jī)制的介紹
一.為什么需要零拷貝技術(shù)?
如今,很多網(wǎng)絡(luò)服務(wù)器都是基于客戶端 - 服務(wù)器這一模型的滩租。在這種模型中赋秀,客戶端向服務(wù)器端請(qǐng)求數(shù)據(jù)或者服務(wù);服務(wù)器端則需要響應(yīng)客戶端發(fā)出的請(qǐng)求律想,并為客戶端提供它所需要的數(shù)據(jù)猎莲。隨著網(wǎng)絡(luò)服務(wù)的逐漸普及,video 這類應(yīng)用程序發(fā)展迅速技即。當(dāng)今的計(jì)算機(jī)系統(tǒng)已經(jīng)具備足夠的能力去處理 video 這類應(yīng)用程序?qū)蛻舳怂斐傻闹刎?fù)荷著洼,但是對(duì)于服務(wù)器端來(lái)說(shuō),它應(yīng)付由 video 這類應(yīng)用程序引起的網(wǎng)絡(luò)通信量就顯得捉襟見(jiàn)肘了。而且身笤,客戶端的數(shù)量增長(zhǎng)迅速豹悬,那么服務(wù)器端就更容易成為性能瓶頸。而對(duì)于負(fù)荷很重的服務(wù)器來(lái)說(shuō)液荸,操作系統(tǒng)通常都是引起性能瓶頸的罪魁禍?zhǔn)渍胺稹Ee個(gè)例子來(lái)說(shuō),當(dāng)數(shù)據(jù)“寫”操作或者數(shù)據(jù)“發(fā)送”操作的系統(tǒng)調(diào)用發(fā)出時(shí)娇钱,操作系統(tǒng)通常都會(huì)將數(shù)據(jù)從應(yīng)用程序地址空間的緩沖區(qū)拷貝到操作系統(tǒng)內(nèi)核的緩沖區(qū)中去涤久。操作系統(tǒng)這樣做的好處是接口簡(jiǎn)單,但是卻在很大程度上損失了系統(tǒng)性能忍弛,因?yàn)檫@種數(shù)據(jù)拷貝操作不單需要占用 CPU 時(shí)間片响迂,同時(shí)也需要占用額外的內(nèi)存帶寬。
一般來(lái)說(shuō)细疚,客戶端通過(guò)網(wǎng)絡(luò)接口卡向服務(wù)器端發(fā)送請(qǐng)求蔗彤,操作系統(tǒng)將這些客戶端的請(qǐng)求傳遞給服務(wù)器端應(yīng)用程序,服務(wù)器端應(yīng)用程序會(huì)處理這些請(qǐng)求疯兼,請(qǐng)求處理完成以后然遏,操作系統(tǒng)還需要將處理得到的結(jié)果通過(guò)網(wǎng)絡(luò)適配器傳遞回去。
二.什么是零拷貝技術(shù)吧彪?
簡(jiǎn)單一點(diǎn)來(lái)說(shuō)待侵,零拷貝就是一種避免 CPU 將數(shù)據(jù)從一塊存儲(chǔ)拷貝到另外一塊存儲(chǔ)的技術(shù)。
零拷貝技術(shù)可以減少數(shù)據(jù)拷貝和共享總線操作的次數(shù)姨裸,消除傳輸數(shù)據(jù)在存儲(chǔ)器之間不必要的中間拷貝次數(shù)秧倾,從而有效地提高數(shù)據(jù)傳輸效率。
而且傀缩,零拷貝技術(shù)減少了用戶應(yīng)用程序地址空間和操作系統(tǒng)內(nèi)核地址空間之間因?yàn)樯舷挛那袚Q而帶來(lái)的開(kāi)銷那先。進(jìn)行大量的數(shù)據(jù)拷貝操作其實(shí)是一件簡(jiǎn)單的任務(wù),從操作系統(tǒng)的角度來(lái)說(shuō)赡艰,如果 CPU 一直被占用著去執(zhí)行這項(xiàng)簡(jiǎn)單的任務(wù)售淡,那么這將會(huì)是很浪費(fèi)資源的;如果有其他比較簡(jiǎn)單的系統(tǒng)部件可以代勞這件事情慷垮,從而使得 CPU 解脫出來(lái)可以做別的事情揖闸,那么系統(tǒng)資源的利用則會(huì)更加有效。
零拷貝技術(shù)的要點(diǎn):
- 避免操作系統(tǒng)內(nèi)核緩沖區(qū)之間進(jìn)行數(shù)據(jù)拷貝操作料身。
- 避免操作系統(tǒng)內(nèi)核和用戶應(yīng)用程序地址空間這兩者之間進(jìn)行數(shù)據(jù)拷貝操作汤纸。
- 用戶應(yīng)用程序可以避開(kāi)操作系統(tǒng)直接訪問(wèn)硬件存儲(chǔ)。
- 數(shù)據(jù)傳輸盡量讓 DMA 來(lái)做惯驼。
DMA:是指外部設(shè)備不通過(guò)CPU而直接與系統(tǒng)內(nèi)存交換數(shù)據(jù)的接口技術(shù)蹲嚣。
零拷貝技術(shù)分類
Linux 中的零拷貝技術(shù)主要有下面這幾種:
- 直接 I/O
- mmap
- sendfile
- splice
本文主要介紹sendfile這種零拷貝技術(shù)..
三.sendfile實(shí)現(xiàn)零拷貝的原理
1.描述
sendfile系統(tǒng)調(diào)用在兩個(gè)文件描述符之間直接傳遞數(shù)據(jù)(完全在內(nèi)核中操作)递瑰,從而避免了數(shù)據(jù)在內(nèi)核緩沖區(qū)和用戶緩沖區(qū)之間的拷貝,操作效率很高隙畜,被稱之為零拷貝抖部。
2.原理
sendfile() 系統(tǒng)調(diào)用利用 DMA 引擎將文件中的數(shù)據(jù)拷貝到操作系統(tǒng)內(nèi)核緩沖區(qū)中,然后數(shù)據(jù)被拷貝到與 socket 相關(guān)的內(nèi)核緩沖區(qū)中去议惰。接下來(lái)慎颗,DMA 引擎將數(shù)據(jù)從內(nèi)核 socket 緩沖區(qū)中拷貝到協(xié)議引擎中去。
sendfile() 系統(tǒng)調(diào)用不需要將數(shù)據(jù)拷貝或者映射到應(yīng)用程序地址空間中去言询,所以 sendfile() 只是適用于應(yīng)用程序地址空間不需要對(duì)所訪問(wèn)數(shù)據(jù)進(jìn)行處理的情況俯萎。因?yàn)?sendfile 傳輸?shù)臄?shù)據(jù)沒(méi)有越過(guò)用戶應(yīng)用程序 / 操作系統(tǒng)內(nèi)核的邊界線,所以 sendfile () 也極大地減少了存儲(chǔ)管理的開(kāi)銷运杭。
簡(jiǎn)單歸納上述的過(guò)程:
- sendfile系統(tǒng)調(diào)用利用DMA引擎將文件數(shù)據(jù)拷貝到內(nèi)核緩沖區(qū)夫啊,之后數(shù)據(jù)被拷貝到內(nèi)核socket緩沖區(qū)中
- DMA引擎將數(shù)據(jù)從內(nèi)核socket緩沖區(qū)拷貝到協(xié)議引擎中
這里沒(méi)有用戶態(tài)和內(nèi)核態(tài)之間的切換,也沒(méi)有內(nèi)核緩沖區(qū)和用戶緩沖區(qū)之間的拷貝辆憔,大大提升了傳輸性能撇眯。
四.帶有 DMA 收集拷貝功能的 sendfile
上面介紹的 sendfile() 技術(shù)在進(jìn)行數(shù)據(jù)傳輸仍然還需要一次多余的數(shù)據(jù)拷貝操作,通過(guò)引入一點(diǎn)硬件上的幫助虱咧,這僅有的一次數(shù)據(jù)拷貝操作也可以避免熊榛。為了避免操作系統(tǒng)內(nèi)核造成的數(shù)據(jù)副本,需要用到一個(gè)支持收集操作的網(wǎng)絡(luò)接口腕巡。主要的方式是待傳輸?shù)臄?shù)據(jù)可以分散在存儲(chǔ)的不同位置上玄坦,而不需要在連續(xù)存儲(chǔ)中存放。這樣一來(lái)绘沉,從文件中讀出的數(shù)據(jù)就根本不需要被拷貝到 socket 緩沖區(qū)中去煎楣,而只是需要將緩沖區(qū)描述符傳到網(wǎng)絡(luò)協(xié)議棧中去,之后其在緩沖區(qū)中建立起數(shù)據(jù)包的相關(guān)結(jié)構(gòu)梆砸,然后通過(guò) DMA 收集拷貝功能將所有的數(shù)據(jù)結(jié)合成一個(gè)網(wǎng)絡(luò)數(shù)據(jù)包转质。網(wǎng)卡的 DMA 引擎會(huì)在一次操作中從多個(gè)位置讀取包頭和數(shù)據(jù)。Linux 2.4 版本中的 socket 緩沖區(qū)就可以滿足這種條件帖世,這種方法不但減少了因?yàn)槎啻紊舷挛那袚Q所帶來(lái)開(kāi)銷,同時(shí)也減少了處理器造成的數(shù)據(jù)副本的個(gè)數(shù)沸枯。對(duì)于用戶應(yīng)用程序來(lái)說(shuō)日矫,代碼沒(méi)有任何改變。
主要過(guò)程如下:
首先绑榴,sendfile() 系統(tǒng)調(diào)用利用 DMA 引擎將文件內(nèi)容拷貝到內(nèi)核緩沖區(qū)去哪轿;然后,將帶有文件位置和長(zhǎng)度信息的緩沖區(qū)描述符添加到 socket 緩沖區(qū)中去翔怎,此過(guò)程不需要將數(shù)據(jù)從操作系統(tǒng)內(nèi)核緩沖區(qū)拷貝到 socket 緩沖區(qū)中窃诉,DMA 引擎會(huì)將數(shù)據(jù)直接從內(nèi)核緩沖區(qū)拷貝到協(xié)議引擎中去杨耙,這樣就避免了最后一次數(shù)據(jù)拷貝。
五.總結(jié)
上述的兩種幾種I/O操作對(duì)比:
1.傳統(tǒng)I/O
硬盤—>內(nèi)核緩沖區(qū)—>用戶緩沖區(qū)—>內(nèi)核socket緩沖區(qū)—>協(xié)議引擎
2.sendfile
硬盤—>內(nèi)核緩沖區(qū)—>內(nèi)核socket緩沖區(qū)—>協(xié)議引擎
3.sendfile( DMA 收集拷貝)
硬盤—>內(nèi)核緩沖區(qū)—>協(xié)議引擎
Tips:用戶態(tài)和內(nèi)核態(tài)切換的代價(jià)在哪飘痛?
首先珊膜,用戶態(tài)一個(gè)進(jìn)程,內(nèi)核態(tài)一個(gè)進(jìn)程宣脉,切換就要進(jìn)行進(jìn)程間的切換车柠。
拿系統(tǒng)調(diào)用舉例來(lái)說(shuō),系統(tǒng)調(diào)用一般都需要保存用戶程序得上下文(context), 在進(jìn)入內(nèi)核得時(shí)候需要保存用戶態(tài)得寄存器塑猖,在內(nèi)核態(tài)返回用戶態(tài)得時(shí)候會(huì)恢復(fù)這些寄存器得內(nèi)容竹祷。這是一個(gè)開(kāi)銷的地方。
如果需要在不同用戶程序間切換的話羊苟,那么還要更新cr3寄存器塑陵,這樣會(huì)更換每個(gè)程序的虛擬內(nèi)存到物理內(nèi)存映射表的地址,也是一個(gè)比較高負(fù)擔(dān)的操作蜡励。
而且內(nèi)核代碼對(duì)用戶不信任令花,需要進(jìn)行額外的檢查。系統(tǒng)調(diào)用的返回過(guò)程有很多額外工作巍虫,比如檢查是否需要調(diào)度等彭则。
六.參考資料
1、Linux 中直接 I/O 機(jī)制的介紹
2占遥、Linux 中的零拷貝技術(shù)俯抖,第 1 部分
3、Linux 中的零拷貝技術(shù)瓦胎,第 2 部分