1. 程式人生 > >32位Linux系統虛擬地址對映

32位Linux系統虛擬地址對映

  IA32體系即Intel32位體系架構,也被稱為i386、X86-32或X86。在Intel公司1985年推出的80386微處理器中首先使用。用以取代之前的X86-16位架構,包括8086、80186、80286晶片。談到這兒,就不得不說說X86架構的發展歷史。
  Intel 8086是由Intel於1978年所設計的16位微處理器晶片,是x86架構的鼻祖。8086是16位CPU,資料匯流排16條,地址匯流排20條,能儲存的地址的大小是2^20=1M。8086增加了4個段暫存器用來儲存各記憶體段的起始地址,這4個段暫存器分別為CS(程式碼段暫存器)、DS(資料段暫存器)、SS(堆疊段暫存器)、ES(擴充套件段暫存器)。

由於地址匯流排共20條,段地址有20位,但是段暫存器只有16位,不能儲存20位的地址。因此,將記憶體的大小劃分為16的倍數(此時還沒有作業系統,直接操作的是實體記憶體)。每塊記憶體起始地址的後四位都為0,段暫存器只儲存地址的高16位。正如前面所講,8086時,地址匯流排最多隻能儲存1M的地址空間。此時,實體記憶體=段基址+邏輯地址/偏移量。此時若要取資料,就需要找到實體記憶體,方法為從DS暫存器中取值,左移4位,就得到了真正的起始地址(DS<<4),再加上變數在該記憶體段上的偏移量(IP暫存器儲存了當前資料在記憶體段上的偏移量),就得到了資料的實體記憶體。DS<<4 + IP = 資料的地址。DS<<4 稱為段基址,稱IP暫存器儲存的數為邏輯地址、偏移量或者偏移地址。8086時,作業系統不存在,無任何許可權控制資訊,通過驅動介面可以任意更改段暫存器的值,無任何保護措施。稱這種模式為真實模式。真實模式下,實體地址=DS<<4 + IP。

  在真實模式下,最大訪問的實體記憶體是1M。CPU通電後,強制進入真實模式,CPU只能訪問1M的實體記憶體。通過Linux核心原始碼也可以看到,Linux核心image都是從0X10000開始載入的,虛擬地址是從0Xc0100000開始載入,因為前1M儲存的是驅動程式碼、顯示卡的快取等等。
  真實模式下訪問記憶體是極其不安全的,對於CPU的暫存器沒有任何有效性的保護。其實,在訪問一塊記憶體時,不僅僅需要知道記憶體的起始地址,還需要知道記憶體段的大小、記憶體的訪問許可權(可讀、可寫或者可執行)。這麼多資訊不可能全都放在段暫存器中,即使80386已經是32位CPU了,CS、DS、SS段暫存器還是16位的(同一體系,必須相容)。為了儲存記憶體段的其他資訊,從80386開始,增加了GDTR
LDTR兩個暫存器,GDTR中儲存的是GDT的地址,但是LDTR中儲存的是LDT的地址在GDT的索引。GDT是全域性的段描述符表,LDT是區域性的段描述符表,這兩個段描述符表也是80386新增的。可以把他們理解為一個結構體陣列,每個元素(段描述符表項)佔8位元組。後面會專門介紹這個段描述符表項。有了段描述符表儲存記憶體段資訊後,段暫存器就空出來了,轉而儲存段描述符表項的索引。其結構如下:

這裡寫圖片描述

  定址時,從暫存器中取出低地址第3位的數字,判斷記憶體段的資訊儲存在GDT還是LDT中,然後以暫存器的高13位數字為索引,在GDT表中找到段描述符表項。段描述符表項結構如下:

這裡寫圖片描述

   該結構是一個段描述符表項,佔8位元組,共64位。其中,有32位儲存記憶體段的起始地址,20位儲存記憶體段的長度,可以儲存的記憶體段的長度為2^20=1M。如果G位為0,長度的單位是byte,記憶體段的總長度是1M;如果G位為1,長度的單位是頁面(4K),記憶體段的總長度是1M*4K=4G。80386開始有了作業系統,將虛擬地址對映為實體地址是由MMU(Memory Management Unit)完成的,這種模式稱為保護模式。
   保護模式下,記憶體分段的地址對映定址過程是:從暫存器中取高13位,以此為索引,在GDT中找到相應的段描述符表項,從段描述符中得到記憶體段的地址,再將記憶體段的長度和偏移量做比較,如果偏移量>記憶體段的長度,則說明資料可能被修改,結束該程序的執行;如果偏移量合法,段基址加上偏移量就是線性地址。因此,保護模式下,記憶體分段的地址對映的定址方式為:GDT[DS>>3].BaseAddr + IP = 線性地址。
   得到線性地址後,需要檢查內是否開啟記憶體分頁機制。若沒有開啟記憶體分頁機制,線性地址就是實體地址。若開啟了記憶體分頁機制,此時的線性地址是虛擬地址,要通過多級頁表對映才能得到實體地址。
   怎樣判斷核心是否開啟記憶體分頁機制呢?不得不提到4個暫存器了,它們分別是CR0、CR2、CR3、CR4暫存器。CR0的最高位PG位若為0,未開啟分頁機制;若為1,開啟分頁機制。CR2儲存發生缺頁異常時的虛擬地址。CR3儲存當前程序的頁目錄的起始地址。CR4的PAE位若為0,未開啟實體地址擴充套件;若為1,開啟了實體地址擴充套件。通過CR0暫存器的PG位的值就可以判斷核心是否開啟分頁機制。
   32位系統需要二級頁面對映,36位系統需要三級頁面對映,64位系統需要四級頁面對映。下面就以32位系統的二級頁面對映為例,具體說一下記憶體分頁機制下,如何進行實體記憶體定址。
   上面說到過,開啟記憶體分頁機制後,通過記憶體分段地址對映的方式得到的線性地址是虛擬地址,還需要對虛擬地址進行記憶體分頁地址對映才能得到實體地址。虛擬地址共32位,其中高10位表示的是頁目錄的下標,次高10位表示頁表的下標,低12位表示物理頁面上的偏移量。具體劃分如下圖示:

這裡寫圖片描述
 
  高10位可以表示2^10=1024個索引,次高10位也可以表示2^10=1024個索引,低12位可以表示2^12=4K的偏移(物理頁面,1頁面=4K,設計並非偶然)。因此,頁目錄(Page Directory)和頁表(Page Table)可以看作含有1024個元素的陣列。每個元素佔4位元組,所以頁目錄、頁表的大小為4K,物理頁面的大小也是4K,頁表和物理頁面的起始地址都是4K的倍數,後16位都為0,所以頁目錄只儲存頁表地址的高20位,頁表只要儲存物理頁面地址的高20位。剩下的12位儲存的內容稍後會談到。
這會兒,記憶體分頁的地址對映過程即將浮出水面了。從CR3暫存器中拿到當前程序頁目錄的地址,根據高10位儲存的頁目錄下標定位到頁目錄中的Entry項,Entry項的高20位儲存的是頁表地址的高20位,將後16位補上0後,就是頁表的完整地址。再根據次高10位,找到頁表的Entry項,Entry項的高20位儲存的是物理頁面的高20位,將後16位補上0後,就是物理頁面的完整地址。最後,物理頁面的地址+低12位的偏移量,就得到了實體地址。
  還有一個小尾巴沒有解決。Entry的高20位儲存了地址的高20位,那低16位儲存的是什麼呢?存許可權!有沒有分配、能不能訪問等等資訊。其中最重要也是最有用的一位是Page Table的Entry項的最低位,present位。若present位=0,表示頁表項所對應的物理頁面在交換分割槽中;若present位=1,表示頁表項所對應的物理頁面就在實體記憶體中。根據LRU最近最久未使用演算法,將實體記憶體中最近最久未使用的頁面交換到交換分割槽中。程式剛開始執行時,會訪問資料、指令等等,發出的第一個邏輯地址就要發生地址對映,但剛開始執行的時候沒有分配任何實體記憶體,發生缺頁異常,作業系統分配物理頁面後,重新進行對映。由於程式執行有“區域性性”原理,頁面一經分配,其他大多數時候訪問的都是這個頁面。所以,一般只有第一次對映時會發生缺頁異常,其他情況下發生缺頁異常的概率非常非常小。
  到這兒,記憶體的段頁式管理就解釋完畢了。下面再附一張圖,幫助大家更好的理解這個過程。
  這裡寫圖片描述
 
    如果你能很好的解釋這張圖,你對以上知識已經掌握了。