專題:Linux內存管理專題
關鍵詞:文件映射、匿名映射、私有映射、共享映射
mmap/munmap是常用的一個系統調用,使用場景是:分配內存、讀寫大文件、連接動態庫文件、多進程間共享內存。
更詳細解讀參考《Linux內存管理 (9)mmap(補充)》。
1. mmap/munmap介紹
mmap/munmap函數聲明如下:
#include <sys/mman.h> void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset); int munmap(void *addr, size_t length);
-
addr:如果不為NULL,內核會在此地址創建映射;否則,內核會選擇一個合適的虛擬地址。
-
length:表示映射到進程地址空間的大小。
-
prot:內存區域的讀/寫/執行屬性。
-
flags:內存映射的屬性,共享、私有、匿名、文件等。
- fd:表示這是一個文件映射,fd是打開文件的句柄。
- offset:在文件映射時,表示相對文件頭的偏移量;返回的地址是偏移量對應的虛擬地址。
#define PROT_READ 0x1 /* page can be read */ #define PROT_WRITE 0x2 /* page can be written */ #define PROT_EXEC 0x4 /* page can be executed */ #define PROT_SEM 0x8 /* page may be used for atomic ops */ #define PROT_NONE 0x0 /* page can not be accessed */ #define PROT_GROWSDOWN 0x01000000 /* mprotect flag: extend change to start of growsdown vma */ #define PROT_GROWSUP 0x02000000 /* mprotect flag: extend change to end of growsup vma */
#define MAP_SHARED 0x01 /* Share changes */---------創建一個共享映射的區域,多個進程可以映射到一個文件,掐進程可以看到映射內容的改變,修改后內容會同步到磁盤中。 #define MAP_PRIVATE 0x02 /* Changes are private */--創建一個私有的寫時復制的映射,其他進程看不到映射內容的改變,也不會同步到磁盤中。 #define MAP_TYPE 0x0f /* Mask for type of mapping */ #define MAP_FIXED 0x10 /* Interpret addr exactly */-使用指定的映射起始地址,如果有start和len參數指定的內存區重疊於現存的映射空間,重疊部分將會被丟棄。如果指定起始地址不可用,操作將會失敗。並且起始地址必須落在頁的邊界上。 #define MAP_ANONYMOUS 0x20 /* don't use a file */---匿名映射,映射區不與任何文件關聯。此時fd應設置為-1。 #ifdef CONFIG_MMAP_ALLOW_UNINITIALIZED # define MAP_UNINITIALIZED 0x4000000 /* For anonymous mmap, memory could be uninitialized */ #else # define MAP_UNINITIALIZED 0x0 /* Don't support this flag */ #endif #define MAP_GROWSDOWN 0x0100 /* stack-like segment */--------------告訴內核VM系統,映射區可以向下擴展。 #define MAP_DENYWRITE 0x0800 /* ETXTBSY */ #define MAP_EXECUTABLE 0x1000 /* mark it as an executable */ #define MAP_LOCKED 0x2000 /* pages are locked */-------------------鎖定映射區頁面,從而防止頁面被交換出內存。 #define MAP_NORESERVE 0x4000 /* don't check for reservations */ #define MAP_POPULATE 0x8000 /* populate (prefault) pagetables */---對文件映射來說,會提前預讀文件內容到映射區域,只支持私有映射。 #define MAP_NONBLOCK 0x10000 /* do not block on IO */--------------和MAP_POPULATE一起使用時才有意義。不執行預讀,只為已存在與內存中的頁面建立頁表入口。 #define MAP_STACK 0x20000 /* give out an address that is best suited for process/thread stacks */ #define MAP_HUGETLB 0x40000 /* create a huge page mapping */
2. mmap映射類型
根據mmap是否映射到文件、是共享還是私有映射,將映射類型分成四類,使用場景如下:
場景 | 私有影射 | 共享映射 |
匿名映射 | 通常用於內存分配 fd=-1,flags=MAP_ANONYMOUS|MAP_PRIVATE |
通常用於進程間內存共享,常用於父子進程之間通信。 FD=-1,flags=MAP_ANONYMOUS|MAP_SHARED |
文件映射 | 通常用於加載動態庫 flags=MAP_PRIVATE |
通常用於內存映射IO、進程間通信、讀寫文件。 flags=MAP_SHARED |
3. mmap流程
用戶空間的mmap,在內核中的起點是mmap_pgoff。
4. mmap使用兩個小問題
問題1:兩次對相同地址執行mmap是否成功?
#include <stdio.h> #include <sys/mman.h> void main(void) { char *pmap1, *pmap2; pmap1 = (char *)mmap(0x20000000, 10240, PROT_READ, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0); if(MAP_FAILED == pmap1) printf("pmap1 failed\n"); pmap2 = (char *)mmap(0x20000000, 1024, PROT_READ, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0); if(MAP_FAILED == pmap2) printf("pmap1 failed\n"); }
在Ubuntu上執行strace ./mmap結果如下:
... mmap(0x20000000, 10240, PROT_READ, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x20000000 mmap(0x20000000, 1024, PROT_READ, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x20000000 ...
如果將第二個mmap的MAP_FIXED去掉呢?結果如下:
... mmap(0x20000000, 10240, PROT_READ, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x20000000 mmap(0x20000000, 1024, PROT_READ, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fcb336e1000 ...
可以看出如果映射區屬性包含MAP_FIXED,則會覆蓋原來區域;如果沒有MAP_FIXED,內核會找到一個區域。
unsigned long mmap_region(struct file *file, unsigned long addr, unsigned long len, vm_flags_t vm_flags, unsigned long pgoff) { ... munmap_back: if (find_vma_links(mm, addr, addr + len, &prev, &rb_link, &rb_parent)) { if (do_munmap(mm, addr, len))-----------------------------將沖突區域去映射 return -ENOMEM; goto munmap_back; } ... }
問題2:在一個播放系統中同時打開幾十個不同高清視頻文件,發現播放有些卡頓,打開文件使用的是mmap,分析原因並解決。
mmap建立文件映射時,只建立了VMA,而沒有分配對應的頁面和建立映射關系。
播放時會不同發生缺頁異常去讀取文件內容,導致性能較差。
解決方法:1.對mmap映射后的地址用madvise(addr, len, MADV_SEQUENTIAL)。
2.通過"blockdev --setra"來增大內核默認預讀窗口,默認是128KB。
5. madvise