Linux內存管理 mmap

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:在文件映射時,表示相對文件頭的偏移量;返回的地址是偏移量對應的虛擬地址。

下面是prot對應的參數組合:


#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 */

 

flags參數組合有:


#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. maisedv

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章