天天看點

linux mmap系統調用brk/mmap mmap 匿名映射/檔案映射MAP_FIXED參考資料

brk/mmap

linux 提供了兩個比較重要的系統調用brk 和mmap,用于向核心申請相應使用者空間,核心會根據系統運作狀态判定是否申請新的VMA來管理新申請的使用者空間,brk和mmap在整個系統中都占有非常重要的地位。

  • brk()系統調用 被gblic進行了進一步封裝成malloc接口,使用者層程式一般都是通過調用malloc,由glibc間接調用brk來向核心申請使用者空間。brk申請的使用者空間屬于堆空間。
  • mmap系統調用也可以向核心申請使用者空間,不過與brk不同的是,mmap申請的空間屬于mapping映射空間部分。

使用者空間部分如下圖所示:

linux mmap系統調用brk/mmap mmap 匿名映射/檔案映射MAP_FIXED參考資料

 mmap

mmap系統調用函數接口如下:

#include <sys/mman.h>

void * mmap(void *addr,size_t length, int prot, int flags, int fd,off_t offset)
           

 參數:

  • addr: 指定映射被放置的虛拟位址,如果将addr指定為NULL,那麼核心會為映射配置設定一個合适的位址。如果addr為一個非NULL值,則核心在選擇位址映射時會将該參數值作為一個提示資訊來處理。不管采用何種方式,核心會選擇一個不與任何既有映射沖突的位址。在處理過程中, 核心會将指定的位址舍入到最近的一個分頁邊界處。
  • length:參數指定了映射的位元組數。盡管length 無需是一個系統分頁大小的倍數,但核心會以分頁大小為機關來建立映射,是以實際上length會被向上提升為分頁大小的下一個倍數。
  • prot: 參數掩碼,用于指定映射上的保護資訊:标記有:
描述
PROT_NONE 區域無法通路
PROT_READ  區域内容可讀取
PROT_WRITE  區域内容可修改
PROT_EXEC  區域内容可執行
  •  flags:用于指定映射類型,
flags 描述
MAP_PRIVATE 建立一個私有映射。映射區域中記憶體發生變化對使用同一映射 的其他程序不可見。對檔案映射來講,所發生的變更将不會反應在底層檔案上。
MAP_SHARED 區域中内容上所發生的變更對使用同一個映射區域的其他程序可見
MAP_ANONYMOUS 建立一個匿名映射
MAP_FIXED 原樣解釋addr
MAP_LOCKED 将映射分頁鎖進記憶體
MAP_HUGETLB 建立一個使用巨頁的映射
MAP_HUGE_2MB 與MAP_HUGETLB一起使用,映射的巨頁的頁大小為2MB
MAP_NORESERVE 控制交換空間預留
MAP_POPULATE 填充一個映射的分頁
MAP_UNINITALIZED 防止一個匿名映射被清零
MAP_32BIT 僅在x86-64系統下支援,映射空間位于前2G空間
MAP_STACK 目前在linux中沒有實作,映射空間為stack 空間
MAP_SHARED_VALIDATE 與MAP_SHARED功能一樣,差別就是MAP_SHARED會忽略未知flag設定,而MAP_SHARED_VALIDATE會對flags進行檢查,如果是unknow flags将會傳回EONNOTSUPP
MAP_SYNC 隻有和MAP_SHARED_VALIDATE一起使用才有效,僅支援DAX 檔案,如果是其他類型檔案将會傳回錯誤。

 匿名映射/檔案映射

mmap按照映射的類型主要可以分為檔案映射和匿名映射。

  • 檔案映射:檔案映射是将一個檔案的一部分直接映射到調用程序的虛拟記憶體中,一旦一個檔案被映射之後就可以通過在相應記憶體區域中操作位元組來通路檔案内容了。映射的分頁會在需要的時候從檔案中(自動)加載。這種映射被稱為基于檔案映射或記憶體映射檔案。
  • 匿名映射:一個匿名映射沒有相應的檔案。相反,這種映射的頁面會被初始化為0.

同時又按照私有映射(MAP_PRIVATE)和共享映射分别将檔案映射和匿名映射劃分成不同使用用途: 

可見性 檔案映射 匿名映射
私有映射(MAP_PRIVATE)  根據檔案記憶體初始化記憶體,其他程序不可見 記憶體配置設定
共享映射(MAP_SHARED) 記憶體映射I/O:程序間共享記憶體(IPC) 程序間共享記憶體(IPC)

 匿名映射

匿名映射沒有對應檔案映射,其建立方法有兩種:

  • 在flags中指定MAP_ANONYMOUS并将fd指定為-1
  • 打開/dev/zero裝置檔案并将得到的檔案描述符傳遞給mmap.。/dev/zero是一個虛拟裝置,當從中讀取資料時它總是會傳回0,而寫入到這個裝置中的資料總是被丢棄。/dev/zero的一個常見用途使用0來組裝一個檔案。

檔案映射

檔案映射建立需要執行下面的步驟:

  • 擷取檔案的一個描述符,通常通過調用open()來完成。
  • 将檔案描述符作為fd參數傳入mmap()調用。

執行上述步驟之後mmap()會将 打開的檔案内容映射到調用程序的位址空間中。一旦mmap()被調用之後就能夠關閉檔案描述符,而不會對映射産生任何影響:

linux mmap系統調用brk/mmap mmap 匿名映射/檔案映射MAP_FIXED參考資料

offset參數指定了從檔案區域中的哪個位元組開始映射,它必須是系統分頁大小的倍數。将offset指定為0會導緻從檔案的起始位置開始映射。length參數指定了映射的位元組數,offset和length參數一起确定了檔案的哪個區域會被映射進記憶體。

共享檔案映射

當多個程序建立同一個檔案區域的共享映射時,它們會共享同樣的記憶體實體分頁。此外,對映射記憶體變更将會反應到檔案上:

linux mmap系統調用brk/mmap mmap 匿名映射/檔案映射MAP_FIXED參考資料

 兩個程序共享同一個檔案映射,當程序A修改檔案的内容将會立即被程序B看到。

記憶體映射I/O

由于共享檔案映射中的内容是從檔案初始化而來的,并且對映射記憶體所做出的變更都會自動反應到檔案上,是以可以簡單地通過通路記憶體中的檔案來執行檔案I/O,而依靠核心來確定對記憶體的變更會傳遞到映射檔案中。(一般來講,一個程式會定義一個結構化資料類型來與磁盤檔案中的内容對應起來,然後使用該資料類型來轉換映射的内容。)這項技術被稱為内容映射I/O,它是使用read()和write()來通路檔案内容這種方法的替代方案。

  • 記憶體映射I/O映射具備兩個潛在的優勢:
  • 使用記憶體通路來取代read()和write()系統調用,可以像通路記憶體一樣通路檔案

在一些情況下,它能夠比使用傳統的I/O系統調用執行檔案I/O這種做法提供更好的性能。

記憶體映射之是以帶來性能優勢:

  • 正常的read()或write()需要兩次傳輸:一次是在檔案和記憶體高速緩沖區之間,另一次是在高速緩沖區和使用者空間緩沖之間。使用mmap()就無限第二次傳輸。對于輸入來講,一旦核心将相應得檔案塊映射進記憶體之後使用者程序就能夠使用這些資料。對于輸出來講,使用者程序僅僅需要修改記憶體中的内容,然後可以依靠核心記憶體管理全來自動更新底層檔案。
  • 除了節省了記憶體空間和使用者空間之間的一次傳輸之外,mmap()還能夠通過減少所需使用的記憶體來提升性能。當使用read()或write()時,資料将被儲存在兩個緩沖區中:一個位于使用者空間,另一個位于核心空間。當使用mmap()時,核心空間和使用者空間會共享同一個緩沖區。此外,如果多個資料正在同一個檔案上執行I/O,那麼它們通過使用mmap()就能夠共享同一個核心緩沖區,進而又能夠節省記憶體的消耗。

記憶體映射I/O所帶來的性能優勢在大型檔案中執行重複随機通路時最有可能展現出來。如果順序通路一個檔案,并假設執行I/O時使用的緩沖區大小足夠大以至于能夠避免執行大量的I/O系統調用,那麼與read()和write()相比,mmap帶來的性能上的提升就非常有限或者說根本就沒有帶來性能上的提升。性能提升的幅度之是以非常有限的原因是不管使用何種技術,真個檔案的内容在磁盤和記憶體之間隻傳輸一次,效率的提高主要得益于減少了使用者空間和核心空間之間得一次資料傳輸,并且與磁盤I/O所需得時間相比,記憶體使用量得降低通常是可以忽略的。

對于小資料量I/O的開銷(即映射、分頁故障、接觸映射以及更新硬體記憶體管理單元的超前轉換緩沖器)實際上要比簡單的read()和write()大。此外,有些時候核心難以高效處理可寫入映射的回寫(在這種情況下,使用msync()或sync_file_range()有助于提高效率)。

MAP_FIXED

MAP_FIXED是mmap() flag中的一個标記位,主要作用就是強制核心原樣地解釋使用者傳遞的addr位址,按照應用程式指定的位址進行配置設定空間,而不是僅僅作為一種提升資訊。如果 mmap函數指定了MAP_FIXED标記位,addr位址必須是頁對齊的。

特别要注意的是如果要增強程式的可移植性,不建議使用MAP_FIXED。因為一旦使用該标記位 就是由使用者強制指定配置設定的使用者空間位址,而每個系統千差萬别,可移植性較差。

如果在調用mmap()時指定了MAP_FIXED,并且記憶體區域的起始位址為addr,覆寫的length位元組與之前的映射分頁重疊了,那麼重疊的分頁會被新映射替代。使用這個特性可移植地将一個檔案的多個部分映射進一塊連續的記憶體區域。

可以利用上述這個特性來增強使用MAP_FIXED的可移植性,具體使用方法:

  • 1:使用mmap()建立一個匿名映射。在mmap()調用中将addr指定為NULL 并且不指定MAP_FIXED标記,這樣就允許核心為映射選擇一個位址。
  • 2:使用一系列指定MAP_FIXED标記的mmap()調用來将檔案區域映射(即重疊)在上一步中建立的映射的不同部分。

盡管可以忽略第一個步驟而直接使用一系列mmap() MAP_FIXED操作來在應用程式選中的位址範圍内建立一組連續的映射,但這種做法的可移植性與上面這種兩步做法相比可移植性要差。一個應用程式應該避免在固定的位址處建立新映射。上面的第一步避免了可移植性問題的出現,因為這一步讓核心選擇一個連續的位址範圍,然後在該位址範圍内建立新映射。

參考資料

《linux/UNIX 系統程式設計手冊》

繼續閱讀