零拷贝机制在文件传输中的使用手法
文章目錄
- 文件傳輸(讀取與發(fā)送)中的拷貝與上下文切換
- 零拷貝技術(shù)
- sendfile
- sendfile + SG-DMA
- mmap + write
- splice
- Direct I/O
- 經(jīng)典應(yīng)用
文件傳輸(讀取與發(fā)送)中的拷貝與上下文切換
如果服務(wù)端要提供文件傳輸?shù)墓δ?#xff0c;最簡單的方式是:
1、將磁盤上的文件讀取出來
2、通過網(wǎng)絡(luò)協(xié)議將內(nèi)容發(fā)送給客戶端
傳統(tǒng)IO的工作方式是,數(shù)據(jù)讀取和寫入從用戶空間到內(nèi)核空間來回賦值,內(nèi)核空間數(shù)據(jù)通過IO接口從磁盤讀取/寫入。
就如同下面這兩個api的使用:
這個場景下會發(fā)生4次數(shù)據(jù)拷貝+4次上下文切換:
read系統(tǒng)調(diào)用,從用戶態(tài)到內(nèi)核態(tài) 切換 ,CPU從磁盤 拷貝 數(shù)據(jù)到內(nèi)核pagecache。
read返回,從內(nèi)核態(tài) 切換 到用戶態(tài),CPU從pagecache 拷貝 數(shù)據(jù)到用戶緩沖區(qū)。
send,可以看作write。
write系統(tǒng)調(diào)用,從用戶態(tài)到內(nèi)核態(tài)切換,CPU從用戶緩沖區(qū)拷貝數(shù)據(jù)到內(nèi)核socket緩沖區(qū)
然后CPU從內(nèi)核socket緩沖區(qū)拷貝數(shù)據(jù)到網(wǎng)卡上
最后write返回,從內(nèi)核態(tài) 切換 到用戶態(tài)。
當然可以使用DMA技術(shù),替代CPU在IO外設(shè)與內(nèi)核緩沖區(qū)之間的拷貝。因為DMA僅僅只能用于設(shè)備之間交換數(shù)據(jù)時的數(shù)據(jù)拷貝,內(nèi)存之間的數(shù)據(jù)拷貝用不了DMA。
這樣優(yōu)化下來會發(fā)生2次CPU數(shù)據(jù)拷貝+2次DMA數(shù)據(jù)拷貝+4次上下文切換,接下來的講解都是基于這個成本來的。
想要提高性能就需要減少上下文切換和CPU拷貝的次數(shù)。
零拷貝技術(shù)
零拷貝是一種高效的數(shù)據(jù)傳輸機制,在追求低延遲的傳輸場景中經(jīng)常使用,具體思想是計算機執(zhí)行操作時,CPU不需要將數(shù)據(jù)從某處內(nèi)存復制到另外一個特定區(qū)域。
現(xiàn)存的比較常用的零拷貝方法有下面幾個:
- sendfile
- mmap + write
- splice
- Direct I/O
不同的技術(shù)使用的場景也是不同的,使用時請結(jié)合業(yè)務(wù)邏輯。
sendfile
應(yīng)用場景:用戶從磁盤讀取文件數(shù)據(jù)后不需要經(jīng)過CPU計算/處理就直接通過網(wǎng)絡(luò)傳輸出去
典型應(yīng)用:MQ
Linux版本:2.1
我們只需要傳遞文件描述符就可以代替數(shù)據(jù)的拷貝了,直接替代read+write操作。sendfile一次系統(tǒng)調(diào)用就相當于之前的兩次系統(tǒng)調(diào)用。這是因為page cache和socket buffer均在內(nèi)核空間,sendfile直接把內(nèi)核緩沖區(qū)數(shù)據(jù)拷貝到socket緩沖區(qū)上了,直接省略掉用戶態(tài)。
成本:1次系統(tǒng)調(diào)用,2次上下文切換,1次CPU數(shù)據(jù)拷貝,2次DMA數(shù)據(jù)拷貝
sendfile + SG-DMA
Linux版本:2.4
如果網(wǎng)卡支持SG-DMA(The Scatter-Gather Direct Memory Access)技術(shù),可以直接將內(nèi)核態(tài)緩沖區(qū)數(shù)據(jù)直接SG-DMA到網(wǎng)卡上,省略了內(nèi)核態(tài)緩沖區(qū)->socket緩沖區(qū)->網(wǎng)卡的步驟。
成本:1次系統(tǒng)調(diào)用,2次上下文切換,1次DMA數(shù)據(jù)拷貝,1次SG-DMA數(shù)據(jù)拷貝
這就是真正的zero-copy,完全沒有通過內(nèi)存層面去拷貝數(shù)據(jù),全程使用DMA傳輸。
局限性:當然sendfile也是有局限性的,它直接隔離了應(yīng)用程序?qū)?shù)據(jù)操作,如果需要從數(shù)據(jù)中提取統(tǒng)計信息或者進行加解密,sendfile根本使用不了。
mmap + write
mmap:memory map,一種內(nèi)存映射文件的方法。即將一個文件或者其他對象映射到進程的地址空間,實現(xiàn)文件磁盤地址和進程虛擬地址空間中一段虛擬地址直接對映。這樣進程就可以采用指針的方式直接讀寫操作這一塊內(nèi)存,系統(tǒng)自動回寫臟頁到對應(yīng)的文件磁盤上。這樣對文件操作就不需要調(diào)用read+write了。并且內(nèi)核空間對這段區(qū)域的修改也直接反映在了用戶空間,從而實現(xiàn)不同進程間的文件共享。
mmap技術(shù)特點如下:
1、用戶空間的mmap file使用虛擬內(nèi)存,實際上不占有物理內(nèi)存,只有內(nèi)核空間的kernel buffer cache才占據(jù)實際物理內(nèi)存
2、mmap需要配合write
3、mmap僅僅避免內(nèi)核空間到用戶空間的CPU數(shù)據(jù)包被,但是內(nèi)核空間內(nèi)部還是需要CPU負責數(shù)據(jù)拷貝
使用mmap流程如下:
1、用戶調(diào)用mmap,從用戶態(tài)切換到內(nèi)核態(tài),將內(nèi)核緩沖區(qū)映射到用戶緩存區(qū)
2、DMA控制器將數(shù)據(jù)從磁盤拷貝到內(nèi)核緩沖區(qū)
3、mmap返回,從內(nèi)核態(tài)切換到用戶態(tài)
4、用戶進程調(diào)用write,嘗試把文件數(shù)據(jù)寫到內(nèi)核socket buffer中,從用戶態(tài)切換到內(nèi)核態(tài)
5、CPU將內(nèi)核緩沖區(qū)數(shù)據(jù)拷貝到socket buffer
6、DMA控制器將數(shù)據(jù)從socket buffer拷貝到網(wǎng)卡
7、write返回
成本:2次系統(tǒng)調(diào)用、4次上下文切換、1次CPU數(shù)據(jù)拷貝、2次DMA數(shù)據(jù)拷貝
應(yīng)用場景
1、多個線程以只讀方式同時訪問一個文件,mmap機制下的多線程共享同一個物理內(nèi)存空間,節(jié)約了內(nèi)存。
例子:多個進程可以依賴于同一個動態(tài)鏈接庫,利用mmap可以實現(xiàn)內(nèi)存僅僅加載一份動態(tài)鏈接庫,多個進程共享此庫
2、mmap可用于進程間通信,對于同一個文件對應(yīng)的mmap分配的物理內(nèi)存天然多線程共享,可以依賴于操作系統(tǒng)的同步原語
3、mmap比sendfile多了一次CPU參與的內(nèi)存拷貝,但是用戶空間與內(nèi)核空間之間不需要數(shù)據(jù)拷貝,所以效率也很高
splice
Linux版本:2.6.17
#include <fcntl.h> ssize_t splice(int fd_in, loff_t *off_in, int fd_out, loff_t *off_out, size_t len, unsigned int flags);splice用于在兩個文件描述符之間移動數(shù)據(jù), 也是零拷貝。
fd_in參數(shù)是待輸入描述符。如果它是一個管道文件描述符,則off_in必須設(shè)置為NULL;否則off_in表示從輸入數(shù)據(jù)流的何處開始讀取,此時若為NULL,則從輸入數(shù)據(jù)流的當前偏移位置讀入。
fd_out/off_out與上述相同,不過是用于輸出。
len參數(shù)指定移動數(shù)據(jù)的長度。
flags參數(shù)則控制數(shù)據(jù)如何移動:
SPLICE_F_NONBLOCK:splice 操作不會被阻塞。然而,如果文件描述符沒有被設(shè)置為不可被阻塞方式的 I/O ,那么調(diào)用 splice 有可能仍然被阻塞。
SPLICE_F_MORE:告知操作系統(tǒng)內(nèi)核下一個 splice 系統(tǒng)調(diào)用將會有更多的數(shù)據(jù)傳來。
SPLICE_F_MOVE:如果輸出是文件,這個值則會使得操作系統(tǒng)內(nèi)核嘗試從輸入管道緩沖區(qū)直接將數(shù)據(jù)讀入到輸出地址空間,這個數(shù)據(jù)傳輸過程沒有任何數(shù)據(jù)拷貝操作發(fā)生。
2. 使用splice時, fd_in和fd_out中必須至少有一個是管道文件描述符。
調(diào)用成功時返回移動的字節(jié)數(shù)量;它可能返回0,表示沒有數(shù)據(jù)需要移動,這通常發(fā)生在從管道中讀數(shù)據(jù)時而該管道沒有被寫入的時候。
失敗時返回-1,并設(shè)置errno
splice系統(tǒng)調(diào)用直接在內(nèi)核空間的read buffer 和socket buffer之間建立了管道,避免了用戶緩沖區(qū)和socket buffer之間的CPU拷貝
成本:1次splice系統(tǒng)調(diào)用、1次pipe調(diào)用、2次上下文切換、2次DMA數(shù)據(jù)拷貝
局限性:
1、用戶程序不能對數(shù)據(jù)進行操作,與sendfile類似
2、Linux管道緩沖機制,可以用于任意兩個文件描述符中傳輸數(shù)據(jù),但是其中一個必須是管道設(shè)備
Direct I/O
緩存文件I/O:用戶空間要讀取一個文件并不是直接與磁盤進行交互看,而是中間夾了一層緩存,即page cache
直接文件I/O:用戶空間讀取文件直接與磁盤交互,數(shù)據(jù)直接存儲在用戶空間中,沒有中間page cache曾,繞過了內(nèi)核。
部分操作系統(tǒng)中,在直接文件I/O模式下,write雖然能夠保證文件數(shù)據(jù)落盤,但是文件元數(shù)據(jù)不一定落盤,所以還需要執(zhí)行一次fsync操作。
局限性:
1、設(shè)備之間數(shù)據(jù)傳輸通過DMA,所以用戶空間的數(shù)據(jù)緩沖區(qū)內(nèi)存頁必須進行頁鎖定,這是為了防止其物理頁地址被交換到磁盤或者被移動到新的地址導致DMA去拷貝數(shù)據(jù)時在指定地址找不到內(nèi)存頁從而引發(fā)缺頁異常,而頁鎖定的開銷也不小,所以應(yīng)用程序必須分配和注冊一個持久的內(nèi)存池,用戶數(shù)據(jù)緩沖。(應(yīng)用程序手動做緩存池)
2、如果在應(yīng)用程序的緩存中沒有找到,那么就直接從磁盤加載,十分緩慢
3、應(yīng)用層引入緩存管理以及底層硬件管理(頁鎖定),很麻煩
經(jīng)典應(yīng)用
在之前的筆記中有談到kafka高性能的原因之一就是使用了zero-copy:消息隊列重要機制講解以及MQ設(shè)計思路(kafka、rabbitmq、rocketmq,這里稍微拓展一下:
生產(chǎn)者發(fā)消息給kafka,kafka將消息持久化落盤。
消費者從kafka拉取消息,kafka從磁盤讀取一批數(shù)據(jù),通過網(wǎng)卡發(fā)送。
接收消息持久化的時候使用到了mmap機制,對接收的數(shù)據(jù)持久化。發(fā)送消息的時候使用sendfile從持久化介質(zhì)中讀取數(shù)據(jù)然后對外發(fā)送。
sendfile避免了內(nèi)核空間到用戶空間的CPU數(shù)據(jù)拷貝,同時sendfile基于page cache實現(xiàn),如果有多個消費者同時消費一個topic消息,消息會在page cache上緩存,就只需要一次磁盤IO了。
所以我們應(yīng)該熟悉掌握sendfile 和 mmap
總結(jié)
以上是生活随笔為你收集整理的零拷贝机制在文件传输中的使用手法的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: dnf男弹药魔法流加点 配上图谢谢
- 下一篇: “君居帝京内”下一句是什么