這一篇,是重點!咱們將去講解操做系統根據代碼(邏輯)地址去訪問真實物理地址的全過程。操作系統
將把全面幾節的東西所有用上,並徹底梳理,完善細節。翻譯
前面講了分段、分頁機制,他們均可以實現,從虛擬地址(地址空間)向物理地址的轉換。可是,實際使用過程當中,使用的是分段+分頁機制,段頁結合。3d
咱們如今採用邊實驗邊講解翻譯全過程。調試
寫了一段 c 代碼,編譯,而後在 Linux 0.11 中,進行調試code
#include <stdio.h> int i = 0x12345678; int main(void) { printf("The logical/virtual address of i is 0x%08x", &i); fflush(stdout); while (i) ; return 0; }
注意:咱們程序中的變量 i 的大小爲 0x12345678。blog
咱們想作的是,經過編譯,找到變量 i 的邏輯地址,而後通過一系列的地址轉換,得到物理地址。經過查看物理地址的內容,是不是 0x12345678。索引
將運行的代碼進行反編譯,能夠看到 cmp dword ptr 這一部分。這一部分,對應的就是上面c語言的 while(i) 部分。進程
能夠看到熟悉的 ds:0x3004
,這是什麼?內存
這就是咱們以前分段章節裏面的間接尋址。也就是說,咱們要找到 ds 段的基址,而後加上3004的偏移量。io
這裏的 ds:0x3004 就是這一部分。你會發現 0x3004 只有16位啊,下圖的偏移量標記的是32位。
由於在 Linux 0.11中,給每一個進程劃分了 64M 的虛擬內存,2的16次方就是64M。
下圖中的偏移量位32位,是給每一個進程劃分了 4G 的虛擬內存。
注意:看下圖紅色方框部分,其中的0-15位選擇符用來選擇程序中的段的。後面的0-31偏移值,是每一個段中的偏移量。
分段機制,假設一個程序中有不少個段(個數由選擇符的位數決定),並且每一個段均可以佔有一個大小的空間(由偏移值位數決定)。
在下圖中,因爲選擇符0-15中只有14位用來指定段的,因此下圖中的虛擬地址,能夠指定214個段,每一個段能夠有4G(232)的大小空間。
從上面,咱們得到變量 i 的虛擬地址爲 ds: 0x3004。
經過下圖,咱們查看寄存器,能夠得到ds=0x0017,因此ds:0x3004=0x0017: 3004。
咱們來看ds=0x0017的解讀。
這其實也叫選擇符,看下圖。
重點看,TI 位,也就是2號位。0x0017=0x 17 = 0x 0001 0111,也就是 TI 位爲1。
當 TI 爲0時,說明咱們要找的有關段表信息就在 GDT表中,咱們能夠經過繼續對 0x0017的3-15位進行解讀,獲取有關段表信息在 GDT表中的索引。
當 TI 爲1的時候,說明咱們要找的 有關段表信息 在 LDT表中。
每一個段都有一個段描述符。
段描述符指定段的大小、訪問權限和段的特權級、段類型以及段的第一字節在線性地址空間中的位置(也就是段基址)。
GDT表,是全局描述表。從這裏的 描述 二字與上面的 段描述符能夠看出:GDT表中保存着上面提到的段描述符。
LDT表,是局部描述表。裏面也保存着段描述符。
此寄存器,記錄着 GDT表的基址。
跟咱們以前說的選擇符是同樣的,它代表了 LDT表在 GDT表中的位置。
咱們能夠這樣理解GDT和LDT:GDT爲一級描述符表,LDT爲二級描述符表。
LDT和GDT從本質上說是相同的,只是LDT嵌套在GDT之中。LDTR記錄局部描述符表的起始位置,與GDTR不一樣,LDTR的內容是一個段選擇子。因爲LDT自己一樣是一段內存,也是一個段,因此它也有個描述符描述它,這個描述符就存儲在GDT中,對應這個表述符也會有一個選擇子,LDTR裝載的就是這樣一個選擇子。
注意,LDT表中也保存着描述符,是咱們須要的。
也就是說,咱們首先要獲取 LDT表的描述符,而後在 LDT表中獲取咱們須要的段描述符。
咱們已經知道,咱們的段選擇符爲ldtr。
因此,咱們如今得得到 GDTR 和 LDTR寄存器中的內容。
能夠看到,LDTR寄存器中的值爲 0x0068, GDTR寄存器中的值爲 0x00005cb8。
因此,咱們將0x0068=0000 0000 0110 1000,咱們保留3-15位,1101=13。
因此咱們如今知道了,咱們須要的段描述符在GDT表開始位置的第13個位置處。
咱們在GDT表得到偏移13個位置處的內容。
咱們已經得到了段描述符的內容了,離目標愈來愈近了。只要解讀出段描述符的內容,咱們就能夠得到段表的基址了。
其解讀以下,咱們利用上面的結果,並結合下圖,去得到基址。
因此,咱們得到 LDT表的物理地址爲0x00fd52d0。
就像咱們以前談到的,LDT表存儲的也是段描述符。
因此咱們也須要像以前那樣,去獲取相應位置的段描述符,而後進行解讀。
還記得咱們以前的ds=0x0017嘛?
0x0017=0x 17 = 0x 0001 0111,其中索引爲2。
如今咱們得到 LDT表基址開始處的內容。
由於索引都是從0開始的,因此獲取的段描述符爲 0x00003ffff 0x10c0f300。
解讀方式如上,這樣咱們求得段表的基址爲0x10000000。
以後,將段基址與偏移量相加,便可得到線性地址。0x10003004。
由於採用了多級頁表,因此分頁頁目錄和頁表。其中位數解讀,如圖所示。
注意,頁目錄的基址存儲在 cr3 寄存器中。
以下圖,咱們得到的頁目錄表的基址爲0x0。
說明頁目錄表的基址爲 0。
由於0x10003004=0x 0001 0000 0000 0000 0011 0000 0000 0100。
因此知道,目錄爲 0001 0000 00 ,爲64。
頁面爲 00 0000 0011,爲3。
偏移爲0000 0000 0100,爲4。
咱們要得到頁目錄號爲64的內容:
能夠看到基址爲12到31爲,因此地址爲0x00fa5000。
頁表所在物理頁框爲0x00fa5000位置,從該位置開始查找3號頁表項,獲得:
這個解讀同上,因此最後得到的基址爲0x00f99000。
加上前面提到的偏移4,最終的物理地址爲:0x00f99004。
最後,咱們查看這個物理地址的內容,發現,是咱們程序中設置的i的值。