mmap進行內存映射的原理
mmap系統調用的最終目的是將,設備或文件映射到用戶進程的虛擬地址空間,實現用戶進程對文件的直接讀寫,這個任務可以分為以下三步:
1.在用戶虛擬地址空間中尋找空閒的滿足要求的一段連續的虛擬地址空間,為映射做准備(由內核mmap系統調用完成)
每個進程擁有3G字節的用戶虛存空間。但是,這並不意味著用戶進程在這3G的范圍內可以任意使用,因為虛存空間最終得映射到某個物理存儲空間(內存或磁盤空間),才真正可以使用。
那麼,內核怎樣管理每個進程3G的虛存空間呢?概括地說,用戶進程經過編譯、鏈接後形成的映象文件有一個代碼段和數據段(包括data段和bss段),其中代碼段在下,數據段在上。數據段中包括了所有靜態分配的數據空間,即全局變量和所有申明為static的局部變量,這些空間是進程所必需的基本要求,這些空間是在建立一個進程的運行映像時就分配好的。除此之外,堆棧使用的空間也屬於基本要求,所以也是在建立進程時就分配好的,如圖3.1所示:
圖3.1 進程虛擬空間的劃分
在內核中,這樣每個區域用一個結構struct vm_area_struct 來表示.它描述的是一段連續的、具有相同訪問屬性的虛存空間,該虛存空間的大小為物理內存頁面的整數倍。可以使用 cat /proc/
下面是struct vm_area_struct結構體的定義:
通常,進程所使用到的虛存空間不連續,且各部分虛存空間的訪問屬性也可能不同。所以一個進程的虛存空間需要多個vm_area_struct結構來描述。在vm_area_struct結構的數目較少的時候,各個vm_area_struct按照升序排序,以單鏈表的形式組織數據(通過vm_next指針指向下一個vm_area_struct結構)。但是當vm_area_struct結構的數據較多的時候,仍然采用鏈表組織的化,勢必會影響到它的搜索速度。針對這個問題,vm_area_struct還添加了vm_avl_hight(樹高)、vm_avl_left(左子節點)、vm_avl_right(右子節點)三個成員來實現AVL樹,以提高vm_area_struct的搜索速度。
假如該vm_area_struct描述的是一個文件映射的虛存空間,成員vm_file便指向被映射的文件的file結構,vm_pgoff是該虛存空間起始地址在vm_file文件裡面的文件偏移,單位為物理頁面。
圖3.2 進程虛擬地址示意圖
因此,mmap系統調用所完成的工作就是准備這樣一段虛存空間,並建立vm_area_struct結構體,將其傳給具體的設備驅動程序.
2. 建立虛擬地址空間和文件或設備的物理地址之間的映射(設備驅動完成)
建立文件映射的第二步就是建立虛擬地址和具體的物理地址之間的映射,這是通過修改進程頁表來實現的.mmap方法是file_opeartions結構的成員:
int (*mmap)(struct file *,struct vm_area_struct *);
linux有2個方法建立頁表:
(1) 使用remap_pfn_range一次建立所有頁表.
int remap_pfn_range(struct vm_area_struct *vma, unsigned long virt_addr, unsigned long pfn, unsigned long size, pgprot_t prot);
返回值:
成功返回 0, 失敗返回一個負的錯誤值
參數說明:
vma 用戶進程創建一個vma區域
virt_addr 重新映射應當開始的用戶虛擬地址. 這個函數建立頁表為這個虛擬地址范圍從 virt_addr 到 virt_addr_size.
pfn 頁幀號, 對應虛擬地址應當被映射的物理地址. 這個頁幀號簡單地是物理地址右移 PAGE_SHIFT 位. 對大部分使用, VMA 結構的 vm_paoff 成員正好包含你需要的值. 這個函數影響物理地址從 (pfn<
size 正在被重新映射的區的大小, 以字節.
prot 給新 VMA 要求的"protection". 驅動可(並且應當)使用在vma->vm_page_prot 中找到的值.
(2) 使用nopage VMA方法每次建立一個頁表項.
struct page *(*nopage)(struct vm_area_struct *vma, unsigned long address, int *type);
返回值:
成功則返回一個有效映射頁,失敗返回NULL.
參數說明:
address 代表從用戶空間傳過來的用戶空間虛擬地址.
返回一個有效映射頁.
(3) 使用方面的限制:
remap_pfn_range不能映射常規內存,只存取保留頁和在物理內存頂之上的物理地址。因為保留頁和在物理內存頂之上的物理地址內存管理系統的各個子模塊管理不到。640 KB 和 1MB 是保留頁可能映射,設備I/O內存也可以映射。如果想把kmalloc()申請的內存映射到用戶空間,則可以通過mem_map_reserve()把相應的內存設置為保留後就可以。
3. 當實際訪問新映射的頁面時的操作(由缺頁中斷完成)
(1) page cache及swap cache中頁面的區分:一個被訪問文件的物理頁面都駐留在page cache或swap cache中,一個頁面的所有信息由struct page來描述。struct page中有一個域為指針mapping ,它指向一個struct address_space類型結構。page cache或swap cache中的所有頁面就是根據address_space結構以及一個偏移量來區分的。
(2) 文件與 address_space結構的對應:一個具體的文件在打開後,內核會在內存中為之建立一個struct inode結構,其中的i_mapping域指向一個address_space結構。這樣,一個文件就對應一個address_space結構,一個 address_space與一個偏移量能夠確定一個page cache 或swap cache中的一個頁面。因此,當要尋址某個數據時,很容易根據給定的文件及數據在文件內的偏移量而找到相應的頁面。
(3) 進程調用mmap()時,只是在進程空間內新增了一塊相應大小的緩沖區,並設置了相應的訪問標識,但並沒有建立進程空間到物理頁面的映射。因此,第一次訪問該空間時,會引發一個缺頁異常。
(4) 對於共享內存映射情況,缺頁異常處理程序首先在swap cache中尋找目標頁(符合address_space以及偏移量的物理頁),如果找到,則直接返回地址;如果沒有找到,則判斷該頁是否在交換區 (swap area),如果在,則執行一個換入操作;如果上述兩種情況都不滿足,處理程序將分配新的物理頁面,並把它插入到page cache中。進程最終將更新進程頁表。
注:對於映射普通文件情況(非共享映射),缺頁異常處理程序首先會在page cache中根據address_space以及數據偏移量尋找相應的頁面。如果沒有找到,則說明文件數據還沒有讀入內存,處理程序會從磁盤讀入相應的頁面,並返回相應地址,同時,進程頁表也會更新.
(5) 所有進程在映射同一個共享內存區域時,情況都一樣,在建立線性地址與物理地址之間的映射之後,不論進程各自的返回地址如何,實際訪問的必然是同一個共享內存區域對應的物理頁面。