什麼是 mmap? 經典題目

平時在面試中你肯定會經常碰見的問題就是:RocketMQ 爲什麼快?Kafka 爲什麼快?什麼是 mmap?

這一類的問題都逃不過的一個點就是零拷貝,雖然還有一些其他的原因,但是今天我們的話題主要就是零拷貝。

在開始談零拷貝之前,首先要對傳統的 IO 方式有一個概念。

基於傳統的 IO 方式,底層實際上通過調用read()write()來實現。

通過read()把數據從硬盤讀取到內核緩衝區,再複製到用戶緩衝區;然後再通過write()寫入到socket緩衝區,最後寫入網卡設備。

整個過程發生了 4 次用戶態和內核態的上下文切換4 次拷貝,具體流程如下:

  1. 用戶進程通過read()方法向操作系統發起調用,此時上下文從用戶態轉向內核態
  2. DMA 控制器把數據從硬盤中拷貝到讀緩衝區
  3. CPU 把讀緩衝區數據拷貝到應用緩衝區,上下文從內核態轉爲用戶態,read()返回
  4. 用戶進程通過write()方法發起調用,上下文從用戶態轉爲內核態
  5. CPU 將應用緩衝區中數據拷貝到 socket 緩衝區
  6. DMA 控制器把數據從 socket 緩衝區拷貝到網卡,上下文從內核態切換回用戶態,write()返回

那麼,這裏指的用戶態內核態指的是什麼?上下文切換又是什麼?

簡單來說,用戶空間指的就是用戶進程的運行空間,內核空間就是內核的運行空間。

如果進程運行在內核空間就是內核態,運行在用戶空間就是用戶態。

爲了安全起見,他們之間是互相隔離的,而在用戶態和內核態之間的上下文切換也是比較耗時的。

從上面我們可以看到,一次簡單的 IO 過程產生了 4 次上下文切換,這個無疑在高併發場景下會對性能產生較大的影響。

那麼什麼又是 DMA 拷貝呢?

因爲對於一個 IO 操作而言,都是通過 CPU 發出對應的指令來完成,但是相比 CPU 來說,IO 的速度太慢了,CPU 有大量的時間處於等待 IO 的狀態。

因此就產生了 DMA(Direct Memory Access)直接內存訪問技術,本質上來說他就是一塊主板上獨立的芯片,通過它來進行內存和 IO 設備的數據傳輸,從而減少 CPU 的等待時間。

但是無論誰來拷貝,頻繁的拷貝耗時也是對性能的影響。

零拷貝技術是指計算機執行操作時,CPU 不需要先將數據從某處內存複製到另一個特定區域,這種技術通常用於通過網絡傳輸文件時節省 CPU 週期和內存帶寬。

那麼對於零拷貝而言,並非真的是完全沒有數據拷貝的過程,只不過是減少用戶態和內核態的切換次數以及 CPU 拷貝的次數。

這裏,僅僅有針對性的來談談幾種常見的零拷貝技術。

mmap+write

mmap+write 簡單來說就是使用mmap替換了 read+write 中的 read 操作,減少了一次 CPU 的拷貝。

mmap主要實現方式是將讀緩衝區的地址和用戶緩衝區的地址進行映射,內核緩衝區和應用緩衝區共享,從而減少了從讀緩衝區到用戶緩衝區的一次 CPU 拷貝。

整個過程發生了 4 次用戶態和內核態的上下文切換3 次拷貝,具體流程如下:

  1. 用戶進程通過mmap()方法向操作系統發起調用,上下文從用戶態轉向內核態
  2. DMA 控制器把數據從硬盤中拷貝到讀緩衝區
  3. 上下文從內核態轉爲用戶態,mmap 調用返回
  4. 用戶進程通過write()方法發起調用,上下文從用戶態轉爲內核態
  5. CPU 將讀緩衝區中數據拷貝到 socket 緩衝區
  6. DMA 控制器把數據從 socket 緩衝區拷貝到網卡,上下文從內核態切換回用戶態,write()返回

mmap的方式節省了一次 CPU 拷貝,同時由於用戶進程中的內存是虛擬的,只是映射到內核的讀緩衝區,所以可以節省一半的內存空間,比較適合大文件的傳輸。

sendfile

相比mmap來說,sendfile同樣減少了一次 CPU 拷貝,而且還減少了 2 次上下文切換。

sendfile是 Linux2.1 內核版本後引入的一個系統調用函數,通過使用sendfile數據可以直接在內核空間進行傳輸,因此避免了用戶空間和內核空間的拷貝,同時由於使用sendfile替代了read+write從而節省了一次系統調用,也就是 2 次上下文切換。

整個過程發生了 2 次用戶態和內核態的上下文切換3 次拷貝,具體流程如下:

  1. 用戶進程通過sendfile()方法向操作系統發起調用,上下文從用戶態轉向內核態
  2. DMA 控制器把數據從硬盤中拷貝到讀緩衝區
  3. CPU 將讀緩衝區中數據拷貝到 socket 緩衝區
  4. DMA 控制器把數據從 socket 緩衝區拷貝到網卡,上下文從內核態切換回用戶態,sendfile調用返回

sendfile方法 IO 數據對用戶空間完全不可見,所以只能適用於完全不需要用戶空間處理的情況,比如靜態文件服務器。

sendfile+DMA Scatter/Gather

Linux2.4 內核版本之後對sendfile做了進一步優化,通過引入新的硬件支持,這個方式叫做 DMA Scatter/Gather 分散 / 收集功能。

它將讀緩衝區中的數據描述信息 -- 內存地址和偏移量記錄到 socket 緩衝區,由 DMA 根據這些將數據從讀緩衝區拷貝到網卡,相比之前版本減少了一次 CPU 拷貝的過程

整個過程發生了 2 次用戶態和內核態的上下文切換2 次拷貝,其中更重要的是完全沒有 CPU 拷貝,具體流程如下:

  1. 用戶進程通過sendfile()方法向操作系統發起調用,上下文從用戶態轉向內核態
  2. DMA 控制器利用 scatter 把數據從硬盤中拷貝到讀緩衝區離散存儲
  3. CPU 把讀緩衝區中的文件描述符和數據長度發送到 socket 緩衝區
  4. DMA 控制器根據文件描述符和數據長度,使用 scatter/gather 把數據從內核緩衝區拷貝到網卡
  5. sendfile()調用返回,上下文從內核態切換回用戶態

DMA gathersendfile一樣數據對用戶空間不可見,而且需要硬件支持,同時輸入文件描述符只能是文件,但是過程中完全沒有 CPU 拷貝過程,極大提升了性能。

對於文章開頭說的兩個場景:RocketMQ 和 Kafka 都使用到了零拷貝的技術。

對於 MQ 而言,無非就是生產者發送數據到 MQ 然後持久化到磁盤,之後消費者從 MQ 讀取數據。

對於 RocketMQ 來說這兩個步驟使用的是mmap+write,而 Kafka 則是使用mmap+write持久化數據,發送數據使用sendfile

由於 CPU 和 IO 速度的差異問題,產生了 DMA 技術,通過 DMA 搬運來減少 CPU 的等待時間。

傳統的 IOread+write方式會產生 2 次 DMA 拷貝 + 2 次 CPU 拷貝,同時有 4 次上下文切換。

而通過mmap+write方式則產生 2 次 DMA 拷貝 + 1 次 CPU 拷貝,4 次上下文切換,通過內存映射減少了一次 CPU 拷貝,可以減少內存使用,適合大文件的傳輸。

sendfile方式是新增的一個系統調用函數,產生 2 次 DMA 拷貝 + 1 次 CPU 拷貝,但是隻有 2 次上下文切換。因爲只有一次調用,減少了上下文的切換,但是用戶空間對 IO 數據不可見,適用於靜態文件服務器。

sendfile+DMA gather方式產生 2 次 DMA 拷貝,沒有 CPU 拷貝,而且也只有 2 次上下文切換。雖然極大地提升了性能,但是需要依賴新的硬件設備支持。

參考:

juejin.cn/post/684490…

www.cnblogs.com/xiaolincodi…

time.geekbang.org/column/arti…

www.toutiao.com/i6898240850…

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://juejin.cn/post/6940528982974545934?utm_source=gold_browser_extension