OS - MMAP初探
文章目錄
- 生猛干貨
- What's mmap
- mmap 函數
- mmap() 的底層原理
- 虛擬內存空間與物理內存空間
- vm_area_struct 結構
- 對文件的讀寫
- 搞定計算機基礎內功
生猛干貨
計算機專業學生、非科班程序員必備! 90%程序員忽略的大廠招聘考點、技能進階秘訣盡在這里!
What’s mmap
https://man7.org/linux/man-pages/man2/mmap.2.html
簡單來說: mmap() 系統調用能夠將文件映射到內存空間,然后可以通過讀寫內存來讀寫文件
mmap 函數
#include <sys/mman.h>void *mmap(void *addr, size_t length, int prot, int flags,int fd, off_t offset);- start:指定要映射的內存地址,一般設置為 NULL 讓操作系統自動選擇合適的內存地址。
- length:映射地址空間的字節數,它從被映射文件開頭 offset 個字節開始算起。
- prot:指定共享內存的訪問權限。可取如下幾個值的可選:PROT_READ(可讀), PROT_WRITE(可寫), PROT_EXEC(可執行), PROT_NONE(不可訪問)。
- flags:由以下幾個常值指定:MAP_SHARED(共享的) MAP_PRIVATE(私有的), - MAP_FIXED(表示必須使用 start 參數作為開始地址,如果失敗不進行修正),其中,MAP_SHARED , MAP_PRIVATE必選其一,而 MAP_FIXED 則不推薦使用。
- fd:表示要映射的文件句柄。
- offset:表示映射文件的偏移量,一般設置為 0 表示從文件頭部開始映射。
函數的返回值為最后文件映射到進程空間的地址,進程可直接操作起始地址為該值的有效地址。
舉個例子
int main() {...fd = open(name, flag, mode);if (fd < 0) {// error process...exit(1);}addr = mmap(NULL, len, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_SHARED, fd, 0);if (addr < 0) {// error process...exit(1);}memset(addr, 0, len);...exit(0); }首先通過 open() 系統調用打開一個文件,然后通過調用 mmap() 把文件映射到內存空間,映射成功后就可以通過操作函數返回的內存地址來對文件進行讀寫操作。
mmap() 的底層原理
虛擬內存空間與物理內存空間
我們先來看下 操作系統的虛擬內存空間與物理內存空間的概念 。
在 32位的 Linux 內核中,每個進程都獨有 4GB 的虛擬內存空間,但所有進程卻共用相同的物理內存空間。
物理內存空間就是安裝在電腦上的內存條,如果內存條只有 1GB,那么物理內存空間就只有 1GB。但虛擬內存空間是邏輯上的內存空間,虛擬內存空間必須映射到物理內存空間才能使用。
虛擬內存空間與物理內存空間映射關系如下:
映射是按內存頁進行的,一個內存頁為 4KB 大小。
在上圖中,P1 是進程1,P2 是進程2。
- 進程1的虛擬內存頁A映射到物理內存頁A
- 進程2的虛擬內存頁A映射到物理內存頁B
- 進程1的虛擬內存頁B和進程2的虛擬內存頁B同時映射到物理內存頁C,也就是說進程1和進程2共享了物理內存頁C
vm_area_struct 結構
在Linux內核中,虛擬內存是用過結構體 vm_area_struct 來管理的,通過 vm_area_struct 結構體可以把虛擬內存劃分為多個用途不相同的內存區,比如可以劃分為數據段區、代碼段區等等.
vm_area_struct
struct vm_area_struct { struct mm_struct * vm_mm; /* The address space we belong to. */ unsigned long vm_start; /* Our start address within vm_mm. */ unsigned long vm_end; /* The first byte after our end addresswithin vm_mm. *//* linked list of VM areas per task, sorted by address */ struct vm_area_struct *vm_next;pgprot_t vm_page_prot; /* Access permissions of this VMA. */ unsigned long vm_flags; /* Flags, listed below. */rb_node_t vm_rb;struct vm_area_struct *vm_next_share; struct vm_area_struct **vm_pprev_share;/* Function pointers to deal with this struct. */ struct vm_operations_struct * vm_ops; ... struct file * vm_file; /* File we map to (can be NULL). */ ... };vm_area_struct 結構各個字段作用:
- vm_mm:指向進程內存空間管理對象。
- vm_start:內存區的開始地址。
- vm_end:內存區的結束地址。
- vm_next:用于連接進程的所有內存區。
- vm_page_prot:指定內存區的訪問權限。
- vm_flags:內存區的一些標志。
- vm_file:指向映射的文件對象。
- vm_ops:內存區的一些操作函數。
vm_area_struct 結構與虛擬內存地址的關系如下圖:
每個進程都由 task_struct 結構進行管理,task_struct 結構中的 mm 成員指向了每個進程的內存管理結構 mm_struct,而 mm_struct 結構的 mmap 成員記錄了進程虛擬內存空間各個內存區的 vm_area_struct 結構鏈表。
當調用 mmap() 時,內核會創建一個 vm_area_struct 結構,并且把 vm_start 和 vm_end 指向虛擬內存空間的某個內存區,并且把 vm_file 字段指向映射的文件對象。然后調用文件對象的 mmap 接口來對 vm_area_struct 結構的 vm_ops 成員進行初始化,如 ext2 文件系統的文件對象會調用 generic_file_mmap() 函數進行初始化,代碼如下:
static struct vm_operations_struct generic_file_vm_ops = { nopage: filemap_nopage, };int generic_file_mmap(struct file * file, struct vm_area_struct * vma) { struct address_space *mapping = file->f_dentry->d_inode->i_mapping; struct inode *inode = mapping->host;if ((vma->vm_flags & VM_SHARED) && (vma->vm_flags & VM_MAYWRITE)) { if (!mapping->a_ops->writepage) return -EINVAL; } if (!mapping->a_ops->readpage) return -ENOEXEC; UPDATE_ATIME(inode); vma->vm_ops = &generic_file_vm_ops; return 0; }vm_operations_struct 結構的 nopage 接口會在訪問內存發生異常時被調用,上面指向的是 filemap_nopage() 函數,filemap_nopage() 函數的主要工作是:
處理過程如下
如上圖所示,虛擬內存頁m 映射到 物理內存頁x,并且把映射的文件的內容讀入到物理內存中,這樣就把內存與文件的映射關系建立起來,對映射的內存區進行讀寫操作實際上就是對文件的讀寫操作。
一般來說,對映射的內存空間進行讀寫并不會實時寫入到文件中,所以要對內存與文件進行同步時需要調用 msync() 函數來實現。
對文件的讀寫
像 read()/write() 這些系統調用,首先需要進行內核空間,然后把文件內容讀入到緩存中,然后再對緩存進行讀寫操作,最后由內核定時同步到文件中。過程如下圖:
而調用 mmap() 系統調用對文件進行映射后,用戶對映射后的內存進行讀寫實際上是對文件緩存的讀寫,所以減少了一次系統調用,從而加速了對文件讀寫的效率。如下圖:
搞定計算機基礎內功
總結
以上是生活随笔為你收集整理的OS - MMAP初探的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Java - 从文件压缩聊一聊I/O一二
- 下一篇: OS - 计算机基本组成