mmap 即 memory map奏甫,也就是內存映射尖飞。
mmap操作提供了一種機制症副,讓用戶程序直接訪問設備內存,這種機制葫松,相比較在用戶空間和內核空間互相拷貝數據瓦糕,效率更高底洗。在要求高性能的應用中比較常用腋么。mmap映射內存必須是頁面大小的整數倍,面向流的設備不能進行mmap亥揖,mmap的實現和硬件有關珊擂。
映射條件:
mmap()必須以PAGE_SIZE為單位進行映射圣勒,而內存也只能以頁為單位進行映射,若要映射非PAGE_SIZE整數倍的地址范圍摧扇,要先進行內存對齊圣贸,強行以PAGE_SIZE的倍數大小進行映射。
頭文件:
<sys/mman.h>
函數原型:
void* mmap(void* start,size_t length,int prot,int flags,int fd,off_t offset);
int munmap(void* start,size_t length);
參數說明:
start:映射區(qū)的開始地址扛稽,設置為0時表示由系統(tǒng)決定映射區(qū)的起始地址吁峻。
length:映射區(qū)的長度。//長度單位是 以字節(jié)為單位在张,不足一內存頁按一內存頁處理
prot:期望的內存保護標志用含,不能與文件的打開模式沖突。是以下的某個值帮匾,可以通過or運算合理地組合在一起
PROT_EXEC //頁內容可以被執(zhí)行
PROT_READ //頁內容可以被讀取
PROT_WRITE //頁可以被寫入
PROT_NONE //頁不可訪問
flags:指定映射對象的類型啄骇,映射選項和映射頁是否可以共享。它的值可以是一個或者多個以下位的組合體
MAP_FIXED //使用指定的映射起始地址瘟斜,如果由start和len參數指定的內存區(qū)重疊于現存的映射空間缸夹,重疊部分將會被丟棄。如果指定的起始地址不可用螺句,操作將會失敗虽惭。并且起始地址必須落在頁的邊界上。
MAP_SHARED //與其它所有映射這個對象的進程共享映射空間壹蔓。對共享區(qū)的寫入趟妥,相當于輸出到文件。直到msync()或者munmap()被調用佣蓉,文件實際上不會被更新披摄。
MAP_PRIVATE //建立一個寫入時拷貝的私有映射。內存區(qū)域的寫入不會影響到原文件勇凭。這個標志和以上標志是互斥的疚膊,只能使用其中一個。
MAP_DENYWRITE //這個標志被忽略虾标。
MAP_EXECUTABLE //同上
MAP_NORESERVE //不要為這個映射保留交換空間寓盗。當交換空間被保留,對映射區(qū)修改的可能會得到保證璧函。當交換空間不被保留傀蚌,同時內存不足,對映射區(qū)的修改會引起段違例信號蘸吓。
MAP_LOCKED //鎖定映射區(qū)的頁面善炫,從而防止頁面被交換出內存。
MAP_GROWSDOWN //用于堆棧库继,告訴內核VM系統(tǒng)箩艺,映射區(qū)可以向下擴展窜醉。
MAP_ANONYMOUS //匿名映射,映射區(qū)不與任何文件關聯(lián)艺谆。
MAP_ANON //MAP_ANONYMOUS的別稱榨惰,不再被使用。
MAP_FILE //兼容標志静汤,被忽略琅催。
MAP_32BIT //將映射區(qū)放在進程地址空間的低2GB,MAP_FIXED指定時會被忽略虫给。當前這個標志只在x86-64平臺上得到支持恢暖。
MAP_POPULATE //為文件映射通過預讀的方式準備好頁表。隨后對映射區(qū)的訪問不會被頁違例阻塞狰右。
MAP_NONBLOCK //僅和MAP_POPULATE一起使用時才有意義杰捂。不執(zhí)行預讀,只為已存在于內存中的頁面建立頁表入口棋蚌。
fd:有效的文件描述詞嫁佳。一般是由open()函數返回,其值也可以設置為-1谷暮,此時需要指定flags參數中的MAP_ANON,表明進行的是匿名映射蒿往。
offset:被映射對象內容的起點。
返回值
成功執(zhí)行時湿弦,mmap()返回被映射區(qū)的指針瓤漏,munmap()返回0。失敗時颊埃,mmap()返回MAP_FAILED[其值為(void *)-1]蔬充,munmap返回-1。errno被設為以下的某個值
EACCES:訪問出錯
EAGAIN:文件已被鎖定班利,或者太多的內存已被鎖定
EBADF:fd不是有效的文件描述詞
EINVAL:一個或者多個參數無效
ENFILE:已達到系統(tǒng)對打開文件的限制
ENODEV:指定文件所在的文件系統(tǒng)不支持內存映射
ENOMEM:內存不足饥漫,或者進程已超出最大內存映射數量
EPERM:權能不足,操作不允許
ETXTBSY:已寫的方式打開文件罗标,同時指定MAP_DENYWRITE標志
SIGSEGV:試著向只讀區(qū)寫入
SIGBUS:試著訪問不屬于進程的內存區(qū)
特點:
- mmap 向應用程序提供的內存訪問接口是內存地址連續(xù)的庸队,但是對應的磁盤文件的 page 可以不是地址連續(xù)的闯割;
- mmap 提供的內存空間是虛擬空間(虛擬內存)彻消,而不是物理空間(物理內存),因此完全可以分配遠遠大于物理內存大小的虛擬空間(例如 16G 內存主機分配 1000G 的 mmap 內存空間)宙拉;
- mmap 負責映射文件邏輯上一段連續(xù)的數據(物理上可以不連續(xù)存儲)映射為連續(xù)內存宾尚,而這里的文件可以是磁盤文件、驅動假造出的文件(例如 DMA 技術)以及設備鼓黔;
- mmap 由操作系統(tǒng)負責管理央勒,對同一個文件地址的映射將被所有線程共享,操作系統(tǒng)確保線程安全以及線程可見性澳化;
- 速度快崔步,mmap 被認為快的原因是因為建立了page到用戶進程的虛地址空間映射,以讀取文件為例缎谷,避免了page從內核空間拷貝到用戶空間井濒。
- 節(jié)約內存,由于用戶空間與內核空間實際上共用同一份數據列林,因此在大文件場景下在實際物理內存占用上有優(yōu)勢瑞你。
使用說明:
- 最終被映射文件(即參數的fd)的內容的長度不會超過文件本身的初始大小,即映射不能改變文件的大邢3铡者甲;
- 在linux中,內存的保護是以頁為基本單位的砌创,即使被映射文件只有一個字節(jié)大小虏缸,內核也會為映射分配一個頁面大小的內存。當被映射文件小于一個頁面大小時嫩实,進程可以對從mmap()返回地址開始的一個頁面大小進行訪問刽辙,而不會出錯;但是甲献,如果對一個頁面以外的地址空間進行訪問宰缤,則導致錯誤發(fā)生。因此晃洒,可用于進程間通信的有效地址空間大小不會超過文件大小及一個頁面大小的和慨灭。
- 文件一旦被映射后,調用mmap()的進程對返回地址的訪問是對某一內存區(qū)域的訪問球及,暫時脫離了磁盤上文件的影響缘挑。所有對mmap()返回地址空間的操作只在內存中有意義,只有在調用了munmap()后或者msync()時桶略,才把內存中的相應內容寫回磁盤文件语淘,所寫內容仍然不能超過文件的大小。
- 可以用于進程通信的有效地址空間大小大體上受限于被映射文件的大小际歼,但不完全受限于文件大小惶翻。假設有兩個進程a和b,a用mmap映射了文件file_test鹅心。在進程a執(zhí)行munmap前吕粗,b也內存映射file_test,有效地址空間大小為a映射的length旭愧,否則受限于文件大小颅筋。
適用場景:
mmap 的適用場景實際上非常受限宙暇,在如下場合下可以選擇使用 mmap 機制:
- 多個線程以只讀的方式同時訪問一個文件,這是因為 mmap 機制下多線程共享了同一物理內存空間议泵,因此節(jié)約了內存占贫。
- mmap 非常適合用于進程間通信,這是因為對同一文件對應的 mmap 分配的物理內存天然多線程共享先口,并可以依賴于操作系統(tǒng)的同步原語型奥。例如多個進程可能依賴于同一個動態(tài)鏈接庫,利用 mmap 可以實現內存僅僅加載一份動態(tài)鏈接庫碉京,多個進程共享此動態(tài)鏈接庫厢汹。
- mmap 雖然比 sendfile 等機制多了一次 CPU 全程參與的內存拷貝,但是用戶空間與內核空間并不需要數據拷貝谐宙,因此在正確使用情況下并不比 sendfile 效率差烫葬。
其他注意項:
基于缺頁異常的懶加載
出于節(jié)約物理內存以及 mmap 方法快速返回的目的,mmap 映射采用懶加載機制凡蜻。具體來說厘灼,通過 mmap 申請 1000G 內存可能僅僅占用了 100MB 的虛擬內存空間,甚至沒有分配實際的物理內存空間咽瓷。當你訪問相關內存地址時设凹,才會進行真正的 write、read 等系統(tǒng)調用茅姜。CPU 會通過陷入缺頁異常的方式來將磁盤上的數據加載到物理內存中闪朱,此時才會發(fā)生真正的物理內存分配。數據一致性由 OS 確保
當發(fā)生數據修改時钻洒,內存出現臟頁奋姿,與磁盤文件出現不一致。mmap 機制下由操作系統(tǒng)自動完成內存數據落盤(臟頁回刷)素标,用戶進程通常并不需要手動管理數據落盤称诗。