1. 程式人生 > >利用bochs將邏輯地址轉化為實體地址理解作業系統的分段分頁機制

利用bochs將邏輯地址轉化為實體地址理解作業系統的分段分頁機制

在步入正題前,我們先來了解幾個概念:邏輯地址、線性地址、虛擬地址、實體地址。
邏輯地址(logical address):Intel為了相容,將遠古時代的段式記憶體管理方式保留了下來。邏輯地址指的是機器語言指令中,用來指定一個運算元或者是一條指令的地址。
線性地址/虛擬地址(linear address/virtual address):跟邏輯地址類似,它也是一個不真實的地址,如果邏輯地址是對應的硬體平臺段式管理轉換前地址的話,那麼線性地址則對應了硬體頁式記憶體的轉換前地址。
實體地址(physical address):用於記憶體晶片級的單元定址,與處理器和CPU連線的地址匯流排相對應。——這個概念應該是這幾個概念中最好理解的一個,但是值得一提的是,雖然可以直接把實體地址理解成插在機器上那根記憶體本身,把記憶體看成一個從0位元組一直到最大空量逐位元組的編號的大陣列,然後把這個陣列叫做實體地址,但是事實上,這只是一個硬體提供給軟體的抽像,記憶體的定址方式並不是這樣。所以,說它是“與地址匯流排相對應”,是更貼切一些,不過拋開對實體記憶體定址方式的考慮,直接把實體地址與物理的記憶體一一對應,也是可以接受的。也許錯誤的理解更利於形而上的抽像。
相關概念可以參考:[邏輯地址、線性地址、實體地址和虛擬地址](http://www.cnblogs.com/diyingyun/archive/2012/01/03/2311327.html)
下面我們利用bochs說說作業系統的段頁式轉換:(本文程式在windows下進行)
關於Bochs的配置這裡就不贅述,網上有很多教程!
首先在Bochs-2.1.1/linux-0.11-devel-040329下執行run.bat得到

記憶體除錯視窗:
1
程式執行視窗:
2
(1)在記憶體除錯視窗中輸入c後
3
4

在程式執行視窗中寫main.c檔案,gcc –o main main.c編譯 執行main
程式程式碼如下:

#include <stdio.h>

int main()
{
    static int a = 1;
    int b = 6;
    int c = 10;
    printf("&a:0x%x\n",&a);
    while(a);
    printf("Program exit!\n");

    return 0;
}

執行結果為:
5


此時,程式阻塞到while(a);處。
我們的目標是將a的值修改為0,從而可以使程式執行通過。
(2)將記憶體除錯視窗 ctrl+c跳出:
6
(3)info cpu(dump_cpu)檢視cpu暫存器內容
7

變數a的值存在資料段,因此通過cpu的資料段暫存器ds的當前值作為段式對映的“選擇碼”,也就是用它作為段描述表中下標。
ds:s=0x17 轉化為二進位制:0000 0000 0001 0111
我們可以看出index = 2,TI = 1(LDT 區域性描述符表),RPL = 3
ldtr:0x68 轉化為二進位制:0000 0000 0110 1000
同上 index = 13,TI = 0(GDT 全域性描述符表),RPL = 0
gdtr:base=0x5cb8(段基址)
(4)xp /2wx 0x5cb8+13*8通過段基址+下標找出LDT(區域性描述符表)的地址
8


段描述符表的結構圖如下:
9
根據段描述符表的結構分析可知LDT的入口地址是:0x00fad2d0
(5)xp /2wx 0x00fad2d0+2*8 通過段基址+偏移量得到線性地址
10
根據段描述符表的結構得到線性地址的基地址0x10000000,而a的地址0x3004,因此可得到a的線性地址為:0x10003004
calc ds:0x3004檢視a的實際線性地址:
11
由此可確認a的線性地址確實為:0x10003004
至此,分段結束。
(6)分頁開始,分頁機制如下圖所示:
12
將線性地址0x10003004,二進位制為0001 0000 0000 0000 0011 0000 0000 0100
按10、10、12位劃分, 0001000000 0000000011 000000000100可得
Director = 64 Table = 3 Offset = 4
首先檢視cr0:0x80000001b最高位為1,說明支援分頁機制;
再檢視cr3: 0x0
xp /wx 0x0+64*4根據cr3和Director得到頁目錄索引:
13
頁目錄索引的有效位為高20位,即0x00faa000,與Table可得到頁表索引:
14
頁表索引的有效位也是高20位,即0x00fa9000,與Offset可得到實際的實體地址:
15
即a的實體地址為0x00fa9004

(7)最後setpmem 0x00fa9004 4 0x0將a的值修改為0,使得程式可以通過。
16
通過xp /wx 0x00fa9004檢視a的值已被修改為0
(8)c 繼續執行發現程式執行視窗的程式結束:
17
完!!!

順便再說一下邏輯地址、線性地址、虛擬地址、實體地址的區別和聯絡
就針對本例子來說:
邏輯地址:0x3004
線性地址(虛擬地址):0x10003004
實體地址:0x00fa9004

未分段前,邏輯地址=線性地址=虛擬地址,通過MMU分頁後轉化為實體地址
Linux下,邏輯地址與線性地址保持一致,因為所有的線性地址都是從0x00000000開始的。
線性地址=0x00000000(相當於基址)+邏輯地址(相當於偏移量)
如果沒有分頁機制,實體地址=邏輯地址

這下真的說完了,如有偏頗,希望同仁批評指正,謝謝!