Linux 中的零拷貝技術

參考內容:
Two new system calls: splice() and sync_file_range()
Linux 中的零拷貝技術1
Linux 中的零拷貝技術2
Zero Copy I: User-Mode Perspective
Linux man-pages splice()

此前在Nginx 文件操作優化中有提到零拷貝技術,它可以有效的改善數據傳輸的性能,但是由於存儲體系結構非常複雜,而且網絡協議棧有時需要對數據進行必要的處理,所以零拷貝技術有可能會產生很多負面影響,甚至會導致零拷貝技術自身的優點完全喪失。

零拷貝就是一種避免 CPU 將一塊存儲拷貝到另一塊存儲的技術。它可以減少數據拷貝和共享總線操作的次數,消除傳輸數據在存儲器之間不必要的中間拷貝次數,從而有效的提高數據傳輸效率,而且零拷貝技術也減少了內核態與用戶態之間切換所帶來的開銷。進行大量的數據拷貝操作是一件簡單的任務,從操作系統的角度來看,如果 CPU 一直被佔用着去執行這項簡單的任務,是極其浪費資源的。如果是高速網絡環境下,很可能就出現這樣的場景。

零拷貝技術分類

現在的零拷貝技術種類很多,也並沒有一個適合於所有場景的零拷貝零拷貝技術,概括起來總共有下面幾種:

  • 直接 I/O:對於這種數據傳輸方式來說,應用程序可以直接訪問硬件存儲,操作系統只是輔助數據傳輸,這類零拷貝技術可以讓數據在應用程序空間和磁盤之間直接傳輸,不需要操作系統提供的頁緩存支持。關於直接 I/O 可以參看Linux 中直接 I/O 機制的介紹

  • 避免數據在內核態與用戶態之間傳輸:在一些場景中,應用程序在數據進行傳輸的過程中不需要對數據進行訪問,那麼將數據從頁緩存拷貝到用戶進程的緩衝區是完全沒有必要的,Linux 中提供的類似系統調用主要有mmap()sendfile()splice()

  • 對數據在頁緩存和用戶進程之間的傳輸進行優化:這類零拷貝技術側重於靈活地處理數據在用戶進程的緩衝區和操作系統頁緩存之間的拷貝操作,此類方法延續了傳統的通信方式,但是更加靈活。在 Linux 中主要利用了「寫時複製」技術。

前兩類方法的目的主要是爲了避免在用戶態和內核態的緩衝區間拷貝數據,第三類方法則是對數據傳輸本身進行優化。我們知道硬件和軟件之間可以通過 DMA 來解放 CPU,但是在用戶空間和內核空間並沒有這種工具,所以此類方法主要是改善數據在用戶地址空間和操作系統內核地址空間之間傳遞的效率。

避免在內核與用戶空間拷貝

Linux 主要提供了mmap()sendfile()splice()三個系統調用來避免數據在內核空間與用戶空間進行不必要的拷貝,在Nginx 文件操作優化sendfile()已經做了比較詳細的介紹了,這裏就不再贅述了,下面主要介紹mmap()splice()

mmap()

當調用mmap()之後,數據會先通過 DMA 拷貝到操作系統的緩衝區,然後應用程序和操作系統共享這個緩衝區,這樣用戶空間與內核空間就不需要任何數據拷貝了,當大量數據需要傳輸的時候,這樣做就會有一個比較好的效率。

但是這種改進是需要代價的,當對文件進行了內存映射,然後調用write()系統調用,如果此時其它進程截斷了這個文件,那麼write()系統調用將會被總線錯誤信號SIGBUG中斷,因爲此時正在存儲的是一個錯誤的存儲訪問,這個信號將會導致進程被殺死。

一般可以通過文件租借鎖來解決這個問題,我們可以通過內核給文件加讀或者寫的租借鎖,當另外一個進程嘗試對用戶正在進行傳輸的文件進行截斷時,內核會給用戶發一個實時RT_SIGNAL_LEASE信號,這個信號會告訴用戶內核破壞了用戶加在那個文件上的寫或者讀租借鎖,write()系統調用就會被中斷,並且進程會被SIGBUS信號殺死。需要注意的是文件租借鎖需要在對文件進行內存映射之前設置。

splice()

sendfile()類似,splice()也需要兩個已經打開的文件描述符,並且其中的一個描述符必須是表示管道設備的描述符,它可以在操作系統地址空間中整塊地移動數據,從而減少大多數數據拷貝操作。適用於可以確定數據傳輸路徑的用戶應用程序,不需要利用用戶地址空間的緩衝區進行顯示的數據傳輸操作。

splice()不侷限於sendfile()的功能,也就是說sendfile()splice()的一個子集,在 Linux 2.6.23 中,sendfile()這種機制的實現已經沒有了,但是這個 API 以及相應的功能還存在,只不過內部已經使用了splice()這種機制來實現了。

寫時複製

在某些情況下,Linux 操作系統內核中的頁緩存可能會被多個應用程序所共享,操作系統有可能會將用戶應用程序地址空間緩衝區中的頁面映射到操作系統內核地址空間中去。如果某個應用程序想要對這共享的數據調用write()系統調用,那麼它就可能破壞內核緩衝區中的共享數據,傳統的write()系統調用並沒有提供任何顯示的加鎖操作,Linux 中引入了寫時複製這樣一種技術用來保護數據。

寫時複製的基本思想是如果有多個應用程序需要同時訪問同一塊數據,那麼可以爲這些應用程序分配指向這塊數據的指針,在每一個應用程序看來,它們都擁有這塊數據的一份數據拷貝,當其中一個應用程序需要對自己的這份數據拷貝進行修改的時候,就需要將數據真正地拷貝到該應用程序的地址空間中去,也就是說,該應用程序擁有了一份真正的私有數據拷貝,這樣做是爲了避免該應用程序對這塊數據做的更改被其他應用程序看到。這個過程對於應用程序來說是透明的,如果應用程序永遠不會對所訪問的這塊數據進行任何更改,那麼就永遠不需要將數據拷貝到應用程序自己的地址空間中去。這也是寫時複製的最主要的優點。

寫時複製的實現需要 MMU 的支持,MMU 需要知曉進程地址空間中哪些特殊的頁面是隻讀的,當需要往這些頁面中寫數據的時候,MMU 就會發出一個異常給操作系統內核,操作系統內核就會分配新的物理存儲空間,即將被寫入數據的頁面需要與新的物理存儲位置相對應。它最大好處就是可以節約內存,不過對於操作系統內核來說,寫時複製增加了其處理過程的複雜性。

發佈了73 篇原創文章 · 獲贊 69 · 訪問量 9萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章