Linux虛擬內存系統詳解


本文章以Linux為例,講解一下虛擬內存系統的工作原理,windows系統的原理也是大同小異,有興趣的讀者可以自行查閱相關資料。

linux內核以及它管理用戶內存的機制,下面我們以應用程序gonzo的內存示意圖為例,進行詳細說明。

  

Linux進程在內核中是以一個task_struct實例來實現的,稱為進程描述符。task_structmm字段指向了內存描述符,即mm_struct,它是一份可執行程序的內存結構概要。如上圖所示,它存儲了內存各個內存端的起始位置和結束位置,進程使用的物理內存頁的數量,進程使用的虛擬地址空間等信息。在內存描述符內部,還有兩個內存管理的重要結構:virtual memory areaspage tables。下圖就是Gonzo的內存區域示意圖:

 

 

 

每一個virtual memory areaVMA)都是一段連續的虛擬內存地址,這些內存區域絕不會重合。一個vm_area_struct描述一個內存區域,包括了它的起始地址和結束地址,內存訪問權限標志位,以及一個vm_file字段(如果有該字段的話,用來指定哪個文件映射到了該內存區域)。VMA不會映射匿名文件。進程內存布局中除了內存映射段外的每一個內存段都對應一個VMA。這種方式盡管在X86機器上很常見,但這並不是硬性要求。VMA們並不關心它們對應的是哪個段。

 

一個程序的VMA們都是作為一個鏈表存在於內存描述符的mmap字段中的,並且按照虛擬地址進行了排序,並且是一個以mm_rb為根節點的紅黑樹。采用紅黑樹的數據結構是為了方便內核給定虛擬地址后快速查找對應的內存區域。當你讀/proc/pid_of_process/maps這個文件時,內核就是簡單的遍歷進程的VMA鏈表並挨個打印。

 

Windows中的EPROCESS塊就是task_structmm_struct的混合,它對應於VMA的是一個稱為Virtual Address Descriptor(VAD)的數據結構,VAD們存儲在一個AVL樹中。有意思的是,Windowslinux的區別真的很小。

 

4GB的虛擬內存地址空間被分成很多頁。X86處理器在32位模式下支持4KB2MB以及4MB大小的頁。LinuxWindows都是用的4KB的頁來分割用戶虛擬地址空間的。0-4096字節落在page 0,4096-8192字節落在page 1,以此類推。VMA的大小一定是page大小的倍數。下圖就是用4KB的頁分割的3GB用戶虛擬地址空間示意:

 

 

 

處理器利用page tables來將虛擬內存地址轉換為物理內存地址。每個進程都有自己的page tables,無論進程切換何時發生,用戶態的page table也會跟着切換。Linux在內存描述符中的pgd字段存儲了一個指向該進程page tables的指針。每一個虛擬內存頁對應一個page table entry,一個X86的頁的結構如下:

 

 

Linux有函數來讀取和設置PTE中的每一個標志位。位P告訴處理器該虛擬頁是否要在物理內存中呈現。如果設為0,訪問該頁時會觸發一個頁錯誤。R/W標志位代表了讀寫權限,如果為0則該頁為只讀。U/S標志位代表了普通用戶和超級用戶,如果設置為0,則該頁只能被內核訪問。這些標志位都是用來實現前面看到的只讀內存和內核態地址空間的。

 

DA標志位代表了dirtyaccessed,一個臟頁表示該頁已經被寫過,一個被訪問過的頁表示該頁被讀過或者寫過。最后,PTE存儲了其對應的物理內存地址的起始地址,4KB對齊。

 

內存保護是以頁為單位進行的,因為每個頁都共用U/SR/W標志位。但是同一個物理內存頁可以對應多個虛擬內存頁,這些不同的虛擬內存也可能有不同的保護標志位,所以要記住:在VMA設置的權限標志位不一定真正的用到了物理內存的保護上。

 

虛擬內存不存儲任何東西,它只是簡單的將程序的地址空間映射到底層的物理內存空間,物理地址空間才是處理器真正操作的內存空間。物理地址空間也被分成了以頁為單位的大小。每個頁是物理內存管理的最小單位。32LinuxWindows都是以4KB為大小划分頁的。下圖所示為一個2GB大小的RAM

 

 

我們把virtual memory areaspage table entry以及page frame放在一起,理解一下它們是如何協作的,下圖是一個用戶堆的示例:

 

  

藍色部分代表了VMA對應的地址范圍,每一項都是一個page table entry,每個箭頭代表從PTE到物理page frame的映射,某些PTE沒有箭頭,代表這些PTEP標志位被清零了。這可能是因為這些頁從來沒有被訪問過或者頁已經被換出了。無論哪種情況,訪問這些頁都會導致缺頁錯誤。

 

一個VMA就像一份你的程序和內核之間合約,你要求完成一些事情,比如內存分配、文件映射等,內核說沒問題,然后它創建或者更新合適的VMA。但是為了效率,內核不會立馬相應你的請求,直到第一次訪問頁產生缺頁錯誤時才會去做,這也是虛擬內存的設計原則。

 

讓我們看下所有的這些數據結構聯合起來是如何工作的,下圖是一個內存分配的示例:

 

當程序通過brk()系統調用請求更多的內存時,內核簡單的更新堆的VMA,這時並沒有page frame分配。

 

 

 

 

文章參考翻譯自:https://manybutfinite.com/post/how-the-kernel-manages-your-memory/


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM