我們平時通過網絡發送文件時會用到的兩個系統調用:
read(file, tmp_buf, len);
write(socket, tmp_buf, len);
調用過程示意圖如下:

在用戶空間調用 read() 讀取文件時發生兩次內存拷貝:
- DMA引擎將文件讀取到內核的文件緩沖區
- 調用返回用戶空間時將內核的文件緩沖區的數據復制到用戶空間的緩沖區
接著調用 write() 把數據寫入 socket 時,又發生了兩次內存拷貝:
- 將用戶空間的緩沖區的數據復制到內核的 socket 緩沖區
- 將內核 socket 緩沖區的數據復制到網絡協議引擎
也就是說,在整個文件發送的過程中,發生了四次內存拷貝。
然后,數據讀取到用戶空間后并沒有做過任何加工處理,因此通過網絡發送文件時,根本沒有必要把文件內容復制到用戶空間。
于是引入了 mmap():
tmp_buf = mmap(file, len);
write(socket, tmp_buf, len);
調用過程示意圖:

- 調用 mmap() 時會將文件直接讀取到內核緩沖區,并把內核緩沖區直接共享到用戶空間
- 調用 write() 時,直接將內核緩沖區的數據復制到網絡協議引擎
這樣一來,就少了用戶空間和內核空間之間的內存復制了。
這種方式會有個問題,當前進程在調用 write() 時,另一個進程把文件清空了,程序就會報出 SIGBUS 類型錯誤。
Linux Kernel 2.1 引進了 sendfile(),只需要一個系統調用來實現文件發送。
sendfile(socket, file, len);
調用過程示意圖:

- 調用 sendfile() 時會直接在內核空間把文件讀取到內核的文件緩沖區
- 將內核的文件緩沖區的數據復制到內核的 socket 緩沖區中
- 將內核的 socket 緩沖區的數據復制到網絡協議引擎
從性能上看,這種方式只是少了一個系統調用而已,還是做了3次拷貝操作。
Linux Kernel 2.4 改進了 sendfile(),調用接口沒有變化:
sendfile(socket, file, len);
調用過程示意圖:

- 調用 sendfile() 時會直接在內核空間把文件讀取到內核的文件緩沖區
- 內核的 socket 緩沖區中保存的是當前要發送的數據在內核的文件緩沖區中的位置和偏移量
- DMA gather copy 將內核的文件緩沖區的數據復制到網絡協議引擎
這樣就只剩下2次拷貝啦。
在許多 http server 中,都引入了 sendfile 的機制,如 nginx、lighttpd 等,它們正是利用 sendfile() 這個特性來實現高性能的文件發送的。
posted on 2011-12-29 21:32
Derek.Guo 閱讀(702)
評論(0) 編輯 收藏 所屬分類:
Linux/Unix