mmap/munmap是常用的一個系統調用,使用場景是:分配內存、讀寫大文件、連接動態庫文件、多進程間共享內存。
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。