傳統的數據傳輸方式
很長一段時間內,數據拷貝的認識僅僅停留在應用程序層,實際上隱藏在背後的數據拷貝行為比想象的要多的多。在傳輸數據的時候,用戶應用程序需要分配一塊合適大小的緩沖區來存放需要傳輸的數據。用戶從應用程序中讀取數據,然後發送出去,只需要兩個系統調用read,write即可完成數據傳輸工作,應用程序並不知道這個數據傳輸過程中操作系統進行了多少次拷貝操作。某些情況下,這些數據拷貝操作會極大的降低數據傳輸的性能。(NIC,Network Interface Card )
傳統的數據拷貝方式,如下圖:
上線文切換,如圖:
涉及的步驟:
(1)read()調用引發從用戶模式到內核模式的上下文切換(第一次切換),在內部,發出sys_read(或者同等內容)從設備中讀取數據,直接內存讀取(direct memory access,DMA)執行了拷貝(第一次拷貝),它從磁盤中讀取內容,然後將他們存儲到一個內核地址空間緩沖區中;
(2)數據從讀緩沖區拷貝到用戶緩沖區(第二次拷貝),read()調用返回。該調用返回引發內核模式到用戶模式的切換(第二次切換)。現在數據被存儲在用戶空間緩沖區中;
(3) send()套接字調用引發用戶模式到內核模式的上下文切換(第三次切換),數據再次被放置到內核地址空間緩沖區中(第三次拷貝)。這次放置的緩沖區與目標套接字關聯;
(4) send()系統調用返回,從內核模式切換到用戶模式(第四次切換),DMA引擎將數據從內核緩沖區傳輸到協議引擎(第四次拷貝)。
DMA允許外圍設備和貯存之間直接傳輸IO數據,DMA依賴於系統。每一種體系結構DMA傳輸不同,編程接口也不同。數據傳輸可以以兩種方式觸發:一種所軟件請求數據,另一種所硬件異步傳輸。以read為例,它即采用第一種方式,其步驟如下:
(1) 進程調用read時,驅動程序分配一個DMA緩沖區,隨後指示硬件傳送它的數據,進程進入睡眠;
(2) 硬件將數據寫入DMA緩沖區並在完成時產生一個中斷;
(3) 中斷處理程序獲取輸入數據,應答中斷,最後喚醒進程,可以讀取數據了。
由此可見,在傳統的數據傳輸中,系統方面總共進行了4次數據拷貝,4次上線文切換,這些都會對服務器性能造成很大影響。
零拷貝概述
簡單的說,零拷貝是一種避免CPU將數據從一快存儲拷貝到另外一塊存儲的技術。零拷貝技術的目標:
避免數據拷貝
#避免操作系統內核緩沖區之間進行數據拷貝操作;
#避免操作系統內核和用戶應用程序地址空間之間進行數據拷貝操作;
#用戶應用程序可以避免操作系統直接訪問硬件存儲;
#數據傳輸盡量讓DMA來處理。
多種操作結合在一起
#避免不必要的系統調用和上下文切換;
#需要拷貝的數據可以先緩存起來;
#對數據進行的處理盡量讓硬件來做。
零拷貝的實現方式分類
直接IO
主要是通過減少操作系統內核緩沖區和應用程序地址空間數據拷貝次數,降低對文件讀取和寫入時帶來的CPU使用和帶寬的開銷。對於某些頁數的應用程序,比如說自緩沖應用程序來說,會是一個比較好的選擇。如果要傳輸的數據量大,使用直接IO的方式進行數據傳輸,而不需要操作系統內核地址空間拷貝數據的參與,這將會提高性能。
直接IO並不是所有的情況下都有效。設置直接IO的開銷非常大,而且不能利用緩存IO的優勢。直接IO的讀操作會造成磁盤的同步讀,執行進程需要在很長的時間才能執行完;而寫操作會導致應用程序關閉緩慢。應用程序使用直接IO進行數據傳輸通常和異步IO結合使用。
linux內核已經為快設備執行直接IO提供了支持,應用程序直接訪問文件而不經過操作系統頁高速緩沖存儲器的時候,打開文件(open() syscall)指定O_DIRECT標示符。
總之,這種數據傳輸方式,應用程序直接訪問硬件存儲,操作系統內核只是輔助數據傳輸;它一般用於操作系統不需要對數據進行處理的情況,數據可以再應用程序地址空間的緩沖區和磁盤之間進行傳輸,而不需要linux操作系統內核提供頁緩存支持。
針對數據傳輸不需要經過應用程序地址空間的零拷貝技術
數據傳輸過程中,避免數據在系統內核地址空間的緩沖區和用戶應用程序地址空間的緩沖區進行拷貝。有時候,應用程序在數據傳輸的過程中不需要對數據進行訪問,將數據從linux的頁緩存拷貝到用戶進程的緩沖區就可以完全避免,傳輸的數據在頁緩沖中就可以處理。在某些情況下,這種零拷貝技術能獲得很好的性能。linux下提供類似的系統調用主要有mmap(),sendfile(),splice().
使用mmap替代read,可以減少CPU拷貝次數。當應用程序調用mmap()之後,數據通過DMA拷貝拷貝到內核緩沖區,應用程序和操作系統共享這個緩沖區。這樣,操作系統內核和應用程序存儲空間不再需要進行任何的數據拷貝操作。當進行write()系統調用時,數據由內核緩沖區拷貝到socket緩沖區,再拷貝到協議引擎中。
這種也比較適用於傳送的數據不需要經過操作系統內核的處理或者不需要經過程序的處理直接傳輸的情況。結合socket也能使用mmap,不過只能在RAW的情況下使用。對於傳統的C/S網絡游戲結構來說,使用的意義不大。
對應用程序地址空間和內核空間的數據傳輸進行優化的零拷貝技術
對數據在linux頁緩存和用戶進程緩沖區之間的傳輸進行優化。該零拷貝技術側重於靈活的處理數據在用戶進程中的緩沖區和操作系統的頁緩沖區之間的拷貝操作。這種方式延續了傳統的通信方式,但是更加靈活。linux中該方法主要利用寫時復制技術。
寫時復制是計算機編程中常見的一種優化策略,基本思想是這樣的:如果多個應用程序需要同時訪問一塊數據,那麼可以為這些應用程序分配指向這塊數據的指針,在每個應用程序看來,他們都擁有這塊數據的一份拷貝,當其中一個應用程序需要對自己的這份數據進行修改時,就需要將數據真正的拷貝到應用程序的地址空間去。如果應用程序永遠不會對這塊數據進行修改,那麼就永遠不需要將數據拷貝到應用程序的地址空間去。在stl中string的實現類似這種策略。
參考:
linux 中的零拷貝技術 第1部分
http://www.ibm.com/developerworks/cn/linux/l-cn-zerocopy1/index.html
linux 中的零拷貝技術 第2部分
http://www.ibm.com/developerworks/cn/linux/l-cn-zerocopy2/index.html
linux系統內核空間與用戶空間通信的實現與分析
http://www.ibm.com/developerworks/cn/linux/l-netlink/
從linux內核訪問用戶空間內存
http://www.ibm.com/developerworks/cn/linux/l-kernel-memory-access/
linux中直接IO機制的介紹
http://www.ibm.com/developerworks/cn/linux/l-cn-directio/
通過零拷貝實現有效的數據傳輸
http://www.ibm.com/developerworks/cn/java/j-zerocopy/