傳統IO與零拷貝的幾種實現


早期的數據IO,由用戶進程向CPU發起,應用程序與磁盤之間的 I/O 操作都是通過 CPU 的中斷完成的。

CPU還要負責將磁盤緩沖區拷貝到內核緩沖區(pageCache),再從內核緩沖區拷貝到用戶緩沖區。

為了減少CPU占用,產生了DMA技術,大大解放了CPU

 

DMA 的全稱叫直接內存存取(Direct Memory Access),是一種允許外圍設備(硬件子系統)直接訪問系統主內存的機制。

目前大多數的硬件設備,包括磁盤控制器、網卡、顯卡以及聲卡等都支持 DMA 技術。

傳統IO方式及DMA技術圖解可以參考之前一篇 https://www.cnblogs.com/ttaall/p/13738562.html

 

DMA技術存在的問題:

舉個例子,從本地文件中發一張圖片給你的好盆友。

傳統的訪問方式是通過 write() 和 read() 兩個系統調用實現的,通過 read() 函數讀取圖片到到緩存區中,然后通過 write() 方法把緩存中的圖片輸出到網絡端口。

read操作:

當應用程序執行 read 系統調用讀取一塊數據的時候,如果這塊數據已經存在於用戶進程的頁內存中,就直接從內存中讀取數據。

如果數據不存在,則先將數據從磁盤加載數據到內核空間的讀緩存(read buffer)中,再從讀緩存拷貝到用戶進程的頁內存中。

write操作:

當應用程序准備好數據,執行 write 系統調用發送網絡數據時,先將數據從用戶空間的頁緩存拷貝到內核空間的網絡緩沖區(socket buffer)中,

然后再將寫緩存中的數據拷貝到網卡設備完成數據發送。

 

 需要進行兩次DMA拷貝,兩次CPU拷貝,四次上下文切換

總共四次拷貝,四次切換,代價屬實有點點大。

  • 上下文切換:當用戶程序向內核發起系統調用時,CPU 將用戶進程從用戶態切換到內核態;當系統調用返回時,CPU 將用戶進程從內核態切換回用戶態。

  • CPU 拷貝:由 CPU 直接處理數據的傳送,數據拷貝時會一直占用 CPU 的資源。

  • DMA 拷貝:由 CPU 向DMA磁盤控制器下達指令,讓 DMA 控制器來處理數據的傳送,數據傳送完畢再把信息反饋給 CPU,從而減輕了 CPU 資源的占有率。

 

 

零拷貝的想法

1.用戶態可以直接操作讀寫,不需要切換用戶態內核態

2.盡量減少拷貝次數,盡量減少上下文切換次數

3.寫時復制,需要寫操作的時候再拷貝,只是讀操作沒必要拷貝

 

用戶態直接IO

用戶態直接 I/O 使得應用進程或運行在用戶態(user space)下的庫函數直接訪問硬件設備。

用戶態直接 I/O 只能適用於不需要內核緩沖區處理的應用程序,這些應用程序通常在進程地址空間有自己的數據緩存機制,稱為自緩存應用程序,如數據庫管理系統 就是一個代表。

其次,這種零拷貝機制會直接操作磁盤 I/O,由於 CPU 和磁盤 I/O 之間的執行時間差距,會造成大量資源的浪費,解決方案是配合異步 I/O 使用。

 

寫時復制

寫時復制指的是當多個進程共享同一塊數據時,如果其中一個進程需要對這份數據進行修改,那么就需要將其拷貝到自己的進程地址空間中。

這樣做並不影響其他進程對這塊數據的操作,每個進程要修改的時候才會進行拷貝,所以叫寫時拷貝。

 

mmap+write零拷貝技術

以mmap+write的方式替代傳統的read+write的方式,減少了一次拷貝

mmap 是 Linux 提供的一種內存映射文件方法,即將一個進程的地址空間中的一段虛擬地址映射到磁盤文件地址

使用 mmap 的目的是將內核中讀緩沖區(read buffer)的地址與用戶空間的緩沖區(user buffer)進行映射。

從而實現內核緩沖區與應用程序內存的共享,省去了將數據從內核讀緩沖區(read buffer)拷貝到用戶緩沖區(user buffer)的過程。

 

 整個拷貝過程會發生 4 次上下文切換,1 次 CPU 拷貝和 2 次 DMA 拷貝。

mmap 主要的用處是提高 I/O 性能,特別是針對大文件。對於小文件,內存映射文件反而會導致碎片空間的浪費。

 

Sendfile零拷貝技術

通過 Sendfile 系統調用,數據可以直接在內核空間內部進行 I/O 傳輸,從而省去了數據在用戶空間和內核空間之間的來回拷貝。

將要讀取的文件緩沖區的文件 fd 和要發送的Socket緩沖區的Socket fd  傳給sendfile函數,

 Sendfile 調用中 I/O 數據對用戶空間是完全不可見的。也就是說,這是一次完全意義上的數據傳輸過程。

也就是說用戶程序不能對數據進行修改,而只是單純地完成了一次數據傳輸過程。

整個拷貝過程會發生 2 次上下文切換,1 次 CPU 拷貝和 2 次 DMA 拷貝。

 

 

Sendfile+DMA gather copy

它只適用於將數據從文件拷貝到 socket 套接字上的傳輸過程。

它將內核空間的讀緩沖區(read buffer)中對應的數據描述信息(內存地址、地址偏移量)記錄到相應的網絡緩沖區( socket buffer)中,

由 DMA 根據內存地址、地址偏移量將數據批量地從讀緩沖區(read buffer)拷貝到網卡設備中。

這樣 DMA 引擎直接利用 gather 操作將頁緩存中數據打包發送到網絡中即可,本質就是和虛擬內存映射的思路類似。

 

 整個拷貝過程會發生 2 次上下文切換、0 次 CPU 拷貝以及 2 次 DMA 拷貝。

 

Splice零拷貝技術

Splice相當於在Sendfile+DMA gather copy上的提升

Splice 系統調用可以在內核空間的讀緩沖區(read buffer)和網絡緩沖區(socket buffer)之間建立管道(pipeline),從而避免了兩者之間的 CPU 拷貝操作。

 

 基於 Splice 系統調用的零拷貝方式,整個拷貝過程會發生 2 次上下文切換,0 次 CPU 拷貝以及 2 次 DMA 拷貝。

  • 用戶進程通過 splice() 函數向內核(kernel)發起系統調用,上下文從用戶態(user space)切換為內核態(kernel space)。

  • CPU 利用 DMA 控制器將數據從主存或硬盤拷貝到內核空間(kernel space)的讀緩沖區(read buffer)。

  • CPU 在內核空間的讀緩沖區(read buffer)和網絡緩沖區(socket buffer)之間建立管道(pipeline)。

  • CPU 利用 DMA 控制器將數據從網絡緩沖區(socket buffer)拷貝到網卡進行數據傳輸。

  • 上下文從內核態(kernel space)切換回用戶態(user space),Splice 系統調用執行返回。

 

對比

無論是傳統 I/O 拷貝方式還是引入零拷貝的方式,2 次 DMA Copy 是都少不了的,因為兩次 DMA 都是依賴硬件完成的。

 

 

kafka實現零拷貝: sendfile函數

2.4 內核改進的的sendfile函數 + 硬件提供的DMA Gather Copy實現零拷貝,將文件通過socket傳送

Kafka底層基於java.nio包下的FileChannel的transferTo: transferTo 可將一個文件直接傳輸到另一個文件

transferTo將FileChannel關聯的文件發送到指定channel,當Comsumer消費數據,Kafka Server基於FileChannel將文件中的消息數據發送到SocketChannel

 

 

RocketMq零拷貝實現: mmap+write()

RocketMQ基於mmap + write的方式實現零拷貝

內部實現基於nio提供的java.nio.MappedByteBuffer,基於FileChannel的map方法得到mmap的緩沖區:

 

 

Netty零拷貝實現:

  • 1 基於操作系統實現的零拷貝,底層基於FileChannel的transferTo方法
  • 2 基於Java 層操作優化,對數組緩存對象(ByteBuf )進行封裝優化,通過對ByteBuf數據建立數據視圖,支持ByteBuf 對象合並,切分,當底層僅保留一份數據存儲,減少不必要拷貝

加上selectorIO多路復用

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM