ia32體系即intel32位體系架構,也被稱為i386、x86-32或x86。在intel公司2023年推出的80386微處理器中首先使用。用以取代之前的x86-16位架構,包括8086、80186、80286晶元。談到這兒,就不得不說說x86架構的發展歷史。
intel 8086是由intel於2023年所設計的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的位址空間。
在實模式下,最大訪問的物理記憶體是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位可以表示210=1024個索引,次高10位也可以表示210=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最近最久未使用演算法,將物理記憶體中最近最久未使用的頁面交換到交換分割槽中。程式剛開始執行時,會訪問資料、指令等等,發出的第乙個邏輯位址就要發生位址對映,但剛開始執行的時候沒有分配任何物理記憶體,發生缺頁異常,作業系統分配物理頁面後,重新進行對映。由於程式執行有「區域性性」原理,頁面一經分配,其他大多數時候訪問的都是這個頁面。所以,一般只有第一次對映時會發生缺頁異常,其他情況下發生缺頁異常的概率非常非常小。
到這兒,記憶體的段頁式管理就解釋完畢了。下面再附一張圖,幫助大家更好的理解這個過程。
Linux虛擬位址對映
我們定義乙個區域性變數,然後列印出這個區域性變數的位址,那麼這個區域性變數的位址是線性位址?實體地址?還是邏輯位址?要明白這些,先來看看以下的知識吧。x86體系 指的是特定微cpu執行的有些 計算機語言 指令集,定義了 晶元的基本用規則 cpu的位數 alu一次性最多能處理的整數的位元組數,也即al...
Linux虛擬位址空間
在多工作業系統中,每個程序都執行在屬於自己的記憶體沙盤中。這個沙盤就是虛擬位址空間 virtual address space 在32位模式下它是乙個4gb的記憶體位址塊,這篇部落格均是x86架構的 1.位址空間分布 2.核心位址空間 從pkmap base 到 fixaddr start用於對映高...
Linux虛擬位址空間
為了防止不同程序同一時刻在物理記憶體中執行而對物理記憶體的爭奪和踐踏,採用了虛擬記憶體。虛擬記憶體技術使得不同程序在執行過程中,它所看得到的是自己獨自占有了當前系統的4g記憶體。所有程序共享同一物理記憶體,每個程序只把自己目前需要的虛擬記憶體空間對映並儲存到物理記憶體上。事實上,在每個程序建立載入時...