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