MemoryMap

在涉及到IO的開發(fā)中,我們經(jīng)??吹搅憧截?zero copy)、內(nèi)存映射(memroy map, 以下簡稱mmap)等技術(shù)被用于提高IO效率,本文將介紹這兩種技術(shù)的基本原理,說明它們是如何提高IO效率的。

相關(guān)概念

Zero copy和mmap涉及到操作系統(tǒng)中的一些基本概念,在了解它們的工作機制前,我們先來復(fù)習(xí)一下這些概念。

虛擬內(nèi)存(virtual memory space)

進程對內(nèi)存的讀寫不是直接使用物理內(nèi)存地址,而是基于虛擬地址。

每個進程運行時,操作系統(tǒng)都會為其創(chuàng)建一個私有的虛擬內(nèi)存,存放進程運行時代碼和數(shù)據(jù)。虛擬內(nèi)存大小取決與操作系統(tǒng)和所在機器的體系結(jié)構(gòu),對于32機器來說,空間大小為4g。

操作系統(tǒng)通過內(nèi)存管理機制,將虛擬內(nèi)存映射到物理內(nèi)存。

虛擬內(nèi)存使得操作系統(tǒng)可以同時支持多個運行進程安全共享物理內(nèi)存,防止進程之間的不安全讀寫。

User space vs kernel space

虛擬內(nèi)存分為兩部分:用戶空間和內(nèi)核空間。用戶空間存放用戶代碼和用戶數(shù)據(jù);內(nèi)核空間存放操作系統(tǒng)代碼。

前面說過,每個進程有自己私有的虛擬內(nèi)存,不同進程的虛擬內(nèi)存中的相同的地址,被映射到物理內(nèi)存中的不同位置。但是內(nèi)核空間是個例外,所有進程是共享內(nèi)核空間的,也就是對不同進程來說,它們內(nèi)核空間內(nèi)的內(nèi)容、地址映射實際上都是相同的。

缺頁中斷(page fault)

操作系統(tǒng)為每個進程的虛擬內(nèi)存和物理內(nèi)存之間建立了一張映射表,需要注意的是,虛擬內(nèi)存中的內(nèi)容只會一部分被裝載到物理內(nèi)存中。

當(dāng)進程訪問的虛擬地址對應(yīng)的內(nèi)容不在物理內(nèi)存時,操作系統(tǒng)會觸發(fā)一個缺頁中斷,將物理內(nèi)存中不用的內(nèi)容暫時置換到磁盤,將需要的內(nèi)容讀取道物理內(nèi)存。通過這種管理模式,我們可以在同時運行多個進程的情況下,讓每個進程覺得自己在獨享整個內(nèi)存空間。

User mode vs kernel mode

操作系統(tǒng)至少為進程提供兩種運行模式:用戶態(tài)(usermode)、內(nèi)核態(tài)(kernel mode)。不同的模式,實際對應(yīng)著不同的運行權(quán)限。

用戶態(tài)的進程,只能訪問用戶空間,不能直接訪問設(shè)備。而內(nèi)核態(tài)的進程,可以訪問用戶空間和內(nèi)核空間,可以訪問硬件設(shè)備。

進程模式切換

一個進程可以通過系統(tǒng)調(diào)用在用戶態(tài)和內(nèi)核態(tài)之間切換,此外,中斷、異常等機制也可以讓進程沖用戶態(tài)切換到內(nèi)核態(tài)。

這里簡單說明下系統(tǒng)調(diào)用的過程;

  1. 用戶態(tài)進程準備好系統(tǒng)調(diào)用的參數(shù);
  2. 進程執(zhí)行trap指令
  3. cpu自動切換到內(nèi)核態(tài)
  4. cpu讀取進程事先設(shè)置的系統(tǒng)調(diào)用參數(shù)
  5. 執(zhí)行指定的系統(tǒng)調(diào)用
  6. 結(jié)束后,進程重新被設(shè)置為用戶態(tài)

工作過程分析

假定我們現(xiàn)在要實現(xiàn)一個讀取文件,在內(nèi)存中處理,然后將其輸出的需求,我們看看在不同的實現(xiàn)方式中,底層究竟是如何工作的。

普通實現(xiàn)(read + write)

首先,我們使用常規(guī)的文件io操作實現(xiàn)需求。

普通IO
  1. 進程通過系統(tǒng)調(diào)用讀取數(shù)據(jù)
  2. 進程切換到內(nèi)核態(tài),通知設(shè)備進行讀取
  3. 設(shè)備準備好的數(shù)據(jù)傳送到內(nèi)核空間
  4. 接收到數(shù)據(jù)后,內(nèi)核態(tài)進程將數(shù)據(jù)從內(nèi)核空間拷貝到用戶空間
  5. 進程切換回用戶態(tài),繼續(xù)執(zhí)行用戶空間的代碼:處理數(shù)據(jù)
  6. 進程通過系統(tǒng)調(diào)用輸出數(shù)據(jù)到設(shè)備
  7. 進程切換到內(nèi)核態(tài),把數(shù)據(jù)從用戶空間拷貝到內(nèi)核空間,通知設(shè)備進行輸出操作
  8. 設(shè)備完成任務(wù)后,進程再次從內(nèi)核態(tài)切換回用戶態(tài)

在上面的過程中,涉及到4次進程模式切換,兩次內(nèi)存拷貝。這些操作對性能會造成一定影響。

sendfile

接下來,我們通過sendfile調(diào)用,減少上述過程中的內(nèi)存拷貝,實現(xiàn)零拷貝。

zero copy

通過sendfile,我們看到進程模式切換從4次減少到2次,拷貝從2次減少到1次。

但是sendfile只能完成文件的拷貝操作,無法處理文件內(nèi)容,mmap則可以幫助我們實現(xiàn)零拷貝下的處理。

mmap

mmap

通過調(diào)用memory map,我們讓操作系統(tǒng)把文件的內(nèi)容映射到內(nèi)存,對內(nèi)存的讀寫將關(guān)聯(lián)到對應(yīng)的文件。而應(yīng)用通過訪問用戶空間操作這部分內(nèi)存,避免了內(nèi)存拷貝操作。

對于內(nèi)存映射,有些地方容易被誤解,這里說明一下。

內(nèi)存映射是文件到內(nèi)存空間的映射

對于應(yīng)用來說,和文件建立映射關(guān)系的是虛擬地址空間,而不是物理內(nèi)存或者Heap。

當(dāng)我們建立一個2g大小的映射時,并不是在heap,更不是在物理內(nèi)存中分配了這么大的空間,僅僅是在虛擬地址空間中劃出了這么大一個區(qū)域而已。

應(yīng)用訪問內(nèi)存映射區(qū)域時,操作系統(tǒng)會把虛擬的地址映射成真正的物理內(nèi)存地址和底層文件的偏移量。如果應(yīng)用訪問的虛擬地址對應(yīng)的文件內(nèi)容尚未被裝入內(nèi)存,操作系統(tǒng)通過缺頁中斷,將內(nèi)存中的部分內(nèi)容交換出去,騰出空間將文件的內(nèi)容讀取到內(nèi)存。

內(nèi)存映射對性能的提升是有條件的

通過內(nèi)存映射訪問文件,雖然減少了內(nèi)存拷貝,減少了系統(tǒng)調(diào)用引起的進程模式切換,但是過程中需要承擔(dān)缺頁中斷的負擔(dān)。

對于小文件的讀取,或者對于append模式的文件讀寫,內(nèi)存映射的性能未必優(yōu)于普通io操作。只有對大文件的隨機訪問,內(nèi)存映射才可能有明顯優(yōu)勢,不過這仍然需要更具體的分析和進一步的的benchmark測試。

Reference

User mode and Kernel mode, System calls, I/O, Exceptions

User space and kernel space

It's all about buffers: zero-copy, mmap and Java NIO

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容