標籤:

Linux 中 mmap() 函數的內存映射問題理解?

mmap()這個函數到底是把 硬碟里的數據映射到物理內存(還是虛擬內存?)中,還是把物理內存中的數據映射到虛擬內存中? 有點暈,我猜測是前者,但不確定。。。

還有,網上說的映射到多個頁上是什麼意思?物理內存不是連續的么,直接把硬碟上的數據dump到物理內存不就行了。

mmap()的好處一個是節約IO讀寫時間?(但是,內存映射了之後,最後我要保存的話還是需要把內存上的數據寫到硬碟的吧?),還有一個是可以實現共享內存通信是吧?沒別的好處了吧?

初次接觸,查了資料還是有點暈,所以問題有點多,望各位大神諒解~


你的腦子是暈的,被聽到的信息搞暈了頭腦,我幫你洗一次腦吧。

1. 請放棄虛擬內存這個概念,那個是廣告性的概念,在開發中沒有意義。開發中只有虛擬空間的概念,進程看到的所有地址組成的空間,就是虛擬空間。虛擬空間是某個進程對分配給它的所有物理地址(已經分配的和將會分配的)的重新映射。

2. mmap的作用,在應用這一層,是讓你把文件的某一段,當作內存一樣來訪問。內核和驅動如何實現的,性能高不高這些問題,這層語義上沒有承諾。你基於功能決定怎麼用它就好了,少胡思亂想。

有了以上兩個,你就可以寫好程序了。下面介紹一下Linux的實現細節,權當好玩,如果你搞不清楚前面兩條,後面的就不要看,否則你又亂掉了。

3. mmap的工作原理,當你發起這個調用的時候,它只是在你的虛擬空間中分配了一段空間,連真實的物理地址都不會分配的,當你訪問這段空間,CPU陷入OS內核執行異常處理,然後異常處理會在這個時間分配物理內存,並用文件的內容填充這片內存,然後才返回你進程的上下文,這時你的程序才會感知到這片內存里有數據

4. 驅動每次讀入多少頁面,頁面的分配演算法等等,都不是系統的承諾,不能作為你編程的依賴。這就是前面說的:不要胡思亂想

5. 至於swap分區的作用,參考這裡:Linux 是怎樣使用內存的? - in nek 的回答

基於題主繼續的問題,我們接著來解釋一下為什麼我建議你放棄虛擬內存而使用虛擬空間的概念。

內存,我們通常指向計算級中的DRAM,上面可以保存數據。為了訪問內存,我們對內存進行編址,所有編址的集合,組成內存空間。內存的空間,從匯流排上看到的結果,我們一般稱為物理地址空間,圖示如下:

你可以看到,物理地址空間不但包括內存,也包括IO,物理空間的大小和地址匯流排的長度相關,可以遠遠大於DRAM的實際大小。

CPU發起訪問內存的操作,需要經過MMU的地址翻譯,這個翻譯本質上是一個轉換演算法pa=f(va),圖示如下:

虛擬地址的空間和指令集的地址長度有關,不一定和物理地址長度一致,比如現在的64位處理器,從VA角度看來,可以訪問64位的地址,但地址匯流排長度只有48位,所以你可以訪問一個位於2^52這個位置的地址,但通過MMU的轉化,這個地址可能只會進入很低的物理地址上(當然,你也可以強行轉化到更高的地址上,只是那個地址會訪問失效或者被截斷使用而已)。

所以,虛擬空間可以很大,但不表示物理內存也需要很大。每個進程有自己的虛擬空間,這些虛擬空間可以映射到物理內存的不同或者相同的位置。示意如下:

Linux執行一個程序,這個程序在磁碟上,為了執行這個程序,需要把程序載入到內存中,這時採用的就是mmap,mmap讓虛擬空間和文件的內容組成的空間(我這裡稱為文件空間)對應,類似這樣:

上面展示的是mmap之後的效果,但文件的內容在磁碟上是不能被CPU訪問的,所以當CPU真的在這個地址上發起讀寫執行等操作時,OS會進入異常,異常中會調用文件系統把一頁或者多頁的文件內容載入到物理內存中,這會變成這樣:

你可以從/proc/&

/maps看到每個進程的mmap狀態,下面是一個init(pid=1)進程的maps文件的內容:

這些分段空間後面的那些,就是每個虛擬空間分段對應的文件。這些文件,稱為這片虛擬空間的backlog文件,它的作用是當這些內存需要被使用的時候,從磁碟中把對應的文件內容載入到物理內存中。

這個map表中,部分內存是沒有backlog文件的,所有不通過mmap某個文件增加到系統中的用戶內存,都是這種類型,比如brk系統調用獲得的內存(在很多libc的實現中,malloc通過這個系統調用實現內存增量),這種內存對於這個進程來說,稱為「匿名內存」,如果你有swap文件,則Linux會給這些內存分一段swap文件作為匿名內存的backlog文件,這樣,當系統內存不足的時候,Linux可以放棄掉一部分物理內存,等後續再從backlog文件中載入,這種backlog文件,有可能是有名文件,也可能是「無名」文件(swap),廣告角度說,這個無名文件會被稱為虛擬內存,但和你關心的用來載入點什麼東西的那個「內存」沒有什麼關係。

[附錄1]跳過Cache的方法

Cache可以通過/proc/sys/vm/drop_caches強行釋放,寫1釋放pagecache,2釋放dentries和inode,3釋放兩者。(這個動作不能釋放臟頁)

如果寫不想要Cache,可以在mount的時候加sync,dirsync參數。


你連文件系統的概念都沒有搞清楚。。去讀一下操作系統概念中的文件系統或者lkp的虛擬文件系統那一章。

mmap主要有2種用法,一個是建立匿名映射,可以起到父子進程之間共享內存的作用。另一個是磁碟文件映射進程的虛擬地址空間(這個和交換分區的概念不同,交換分區的概念是基於頁面置換的實現),可以節省一次內存拷貝.

mmap的主要的好處在於,減少一次內存拷貝。在我們平時vfs的read/write系統調用中,文件內容的拷貝要多經歷內核緩衝區這個階段,所以比mmap多了一次內存拷貝,mmap只有用戶空間的內存拷貝(這個階段read/write也有)。正是因為減少了從Linux的頁緩存到用戶空間的緩衝區的這一次拷貝,所以mmap大大提高了性能,mmap也被稱為zero-copy技術。

mmap()的實現主要是先建立一個vm_area_struct的數據結構,這個數據結構表示了進程虛擬地址空間中的一段。然後這個結構把磁碟文件的區域和虛擬地址空間中的一段連接了起來。但是這時候物理內存中還是沒有內容的,而mmu是在虛擬地址空間和物理內存地址之間建立映射的。

所以要訪問到這段虛擬地址空間時,由於只是建立了映射,而實際的頁面還沒有放置到頁框中,所以會產生一個缺頁中斷陷入內核,然後會首先在硬碟的swap頁面中尋找這個頁面是否被交換出去,如果沒有找到這個頁面,那麼會依照mmap建立的vm_area_struct這個數據結構把磁碟中的文件讀到物理內存中,這樣就可以訪問了。


內存是外存的緩存,除開一些中間結果,內存中的數據總是要輸出或者叫同步到外存中去的(常見的外存包括硬碟/網卡緩衝區/串口緩衝區/幀緩衝。。。)。

mmap就是給你手工管理這個緩存的一一對應關係(內存地址 對應 linux的外存統一資源描述符:文件描述符)的一種手段。其中的細節,樓上很多回答說了很多,我就不絮叨了...在虛擬內存問題上,其解決方法本質也和cpu緩存不夠了咋解決沒區別(cpu指令本身也是只操作寄存器和高速緩存的,從來不直接操作內存的)。

寄存器和cpu高速緩存是內存的緩存,內存是外存的緩存。。

不過還是要提一句: 字元設備(各種網口,串口)和塊設備(可以fopen的玩意兒)的緩衝同步策略不一樣,導致前者一般沒法用mmap這樣的方法來控制和獲取對應關係,而只能通過流介面(read/write)來進行操作(每一次往「流」里灌水或抽水 都有機會或者必然觸發緩衝同步)。

別被映射這個詞的忽悠了: 這裡的map只是表示關係,而實質只是一個緩存而已。


以下都是指 linux x86_64 架構下的情況.

kernel使用一個數據結構(mm_struct.mmlist)記錄著一個進程聲明要使用(mmap做的事)的內存地址塊(vm_area_struct).

當進程真的用到這個地址塊里的東西時, page fault了.

在page fault的處理函數中,kernel檢查fault的內存地址在不在其聲明要使用的那些地址塊里,如果在,就做好page table map(物理內存和硬碟數據在這裡才開始使用),如果不在,就 segmentation fault 了.


mmap只是把fd映射到虛擬地址上,然後你可以像訪問內存一樣訪問這個fd的資源。具體fd可以是很多東西,比如文件。

如果虛擬地址指向的是物理地址,那都要過MMU的,並且是以內存分頁為單位的,一般是4K。虛擬地址是連續的不代表分配到的物理內存分頁也是連續的。這就是多個分頁的意思。

這兩個東西完全是兩個不同的概念。mmap具體有什麼用得看你怎麼用了,共享內存只是其中一種用法而已。你直接映射到設備上當驅動用也行啊


和swap是一個概念。只不過swap是把一些內存空間和交換分區關聯起來了 而mmap則是把內存空間和特定的文件關聯起來了

比方說 mmap了一個1G的文件到內存地址X 那麼當你第一次讀[x, x+1g] 的地址時,就產生一次缺頁中斷 OS就把對應的數據裝到物理內存 然後建立映射 最後用戶進程就可以讀到數據了


你理解太膚淺而且有錯誤,如果想深入了解,需要從虛擬內存入手


常規用戶態編程從邏輯上講不要去考慮物理頁,這是OS的事情。

當然根據The Law of Leaky Abstractions這麼做肯定有問題------比如care性能的時候 。

此時需要對用戶態進程的內存和物理頁之間的關係,OS背後是如何處理的, 等等,有所了解。

Linux virtual memory細節非常複雜,通透理解 Linux virtual memory 實現的,即使在kernel committer里也是非常非常少的人有這個能力,它可以說是kernel最複雜部分(沒有之一)

可以去讀讀Linux kernel相關的書,有個大概概念就可以了。


建議題主仔細閱讀CSAPP 第九章 虛擬內存. 書中講得非常清楚.


@in nek 的答案純屬胡扯,強行把平台無關的概念扯到某種特定OS上。

1. 你需要先了解何為虛擬地址空間(解決你第一個問題和第二個問題)

cpu 為什麼要使用虛擬地址空間與物理地址空間映射?解決了什麼樣的問題? - 中央處理器 (CPU)

再去維基百科或者自己找本不坑爹的教材(不過理解概念的話,國內的坑爹過時教材也許也夠用)自己啃吧,他們寫的肯定比我寫得好。

無論何種平台,你作為一個用戶程序,無法操縱實際使用的東西是「在物理內存中」還是「被交換到虛擬內存中」。但是操作系統可以保證給你的地址在你使用時可以分配到內存(此處不嚴謹,沒必要細摳異常情況)

2. mmap和其他文件讀寫方式有何不同?

以最簡單的(f)open/(f)close,來說

用法不同

同步機制不同


哈哈


推薦閱讀:

google是如何為全球用戶提供高速高效服務的?
程序崩潰,為啥叫core掉,而不叫崩潰、宕機,是有什麼來歷嗎?
嵌入式方向可以完全不學 Linux 嗎?
打算學慣用C語言進行linux網路編程,求推薦學習路徑?
不考慮非同步io的epoll方式的非阻塞伺服器端為了接收文件,臨時把socket設為阻塞,做法正確嗎?

TAG:Linux | Linux開發 |