本文章以Linux爲例,講解一下虛擬內存系統的工做原理,windows系統的原理也是大同小異,有興趣的讀者能夠自行查閱相關資料。linux
linux內核以及它管理用戶內存的機制,下面咱們以應用程序gonzo的內存示意圖爲例,進行詳細說明。windows
Linux進程在內核中是以一個task_struct實例來實現的,稱爲進程描述符。task_struct的mm字段指向了內存描述符,即mm_struct,它是一份可執行程序的內存結構概要。如上圖所示,它存儲了內存各個內存端的起始位置和結束位置,進程使用的物理內存頁的數量,進程使用的虛擬地址空間等信息。在內存描述符內部,還有兩個內存管理的重要結構:virtual memory areas和page tables。下圖就是Gonzo的內存區域示意圖:數據結構
每個virtual memory area(VMA)都是一段連續的虛擬內存地址,這些內存區域毫不會重合。一個vm_area_struct描述一個內存區域,包括了它的起始地址和結束地址,內存訪問權限標誌位,以及一個vm_file字段(若是有該字段的話,用來指定哪一個文件映射到了該內存區域)。VMA不會映射匿名文件。進程內存佈局中除了內存映射段外的每個內存段都對應一個VMA。這種方式儘管在X86機器上很常見,但這並非硬性要求。VMA們並不關心它們對應的是哪一個段。函數
一個程序的VMA們都是做爲一個鏈表存在於內存描述符的mmap字段中的,而且按照虛擬地址進行了排序,而且是一個以mm_rb爲根節點的紅黑樹。採用紅黑樹的數據結構是爲了方便內核給定虛擬地址後快速查找對應的內存區域。當你讀/proc/pid_of_process/maps這個文件時,內核就是簡單的遍歷進程的VMA鏈表並挨個打印。佈局
Windows中的EPROCESS塊就是task_struct和mm_struct的混合,它對應於VMA的是一個稱爲Virtual Address Descriptor(VAD)的數據結構,VAD們存儲在一個AVL樹中。有意思的是,Windows和linux的區別真的很小。post
4GB的虛擬內存地址空間被分紅不少頁。X86處理器在32位模式下支持4KB、2MB以及4MB大小的頁。Linux和Windows都是用的4KB的頁來分割用戶虛擬地址空間的。0-4096字節落在page 0,4096-8192字節落在page 1,以此類推。VMA的大小必定是page大小的倍數。下圖就是用4KB的頁分割的3GB用戶虛擬地址空間示意:spa
處理器利用page tables來將虛擬內存地址轉換爲物理內存地址。每一個進程都有本身的page tables,不管進程切換什麼時候發生,用戶態的page table也會跟着切換。Linux在內存描述符中的pgd字段存儲了一個指向該進程page tables的指針。每個虛擬內存頁對應一個page table entry,一個X86的頁的結構以下:翻譯
Linux有函數來讀取和設置PTE中的每個標誌位。位P告訴處理器該虛擬頁是否要在物理內存中呈現。若是設爲0,訪問該頁時會觸發一個頁錯誤。R/W標誌位表明了讀寫權限,若是爲0則該頁爲只讀。U/S標誌位表明了普通用戶和超級用戶,若是設置爲0,則該頁只能被內核訪問。這些標誌位都是用來實現前面看到的只讀內存和內核態地址空間的。設計
D和A標誌位表明了dirty和accessed,一個髒頁表示該頁已經被寫過,一個被訪問過的頁表示該頁被讀過或者寫過。最後,PTE存儲了其對應的物理內存地址的起始地址,4KB對齊。指針
內存保護是以頁爲單位進行的,由於每一個頁都共用U/S和R/W標誌位。可是同一個物理內存頁能夠對應多個虛擬內存頁,這些不一樣的虛擬內存也可能有不一樣的保護標誌位,因此要記住:在VMA設置的權限標誌位不必定真正的用到了物理內存的保護上。
虛擬內存不存儲任何東西,它只是簡單的將程序的地址空間映射到底層的物理內存空間,物理地址空間纔是處理器真正操做的內存空間。物理地址空間也被分紅了以頁爲單位的大小。每一個頁是物理內存管理的最小單位。32位Linux和Windows都是以4KB爲大小劃分頁的。下圖所示爲一個2GB大小的RAM
咱們把virtual memory areas,page table entry以及page frame放在一塊兒,理解一下它們是如何協做的,下圖是一個用戶堆的示例:
藍色部分表明了VMA對應的地址範圍,每一項都是一個page table entry,每一個箭頭表明從PTE到物理page frame的映射,某些PTE沒有箭頭,表明這些PTE的P標誌位被清零了。這多是由於這些頁歷來沒有被訪問過或者頁已經被換出了。不管哪一種狀況,訪問這些頁都會致使缺頁錯誤。
一個VMA就像一份你的程序和內核之間合約,你要求完成一些事情,好比內存分配、文件映射等,內核說沒問題,而後它建立或者更新合適的VMA。可是爲了效率,內核不會立馬相應你的請求,直到第一次訪問頁產生缺頁錯誤時纔會去作,這也是虛擬內存的設計原則。
讓咱們看下全部的這些數據結構聯合起來是如何工做的,下圖是一個內存分配的示例:
當程序經過brk()系統調用請求更多的內存時,內核簡單的更新堆的VMA,這時並無page frame分配。
文章參考翻譯自:https://manybutfinite.com/post/how-the-kernel-manages-your-memory/