零拷貝總結

1.緩衝區

一個java進程發起read請求加載數據大致的流程圖:
在這裏插入圖片描述

2.虛擬內存

在這裏插入圖片描述
所有現代操作系統都使用虛擬內存,使用虛擬地址取代物理地址,好處是:
1)多個虛擬地址可以指向同一個物理內存地址
2)虛擬內存空間可大於實際可用的物理地址;
利用第一條特性可以把內核空間地址和用戶空間的虛擬地址映射到同一個物理地址,這樣DMA就可以填充對內核和用戶空間進程同時可見的緩衝區了。

3.傳統的文件數據

在這裏插入圖片描述
步驟:
1)系統調用read方法,上下文切換到內核態(切換),DMA把磁盤數據複製到內核緩衝區
2)read調用返回,上下文切換到用戶進程(切換),CPU把數據複製到用戶的緩衝區
3)write系統調用,上下文切換到內核態(切換),CPU把數據複製到內核Socket緩衝區
4)write調用返回,上下文切換到用戶進程(切換)
5)DMA把數據複製到網卡緩衝區
總結:4次上下文切換 + 2次CPU複製 + 2次DMA複製。

4.多次拷貝原因分析

1)操作系統爲了保護系統不被應用程序破壞,設置了用戶態和內核態。用戶態想要獲取系統資源(例如訪問硬盤), 必須通過系統調用進入到內核態, 由內核態獲取到系統資源,再切換回用戶態返回應用程序。
2)出於異步寫入避免每次寫都需要IO操作等性能優化, OS在內核態中也增加了一個"內核緩衝區",讀取數據時先讀取到kernel buffer, 再由kernel buffer複製到應用程序buffer。

5.系統調用mmap()來代替read

在這裏插入圖片描述
在這裏插入圖片描述
步驟:
1)系統調用mmap(),上下文切換到內核態(切換),DMA把磁盤數據複製到內核緩衝區。
2)操作系統把這段內核緩衝區與應用程序共享(就不需要往用戶空間拷貝了)。
3)mmap()調用返回,上下文切換到用戶態(切換)。
4)write系統調用,上下文切換到內核態(切換),CPU把內核緩衝區數據複製到內核緩衝區
5)write調用返回,上下文切換到用戶進程(切換)
6)DMA把數據複製到網卡緩衝區(協議棧)
總結:
1)4次上下文切換 + 1次CPU複製 + 2次DMA複製。
2)有陷阱,當程序map了一個文件,但是被另一個進程截斷(truncate)時, write系統調用會因爲訪問非法地址而被SIGBUS信號終止,會使你進程被殺掉。可以使用文件鎖來解決。

6.sendfile調用(內核版本2.1)

在這裏插入圖片描述
總結:
2次上下文切換 + 1次CPU拷貝 + 2次DMA拷貝。

7.sendfile調用(內核版本2.4)

在這裏插入圖片描述
步驟:
1)應用程序開始讀文件的操作
2)應用程序發起系統調用, 從用戶態進入到內核態(第一次上下文切換)
3)內核態中把數據從硬盤文件讀取到內核中間緩衝區
4)內核態中把數據在內核緩衝區的位置(offset)和數據大小(size)兩個信息追加(append)到socket的緩衝區中去
5)網卡的buf上根據socekt緩衝區的offset和size從內核緩衝區中直接拷貝數據
6)從內核態返回到用戶態(第二次上下文切換)
相比於sendfile2.1的優化點:
sendfile2.1中會將內核緩衝區的全量數據CPU拷貝到Socket緩衝區。而sendfile2.4中的CPU拷貝只是offset、size等一點點信息。
總結:
2次上下文切換 + 2次DMA拷貝。

8.零拷貝的零體現在哪

零拷貝,是從OS角度來看,內核中沒有數據是重複。sendFile 2.4中,只有 kernel buffer 有一份數據,是零拷貝。而mmap和sendFile 2.1 方式,在內核中有 2 份數據,算不上零拷貝。

java零拷貝

1)MappedByteBuffer
2)DirectByteBuffer
3)Channel-to-Channel傳輸

其他零拷貝

1)Netty零拷貝
2)RocketMQ採用零拷貝mmap+write
3)kafka中存在大量的網絡數據持久化和磁盤文件通過網絡發送的過程,使用了sendfile零拷貝方式;

10.總結

1)mmap方式:4次上下文切換 + 1次CPU複製 + 2次DMA複製。 2)sendFile 2.1 方式:2次上下文切換 + 1次CPU拷貝 + 2次DMA拷貝。 3)sendFile 2.4方式:2次上下文切換 + 2次DMA拷貝。 4)零拷貝,更少的數據複製,更少的上下文切換,更少的 CPU 緩存僞共享以及無 CPU 校驗和計算。