零拷貝sendfile解析

傳統方式read/write send/recv

在傳統的文件傳輸裏面(read/write方式),在實現上事實上是比較複雜的,需要通過屢次上下文的切換。咱們看一下例如如下兩行代碼:   html

1. read(file, tmp_buf, len);       linux

2. write(socket, tmp_buf, len);  nginx

 以上兩行代碼是傳統的read/write方式進行文件到socket的傳輸。web

當需要對一個文件進行傳輸的時候,其詳細流程細節例如如下:緩存

一、調用read函數,文件數據被copy到內核緩衝區服務器

二、read函數返回。文件數據從內核緩衝區copy到用戶緩衝區網絡

三、write函數調用。將文件數據從用戶緩衝區copy到內核與socket相關的緩衝區。socket

四、數據從socket緩衝區copy到相關協議引擎。函數

以上細節是傳統read/write方式進行網絡文件傳輸的方式,咱們可以看到,在這個過程其中。文件數據其實是通過了四次copy操做:性能

硬盤—>內核buf—>用戶buf—>socket相關緩衝區(內核)—>協議引擎

新方式sendfile

而sendfile系統調用則提供了一種下降以上屢次copy。提高文件傳輸性能的方法。

Sendfile系統調用是在2.1版本號內核時引進的:

1. sendfile(socket, file, len);   

執行流程例如如下:

一、sendfile系統調用,文件數據被copy至內核緩衝區

二、再從內核緩衝區copy至內核中socket相關的緩衝區

三、最後再socket相關的緩衝區copy到協議引擎

相較傳統read/write方式,2.1版本號內核引進的sendfile已經下降了內核緩衝區到user緩衝區。再由user緩衝區到socket相關 緩衝區的文件copy,而在內核版本號2.4以後,文件描寫敘述符結果被改變,sendfile實現了更簡單的方式,系統調用方式仍然同樣,細節與2.1版本號的 不一樣之處在於,當文件數據被拷貝到內核緩衝區時,再也不將所有數據copy到socket相關的緩衝區,而是隻將記錄數據位置和長度相關的數據保存到 socket相關的緩存,而實際數據將由DMA模塊直接發送到協議引擎,再次下降了一次copy操做。

 

1、典型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資源。

 

2、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等。

3、Java NIO中的transferTo()
Java NIO中
FileChannel.transferTo(long position, long count, WriteableByteChannel target)
方法將當前通道中的數據傳送到目標通道target中,在支持Zero-Copy的linux系統中,transferTo()的實現依賴於sendfile()調用。

 

4、參考文檔
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

相關文章
相關標籤/搜索