2008年8月13日 星期三

什麼是Zero-Copy?

什麼是Zero-Copy?
想要了解這個名詞是指什麼意思的話,讓我們從一個簡單的範例開始,這個範例是server讀取一個檔案,然後把檔案資料經由socket將資料傳送給client。
簡化範例程式如下:

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

Figure 1的圖說明了,這兩行程式實際的運作流程。(1)當執行read函式後,進入Kernel的syscall read(),檔案資料會經由DMA傳到Kernel管的Buffer,然後再由CPU將檔案資料搬到user buffer(tmp_buf)裏。
(2)執行write後,Kernel的syscall write(),再用CPU去把user buffer的資料搬到socket buffer裏,資料進到socket buffer後,會再經由DMA的方式將資料送出去給client。

問題分析:
從整個流程你應該會發現有一堆資料是重覆的,如果能把這些部份改掉,那就可以減少記憶體的消耗並增加效能。
以硬體的角度來看,其實是可以做到直接跳過記憶體的資料暫存的,直接把檔案資料傳到網路去,這樣子的功能是最直接最有效率的,但並不是所有的硬體都支援這種方式。

那我們是否可以減少user buffer這個部份呢?答案是肯定的,我們必需使用mmap來取代read的功能。
簡化範例程式如下:

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

Figure 2的圖說明了,這兩行程式實際的運作流程。(1)mmap執行後,如同read system call會將檔案資料經由DMA複製一份到kernel buffer,但不同的地方是,read()會需要把kernel buffer複製到user buffer,mmap()並不會,mmap的user buffer跟kernel buffer是同一個位置,所以mmap可以減少一次CPU copy。
(2)write()執行,把kernel buffer經由CPU複製到socket buffer,然後再經由DMA複製到client去。

問題分析:
但是使用mmap來改善並不是不需要付出代價的,當你使用mmap+write的方法時,假設同時又有另外一支程式對同一個檔案執行write時,將會引發SIGBUS的訊號,因為你執行了一個錯誤的記憶體存取,而它的預設處理行為是,系統砍掉你的程式,並且產生core dump。
當然一支網路程式不應該這麼做處理的,有兩個方式來處理這個情況:
(1)方法一:在SIGBUS訊號設置callback function,當SIGBUS出現時由這個新設置的handler來進行處理,但這種方式不好,因為它是事後去補救,並不是正規的解決方法。
(2)方法二:使用租約(lease)的方式(windows裏稱opportunistic locking機會鎖)
如:
if(fcntl(fd, F_SETSIG, RT_SIGNAL_LEASE) == -1) {
perror("kernel lease set signal");
return -1;
}
/* l_type can be F_RDLCK F_WRLCK */
if(fcntl(fd, F_SETLEASE, l_type)){
perror("kernel lease set type");
return -1;
}

更好的做法是使用sendfile函式
簡化範例程式如下:
sendfile(socket, file, len);
Figure 3的圖說明了,這行程式在Kernel 2.1版本的實際運作流程,sendfile直接取代了read/write兩個函式,並且減少了context switch的次數。(1)sendfile執行後,檔案資料會經由DMA傳給Kernel buffer,再由CPU複製到socket buffer去
(2)再把socket buffer的資料經由DMA傳給client去,所以執行了2次DMA Copy及1次的CPU Copy,總共3次的資料複製。

問題分析:
所以到目前為止我們已看到改善了不少地方了,但還是有一份重複的資料,那就是socket buffer,這份資料是否也可以不要呢?基本上也是可行的,只要硬體提供一點點幫助是可以做到的,那就是gather(聚合)的功能,這個功能主要的目的是,待發送端不要求存放的資料位址是連續的記憶體空間,可以是分散在記憶體的各個位置。所以到了2.4的kernel以後,socket buffer的descriptor做了一些變動,以支援gather的需求,而這個功能就是Zero-Copy。
這種方式不僅僅是減少了context switch而且也減少了buffer的使用,從上層的程式來講,也不需要做任何的變動。所以程式同樣的還是底下這行
sendfile(socket, file, len);
Figure 4的圖說明了,這行程式在Kernel 2.4版本的實際運作流程(1)sendfile執行後,檔案資料經由DMA傳給Kernel buffer,但已不會再把資料copy到socket buffer了,socket buffer只會去管有那些Kernel buffer的address及資料長度,所以圖是用apend。
(2)資料傳給client去也是用DMA的方式,但來源變成kernel buffer了。

所以就完成了,不需要CPU去搬資料,而是純DMA搬資料的Zero-Copy了。

原本資料來源:
http://www.linuxjournal.com/article/6345

測試文章:有人針對這篇文章去進行測試的實驗結果
雖然它測起來的傳輸速度似乎沒差,但應再加上CPU負載的資料及記憶體使用量去分析,如果速度一樣但CPU loading變輕,client端很多但記憶體使用量減少,那還是有很高的實用價值。
但還有個問題,網路傳輸不見得只有直接傳檔案啊,如果是傳非檔案的資料,sendfile還是適用嗎?
http://bbs.lpi-china.org/viewthread.php?tid=4292&extra=page%3D1

沒有留言:

一個小故事讓我們明白資金流通的意義

“又是炎熱小鎮慵懶的一天。太陽高掛,街道無人,每個人都債台高築,靠信用度日。這時,從外地來了一位有錢的旅客,他進了一家旅館,拿出一張1000 元鈔票放在櫃檯,說想先看看房間,挑一間合適的過夜,就在此人上樓的時候---- 店主抓了這張1000 元鈔,跑到隔壁屠戶那裡支付了他欠的肉錢...