1. 程式人生 > >【基礎知識思考整理 】Zero-copy原理理解(使用者角度)

【基礎知識思考整理 】Zero-copy原理理解(使用者角度)

關於Zero-Copy的原理。主要參照的是一篇03年的文章[1](Linux Journal),原理講得很明白。

首先需要知道應用場景:
適用於靜態資源從磁碟到網路的傳送(中間不對資源進行改變),這在web server提供的功能中很常見,一個例子是:儲存在磁碟上的一張圖片應某個網路請求被從磁碟中取出並通過socket傳送至請求方。

Linux系統分為user space和kernel space,user space中的很多操作需要通過系統呼叫陷入到核心中操作,比如檔案讀取操作。程式設計師通過某種程式語言提供的程式設計介面進行檔案讀取操作,比如C中的read,這個C語言的實現是通過呼叫kernel space提供的系統呼叫read實現的。在從user space陷入到kernel space的過程中,需要進行contex switch。同理,在從kernel space到user space的過程中也需要進行contex switch。

網路埠傳送資料的時候,有一塊socket buffer作為要傳送資料的快取,然後驅動從其中讀取資料並組包傳送。

對於上述的一張圖片的請求和傳送,從操作結果或者效果的角度看:我需要做的是將磁碟中的一部分資料直接(不加改變地)搬運到socket的buffer中進行組包再送到傳送佇列中等待發送的。可以想到:我們從user space發起任務到回到user space,至少要經歷兩次contex switch,資料從磁碟到傳送佇列,至少也要經過兩次copy(磁碟到socket buffer和socket buffer到傳送佇列)。

Zero在我的理解中是指在上述過程中不存在多餘copy的技術。

怎麼會存在多餘的copy呢?多餘的copy有什麼壞處嗎?直接按照上述的過程進行不是就能夠做到沒有多餘copy了嗎?
多餘的copy應該是軟硬體歷史的原因,這在後面的zero-copy的發展過程中可以看到。多餘的copy會佔用CPU時間片和記憶體頻寬,造成系統性能的降低。Zero-copy就是想做到上述的過程,即最直觀的過程。需要說明的是,並不是所有的資料的傳輸都適用zero-copy的,而且很大程度上不適用zero-copy的計算要比這種資料傳輸的應用要多得多,我們必須意識到這中佔大多數應用的存在,畢竟使用計算機做的事情並不僅限於儲存和傳輸。個人感覺zero-copy是一種具體問題具體分析和精細化功能實現以換取效能的過程。

我們來看看歷史上對上述一張圖片的響應的實現方式的變化:

實現1:contex switch 4次,copy 4次

read(file, tmp_buf, len);
write(socket, tmp_buf, len);

這裡寫圖片描述
上面是系統呼叫,下面是記憶體copy。這種方式沒有什麼可說的,是沒有技巧性的中規中矩做法。

實現2:contex switch 4次,copy 3次

tmp_buf = mmap(file, len);
write(socket, tmp_buf, len);

這裡寫圖片描述
使用mmap在kernel space和user space之間構造了一塊共享記憶體空間,相當於user space和kernel space都是訪問統一塊實體記憶體(但是在user space和kernel space中分別被對映到各自的虛擬地址空間中)。這種方式因為這塊共享記憶體會產生問題:當另一個程序操作同一個檔案的時候,檔案更改,kernel buffer中的內容相當於失效,此時,SIGBUS匯流排錯誤會終止之前的程序併產生core錯誤。解決的方法可以是設定訊號的處理函式,但是這種方式可能會遮蔽掉真正發生錯誤的情況;或者是對操作的檔案進行類似於加鎖的操作(讀寫鎖等)。或者如果真的想減少copy次數,只能讓作業系統核心和硬體進行支援了,這就是後面的實現。

實現3:contex switch 2次,copy 3次

sendfile(socket, file, len);            //since kernel 2.1

這裡寫圖片描述
新的系統呼叫被新增到linux kernel中以支援檔案間的copy操作,主要是不用在不必要的時候bother user space。這種方式的好處是減少了contex switch的次數,但是實際上對於實現2中存在的另一個程序修改使用的同一個檔案的情況並不能很好地解決。

實現4:contex switch 2次,copy 2次

sendfile(socket, file, len);            //since kernel 2.4

這裡寫圖片描述
在這種方式中,socket適應實現3的方式,並且獲得了硬體的相關的支援,在實現3中的第2步的copy在這種方式中只是copy了必要的描述資訊:檔案描述符和長度。相比於要copy的資料來說,這大大減少了copy需要的時間。

上述基本是對Zero-copy的理解,相關的內容可能比較老舊,在新版本的kernel中可能已經改變,但是不影響對原理的理解。如果後面涉及到相關的知識,比如說在不同程式語言中的使用等,再進行相應補充。

#include <sys/socket.h>
ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count);