零拷貝sendfile解析
傳統方式read/write send/recv
在傳統的文件傳輸里面(read/write方式),在實現上事實上是比較復雜的,須要經過多次上下文的切換。我們看一下例如以下兩行代碼:
1. read(file, tmp_buf, len);
2. write(socket, tmp_buf, len);
以上兩行代碼是傳統的read/write方式進行文件到socket的傳輸。
當須要對一個文件進行傳輸的時候,其詳細流程細節例如以下:
1、調用read函數,文件數據被copy到內核緩沖區
2、read函數返回。文件數據從內核緩沖區copy到用戶緩沖區
3、write函數調用。將文件數據從用戶緩沖區copy到內核與socket相關的緩沖區。
4、數據從socket緩沖區copy到相關協議引擎。
以上細節是傳統read/write方式進行網絡文件傳輸的方式,我們能夠看到,在這個過程其中。文件數據實際上是經過了四次copy操作:
硬盤—>內核buf—>用戶buf—>socket相關緩沖區(內核)—>協議引擎
新方式sendfile
而sendfile系統調用則提供了一種降低以上多次copy。提升文件傳輸性能的方法。
Sendfile系統調用是在2.1版本號內核時引進的:
1. sendfile(socket, file, len);
執行流程例如以下:
1、sendfile系統調用,文件數據被copy至內核緩沖區
2、再從內核緩沖區copy至內核中socket相關的緩沖區
3、最后再socket相關的緩沖區copy到協議引擎
相較傳統read/write方式,2.1版本號內核引進的sendfile已經降低了內核緩沖區到user緩沖區。再由user緩沖區到socket相關 緩沖區的文件copy,而在內核版本號2.4之后,文件描寫敘述符結果被改變,sendfile實現了更簡單的方式,系統調用方式仍然一樣,細節與2.1版本號的 不同之處在于,當文件數據被拷貝到內核緩沖區時,不再將全部數據copy到socket相關的緩沖區,而是只將記錄數據位置和長度相關的數據保存到 socket相關的緩存,而實際數據將由DMA模塊直接發送到協議引擎,再次降低了一次copy操作。
一、典型IO調用的問題
一個典型的web服務器傳送靜態文件(如CSS,JS,圖片等)的過程如下:
read(file, tmp_buf, len);
write(socket, tmp_buf, len);
首先調用read將文件從磁盤讀取到tmp_buf,然后調用write將tmp_buf寫入到socket,在這過程中會出現四次數據copy,過程如圖1所示

圖1
1。當調用read系統調用時,通過DMA(Direct Memory Access)將數據copy到內核模式
2。然后由CPU控制將內核模式數據copy到用戶模式下的 buffer中
3。read調用完成后,write調用首先將用戶模式下 buffer中的數據copy到內核模式下的socket buffer中
4。最后通過DMA copy將內核模式下的socket buffer中的數據copy到網卡設備中傳送。
從上面的過程可以看出,數據白白從內核模式到用戶模式走了一 圈,浪費了兩次copy,而這兩次copy都是CPU copy,即占用CPU資源。
二、Zero-Copy&Sendfile()
Linux 2.1版本內核引入了sendfile函數,用于將文件通過socket傳送。
sendfile(socket, file, len);
該函數通過一次系統調用完成了文件的傳送,減少了原來 read/write方式的模式切換。此外更是減少了數據的copy,sendfile的詳細過程圖2所示:

圖2
通過sendfile傳送文件只需要一次系統調用,當調用 sendfile時:
1。首先通過DMA copy將數據從磁盤讀取到kernel buffer中
2。然后通過CPU copy將數據從kernel buffer copy到sokcet buffer中
3。最終通過DMA copy將socket buffer中數據copy到網卡buffer中發送
sendfile與read/write方式相比,少了 一次模式切換一次CPU copy。但是從上述過程中也可以發現從kernel buffer中將數據copy到socket buffer是沒必要的。
為此,Linux2.4內核對sendfile做了改進,如圖3所示

圖3
改進后的處理過程如下:
1。DMA copy將磁盤數據copy到kernel buffer中
2。向socket buffer中追加當前要發送的數據在kernel buffer中的位置和偏移量
3。DMA gather copy根據socket buffer中的位置和偏移量直接將kernel buffer中的數據copy到網卡上。
經過上述過程,數據只經過了2次copy就從磁盤傳送出去了。
(可能有人要糾結“不是說Zero-Copy么?怎么還有兩次copy啊”,事實上這個Zero copy是針對內核來講的,數據在內核模式下是Zero-copy的。話說回來,文件本身在瓷盤上要真是完全Zero-copy就能傳送,那才見鬼了 呢)。
當前許多高性能http server都引入了sendfile機制,如nginx,lighttpd等。
三、Java NIO中的transferTo()
Java NIO中
FileChannel.transferTo(long position, long count, WriteableByteChannel target)
方法將當前通道中的數據傳送到目標通道target中,在支持Zero-Copy的linux系統中,transferTo()的實現依賴于sendfile()調用。
四、參考文檔
《Zero Copy I: User-Mode Perspective》http://www.linuxjournal.com/article/6345?page=0,0
《Efficient data transfer through zero copy》http://www.ibm.com/developerworks/linux/library/j-zerocopy
《The C10K problem》http://www.kegel.com/c10k.html

浙公網安備 33010602011771號