0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

如何實(shí)現(xiàn)文件傳輸

科技綠洲 ? 來源:說出你的愿望吧 ? 作者:說出你的愿望吧 ? 2023-11-13 15:32 ? 次閱讀

你會(huì)如何實(shí)現(xiàn)文件傳輸?

服務(wù)器提供文件傳輸功能,需要將磁盤上的文件讀取出來,通過網(wǎng)絡(luò)協(xié)議發(fā)送到客戶端。如果需要你自己編碼實(shí)現(xiàn)這個(gè)文件傳輸功能,你會(huì)怎么實(shí)現(xiàn)呢?

通常,你會(huì)選擇最直接的方法:從網(wǎng)絡(luò)請求中找出文件在磁盤中的路徑后,如果這個(gè)文件比較大,假設(shè)有 320MB,可以在內(nèi)存中分配 32KB 的緩沖區(qū),再把文件分成一萬份,每份只有 32KB,這樣,從文件的起始位置讀入 32KB 到緩沖區(qū),再通過網(wǎng)絡(luò) API 把這 32KB 發(fā)送到客戶端。接著重復(fù)一萬次,直到把完整的文件都發(fā)送完畢。如下圖所示:

圖片

不過這個(gè)方案性能并不好,主要有兩個(gè)原因。

首先,它至少經(jīng)歷了 4 萬次 用戶態(tài)與內(nèi)核態(tài)的上下文切換 。因?yàn)?每處理 32KB 的消息,就需要一次 read 調(diào)用和一次 write 調(diào)用 ,每次系統(tǒng)調(diào)用****都得先從用戶態(tài)切換到內(nèi)核態(tài),等內(nèi)核完成任務(wù)后,再從內(nèi)核態(tài)切換回用戶態(tài) ??梢姡刻幚?32KB,就有 4 次上下文切換,重復(fù) 1 萬次后就有 4 萬次切換。

這個(gè)系統(tǒng)調(diào)用的內(nèi)容,我們可以結(jié)合下面補(bǔ)充的三種“ 上下文切換 ”來理解,上下文切換分別是進(jìn)程,線程,中斷三種。### 補(bǔ)充:進(jìn)程上下文切換

Linux 按照特權(quán)等級(jí),把 進(jìn)程的運(yùn)行空間分為內(nèi)核空間和用戶空間 ,分別對(duì)應(yīng)著下圖中, CPU 特權(quán)等級(jí)的 Ring 0 和 Ring 3 。內(nèi)核空間(Ring 0)具有最高權(quán)限,可以直接訪問所有資源,而用戶空間(Ring 3)只能訪問受限資源, 不能直接訪問內(nèi)存等硬件設(shè)備,必須通過系統(tǒng)調(diào)用陷入到內(nèi)核中 ,才能訪問這些特權(quán)資源。

圖片

換個(gè)角度看,也就是說,進(jìn)程既可以在用戶空間運(yùn)行,又可以在內(nèi)核空間中運(yùn)行。 進(jìn)程在用戶空間運(yùn)行時(shí),被稱為進(jìn)程的用戶態(tài),而陷入內(nèi)核空間的時(shí)候,被稱為進(jìn)程的內(nèi)核態(tài) 。從用戶態(tài)到內(nèi)核態(tài)的轉(zhuǎn)變,需要通過系統(tǒng)調(diào)用來完成。比如,當(dāng)我們查看文件內(nèi)容時(shí),就需要多次系統(tǒng)調(diào)用來完成:首先調(diào)用 open() 打開文件,然后調(diào)用 read() 讀取文件內(nèi)容,并調(diào)用 write() 將內(nèi)容寫到標(biāo)準(zhǔn)輸出,最后再調(diào)用 close() 關(guān)閉文件。

那么系統(tǒng)調(diào)用的過程是如何發(fā)生 CPU 上下文的切換的呢?我們再了解兩個(gè)概念:1. CPU 寄存器 ,是 CPU 內(nèi)置的容量小、但速度極快的內(nèi)存 。

  1. 程序計(jì)數(shù)器 ,則是用來 存儲(chǔ) CPU 正在執(zhí)行的指令位置 、 或者即將執(zhí)行的下一條指令位置 。它們都是 CPU 在 運(yùn)行任何任務(wù)前,必須的依賴環(huán)境 ,因此也被叫做 CPU 上下文 。

知道了什么是 CPU 上下文,我想你也很容易理解 CPU 上下文切換。 CPU 上下文切換 ,就是先把前一個(gè)任務(wù)的 CPU 上下文(也就是 CPU 寄存器和程序計(jì)數(shù)器)保存起來,然后加載新任務(wù)的上下文到這些寄存器和程序計(jì)數(shù)器,最后再跳轉(zhuǎn)到程序計(jì)數(shù)器所指的新位置,運(yùn)行新任務(wù)。而這些 保存下來的上下文,會(huì)存儲(chǔ)在系統(tǒng)內(nèi)核中,并在任務(wù)重新調(diào)度執(zhí)行時(shí)再次加載進(jìn)來。這樣就能保證任務(wù)原來的狀態(tài)不受影響,讓任務(wù)看起來還是連續(xù)運(yùn)行 。

回到系統(tǒng)調(diào)用的問題上,CPU 寄存器里原來 用戶態(tài)的指令位置,需要先保存起來 。接著,為了執(zhí)行內(nèi)核態(tài)代碼,CPU 寄存器需要 更新為內(nèi)核態(tài)指令的新位置。最后才是跳轉(zhuǎn)到內(nèi)核態(tài)運(yùn)行內(nèi)核任務(wù)。 而系統(tǒng)調(diào)用結(jié)束后,CPU 寄存器需要 恢復(fù)原來保存的用戶態(tài) ,然后再切換到用戶空間,繼續(xù)運(yùn)行進(jìn)程。所以,一次系統(tǒng)調(diào)用的過程,其實(shí)是發(fā)生了兩次 CPU 上下文切換。

不過,需要注意的是,系統(tǒng)調(diào)用過程中,并不會(huì)涉及到虛擬內(nèi)存等進(jìn)程用戶態(tài)的資源,也不會(huì)切換進(jìn)程。這跟我們通常所說的進(jìn)程上下文切換是不一樣的:

  1. 進(jìn)程上下文切換,是指從一個(gè)進(jìn)程切換到另一個(gè)進(jìn)程運(yùn)行。
  2. 系統(tǒng)調(diào)用過程中一直是同一個(gè)進(jìn)程在運(yùn)行。

那么,進(jìn)程上下文切換跟系統(tǒng)調(diào)用又有什么區(qū)別呢?首先,你需要知道, 進(jìn)程是由內(nèi)核來管理和調(diào)度的,進(jìn)程的切換只能發(fā)生在內(nèi)核態(tài) 。所以,進(jìn)程的上下文不僅包括了虛擬內(nèi)存、棧、全局變量等用戶空間的資源,還包括了內(nèi)核堆棧、寄存器等內(nèi)核空間的狀態(tài)。因此,進(jìn)程的上下文切換就比系統(tǒng)調(diào)用時(shí)多了一步:在保存當(dāng)前進(jìn)程的內(nèi)核狀態(tài)和 CPU 寄存器之前, 需要先把該進(jìn)程的虛擬內(nèi)存、棧等保存下來;而加載了下一進(jìn)程的內(nèi)核態(tài)后,還需要刷新進(jìn)程的虛擬內(nèi)存和用戶棧

保存上下文和恢復(fù)上下文的過程并不是“免費(fèi)”的,需要內(nèi)核在 CPU 上運(yùn)行才能完成。

圖片

每次上下文切換都需要幾十納秒到數(shù)微秒的 CPU 時(shí)間。這個(gè)時(shí)間還是相當(dāng)可觀的,特別是在進(jìn)程上下文切換次數(shù)較多的情況下,很容易導(dǎo)致 CPU 將大量時(shí)間耗費(fèi)在寄存器、內(nèi)核棧以及虛擬內(nèi)存等資源的保存和恢復(fù)上,進(jìn)而大大縮短了真正運(yùn)行進(jìn)程的時(shí)間。Linux 通過 TLB(Translation Lookaside Buffer)來管理虛擬內(nèi)存到物理內(nèi)存的映射關(guān)系。當(dāng)虛擬內(nèi)存更新后,TLB 也需要刷新,內(nèi)存的訪問也會(huì)隨之變慢。特別是在多處理器系統(tǒng)上,緩存是被多個(gè)處理器共享的,刷新緩存不僅會(huì)影響當(dāng)前處理器的進(jìn)程,還會(huì)影響共享緩存的其他處理器的進(jìn)程。

TLB,這個(gè)東西的資料比較晦澀難懂,我大致搜了一下,非常多的專業(yè)術(shù)語,不太建議大家展開了,等到我們真的要用上的時(shí)候,再去了解也不晚,大致內(nèi)容我覺得如果要展開,那就展開我下面的這個(gè)部分就已經(jīng)足夠了。

TLB是一種高速緩存,內(nèi)存管理硬件使用它來改善虛擬地址到物理地址的轉(zhuǎn)換速度。當(dāng)前所有的個(gè)人桌面,筆記本和服務(wù)器處理器都使用TLB來進(jìn)行 虛擬地址到物理地址的映射 。使用TLB內(nèi)核可以快速的找到虛擬地址指向物理地址,而不需要請求RAM內(nèi)存獲取虛擬地址到物理地址的映射關(guān)系。

虛擬地址和物理地址的話,大致是這么理解的。每個(gè)進(jìn)程都有自己獨(dú)立的4G內(nèi)存空間,各個(gè)進(jìn)程的內(nèi)存空間具有類似的結(jié)構(gòu)。一個(gè)新進(jìn)程建立的時(shí)候,將會(huì)建立起自己的內(nèi)存空間,此進(jìn)程的數(shù)據(jù),代碼等從磁盤拷貝到自己的進(jìn)程空間,哪些數(shù)據(jù)在哪里,都由進(jìn)程控制表中的task_struct記錄,它會(huì)有一條鏈表,記錄中內(nèi)存空間的分配情況,哪些地址有數(shù)據(jù),哪些地址無數(shù)據(jù),哪些可讀,哪些可寫,都可以通過這個(gè)鏈表記錄。每個(gè)進(jìn)程已經(jīng)分配的內(nèi)存空間,都與對(duì)應(yīng)的磁盤空間映射

可是計(jì)算機(jī)明明沒有那么多內(nèi)存(n個(gè)進(jìn)程的話就需要n*4G)內(nèi)存。還有建立一個(gè)進(jìn)程,就要把磁盤上的程序文件拷貝到進(jìn)程對(duì)應(yīng)的內(nèi)存中去,對(duì)于一個(gè)程序?qū)?yīng)的多個(gè)進(jìn)程這種情況是根本不需要這樣操作的。

所以,每個(gè)進(jìn)程的4G內(nèi)存空間只是 虛擬內(nèi)存空間 ,每次 訪問內(nèi)存空間的某個(gè)地址,都需要把地址翻譯為實(shí)際物理內(nèi)存地址 。 所有進(jìn)程共享同一物理內(nèi)存 ,每個(gè)進(jìn)程只把自己目前需要的虛擬內(nèi)存空間映射并存儲(chǔ)到物理內(nèi)存上。進(jìn)程要知道哪些內(nèi)存地址上的數(shù)據(jù)在物理內(nèi)存上,哪些不在,還有在物理內(nèi)存上的哪里,需要用頁表來記錄。頁表的每一個(gè)表項(xiàng)分兩部分,第一部分記錄 此頁是否在物理內(nèi)存上 ,第二部分記錄 物理內(nèi)存頁的地址 (如果在的話)。當(dāng)進(jìn)程訪問某個(gè)虛擬地址,去看頁表,如果發(fā)現(xiàn)對(duì)應(yīng)的數(shù)據(jù)不在物理內(nèi)存中,則缺頁異常。缺頁異常的處理過程,就是把進(jìn)程需要的數(shù)據(jù)從磁盤上拷貝到物理內(nèi)存中。

知道了進(jìn)程上下文切換潛在的性能問題后,我們再來看,究竟什么時(shí)候會(huì)切換進(jìn)程上下文。顯然,只有在進(jìn)程調(diào)度的時(shí)候,才需要切換上下文。Linux 為每個(gè) CPU 都維護(hù)了一個(gè)就緒隊(duì)列,將活躍進(jìn)程(即正在運(yùn)行和正在等待 CPU 的進(jìn)程)按照優(yōu)先級(jí)和等待 CPU 的時(shí)間排序,然后選擇最需要 CPU 的進(jìn)程,也就是優(yōu)先級(jí)最高和等待 CPU 時(shí)間最長的進(jìn)程來運(yùn)行。

那么,進(jìn)程在什么時(shí)候才會(huì)被調(diào)度到 CPU 上運(yùn)行呢?最容易想到的一個(gè)時(shí)機(jī),就是進(jìn)程執(zhí)行完終止了,它之前使用的 CPU 會(huì)釋放出來,這個(gè)時(shí)候再從就緒隊(duì)列里,拿一個(gè)新的進(jìn)程過來運(yùn)行。其實(shí)還有很多其他場景,也會(huì)觸發(fā)進(jìn)程調(diào)度,在這里我給你逐個(gè)梳理下。

其一,為了保證所有進(jìn)程可以得到 公平調(diào)度 ,CPU 時(shí)間被劃分為一段段的時(shí)間片,這些 時(shí)間片再被輪流分配給各個(gè)進(jìn)程 。這樣,當(dāng)某個(gè)進(jìn)程的時(shí)間片耗盡了,就會(huì)被系統(tǒng)掛起,切換到其它正在等待 CPU 的進(jìn)程運(yùn)行。
其二,進(jìn)程在 系統(tǒng)資源不足 (比如內(nèi)存不足)時(shí),要等到資源滿足后才可以運(yùn)行,這個(gè)時(shí)候進(jìn)程也會(huì)被掛起,并由系統(tǒng)調(diào)度其他進(jìn)程運(yùn)行。
其三,當(dāng)進(jìn)程通過 sleep 這樣的方法 將自己主動(dòng)掛起時(shí) ,自然也會(huì)重新調(diào)度。
其四,當(dāng)有優(yōu)先級(jí)更高的進(jìn)程運(yùn)行時(shí),為了保證高優(yōu)先級(jí)進(jìn)程的運(yùn)行,當(dāng)前進(jìn)程會(huì)被掛起,由高優(yōu)先級(jí)進(jìn)程來運(yùn)行。
最后一個(gè),發(fā)生硬件中斷時(shí),CPU 上的進(jìn)程會(huì)被中斷掛起,轉(zhuǎn)而執(zhí)行內(nèi)核中的中斷服務(wù)程序。

線程上下文切換

線程與進(jìn)程最大的區(qū)別在于, 線程是調(diào)度的基本單位,而進(jìn)程則是資源擁有的基本單位 。說白了,所謂內(nèi)核中的任務(wù)調(diào)度,實(shí)際上的 調(diào)度對(duì)象是線程 ;而進(jìn)程只是給線程 提供了虛擬內(nèi)存、全局變量等資源 。所以,對(duì)于線程和進(jìn)程,我們可以這么理解:

  1. 當(dāng)進(jìn)程只有一個(gè)線程時(shí),可以認(rèn)為進(jìn)程就等于線程。
  2. 當(dāng)進(jìn)程擁有多個(gè)線程時(shí),這些線程會(huì)共享相同的虛擬內(nèi)存和全局變量等資源。這些資源在上下文切換時(shí)是不需要修改的。
  3. 另外,線程也有自己的私有數(shù)據(jù),比如棧和寄存器等,這些在上下文切換時(shí)也是需要保存的。

這么一來,線程的上下文切換其實(shí)就可以分為兩種情況:

第一種, 前后兩個(gè)線程屬于不同進(jìn)程。此時(shí),因?yàn)橘Y源不共享,所以切換過程就跟進(jìn)程上下文切換是一樣。

第二種,前后兩個(gè)線程屬于同一個(gè)進(jìn)程。此時(shí),因?yàn)樘摂M內(nèi)存是共享的,所以在切換時(shí), 虛擬內(nèi)存這些資源就保持不動(dòng),只需要切換線程的私有數(shù)據(jù)、寄存器等不共享的數(shù)據(jù) 。到這里你應(yīng)該也發(fā)現(xiàn)了,雖然同為上下文切換,但同進(jìn)程內(nèi)的線程切換,要比多進(jìn)程間的切換消耗更少的資源,而這,也正是多線程代替多進(jìn)程的一個(gè)優(yōu)勢。

中斷上下文切換

一個(gè)場景也會(huì)切換 CPU 上下文,那就是中斷。為了快速響應(yīng)硬件的事件,中斷處理會(huì)打斷進(jìn)程的正常調(diào)度和執(zhí)行,轉(zhuǎn)而調(diào)用中斷處理程序,響應(yīng)設(shè)備事件。而在打斷其他進(jìn)程時(shí),就需要將進(jìn)程當(dāng)前的狀態(tài)保存下來,這樣在中斷結(jié)束后,進(jìn)程仍然可以從原來的狀態(tài)恢復(fù)運(yùn)行。

跟進(jìn)程上下文不同,中斷上下文切換并不涉及到進(jìn)程的用戶態(tài)。所以,即便中斷過程打斷了一個(gè)正處在用戶態(tài)的進(jìn)程,也不需要保存和恢復(fù)這個(gè)進(jìn)程的虛擬內(nèi)存、全局變量等用戶態(tài)資源。中斷上下文,其實(shí)只包括內(nèi)核態(tài)中斷服務(wù)程序執(zhí)行所必需的狀態(tài),包括 CPU 寄存器、內(nèi)核堆棧、硬件中斷參數(shù)等。對(duì)同一個(gè) CPU 來說,中斷處理比進(jìn)程擁有更高的優(yōu)先級(jí),所以中斷上下文切換并不會(huì)與進(jìn)程上下文切換同時(shí)發(fā)生。

同樣道理,由于中斷會(huì)打斷正常進(jìn)程的調(diào)度和執(zhí)行,所以大部分中斷處理程序都短小精悍,以便盡可能快的執(zhí)行結(jié)束。另外,跟進(jìn)程上下文切換一樣,中斷上下文切換也需要消耗 CPU,切換次數(shù)過多也會(huì)耗費(fèi)大量的 CPU,嚴(yán)重降低系統(tǒng)的整體性能。

總結(jié)一下,CPU 上下文切換,是保證 Linux 系統(tǒng)正常工作的核心功能之一,一般情況下不需要我們特別關(guān)注。但過多的上下文切換,會(huì)把 CPU 時(shí)間消耗在寄存器、內(nèi)核棧以及虛擬內(nèi)存等數(shù)據(jù)的保存和恢復(fù)上,從而縮短進(jìn)程真正運(yùn)行的時(shí)間,導(dǎo)致系統(tǒng)的整體性能大幅下降。

回到零拷貝的事兒上

剛剛我們的場景,每處理 32KB,就有 4 次上下文切換,重復(fù) 1 萬次后就有 4 萬次切換。上下文切換的成本并不小,雖然一次切換僅消耗幾十納秒到幾微秒,但高并發(fā)服務(wù)就會(huì)放大這類時(shí)間的消耗。其次,這個(gè)方案做了 4 萬次內(nèi)存拷貝,對(duì) 320MB 文件拷貝的字節(jié)數(shù)也翻了 4 倍,到了 1280MB。很顯然,過多的內(nèi)存拷貝無謂地消耗了 CPU 資源,降低了系統(tǒng)的并發(fā)處理能力。所以要想提升傳輸文件的性能,需要從降低上下文切換的頻率和內(nèi)存拷貝次數(shù)兩個(gè)方向入手。

零拷貝如何提升文件傳輸性能?

再提一句,為什么讀取磁盤文件時(shí),一定要做上下文切換呢?這是因?yàn)椋?讀取磁盤或者操作網(wǎng)卡都由操作系統(tǒng)內(nèi)核完成 。 內(nèi)核負(fù)責(zé)管理系統(tǒng)上的所有進(jìn)程 ,它的權(quán)限最高,工作環(huán)境與用戶進(jìn)程完全不同。只要我們的代碼執(zhí)行 read 或者 write 這樣的系統(tǒng)調(diào)用,一定會(huì)發(fā)生 2 次上下文切換: 首先從用戶態(tài)切換到內(nèi)核態(tài),當(dāng)內(nèi)核執(zhí)行完任務(wù)后,再切換回用戶態(tài)交由進(jìn)程代碼執(zhí)行 。因此,如果想減少上下文切換次數(shù),就一定要減少系統(tǒng)調(diào)用的次數(shù)。解決方案就是把 read、write 兩次系統(tǒng)調(diào)用合并成一次,在內(nèi)核中完成磁盤與網(wǎng)卡的數(shù)據(jù)交換。

其次,我們應(yīng)該考慮如何減少內(nèi)存拷貝次數(shù)。每周期中的 4 次內(nèi)存拷貝,其中與物理設(shè)備相關(guān)的 2 次拷貝是必不可少的,包括: 把磁盤內(nèi)容拷貝到內(nèi)存,以及把內(nèi)存拷貝到網(wǎng)卡 。但另外 2 次與用戶緩沖區(qū)相關(guān)的拷貝動(dòng)作都不是必需的,因?yàn)樵诎汛疟P文件發(fā)到網(wǎng)絡(luò)的場景中,用戶緩沖區(qū)沒有必須存在的理由。如果內(nèi)核在讀取文件后, 直接把 PageCache 中的內(nèi)容拷貝到 Socket 緩沖區(qū) ,待到網(wǎng)卡發(fā)送完畢后,再通知進(jìn)程,這樣就只有 2 次上下文切換,和 3 次內(nèi)存拷貝。

圖片

如果網(wǎng)卡支持 SG-DMA(The Scatter-Gather Direct Memory Access)技術(shù),還可以再去除 Socket 緩沖區(qū)的拷貝,這樣一共只有 2 次內(nèi)存拷貝。在DMA傳輸數(shù)據(jù)的過程中,要求源物理地址和目標(biāo)物理地址必須是連續(xù)的。可是連續(xù)的存儲(chǔ)器地址在物理上不一定是連續(xù)的,所以DMA傳輸要分成多次完成。如果在傳輸完一塊物理上連續(xù)的數(shù)據(jù)后引起一次中斷,然后再由主機(jī)進(jìn)行下一塊物理上連續(xù)的數(shù)據(jù)傳輸。Scatter-gather DMA方式則不同,它使用一個(gè)鏈表描述物理上不連續(xù)的存儲(chǔ)空間,然后把鏈表首地址告訴DMA master。DMA master在傳輸完一塊物理連續(xù)的數(shù)據(jù)后,不用發(fā)起中斷,而是根據(jù)鏈表來傳輸下一塊物理上連續(xù)的數(shù)據(jù),直到傳輸完畢后再發(fā)起一次中斷。

圖片

實(shí)際上,這就是零拷貝技術(shù)。它是操作系統(tǒng)提供的新函數(shù),同時(shí)接收文件描述符和 TCP socket 作為輸入?yún)?shù),這樣 執(zhí)行時(shí)就可以完全在內(nèi)核態(tài)完成內(nèi)存拷貝,既減少了內(nèi)存拷貝次數(shù),也降低了上下文切換次數(shù) 。而且,零拷貝取消了用戶緩沖區(qū)后,不只降低了用戶內(nèi)存的消耗,還通過 最大化利用 socket 緩沖區(qū)中的內(nèi)存,間接地再一次減少了系統(tǒng)調(diào)用的次數(shù) ,從而帶來了大幅減少上下文切換次數(shù)的機(jī)會(huì)

你可以回憶下,沒用零拷貝時(shí),為了傳輸 320MB 的文件,在用戶緩沖區(qū)分配了 32KB 的內(nèi)存,把文件分成 1 萬份傳送,然而,這 32KB 是怎么來的?為什么不是 32MB 或者 32 字節(jié)呢?這是因?yàn)?,在沒有零拷貝的情況下,我們希望內(nèi)存的利用率最高。 如果用戶緩沖區(qū)過大,它就無法一次性把消息全拷貝給 socket 緩沖區(qū) (這里是socket的大小有所限制);如果用戶緩沖區(qū)過小,則會(huì)導(dǎo)致過多的 read/write 系統(tǒng)調(diào)用 。

那用戶緩沖區(qū)為什么不與 socket 緩沖區(qū)大小一致呢?這是因?yàn)椋?socket 緩沖區(qū)的可用空間是動(dòng)態(tài)變化的 ,它既用于 TCP 滑動(dòng)窗口,也用于應(yīng)用緩沖區(qū),還受到整個(gè)系統(tǒng)內(nèi)存的影響。尤其在長肥網(wǎng)絡(luò)中,它的變化范圍特別大。

零拷貝使我們不必關(guān)心 socket 緩沖區(qū)的大小。比如,調(diào)用零拷貝發(fā)送方法時(shí), 盡可以把發(fā)送字節(jié)數(shù)設(shè)為文件的所有未發(fā)送字節(jié)數(shù) ,例如 320MB,也許此時(shí) socket 緩沖區(qū)大小為 1.4MB,那么一次性就會(huì)發(fā)送 1.4MB 到客戶端,而不是只有 32KB。這意味著對(duì)于 1.4MB 的 1 次零拷貝,僅帶來 2 次上下文切換,而不使用零拷貝且用戶緩沖區(qū)為 32KB 時(shí),經(jīng)歷了 176 次(4 * 1.4MB/32KB)上下文切換。

綜合上述,對(duì)文章開頭提到的 320MB 文件的傳輸,當(dāng) socket 緩沖區(qū)在 1.4MB 左右時(shí),只需要 4 百多次上下文切換,以及 4 百多次內(nèi)存拷貝,拷貝的數(shù)據(jù)量也僅有 640MB,這樣,不只請求時(shí)延會(huì)降低,處理每個(gè)請求消耗的 CPU 資源也會(huì)更少,從而支持更多的并發(fā)請求。

此外,零拷貝還使用了 PageCache 技術(shù)。### PageCache,磁盤高速緩存

回顧上文中,你會(huì)發(fā)現(xiàn),讀取文件時(shí),是先把磁盤文件拷貝到 PageCache 上,再拷貝到進(jìn)程中。為什么這樣做呢?有兩個(gè)原因所致。

由于磁盤比內(nèi)存的速度慢許多,所以我們應(yīng)該想辦法把 讀寫磁盤替換成讀寫內(nèi)存 ,比如把磁盤中的數(shù)據(jù)復(fù)制到內(nèi)存中,就可以用讀內(nèi)存替換讀磁盤。但是,內(nèi)存空間遠(yuǎn)比磁盤要小,內(nèi)存中注定只能復(fù)制一小部分磁盤中的數(shù)據(jù)。通常,剛被訪問的數(shù)據(jù)在短時(shí)間內(nèi)再次被訪問的概率很高。用 PageCache 緩存最近訪問的數(shù)據(jù),當(dāng)空間不足時(shí) 淘汰最久未被訪問的緩存 (即 LRU 算法)。 讀磁盤時(shí)優(yōu)先到 PageCache 中找一找 ,如果數(shù)據(jù) 存在便直接返回 ,這便大大提升了讀磁盤的性能。

而且讀取磁盤數(shù)據(jù)時(shí),需要先找到數(shù)據(jù)所在的位置,對(duì)于機(jī)械磁盤來說,就是旋轉(zhuǎn)磁頭到數(shù)據(jù)所在的扇區(qū),再開始順序讀取數(shù)據(jù)。其中,旋轉(zhuǎn)磁頭耗時(shí)很長,為了降低它的影響,PageCache 使用了預(yù)讀功能。也就是說,雖然 read 方法只讀取了 0-32KB 的字節(jié),但內(nèi)核會(huì)把其后的 32-64KB 也讀取到 PageCache,這后 32KB 讀取的成本很低。如果在 32-64KB 淘汰出 PageCache 前,進(jìn)程讀取到它了,收益就非常大。這一講的傳輸文件場景中這是必然發(fā)生的。

綜上可以看到 PageCache 的優(yōu)點(diǎn),它在 90% 以上場景下都會(huì)提升磁盤性能,但在某些情況下,PageCache 會(huì)不起作用,甚至由于多做了一次內(nèi)存拷貝,造成性能的降低。在這些場景中,使用了 PageCache 的零拷貝也會(huì)損失性能。

具體就是在傳輸大文件的時(shí)候。比如,你有很多 GB 級(jí)的文件需要傳輸,每當(dāng)用戶訪問這些大文件時(shí),內(nèi)核就會(huì)把它們載入到 PageCache 中,這些大文件很快會(huì)把有限的 PageCache 占滿。然而,由于文件太大, 文件中某一部分內(nèi)容被再次訪問到的概率其實(shí)非常低 。這帶來了 2 個(gè)問題:首先,由于 PageCache 長期被大文件占據(jù), 熱點(diǎn)小文件就無法充分使用 PageCache,它們讀起來變慢了 ;其次, PageCache 中的大文件沒有享受到緩存的好處,但卻耗費(fèi) CPU 多拷貝到 PageCache 一次 。所以,高并發(fā)場景下,為了防止 PageCache 被大文件占滿后不再對(duì)小文件產(chǎn)生作用, 大文件不應(yīng)使用 PageCache,進(jìn)而也不應(yīng)使用零拷貝技術(shù)處理。 用看電影來舉例的話,就是我只想看前10分鐘,就要把整部都下下來,這明顯是虧的。而高并發(fā)場景處理大文件時(shí),應(yīng)當(dāng)使用異步 IO 和直接 IO 來替換零拷貝技術(shù)。

異步 IO + 直接 IO

回到開頭的例子,當(dāng)調(diào)用 read 方法讀取文件時(shí),實(shí)際上 read 方法會(huì)在磁盤尋址過程中阻塞等待,導(dǎo)致進(jìn)程無法并發(fā)地處理其他任務(wù),如下圖所示:也就是在拉數(shù)據(jù)的過程中,一整個(gè)流程下來進(jìn)程都是阻塞的意思。

圖片

異步 IO(異步 IO 既可以處理網(wǎng)絡(luò) IO,也可以處理磁盤 IO,這里我們只關(guān)注磁盤 IO)可以解決阻塞問題。它把讀操作分為兩部分,前半部分 向內(nèi)核發(fā)起讀請求,但不等待數(shù)據(jù)就位就立刻返回,此時(shí)進(jìn)程可以并發(fā)地處理其他任務(wù) 。當(dāng) 內(nèi)核將磁盤中的數(shù)據(jù)拷貝到進(jìn)程緩沖區(qū)后,進(jìn)程將接收到內(nèi)核的通知,再去處理數(shù)據(jù) ,這是異步 IO 的后半部分。如下圖所示:

圖片

從圖中可以看到,異步 IO 并沒有拷貝到 PageCache 中,這其實(shí)是異步 IO 實(shí)現(xiàn)上的缺陷。 經(jīng)過 PageCache 的 IO 我們稱為緩存 IO ,它與虛擬內(nèi)存系統(tǒng)耦合太緊,導(dǎo)致異步 IO 從誕生起到現(xiàn)在都不支持緩存 IO。繞過 PageCache 的 IO 是個(gè)新物種,我們把它稱為直接 IO。對(duì)于磁盤,異步 IO 只支持直接 IO。

直接 IO 的應(yīng)用場景并不多,主要有兩種:第一,應(yīng)用程序已經(jīng)實(shí)現(xiàn)了磁盤文件的緩存,不需要 PageCache 再次緩存,引發(fā)額外的性能消耗。比如 MySQL 等數(shù)據(jù)庫就使用直接 IO;第二,高并發(fā)下傳輸大文件,我們上文提到過,大文件難以命中 PageCache 緩存,又帶來額外的內(nèi)存拷貝,同時(shí)還擠占了小文件使用 PageCache 時(shí)需要的內(nèi)存,因此,這時(shí)應(yīng)該使用直接 IO。

直接 IO 的缺點(diǎn)就是無法享受 PageCache 的好處,也就是內(nèi)核(IO 調(diào)度算法)會(huì)試圖緩存盡量多的連續(xù) IO 在 PageCache 中,最后合并成一個(gè)更大的 IO 再發(fā)給磁盤,這樣可以減少磁盤的尋址操作;另外,內(nèi)核也會(huì)預(yù)讀后續(xù)的 IO 放在 PageCache 中,減少磁盤操作。這些它都是做不到的### 小結(jié)

基于用戶緩沖區(qū)傳輸文件時(shí),過多的內(nèi)存拷貝與上下文切換次數(shù)會(huì)降低性能。零拷貝技術(shù)在內(nèi)核中完成內(nèi)存拷貝,天然降低了內(nèi)存拷貝次數(shù)。它通過一次系統(tǒng)調(diào)用合并了磁盤讀取與網(wǎng)絡(luò)發(fā)送兩個(gè)操作,降低了上下文切換次數(shù)。尤其是,由于拷貝在內(nèi)核中完成,它可以最大化使用 socket 緩沖區(qū)的可用空間,從而提高了一次系統(tǒng)調(diào)用中處理的數(shù)據(jù)量,進(jìn)一步降低了上下文切換次數(shù)。

零拷貝技術(shù)基于 PageCache,而 PageCache 緩存了最近訪問過的數(shù)據(jù),提升了訪問緩存數(shù)據(jù)的性能,同時(shí),為了解決機(jī)械磁盤尋址慢的問題,它還協(xié)助 IO 調(diào)度算法實(shí)現(xiàn)了 IO 合并與預(yù)讀(這也是順序讀比隨機(jī)讀性能好的原因),這進(jìn)一步提升了零拷貝的性能。幾乎所有操作系統(tǒng)都支持零拷貝,如果應(yīng)用場景就是把文件發(fā)送到網(wǎng)絡(luò)中,那么零拷貝確實(shí)是個(gè)好方法。

Tips:其實(shí)這里如果是使用SSD這類固態(tài)硬盤(不用旋轉(zhuǎn)磁頭),PageCache就沒有很大的影響,細(xì)節(jié)請參照我上一篇的那個(gè)SSD的文

不過,零拷貝有一個(gè)缺點(diǎn),就是不允許進(jìn)程對(duì)文件內(nèi)容作一些加工再發(fā)送,比如數(shù)據(jù)壓縮后再發(fā)送。另外,當(dāng) PageCache 引發(fā)負(fù)作用時(shí),也不能使用零拷貝,此時(shí)可以用異步 IO+ 直接 IO 替換。我們 通常會(huì)設(shè)定一個(gè)文件大小閾值,針對(duì)大文件使用異步 IO 和直接 IO,而對(duì)小文件使用零拷貝 。

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報(bào)投訴
  • 硬件
    +關(guān)注

    關(guān)注

    11

    文章

    3112

    瀏覽量

    65846
  • 網(wǎng)絡(luò)協(xié)議

    關(guān)注

    3

    文章

    251

    瀏覽量

    21467
  • 磁盤
    +關(guān)注

    關(guān)注

    1

    文章

    355

    瀏覽量

    25090
  • 編碼
    +關(guān)注

    關(guān)注

    6

    文章

    915

    瀏覽量

    54651
  • 文件傳輸
    +關(guān)注

    關(guān)注

    0

    文章

    34

    瀏覽量

    8290
收藏 人收藏

    評(píng)論

    相關(guān)推薦

    如何高效實(shí)現(xiàn)文件傳輸

    服務(wù)器提供文件傳輸功能,需要將磁盤上的文件讀取出來,通過網(wǎng)絡(luò)協(xié)議發(fā)送到客戶端。如果需要你自己編碼實(shí)現(xiàn)這個(gè)文件傳輸功能,你會(huì)怎么實(shí)現(xiàn)呢?
    發(fā)表于 08-01 16:16 ?460次閱讀
    如何高效<b class='flag-5'>實(shí)現(xiàn)</b><b class='flag-5'>文件傳輸</b>

    飛凌全志T527開發(fā)板實(shí)現(xiàn)局域網(wǎng)內(nèi)文件傳輸功能

    之前玩開發(fā)板的時(shí)候,如果需要實(shí)現(xiàn)主機(jī)與開發(fā)板之間的文件傳輸,通常是通過掛載NFS的方式,而飛凌的OKT527板載WIFI,并且官方提供的鏡像中已經(jīng)將其成功驅(qū)動(dòng),那我們就可以通過WIFI連接家中
    發(fā)表于 07-29 13:55

    在Ubuntu上怎樣去驗(yàn)證NFS是否可用呢

    在Ubuntu上怎樣去驗(yàn)證NFS是否可用呢?在Ubuntu上NFS是如何實(shí)現(xiàn)文件傳輸的呢?
    發(fā)表于 12-27 06:52

    Hi3516聯(lián)網(wǎng)

    有在Hi3516上成功聯(lián)網(wǎng)實(shí)現(xiàn)文件傳輸的嗎?
    發(fā)表于 03-23 11:38

    【正點(diǎn)原子STM32精英V2開發(fā)板體驗(yàn)】使用xmodem協(xié)議基于串口實(shí)現(xiàn)文件傳輸

    ,通過串口的xmodem協(xié)議實(shí)現(xiàn)文件傳輸是一個(gè)不錯(cuò)的選擇。這一篇就演示移植xmodem,并進(jìn)行文件的導(dǎo)入導(dǎo)出測試,為了查看文件實(shí)現(xiàn)了ls的s
    發(fā)表于 04-15 17:32

    嵌入式FTP服務(wù)器的設(shè)計(jì)與實(shí)現(xiàn)

    隨著嵌入式設(shè)備的不斷發(fā)展,其對(duì)通信也提出了越來越高的要求。FTP(File Transfer Protocol)作為internet上最早提供的服務(wù)之一,至今仍然被人們廣泛使用,F(xiàn)TP是實(shí)現(xiàn)文件傳輸服務(wù)的最主要
    發(fā)表于 06-14 06:50 ?1428次閱讀
    嵌入式FTP服務(wù)器的設(shè)計(jì)與<b class='flag-5'>實(shí)現(xiàn)</b>

    基于QtopiaCore的MP3的研究實(shí)驗(yàn)

    介紹一種基于Qt/embedded Linux(Qtopia Core)的網(wǎng)絡(luò)MP3 播放器的設(shè)計(jì)方法,通過FTP,將網(wǎng)絡(luò)上FTP 服務(wù)站點(diǎn)中的MP3 音樂文件下載到本地,使用文件流對(duì)MP3 進(jìn)行軟解碼,實(shí)現(xiàn)
    發(fā)表于 09-22 16:17 ?4次下載
    基于QtopiaCore的MP3的研究實(shí)驗(yàn)

    華為推出一碰傳技術(shù)只需將手機(jī)與電腦碰觸即可實(shí)現(xiàn)文件傳輸

    與以往WiFi、藍(lán)牙、線纜的傳輸不同,“一碰傳”技術(shù)只需將華為Mate Pro手機(jī)的前部,與MateBook電腦觸控板右側(cè)輕輕一碰,即可實(shí)現(xiàn)文件間的傳輸
    發(fā)表于 02-25 10:58 ?1.6w次閱讀

    基于DSP的嵌入式FTP服務(wù)器實(shí)現(xiàn)方法介紹

    隨著嵌入式設(shè)備的不斷發(fā)展,其對(duì)通信也提出了越來越高的要求。FTP(File Transfer Protocol)作為internet上最早提供的服務(wù)之一,至今仍然被人們廣泛使用,F(xiàn)TP是實(shí)現(xiàn)文件傳輸
    發(fā)表于 11-15 08:35 ?1424次閱讀
    基于DSP的嵌入式FTP服務(wù)器<b class='flag-5'>實(shí)現(xiàn)</b>方法介紹

    基于TCP/IP編程實(shí)現(xiàn)文件傳輸的案例分析

    本文就是考慮到這一現(xiàn)狀,結(jié)合基于Linux操作系統(tǒng)下的TCP/IP網(wǎng)絡(luò)通信原理,給出了一種基于TCP/IP編程實(shí)現(xiàn)文件傳輸的實(shí)例,因此,TCP/IP網(wǎng)絡(luò)通信研究具有十分重要的意義。
    發(fā)表于 03-29 10:58 ?4004次閱讀
    基于TCP/IP編程<b class='flag-5'>實(shí)現(xiàn)</b><b class='flag-5'>文件傳輸</b>的案例分析

    RayLink遠(yuǎn)控軟件又推出2個(gè)重磅寶藏功能免費(fèi)用

    RayLink遠(yuǎn)控時(shí)傳輸文件,一是可以選擇剪切板傳輸,只需通過鼠標(biāo)右鍵,選中要傳輸文件,選擇 “復(fù)制 ”或“Ctrl+C”,然后在遠(yuǎn)控界面
    的頭像 發(fā)表于 12-20 16:16 ?519次閱讀
    RayLink遠(yuǎn)控軟件又推出2個(gè)重磅寶藏功能免費(fèi)用

    FTP服務(wù)器搭建詳細(xì)步驟

    FTP服務(wù)器是一種用于文件傳輸的服務(wù)器,它可以讓用戶通過FTP客戶端上傳和下載文件。FTP服務(wù)器是一種非常實(shí)用的文件傳輸工具,可以方便地實(shí)現(xiàn)文件傳輸
    的頭像 發(fā)表于 04-12 14:39 ?3.2w次閱讀

    一般會(huì)如何實(shí)現(xiàn)文件傳輸?零拷貝如何提升文件傳輸性能?

    服務(wù)器提供文件傳輸功能,需要將磁盤上的文件讀取出來,通過網(wǎng)絡(luò)協(xié)議發(fā)送到客戶端。
    的頭像 發(fā)表于 07-26 14:43 ?851次閱讀
    一般會(huì)如何<b class='flag-5'>實(shí)現(xiàn)</b><b class='flag-5'>文件傳輸</b>?零拷貝如何提升<b class='flag-5'>文件傳輸</b>性能?

    中文應(yīng)用筆記《利用 MPLAB? Harmony v3 TCP/IP 協(xié)議棧在SAM E54 MCU上實(shí)現(xiàn)文件傳輸協(xié)議》

    利用 MPLAB Harmony v3 TCP/IP 協(xié)議棧 在SAM E54 MCU上 實(shí)現(xiàn)文件傳輸協(xié)議 簡介 文件傳輸協(xié)議(File Transfer Protocol,F(xiàn)TP)是TCP/IP
    的頭像 發(fā)表于 09-26 17:30 ?7599次閱讀
    中文應(yīng)用筆記《利用 MPLAB? Harmony v3 TCP/IP 協(xié)議棧在SAM E54 MCU上<b class='flag-5'>實(shí)現(xiàn)</b><b class='flag-5'>文件傳輸</b>協(xié)議》

    利用 MPLAB? Harmony v3 TCP/IP協(xié)議棧在SAM E54 MCU 上實(shí)現(xiàn)文件傳輸協(xié)議

    電子發(fā)燒友網(wǎng)站提供《利用 MPLAB? Harmony v3 TCP/IP協(xié)議棧在SAM E54 MCU 上實(shí)現(xiàn)文件傳輸協(xié)議.pdf》資料免費(fèi)下載
    發(fā)表于 12-18 11:03 ?0次下載
    利用 MPLAB? Harmony v3 TCP/IP協(xié)議棧在SAM E54 MCU 上<b class='flag-5'>實(shí)現(xiàn)</b><b class='flag-5'>文件傳輸</b>協(xié)議