mmap/munmap接口是用戶空間的最常用的一個系統調用接口,無論是在用戶程序中分配內存、讀寫大文件,鏈接動態庫文件,還是多進程間共享內存,都可以看到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參數通常表示映射頁面的的讀寫權限,可以有如下參數組合:
- PROT_EXEC:表示映射的頁面是可以執行的。
- PROT_READ:表示映射的頁面是可以讀取的。
- PROT_WRITE:表示映射的頁面是可以寫入的。
- PROT_NONE:表示映射的頁面是不可訪問的。
flags參數也是一個重要的參數,有如下常見的參數:
- MAP_SHARED:創建一個共享映射的區域。多個進程可以通過共享映射方式來映射一個文件,這樣其他進程也可以看到映射內容的改變,修改后的內容會同步到磁盤文件中。
- MAP_PRIVATE:創建一個私有的寫時復制的映射。多個進程可以通過私有映射的方式來映射一個文件,這樣其他進程不會看到映射內容的改變,修改后的內容也不會同步到磁盤文件中。
- MAP_ANONYMOUS:創建一個匿名映射,即沒有關聯到文件的映射。
- MAP_FIXED:使用參數addr創建映射,如果內核無法映射指定地址addr,那么mmap會返回失敗,參數addr要求按頁對齊。如果addr和length指定的進程地址空間和已有的VMA區域重疊,那么內核會調用do_munmap()函數把這段重疊區域銷毀,然后重新映射新的內容。
- MAP_POPULATE:對于文件映射來說,會提前預讀文件內容到映射區域,該特性只支持私用映射。
參數fd可以看出mmap映射是否和文件相關聯,因此linux內核中映射可以分為匿名映射和文件映射。
- 匿名映射:沒有映射對應的相關文件,這種映射的內存區域的內容會被初始化為0。
- 文件映射:映射和實際文件相關聯,通常是把文件的內容映射到進程地址空間,這樣應用程序就可以像操作進程地址空間一樣讀寫文件。
最后根據文件關聯性和映射區域是否共享等屬性,又可以分為如下4種,見表2.1。
- 私有匿名映射
當使用參數fd=-1且flags=MAP_ANONYMOUS | MAP_PRIVATE時,創建的mmap映射是私有匿名映射。私有匿名映射最常見的用途是在glibc分配大塊內存中,當需要的分配的內存大于MMAP_THREASHOLD(128KB)時,glibc會默認使用mmap代替brk來分配內存。
- 共享匿名映射
當使用參數fd=-1且flags=MAP_ANONYMOUS | MAP_SHARED。在這種情況下,創建共享匿名映射。共享匿名映射讓相關進程共享一塊內存區域,通常用于父子進程的之間通信。
創建共享匿名映射有如下兩種方式:
(1)fd=-1且flags= MAP_ANONYMOUS|MAP_SHARED。在這種情況下,do_mmap_pgoff()->mmap()函數最終調用shmem_zero_setup()來打開一個"/dev/zero"特殊的設備文件。
(2)另外一個是直接打開"/dev/zero"設備文件,然后使用這個文件句柄來創建mmap。
- 私有文件映射
私有文件映射時flags的標志位被設置為MAP_PRIVATE,那么就會創建私有文件映射。
私有文件映射的最常用的場景是加載動態共享庫。
- 共享文件映射
創建文件映射時flags的標志位被設置為MAP_SHARED,那么就會創建共享文件映射。如果prot參數指定了PROT_WRITE,那么打開文件需要制定O_RDWR標志位。共享文件映射通常有如下場景:
(1)讀寫文件:
把文件內容映射到進程地址空間,同時對映射的內容做了修改,內核的回寫機制(writeback)最終會把修改的內容同步到磁盤中。
(2)進程間通信:
進程之間的進程地址空間相互隔離,一個進程不能訪問到另外一個進程的地址空間。如果多個進程都同時映射到一個相同的文件,就實現了多進程間的共享內存的通信。如果一個進程對映射內容做了修改,那么另外的進程是可以看到的。