18.零拷貝深入剖析及用戶空間與內(nèi)核空間切換方式

轉(zhuǎn)自并發(fā)編程網(wǎng)http://ifeve.com/linux%E9%9B%B6%E6%8B%B7%E8%B4%9D%E5%8E%9F%E7%90%86/
到目前為止骑祟,幾乎所有人都聽說過Linux下所謂的零拷貝功能赊琳,但我經(jīng)常遇到對(duì)這個(gè)主題沒有完全了解的人。 正因?yàn)槿绱巳刑疲覜Q定寫幾篇文章,深入探討這個(gè)問題扫俺,希望能解開這個(gè)有用的特性路鹰。在這篇文章中惨篱,我們從一個(gè)用戶的角度看零拷貝盏筐,血淋淋的內(nèi)核級(jí)細(xì)節(jié)被有意省略。

什么是零拷貝?
為了更好地理解問題的解決方案砸讳,我們首先需要了解問題本身琢融。讓我們來看看網(wǎng)絡(luò)服務(wù)器處理的簡單過程中所涉及到的內(nèi)容,它將存儲(chǔ)在文件中的數(shù)據(jù)存儲(chǔ)到網(wǎng)絡(luò)上的客戶端中簿寂。這里有一些示例代碼:

read(file, tmp_buf, len);
write(socket, tmp_buf, len);

看起來很簡單;你會(huì)認(rèn)為只有這兩個(gè)系統(tǒng)調(diào)用不會(huì)有太多的開銷. 事實(shí)上漾抬,這與事實(shí)并無太大的距離。在這兩個(gè)調(diào)用的后面陶耍,數(shù)據(jù)至少被復(fù)制了4次奋蔚,并且?guī)缀跻呀?jīng)執(zhí)行了許多用戶/內(nèi)核上下文切換(實(shí)際上這個(gè)過程要復(fù)雜得多,但我想讓它保持簡單)。 為了更好地了解所涉及的過程泊碑,請(qǐng)看圖1坤按。頂部顯示了上下文切換,而底部顯示了復(fù)制操作馒过。


在兩個(gè)示例系統(tǒng)調(diào)用中復(fù)制.png

步驟一:讀系統(tǒng)調(diào)用會(huì)導(dǎo)致從用戶模式到內(nèi)核模式的上下文切換臭脓。第一個(gè)復(fù)制由DMA引擎執(zhí)行,它讀取磁盤中的文件內(nèi)容并將其存儲(chǔ)到內(nèi)核地址空間緩沖區(qū)中腹忽。

第二步:將數(shù)據(jù)從內(nèi)核緩沖區(qū)復(fù)制到用戶緩沖區(qū)来累,read系統(tǒng)調(diào)用返回。調(diào)用的返回導(dǎo)致了從內(nèi)核返回到用戶模式的上下文切換窘奏,現(xiàn)在嘹锁,數(shù)據(jù)存儲(chǔ)在用戶地址空間緩沖區(qū)中,它可以再次開始向下移動(dòng)着裹。

第三步:write系統(tǒng)調(diào)用導(dǎo)致從用戶模式到內(nèi)核模式的上下文切換领猾,執(zhí)行第三個(gè)復(fù)制,將數(shù)據(jù)再次放入內(nèi)核地址空間緩沖區(qū)中骇扇。但是這一次摔竿,數(shù)據(jù)被放入一個(gè)不同的緩沖區(qū),這個(gè)緩沖區(qū)是與套接字相關(guān)聯(lián)的少孝。

第四步:寫系統(tǒng)調(diào)用返回继低,創(chuàng)建第四個(gè)上下文切換。DMA引擎將數(shù)據(jù)從內(nèi)核緩沖區(qū)傳遞到協(xié)議engin時(shí)稍走,第四個(gè)復(fù)制發(fā)生了獨(dú)立和異步的情況袁翁。你可能會(huì)問自己,“你說的獨(dú)立和異步是什么意思婿脸?”在調(diào)用返回之前梦裂,數(shù)據(jù)不是傳輸?shù)膯幔俊? 實(shí)際上盖淡,調(diào)用返回并不能保證傳輸;它甚至不能保證傳輸?shù)拈_始。它只是意味著以太網(wǎng)驅(qū)動(dòng)程序在其隊(duì)列中有空閑的描述符并接受了我們的傳輸數(shù)據(jù) 凿歼,在我們的之前可能會(huì)有很多的數(shù)據(jù)包在排隊(duì)褪迟。除非驅(qū)動(dòng)/硬件實(shí)現(xiàn)了優(yōu)先級(jí)環(huán)或隊(duì)列,否則數(shù)據(jù)將以先入先出的方式傳輸答憔。(圖1中派生的DMA copy表明了最后一個(gè)復(fù)制可以被延遲的事實(shí))味赃。

正如您所看到的,大量的數(shù)據(jù)復(fù)制并不是真正需要的虐拓⌒乃祝可以消除一些重復(fù),以減少開銷并提高性能。 作為一名驅(qū)動(dòng)開發(fā)人員城榛,我使用的硬件具有一些非常高級(jí)的特性揪利。一些硬件可以完全繞過主存,直接將數(shù)據(jù)傳輸?shù)搅硪粋€(gè)設(shè)備上狠持。 該特性消除了系統(tǒng)內(nèi)存中的復(fù)制疟位,這是一件很好的事情,但并不是所有的硬件都支持它喘垂。還有一個(gè)問題是甜刻,磁盤上的數(shù)據(jù)必須重新打包以供網(wǎng)絡(luò)使用,這帶來了一些復(fù)雜的問題正勒。 為了消除開銷得院,我們可以從消除內(nèi)核和用戶緩沖區(qū)之間的一些復(fù)制開始。

消除復(fù)制的一種方法是跳過調(diào)用read和調(diào)用mmap章贞。例如:

tmp_buf = mmap(file, len);
write(socket, tmp_buf, len);

為了更好地了解過程祥绞,請(qǐng)查看圖2。上下文切換保持不變阱驾。

調(diào)用mmap

第一步:mmap系統(tǒng)調(diào)用將文件內(nèi)容復(fù)制到DMA引擎的內(nèi)核緩沖區(qū)中就谜。然后在用戶進(jìn)程中共享緩沖區(qū),而不需要在內(nèi)核和用戶內(nèi)存空間之間執(zhí)行任何復(fù)制里覆。

第二步:write系統(tǒng)調(diào)用導(dǎo)致內(nèi)核將數(shù)據(jù)從原始內(nèi)核緩沖區(qū)復(fù)制到與套接字關(guān)聯(lián)的內(nèi)核緩沖區(qū)中丧荐。

第三步:當(dāng)DMA引擎將數(shù)據(jù)從內(nèi)核套接字緩沖區(qū)傳遞到協(xié)議引擎時(shí),第三次復(fù)制發(fā)生喧枷。

通過使用mmap而不是讀取虹统,我們將內(nèi)核必須復(fù)制的數(shù)據(jù)量減少了一半。當(dāng)大量數(shù)據(jù)被傳輸時(shí)隧甚,這將產(chǎn)生相當(dāng)好的結(jié)果车荔。然而,這種改進(jìn)并不是沒有代價(jià)的;使用mmap+write方法時(shí)存在一些隱藏的缺陷戚扳。當(dāng)您的內(nèi)存映射一個(gè)文件忧便,然后調(diào)用write,而另一個(gè)進(jìn)程截?cái)嘞嗤奈募r(shí)帽借,您將陷入其中之一珠增。 您的write系統(tǒng)調(diào)用將被總線錯(cuò)誤信號(hào)SIGBUS中斷,因?yàn)槟鷪?zhí)行了一個(gè)糟糕的內(nèi)存訪問砍艾。該信號(hào)的默認(rèn)行為是殺死進(jìn)程并轉(zhuǎn)儲(chǔ)內(nèi)核——而不是網(wǎng)絡(luò)服務(wù)器最理想的操作蒂教。有兩種方法可以解決這個(gè)問題。

第一種方法是為SIGBUS信號(hào)安裝一個(gè)信號(hào)處理程序脆荷,然后在處理程序中簡單地調(diào)用return凝垛。通過這樣做懊悯,write系統(tǒng)調(diào)用將返回它在被中斷之前所寫的字節(jié)數(shù),以及errno設(shè)置為成功梦皮。我必須指出炭分,這將是一個(gè)糟糕的解決方案,一個(gè)治療癥狀届氢,而不是病根的解決方案欠窒。因?yàn)镾IGBUS信號(hào)表明這個(gè)過程出現(xiàn)了嚴(yán)重的問題,所以我不建議使用這個(gè)作為解決方案退子。

第二個(gè)解決方案涉及文件租賃(在Microsoft Windows中稱為“機(jī)會(huì)鎖定”)岖妄。這是解決這個(gè)問題的正確方法。通過使用文件描述符上的租賃,你將在內(nèi)核上租賃獲取一個(gè)特定的文件寂祥。通過在文件描述符上使用租賃荐虐,可以在特定文件上使用內(nèi)核進(jìn)行租約。然后可以從內(nèi)核請(qǐng)求讀/寫租約丸凭。 當(dāng)另一個(gè)進(jìn)程試圖截?cái)嗾趥鬏數(shù)奈募r(shí)福扬,內(nèi)核會(huì)向您發(fā)送一個(gè)實(shí)時(shí)信號(hào),即RT_SIGNAL_LEASE信號(hào)惜犀。它告訴您內(nèi)核正在破壞您在該文件上的寫或讀租約铛碑。在程序訪問一個(gè)無效的地址并被SIGBUS信號(hào)殺死之前,您的write調(diào)用會(huì)被中斷虽界。write調(diào)用的返回值是在中斷之前寫入的字節(jié)數(shù)汽烦,而errno將被設(shè)置為成功。下面是一些演示如何從內(nèi)核獲得租約的示例代碼:

if(fcntl(fd, F_SETSIG, RT_SIGNAL_LEASE) == -1) {
    perror("kernel lease set signal");
    return -1;
}
/* l_type can be F_RDLCK F_WRLCK */
if(fcntl(fd, F_SETLEASE, l_type)){
    perror("kernel lease set type");
    return -1;
}

Sendfile
在內(nèi)核版本2.1中莉御,引入了sendfile系統(tǒng)調(diào)用撇吞,以簡化網(wǎng)絡(luò)和兩個(gè)本地文件之間的數(shù)據(jù)傳輸。sendfile的引入不僅減少了數(shù)據(jù)復(fù)制礁叔,還減少了上下文切換牍颈。使用它是這樣的:

sendfile(socket, file, len);

為了更好地了解過程,請(qǐng)查看圖3琅关。

image.png

第一步:sendfile系統(tǒng)調(diào)用將把文件內(nèi)容復(fù)制到DMA引擎的內(nèi)核緩沖區(qū)中煮岁。然后將數(shù)據(jù)復(fù)制到與套接字相關(guān)聯(lián)的內(nèi)核緩沖區(qū)中。

步驟二:當(dāng)DMA引擎將數(shù)據(jù)從內(nèi)核套接字緩沖區(qū)傳遞到協(xié)議引擎時(shí)涣易,第三次復(fù)制發(fā)生人乓。

您可能想知道,如果另一個(gè)進(jìn)程截?cái)嗔宋覀冇胹endfile系統(tǒng)調(diào)用發(fā)送的文件都毒,會(huì)發(fā)生什么。如果我們不注冊(cè)任何信號(hào)處理程序碰缔,sendfile調(diào)用只需返回它在被中斷之前傳輸?shù)淖止?jié)數(shù)账劲,而errno將被設(shè)置為成功。

如果我們?cè)谡{(diào)用sendfile之前從文件的內(nèi)核獲得一個(gè)租約,但是瀑焦,行為和返回狀態(tài)完全相同腌且。在sendfile調(diào)用返回之前,我們還獲得了RT_SIGNAL_LEASE信號(hào)榛瓮。

到目前為止铺董,我們已經(jīng)能夠避免內(nèi)核生成幾個(gè)復(fù)制,但是我們?nèi)匀恢皇O乱粋€(gè)復(fù)制禀晓。這個(gè)可以避免嗎? 當(dāng)然精续,在硬件的幫助下。為了消除內(nèi)核所做的所有數(shù)據(jù)復(fù)制粹懒,我們需要一個(gè)支持收集操作的網(wǎng)絡(luò)接口重付。 這僅僅意味著等待傳輸?shù)臄?shù)據(jù)不需要在連續(xù)的內(nèi)存中它可以分散在不同的內(nèi)存位置。在內(nèi)核版本2.4中凫乖,修改了套接字緩沖區(qū)描述符以適應(yīng)這些需求——在Linux下稱為零拷貝确垫。這種方法不僅減少了多個(gè)上下文切換,還消除了處理器的數(shù)據(jù)復(fù)制帽芽。對(duì)于用戶級(jí)應(yīng)用程序删掀,沒有任何更改,因此代碼仍然是這樣:

sendfile(socket, file, len);

為了更好地了解過程导街,請(qǐng)查看圖4披泪。

支持集合的硬件可以從多個(gè)內(nèi)存位置組裝數(shù)據(jù),從而消除另一個(gè)復(fù)制.png

第一步:sendfile系統(tǒng)調(diào)用將把文件內(nèi)容復(fù)制到DMA引擎的內(nèi)核緩沖區(qū)中菊匿。

第二步:沒有將數(shù)據(jù)復(fù)制到套接字緩沖區(qū)中付呕。相反,只有帶有關(guān)于數(shù)據(jù)的位置和長度的信息的描述符被追加到套接字緩沖區(qū)跌捆。DMA引擎直接將數(shù)據(jù)從內(nèi)核緩沖區(qū)傳遞到協(xié)議引擎徽职,從而消除剩余的最終復(fù)制。

因?yàn)閿?shù)據(jù)實(shí)際上仍然是從磁盤復(fù)制到內(nèi)存和從存儲(chǔ)器到導(dǎo)線佩厚,有些人可能會(huì)認(rèn)為這不是一個(gè)真正的零拷貝姆钉。但是,這是從操作系統(tǒng)的角度來看是零拷貝抄瓦,因?yàn)閿?shù)據(jù)不是在內(nèi)核緩沖區(qū)之間復(fù)制的潮瓶。當(dāng)使用零拷貝時(shí),除了復(fù)制避免之外钙姊,還可以使用其他性能優(yōu)勢(shì)毯辅,例如更少的上下文切換、更少的CPU數(shù)據(jù)緩存污染和沒有CPU校驗(yàn)和計(jì)算煞额。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末思恐,一起剝皮案震驚了整個(gè)濱河市沾谜,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌胀莹,老刑警劉巖基跑,帶你破解...
    沈念sama閱讀 218,386評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異描焰,居然都是意外死亡媳否,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,142評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門荆秦,熙熙樓的掌柜王于貴愁眉苦臉地迎上來篱竭,“玉大人,你說我怎么就攤上這事萄凤∈页椋” “怎么了?”我有些...
    開封第一講書人閱讀 164,704評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵靡努,是天一觀的道長坪圾。 經(jīng)常有香客問我,道長惑朦,這世上最難降的妖魔是什么兽泄? 我笑而不...
    開封第一講書人閱讀 58,702評(píng)論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮漾月,結(jié)果婚禮上病梢,老公的妹妹穿的比我還像新娘。我一直安慰自己梁肿,他們只是感情好蜓陌,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,716評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著吩蔑,像睡著了一般钮热。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上烛芬,一...
    開封第一講書人閱讀 51,573評(píng)論 1 305
  • 那天隧期,我揣著相機(jī)與錄音,去河邊找鬼赘娄。 笑死仆潮,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的遣臼。 我是一名探鬼主播性置,決...
    沈念sama閱讀 40,314評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼揍堰!你這毒婦竟也來了蚌讼?” 一聲冷哼從身側(cè)響起辟灰,我...
    開封第一講書人閱讀 39,230評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎篡石,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體西采,經(jīng)...
    沈念sama閱讀 45,680評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡凰萨,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,873評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了械馆。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片胖眷。...
    茶點(diǎn)故事閱讀 39,991評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖霹崎,靈堂內(nèi)的尸體忽然破棺而出珊搀,到底是詐尸還是另有隱情,我是刑警寧澤尾菇,帶...
    沈念sama閱讀 35,706評(píng)論 5 346
  • 正文 年R本政府宣布境析,位于F島的核電站,受9級(jí)特大地震影響派诬,放射性物質(zhì)發(fā)生泄漏劳淆。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,329評(píng)論 3 330
  • 文/蒙蒙 一默赂、第九天 我趴在偏房一處隱蔽的房頂上張望沛鸵。 院中可真熱鬧,春花似錦缆八、人聲如沸曲掰。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,910評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽栏妖。三九已至,卻和暖如春冯挎,著一層夾襖步出監(jiān)牢的瞬間底哥,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,038評(píng)論 1 270
  • 我被黑心中介騙來泰國打工房官, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留趾徽,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,158評(píng)論 3 370
  • 正文 我出身青樓翰守,卻偏偏與公主長得像孵奶,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子蜡峰,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,941評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容