內存訪問全過程

這一篇,是重點!我們將去講解操作系統根據代碼(邏輯)地址去訪問真實物理地址的全過程。

將把全面幾節的東西全部用上,並完全梳理,完善細節。

前面講了分段、分頁機制,他們都可以實現,從虛擬地址(地址空間)向物理地址的轉換。但是,實際使用過程中,使用的是分段+分頁機制,段頁結合。

段頁結合

全過程分析(高能)

我們現在採用邊實驗邊講解翻譯全過程。

寫了一段 c 代碼,編譯,然後在 Linux 0.11 中,進行調試

#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。

我們想做的是,通過編譯,找到變量 i 的邏輯地址,然後經過一系列的地址轉換,獲得物理地址。通過查看物理地址的內容,是否是 0x12345678。

獲得虛擬地址

將運行的代碼進行反編譯,可以看到 cmp dword ptr 這一部分。這一部分,對應的就是上面c語言的 while(i) 部分。

可以看到熟悉的ds:0x3004,這是什麼?

這就是我們之前分段章節裏面的間接尋址。也就是說,我們要找到 ds 段的基址,然後加上3004的偏移量。

這裏的 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表,是全局描述表。從這裏的 描述 二字與上面的 段描述符可以看出:GDT表中保存着上面提到的段描述符。

LDT表

LDT表,是局部描述表。裏面也保存着段描述符。

GDTR寄存器

此寄存器,記錄着 GDT表的基址。

LDTR寄存器

跟我們之前說的選擇符是一樣的,它表明了 LDT表在 GDT表中的位置。

我們可以這樣理解GDT和LDT:GDT爲一級描述符表,LDT爲二級描述符表。

LDT和GDT從本質上說是相同的,只是LDT嵌套在GDT之中。LDTR記錄局部描述符表的起始位置,與GDTR不同,LDTR的內容是一個段選擇子。由於LDT本身同樣是一段內存,也是一個段,所以它也有個描述符描述它,這個描述符就存儲在GDT中,對應這個表述符也會有一個選擇子,LDTR裝載的就是這樣一個選擇子。

注意,LDT表中也保存着描述符,是我們需要的。

也就是說,我們首先要獲取 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的值。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章