記憶體訪問全過程

土堆碎念發表於2020-05-10

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

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

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

段頁結合

全過程分析(高能)

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

寫了一段 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的值。

相關文章