摘要:調(diào)用返回導(dǎo)致上下文從內(nèi)核切換回用戶模式,現(xiàn)在數(shù)據(jù)存儲(chǔ)在用戶地址空間的緩沖區(qū),并且可以再次開始向下復(fù)制數(shù)據(jù)。在內(nèi)核版本中,套接字緩沖區(qū)描述符被修改,以適應(yīng)這些需求下稱為零拷貝。
? 到目前為止,每個(gè)人都聽說過Linux下所謂的零拷貝功能,但我遇到有些人對(duì)這個(gè)主題沒有完全理解,正因?yàn)槿绱耍覜Q定寫幾篇文章來更深入研究下這個(gè)問題,希望能夠闡明這個(gè)有用的特性;這本文中,我們將從用戶模式的應(yīng)用程序角度來看看零拷貝,故省去復(fù)雜的內(nèi)核級(jí)別細(xì)節(jié)。
什么是零拷貝?
為了更好的理解問題的解決方案,我們需要首先來理解下問題本身,讓我們來看看網(wǎng)絡(luò)客戶端下載存儲(chǔ)在d?mon服務(wù)器的一個(gè)文件的簡(jiǎn)單過程,下面是一些實(shí)例代碼:
read(file, tmp_buf, len); write(socket, tmp_buf, len);
看起來很簡(jiǎn)單,你可能認(rèn)為只有兩個(gè)系統(tǒng)調(diào)用并沒有太多的開銷,實(shí)際上這與實(shí)際情況相差甚遠(yuǎn)。在這兩個(gè)調(diào)用背后,數(shù)據(jù)至少被復(fù)制了四次,并且執(zhí)行了幾乎數(shù)量一樣多的用戶/內(nèi)核上下文的切換(實(shí)際上這個(gè)過程還要更加復(fù)雜,但這里我只想保持簡(jiǎn)單),為了更好的了解涉及的過程,請(qǐng)看圖1,上半部分表示上下文切換,下半部分表示數(shù)據(jù)復(fù)制操作
圖1所示,兩個(gè)系統(tǒng)調(diào)用過程中的數(shù)據(jù)復(fù)制。
第一步: 讀系統(tǒng)調(diào)用導(dǎo)致用戶空間切換到內(nèi)核空間,第一次數(shù)據(jù)復(fù)制由DMA引擎執(zhí)行,該引擎讀取文件內(nèi)容并且存儲(chǔ)到內(nèi)核地址空間緩沖區(qū)
第二步:數(shù)據(jù)從內(nèi)核緩沖區(qū)復(fù)制到用戶緩沖區(qū),然后讀系統(tǒng)調(diào)用返回。調(diào)用返回導(dǎo)致上下文從內(nèi)核切換回用戶模式,現(xiàn)在數(shù)據(jù)存儲(chǔ)在用戶地址空間的緩沖區(qū),并且可以再次開始向下復(fù)制數(shù)據(jù)。
第三步:寫系統(tǒng)調(diào)用導(dǎo)致上下文從用戶模式切換到內(nèi)核模式。第三次數(shù)據(jù)復(fù)制是再次執(zhí)行把數(shù)據(jù)復(fù)制到內(nèi)核地址空間的緩沖區(qū),不過這一次,數(shù)據(jù)被放到了不同的緩沖區(qū),這個(gè)緩沖區(qū)是跟套接字相關(guān)聯(lián)的。
第四步:寫系統(tǒng)調(diào)用返回,創(chuàng)造了第四次上下文切換。第四次數(shù)據(jù)復(fù)制是由DMA引擎獨(dú)立、異步的從內(nèi)核緩沖區(qū)傳遞到協(xié)議引擎。你可能會(huì)問自己,獨(dú)立和異步是什么意思?數(shù)據(jù)不是在系統(tǒng)調(diào)用之前傳輸?shù)膯幔肯到y(tǒng)調(diào)用返回,實(shí)際上并不能保證傳輸,甚至不能保證傳輸?shù)拈_始。這僅僅意味著以太網(wǎng)驅(qū)動(dòng)程序在隊(duì)列中有空閑的描述符,并且接受了我們的數(shù)據(jù)進(jìn)行傳輸,在我們之前可能有很多的數(shù)據(jù)包在排隊(duì),除非驅(qū)動(dòng)程序/硬件實(shí)現(xiàn)優(yōu)先級(jí)的環(huán)或隊(duì)列,否則數(shù)據(jù)是以先到先出的方式傳輸?shù)模ㄉ蠄D中DMA復(fù)制說明了最后一個(gè)復(fù)制可以延遲的事實(shí))
正如您看到的,很多的數(shù)據(jù)復(fù)制并不是需要的,可以消除一些重復(fù)的復(fù)制,以減少開銷并提高性能;作為一個(gè)驅(qū)動(dòng)程序開發(fā)人員,我使用一些硬件的高級(jí)特性,可以完全繞開主存儲(chǔ)器直接傳輸數(shù)據(jù)到另一個(gè)設(shè)備,這個(gè)特性消除了系統(tǒng)內(nèi)存中的數(shù)據(jù)復(fù)制,是一個(gè)好東西,但不是所有的硬件都支持它。還存在磁盤數(shù)據(jù)必須重新轉(zhuǎn)換成網(wǎng)絡(luò)數(shù)據(jù)的問題,這帶來了一些復(fù)雜性;為了消除開銷,我們可以從消除內(nèi)核與用戶緩沖區(qū)之間的一些數(shù)據(jù)復(fù)制開始。
消除復(fù)制的一種方法就是跳過read調(diào)用,轉(zhuǎn)而調(diào)用mmap,列如:
tmp_buf = mmap(file, len); write(socket, tmp_buf, len);
為了更好的了解所涉及的過程,請(qǐng)看圖2,上下文切換保持不變。
圖2,mmap調(diào)用
第一步:mmap系統(tǒng)調(diào)用導(dǎo)致文件內(nèi)容被DMA引擎復(fù)制到內(nèi)核緩沖區(qū)中。然后與用戶進(jìn)程共享緩沖區(qū),而不需要在內(nèi)核和用戶內(nèi)存空間之間執(zhí)行任何數(shù)據(jù)復(fù)制。
第二步:寫系統(tǒng)調(diào)用使內(nèi)核將原始內(nèi)核緩沖區(qū)中的數(shù)據(jù)復(fù)制到與套接字關(guān)聯(lián)的內(nèi)核緩沖區(qū)中。
第三步:第三次復(fù)制發(fā)生在DMA引擎將數(shù)據(jù)從內(nèi)核套接字緩沖區(qū)傳遞到協(xié)議引擎時(shí)。
使用mmap代替read,我們將內(nèi)核的數(shù)據(jù)復(fù)制減少了一半,這在傳輸大量數(shù)據(jù)時(shí)產(chǎn)生了相當(dāng)好的效果,然而這種改進(jìn)并不是沒有代價(jià)的,使用mmap+write方式存在一些隱藏的缺陷。當(dāng)您在內(nèi)存映射了一個(gè)文件時(shí), 這時(shí)如果正好有一個(gè)進(jìn)程使用write修改了這個(gè)文件使之變小了,這時(shí)有可能會(huì)訪問到映射文件之外的內(nèi)存,進(jìn)程將收到SIGBUS信號(hào)而退出,這不是網(wǎng)絡(luò)服務(wù)器最理想的操作,有兩種方法可以解決這個(gè)問題 。
第一種方法是為SIGBUS信號(hào)安裝一個(gè)信號(hào)處理程序,然后在處理程序中簡(jiǎn)單地調(diào)用return。通過這樣做,write系統(tǒng)調(diào)用返回它在被中斷之前所寫的字節(jié)數(shù),errno設(shè)置為成功。讓我指出,這將是一個(gè)壞的解決方案,只看到了問題的表面而沒有解決問題的本質(zhì),因?yàn)镾IGBUS信號(hào)表明這個(gè)過程出了嚴(yán)重問題,所以我不鼓勵(lì)將此作為解決方案使用。
第二種解決方案涉及從內(nèi)核中租借文件(在Microsoft Windows中稱為“opportunistic locking”)。這是解決這個(gè)問題的正確方法。通過在文件描述符上使用租借,可以在特定文件上使用內(nèi)核。然后,您可以從內(nèi)核請(qǐng)求讀/寫租約。當(dāng)另一個(gè)進(jìn)程試圖截?cái)嗄獋鬏數(shù)奈募r(shí),內(nèi)核會(huì)向您發(fā)送實(shí)時(shí)信號(hào)RT_SIGNAL_LEASE信號(hào)。它告訴您,內(nèi)核正在破壞該文件上的寫或讀租約。在程序訪問無效地址并被SIGBUS信號(hào)殺死之前,寫調(diào)用被中斷。write調(diào)用的返回值是在中斷之前寫入的字節(jié)數(shù),errno將被設(shè)置為success。下面是一些示例代碼,展示了如何從內(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; }
您應(yīng)該在mmaping文件之前獲得您的租約,并在完成之后破壞您的租約。這是通過使用F_UNLCK的租賃類型調(diào)用fcntl F_SETLEASE來實(shí)現(xiàn)的。
Sendfile
在內(nèi)核版本2.1中,引入了sendfile系統(tǒng)調(diào)用,以簡(jiǎn)化通過網(wǎng)絡(luò)和兩個(gè)本地文件之間的數(shù)據(jù)傳輸。sendfile的引入不僅減少了數(shù)據(jù)復(fù)制,也減少了上下文切換。像這樣使用它:
sendfile(socket, file, len);
為了更好地了解所涉及的流程,請(qǐng)看圖3
圖3,用Sendfile代替讀和寫
第一步:sendfile系統(tǒng)調(diào)用導(dǎo)致文件內(nèi)容被DMA引擎復(fù)制到內(nèi)核緩沖區(qū)中。然后內(nèi)核將數(shù)據(jù)復(fù)制到與套接字關(guān)聯(lián)的內(nèi)核緩沖區(qū)中。
第二步:第三次復(fù)制發(fā)生在DMA引擎將數(shù)據(jù)從內(nèi)核套接字緩沖區(qū)傳遞到協(xié)議引擎時(shí)。
您可能想知道,如果另一個(gè)進(jìn)程截?cái)嗔宋覀兺ㄟ^sendfile系統(tǒng)調(diào)用傳輸?shù)奈募?,?huì)發(fā)生什么情況。如果我們不注冊(cè)任何信號(hào)處理程序,sendfile調(diào)用只返回它在中斷之前傳輸?shù)淖止?jié)數(shù),errno將被設(shè)置為成功。
但是,如果在調(diào)用sendfile之前從內(nèi)核獲得文件的租約,則行為和返回狀態(tài)完全相同。在sendfile調(diào)用返回之前,我們還得到了RT_SIGNAL_LEASE信號(hào)。
到目前為止,我們已經(jīng)能夠避免讓內(nèi)核復(fù)制幾個(gè)副本,但是仍然只剩下一個(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
圖4,支持gather的硬件可以從多個(gè)內(nèi)存位置組裝數(shù)據(jù),從而消除另一個(gè)副本
第一步:sendfile系統(tǒng)調(diào)用導(dǎo)致文件內(nèi)容被DMA引擎復(fù)制到內(nèi)核緩沖區(qū)中。
第二步:沒有數(shù)據(jù)復(fù)制到套接字緩沖區(qū)中。相反,只有包含有關(guān)數(shù)據(jù)位置和長度信息的描述符被附加到套接字緩沖區(qū),DMA引擎直接將數(shù)據(jù)從內(nèi)核緩沖區(qū)傳遞到協(xié)議引擎,從而消除了剩余的最終副本。
因?yàn)閿?shù)據(jù)實(shí)際上仍然是從磁盤復(fù)制到內(nèi)存,從內(nèi)存寫出去,有些人可能會(huì)說這不是一個(gè)真正的零拷貝。但是,從操作系統(tǒng)的角度來看,這是零副本,因?yàn)閿?shù)據(jù)不會(huì)在內(nèi)核緩沖區(qū)之間重復(fù)。當(dāng)使用零副本時(shí),除了避免復(fù)制之外,還可以獲得其他性能優(yōu)勢(shì),例如更少的上下文切換、更少的CPU數(shù)據(jù)緩存污染和沒有CPU校驗(yàn)和計(jì)算。
Linux下的zero copy的實(shí)現(xiàn)還遠(yuǎn)未完成,在不久的將來可能會(huì)發(fā)生變化。應(yīng)該添加更多的功能。例如,sendfile調(diào)用不支持向量傳輸,服務(wù)器(如Samba和Apache)必須使用多個(gè)sendfile調(diào)用并設(shè)置TCP_CORK標(biāo)志。TCP_CORK也與TCP_NODELAY不兼容,當(dāng)我們想給數(shù)據(jù)添加頭信息時(shí)使用。這是一個(gè)很好的例子,說明了一個(gè)vectored調(diào)用可以消除對(duì)多個(gè)sendfile調(diào)用的需求和當(dāng)前實(shí)現(xiàn)強(qiáng)制執(zhí)行的延遲。
當(dāng)前sendfile中一個(gè)相當(dāng)令人不快的限制是,在傳輸大于2GB的文件時(shí)不能使用它。這樣大的文件在今天并不少見,而且在退出時(shí)必須復(fù)制所有的數(shù)據(jù)是相當(dāng)令人失望的。因?yàn)閟endfile和mmap方法在本例中都不可用,所以sendfile64在未來的內(nèi)核版本中非常有用。
結(jié)論
盡管有一些缺點(diǎn),但是zero-copy sendfile是一個(gè)有用的特性,我希望您已經(jīng)發(fā)現(xiàn)本文提供了足夠的信息,可以開始在您的程序中使用它
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/77083.html
摘要:它是在的基礎(chǔ)上改進(jìn)的一種方案,通過對(duì)文件描述符上的事件狀態(tài)進(jìn)行判斷。檢索新的事件執(zhí)行與相關(guān)的回調(diào)幾乎所有情況下,除了關(guān)閉的回調(diào)函數(shù),它們由計(jì)時(shí)器和排定的之外,其余情況將在此處阻塞。執(zhí)行事件的,例如或者。 前言 學(xué)習(xí)Node就繞不開異步IO, 異步IO又與事件循環(huán)息息相關(guān), 而關(guān)于這一塊一直沒有仔細(xì)去了解整理過, 剛好最近在做項(xiàng)目的時(shí)候, 有了一些思考就記錄了下來, 希望能盡量將這一塊的...
摘要:根據(jù)瀏覽器設(shè)備的繪制限制來更新動(dòng)畫,回調(diào)的次數(shù)常是每秒次。鼠標(biāo)移入則停止自動(dòng)改變樹枝狀態(tài),轉(zhuǎn)為由鼠標(biāo)的橫縱坐標(biāo)控制?;镜纳羁截惙椒〝?shù)組,等方法,新增運(yùn)算符對(duì)象思路是把對(duì)象拆開分別賦值,同樣可以使用新增運(yùn)算符,循環(huán)等。 canvas動(dòng)畫 ???????動(dòng)畫的形成:先畫出一幅圖,改變其中的一些參數(shù),重新繪制圖片...不斷的重復(fù)形成動(dòng)畫。 fillStyle:設(shè)置或返回填充繪畫的顏色,漸...
閱讀 4364·2021-09-24 09:47
閱讀 1215·2021-09-03 10:33
閱讀 2101·2019-08-30 11:13
閱讀 1059·2019-08-30 10:49
閱讀 1783·2019-08-29 16:13
閱讀 2067·2019-08-29 11:28
閱讀 3122·2019-08-26 13:31
閱讀 3659·2019-08-23 17:14