Mmap內存映射的原理以及實現
面試和工作中可能會用到mmap內存映射,今天就來聊一聊
1、mmap基礎概念
- mmap 即 memory map,也就是內存映射;
- mmap 是一種內存映射文件的方法,即將一個文件或者其它對象映射到進程的地址空間,實現文件磁盤地址和進程虛擬地址空間中一段虛擬地址的一一對映關系;
- 實現這樣的映射關系后,進程就可以采用指針的方式讀寫操作這一段內存,而系統會自動回寫臟頁面到對應的文件磁盤上;
- 即完成了對文件的操作而不必再調用 read、write 等系統調用函數。相反,內核空間對這段區域的修改也直接反映用戶空間,從而可以實現不同進程間的文件共享;
mmap 具有如下的特點:
- mmap 向應用程序提供的內存訪問接口是內存地址連續的,但是對應的磁盤文件的 block 可以不是地址連續的;
- mmap 提供的內存空間是虛擬空間(虛擬內存),而不是物理空間(物理內存),因此完全可以分配遠遠大于物理內存大小的虛擬空間(例如 16G 內存主機分配 1000G 的 mmap 內存空間);
- mmap 負責映射文件邏輯上一段連續的數據(物理上可以不連續存儲)映射為連續內存,而這里的文件可以是磁盤文件、驅動假造出的文件(例如 DMA 技術)以及設備;
- mmap 由操作系統負責管理,對同一個文件地址的映射將被所有線程共享,操作系統確保線程安全以及線程可見性;
- mmap 的設計很有啟發性。基于磁盤的讀寫單位是 block(一般大小為 4KB),而基于內存的讀寫單位是地址(雖然內存的管理與分配單位是 4KB)。換言之,CPU 進行一次磁盤讀寫操作涉及的數據量至少是 4KB,但是進行一次內存操作涉及的數據量是基于地址的,也就是通常的 64bit(64 位操作系統)。mmap 下進程可以采用指針的方式進行讀寫操作,這是值得注意的;
2、mmap內存映射原理
mmap內存映射的實現過程,總的來說可以分為三個階段:
2.1進程啟動映射過程,并在虛擬地址空間中為映射創建虛擬映射區域;
- 進程在用戶空間調用庫函數mmap,原型:void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset);
- 在當前進程的虛擬地址空間中,尋找一段空閑的滿足要求的連續的虛擬地址
- 為此虛擬區分配一個vm_area_struct結構,接著對這個結構的各個域進行了初始化
- 將新建的虛擬區結構(vm_area_struct)插入進程的虛擬地址區域鏈表或樹中
2.2調用內核空間的系統調用函數mmap(不同于用戶空間函數),實現文件物理地址和進程虛擬地址的一一映射關系
- 為映射分配了新的虛擬地址區域后,通過待映射的文件指針,在文件描述符表中找到對應的文件描述符,通過文件描述符,鏈接到內核“已打開文件集”中該文件的文件結構體(struct file),每個文件結構體維護著和這個已打開文件相關各項信息;
- 通過該文件的文件結構體,鏈接到file_operations模塊,調用內核函數mmap,其原型為:int mmap(struct file *filp, struct vm_area_struct *vma),不同于用戶空間庫函數;
- 內核mmap函數通過虛擬文件系統inode模塊定位到文件磁盤物理地址;
- 通過remap_pfn_range函數建立頁表,即實現了文件地址和虛擬地址區域的映射關系。此時,這片虛擬地址并沒有任何數據關聯到主存中;
2.3進程發起對這片映射空間的訪問,引發缺頁異常,實現文件內容到物理內存(主存)的拷貝
- 前兩個階段僅在于創建虛擬區間并完成地址映射,但是并沒有將任何文件數據的拷貝至主存。真正的文件讀取是當進程發起讀或寫操作時;
- 進程的讀或寫操作訪問虛擬地址空間這一段映射地址,通過查詢頁表,發現這一段地址并不在物理頁面上。因為目前只建立了地址映射,真正的硬盤數據還沒有拷貝到內存中,因此引發缺頁異常;
- 缺頁異常進行一系列判斷,確定無非法操作后,內核發起請求調頁過程。
- 調頁過程先在交換緩存空間(swap cache)中尋找需要訪問的內存頁,如果沒有則調用nopage函數把所缺的頁從磁盤裝入到主存中;
- 1之后進程即可對這片主存進行讀或者寫的操作,如果寫操作改變了其內容,一定時間后系統會自動回寫臟頁面到對應磁盤地址,也即完成了寫入到文件的過程;
- 修改過的臟頁面并不會立即更新回文件中,而是有一段時間的延遲,可以調用msync()來強制同步, 這樣所寫的內容就能立即保存到文件里了;
3、mmap函數實例分析
3.1mmap函數的原型
參數addr:指定映射的起始地址,通常設為NULL,由內核來分配
參數length:代表將文件中映射到內存的部分的長度。
參數prot:映射區域的保護方式。可以為以下幾種方式的組合:
- PROT_EXEC 映射區域可被執行
- PROT_READ 映射區域可被讀取
- PROT_WRITE 映射區域可被寫入
- PROT_NONE 映射區域不能存取
參數flags:映射區的特性標志位,常用的兩個選項是:
- MAP_SHARD:寫入映射區的數據會復制回文件,且運行其他映射文件的進程共享
- MAP_PRIVATE:對映射區的寫入操作會產生一個映射區的復制,對此區域的修改不會寫會原文件
參數fd:要映射到內存中的文件描述符,有open函數打開文件時返回的值。
參數offset:文件映射的偏移量,通常設置為0,代表從文件最前方開始對應,offset必須是分頁大小的整數倍。
函數返回值:實際分配的內存的起始地址
3.2munmap函數
與mmap函數成對使用的是munmap函數,它是用來解除映射的函數;
- 參數start:映射的起始地址
- 參數length:文件中映射到內存的部分的長度
- 返回值:解除成功返回0,失敗返回-1
3.3實例
下面是一個mmap使用的實例代碼
這段代碼實現了將測試文件testdata打開,并用mmap函數將文件映射到虛擬內存中,通過指針start對文件進行讀寫。在終端中可看到由文件讀取的數據。程序結束后,可以查看testdata文件,來查看寫入的數據