1 把磁盤文件讀入內核緩衝區
2 從內核緩衝區讀到內存
3 處理(靜態資源不需處理)
4 發送到網卡的內核緩衝區(發送緩存)
5 網卡發送數據
數據從第一步中的內核緩衝區到第四步的內核緩衝區白白繞了一個圈,沒有任何變化浪費了時間
而sendfile系統調用就是來解決這個問題的。sendfile省略了上面的 2、3步,磁盤文件被直接發送到了網卡的內存緩衝區,減少了數據複製和內核態切換的開銷 。
現在流行的 web 服務器裏面都提供 sendfile 選項用來提高服務器性能,那到底 sendfile 是什麼,怎麼影響性能的呢?sendfile 實際上是 Linux 2.0+ 以後的推出的一個系統調用,web 服務器可以通過調整自身的配置來決定是否利用 sendfile 這個系統調用。先來看一下不用 sendfile 的傳統網絡傳輸過程:
read(file, tmp_buf, len);
write(socket, tmp_buf, len);硬盤 >> kernel buffer >> user buffer >> kernel socket buffer >> 協議棧
一般來說一個網絡應用是通過讀硬盤數據,然後寫數據到 socket 來完成網絡傳輸的。上面2行用代碼解釋了這一點,不過上面2行簡單的代碼掩蓋了底層的很多操作。來看看底層是怎麼執行上面2行代碼的:
1、系統調用 read() 產生一個上下文切換:從 user mode 切換到 kernel mode,然後 DMA 執行拷貝,把文件數據從硬盤讀到一個 kernel buffer 裏。
2、數據從 kernel buffer 拷貝到 user buffer,然後系統調用 read() 返回,這時又產生一個上下文切換:從kernel mode 切換到 user mode。
3、系統調用 write() 產生一個上下文切換:從 user mode 切換到 kernel mode,然後把步驟2讀到 user buffer 的數據拷貝到 kernel buffer(數據第2次拷貝到 kernel buffer),不過這次是個不同的 kernel buffer,這個 buffer 和 socket 相關聯。
4、系統調用 write() 返回,產生一個上下文切換:從 kernel mode 切換到 user mode(第4次切換了),然後 DMA 從 kernel buffer 拷貝數據到協議棧(第4次拷貝了)。
上面4個步驟有4次上下文切換,有4次拷貝,我們發現如果能減少切換次數和拷貝次數將會有效提升性能。在kernel 2.0+ 版本中,系統調用 sendfile() 就是用來簡化上面步驟提升性能的。sendfile() 不但能減少切換次數而且還能減少拷貝次數。
再來看一下用 sendfile() 來進行網絡傳輸的過程:
sendfile(socket, file, len);
硬盤 >> kernel buffer (快速拷貝到kernel socket buffer) >> 協議棧
1、系統調用 sendfile() 通過 DMA 把硬盤數據拷貝到 kernel buffer,然後數據被 kernel 直接拷貝到另外一個與 socket 相關的 kernel buffer。這裏沒有 user mode 和 kernel mode 之間的切換,在 kernel 中直接完成了從一個 buffer 到另一個 buffer 的拷貝。
2、DMA 把數據從 kernel buffer 直接拷貝給協議棧,沒有切換,也不需要數據從 user mode 拷貝到 kernel mode,因爲數據就在 kernel 裏。
步驟減少了,切換減少了,拷貝減少了,自然性能就提升了。這就是爲什麼說在 Nginx 配置文件裏打開 sendfile on 選項能提高 web serve r性能的原因。