一、典型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