在實際應用中,如果我們需要把磁盤中的某個文件內容發送到遠程服務器上,那么它必須要經過幾個拷貝的過程:
- 從磁盤中讀取目標文件內容拷貝到內核緩沖區
- CPU控制器再把內核緩沖區的數據復制到用戶空間的緩沖區
- 在應用程序中,調用
write()
方法,把用戶空間緩沖區中的數據拷貝到內核下的Socket Buffer中。 - 最后,把在內核模式下的Socket Buffer中的數據復制到網卡緩沖區(NIC Buffer),網卡緩沖區再把數據傳輸到目標服務器上。
在這個過程中可以發現,數據從磁盤到最終發送出去,要經歷4次拷貝,而在這四次拷貝過程中,有兩次拷貝是浪費的,分別是:從內核空間復制到用戶空間和從用戶空間再次復制到內核空間。除此之外,由于用戶空間和內核空間的切換會帶來CPU的上下文切換,對于CPU性能也會造成性能影響。
零拷貝
,就是把這兩次多余的拷貝省略掉,應用程序可以直接把磁盤中的數據從內核中直接傳輸給Socket,而不需要再經過應用程序所在的用戶空間。零拷貝通過DMA(Direct Memory Access)
技術把文件內容復制到內核空間中的Read Buffer,接著把包含數據位置和長度信息的文件描述符加載到Socket Buffer中,DMA引擎直接可以把數據從內核空間中傳遞給網卡設備。在這個流程中,數據只經歷了兩次拷貝就發送到了網卡中,并且減少了兩次CPU的上下文切換,對于效率有非常大的提高。
所謂零拷貝,并不是完全沒有數據復制,只是相對于用戶空間來說,不再需要進行數據拷貝。對于前面說的整個流程來說,零拷貝只是減少了不必要的拷貝次數而已。在程序中實現零拷貝的方法有:
- 在Linux中,零拷貝技術依賴于底層的sendfile()方法實現
- 在Java中,FileChannel.transferTo()方法的底層實現就是sendfile()方法
- mmap的文件映射機制,將磁盤文件映射到內存,用戶通過修改內存就能修改磁盤文件,使用這種方式可以獲取很大的I/O提升,省去了用戶空間到內核空間復制的開銷。