Linux网络编程 | 零拷贝 :sendfile、mmap、splice、tee
文章目錄
- 傳統(tǒng)文件傳輸?shù)膯栴}
- Linux中實現(xiàn)零拷貝的方法
傳統(tǒng)文件傳輸?shù)膯栴}
在網(wǎng)絡編程中,如果我們想要提供文件傳輸?shù)墓δ?#xff0c;最簡單的方法就是用read將數(shù)據(jù)從磁盤上的文件中讀取出來,再將其用write寫入到socket中,通過網(wǎng)絡協(xié)議發(fā)送給客戶端。
ssize_t read(int fd, void *buf, size_t count); ssize_t write(int fd, const void *buf, size_t count);但是就是這兩個簡單的操作,卻帶來了大量的性能丟失
例如我們的服務器需要為客戶端提供一個下載操作,此時的操作如下
從上圖可以看出,雖然僅僅只有這兩行代碼,但是卻在發(fā)生了四次用戶態(tài)和內(nèi)核態(tài)的上下文切換,以及四次數(shù)據(jù)拷貝,也就是在這個地方產(chǎn)生了大量不必要的損耗。
那么為什么會發(fā)生這些操作呢?
上下文切換
由于read和recv是系統(tǒng)調(diào)用,所以每次調(diào)用該函數(shù)我們都需要從用戶態(tài)切換至內(nèi)核態(tài),等待內(nèi)核完成任務后再從內(nèi)核態(tài)切換回用戶態(tài)。
數(shù)據(jù)拷貝
上面也說了,由于數(shù)據(jù)的讀取與寫入都是由系統(tǒng)進行的,那么我們就得將數(shù)據(jù)從用戶的緩沖區(qū)中拷貝到內(nèi)核,
- 第一次拷貝:將磁盤中的數(shù)據(jù)拷貝到內(nèi)核的緩沖區(qū)中
- 第二次拷貝:內(nèi)核將數(shù)據(jù)處理完,接著拷貝到用戶緩沖區(qū)中
- 第三次拷貝:此時需要通過socket將數(shù)據(jù)發(fā)送出去,將用戶緩沖區(qū)中的數(shù)據(jù)拷貝至內(nèi)核中socket的緩沖區(qū)中
- 第四次拷貝:把內(nèi)核中socket緩沖區(qū)的數(shù)據(jù)拷貝到網(wǎng)卡的緩沖區(qū)中,通過網(wǎng)卡將數(shù)據(jù)發(fā)送出去。
所以要想優(yōu)化傳輸性能,就要從減少數(shù)據(jù)拷貝和用戶態(tài)內(nèi)核態(tài)的上下文切換下手,這也就是零拷貝技術(shù)的由來。
什么是零拷貝呢?
零拷貝的主要任務就是避免CPU將數(shù)據(jù)從一塊存儲中拷貝到另一塊存儲,主要就是利用各種技術(shù),避免讓CPU做大量的數(shù)據(jù)拷貝任務,以此減少不必要的拷貝。或者借助其他的一些組件來完成簡單的數(shù)據(jù)傳輸任務,讓CPU解脫出來專注別的任務,使得系統(tǒng)資源的利用更加有效
Linux中實現(xiàn)零拷貝的方法
Linux中實現(xiàn)零拷貝的方法主要有以下幾種,下面一一對其進行介紹
sendfile
sendfile函數(shù)的作用是直接在兩個文件描述符之間傳遞數(shù)據(jù)。由于整個操作完全在內(nèi)核中(直接從內(nèi)核緩沖區(qū)拷貝到socket緩沖區(qū)),從而避免了內(nèi)核緩沖區(qū)和用戶緩沖區(qū)之間的數(shù)據(jù)拷貝。
需要注意的是,in_fd必須是一個支持類似mmap函數(shù)的文件描述符,不能是socket或者管道,而out_fd必須是一個socket,由此可見sendfile是專門為了在網(wǎng)絡上傳輸文件而實現(xiàn)的函數(shù)。
#include <sys/sendfile.h> ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count);參數(shù):
out_fd : 待寫入內(nèi)容的文件描述符
in_fd : 待讀出內(nèi)容的文件描述符
offset : 文件的偏移量
count : 需要傳輸?shù)淖止?jié)數(shù)
返回值:
成功:返回傳輸?shù)淖止?jié)數(shù)
失敗:返回-1并設置errno
mmap
mmap用于申請一段內(nèi)存空間,也就是我們在進程間通信中提到過的共享內(nèi)存,通過將內(nèi)核緩沖區(qū)的數(shù)據(jù)映射到用戶空間中,兩者通過共享緩沖區(qū)直接訪問統(tǒng)一資源,此時內(nèi)核與用戶空間就不需要再進行任何的數(shù)據(jù)拷貝操作了
其中mmap用于申請空間,額munmap用于釋放這段空間。
參數(shù):
addr : 內(nèi)存的起始地址,如果設置為空則系統(tǒng)會自動分配
length : 指定內(nèi)存段的長度
prot : 內(nèi)存段的訪問權(quán)限,通過按位與或可以取以下幾種值
flag : 選項
fd : 被映射文件對應的文件描述符
offset : 文件的偏移量
返回值:
成功:成功時返回指向內(nèi)存區(qū)域的指針
失敗:返回MAP_FAILED并設置errno
splice
splice函數(shù)用于在兩個文件描述符之間移動數(shù)據(jù),而不需要數(shù)據(jù)在內(nèi)核空間和用戶空間中來回拷貝
需要注意的是,使用splice函數(shù)時fd_in和fd_out至少有一個是管道文件描述符,即
#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);參數(shù):
out_fd : 待寫入內(nèi)容的文件描述符
off_out : 待寫入文件描述符的偏移量,如果文件描述符為管道則必須為空
in_fd : 待讀出內(nèi)容的文件描述符
off_in : 待讀出文件描述符的偏移量,如果文件描述符為管道則必須為空
len : 需要復制的字節(jié)數(shù)
flags : 選項
返回值:
成功:返回在兩個文件描述符之間復制的字節(jié)數(shù)
沒有數(shù)據(jù):返回0
失敗:返回-1并設置errno
可能產(chǎn)生的errno
tee
tee函數(shù)用于在兩個管道文件描述符之間復制數(shù)據(jù),并且它是直接復制,不會將數(shù)據(jù)讀出,所以源文件上的數(shù)據(jù)仍可以用于后面的讀操作
參數(shù):
out_fd : 待寫入內(nèi)容的文件描述符
in_fd : 待讀出內(nèi)容的文件描述符
len : 需要復制的字節(jié)數(shù)
flags : 選項
返回值:
成功:返回在兩個文件描述符之間復制的字節(jié)數(shù)
沒有數(shù)據(jù):返回0
失敗:返回-1并設置errno
超強干貨來襲 云風專訪:近40年碼齡,通宵達旦的技術(shù)人生
總結(jié)
以上是生活随笔為你收集整理的Linux网络编程 | 零拷贝 :sendfile、mmap、splice、tee的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Linux网络编程 | 高性能定时器 :
- 下一篇: linux 其他常用命令