在 Linux 內核版本 2.1 中,提供了一個專門發送文件的系統調用函數 sendfile(),函數形式如下:
它的前兩個參數分別是目的端和源端的文件描述符,后面兩個參數是源端的偏移量和復制數據的長度,返回值是實際復制數據的長度。
首先,它可以替代前面的 read() 和 write() 這兩個系統調用,這樣就可以減少一次系統調用,也就減少了 2 次上下文切換的開銷。
其次,該系統調用,可以直接把內核緩沖區里的數據拷貝到 socket 緩沖區里,不再拷貝到用戶態,這樣就只有 2 次上下文切換,和 3 次數據拷貝。如下圖:
但是這還不是真正的零拷貝技術,如果網卡支持 SG-DMA(The Scatter-Gather Direct Memory Access)技術(和普通的 DMA 有所不同),我們可以進一步減少通過 CPU 把內核緩沖區里的數據拷貝到 socket 緩沖區的過程。 你可以在你的 Linux 系統通過下面這個命令,查看網卡是否支持 scatter-gather 特性:
于是,從 Linux 內核 2.4 版本開始起,對于支持網卡支持 SG-DMA 技術的情況下, sendfile() 系統調用的過程發生了點變化,具體過程如下:
步,通過 DMA 將磁盤上的數據拷貝到內核緩沖區里;第二步,緩沖區描述符和數據長度傳到 socket 緩沖區,這樣網卡的 SG-DMA 控制器就可以直接將內核緩存中的數據拷貝到網卡的緩沖區里,此過程不需要將數據從操作系統內核緩沖區拷貝到 socket 緩沖區中,這樣就減少了一次數據拷貝;
所以,這個過程之中,只進行了 2 次數據拷貝,如下圖:
這就是所謂的零拷貝(Zero-copy)技術,因為我們沒有在內存層面去拷貝數據,也就是說全程沒有通過 CPU 來搬運數據,所有的數據都是通過 DMA 來進行傳輸的。
零拷貝技術的文件傳輸方式相比傳統文件傳輸的方式,減少了 2 次上下文切換和數據拷貝次數,只需要 2 次上下文切換和數據拷貝次數,就可以完成文件的傳輸,而且 2 次的數據拷貝過程,都不需要通過 CPU,2 次都是由 DMA 來搬運。