x86彙編分頁模式實驗 –《ORANGE’S一個操作系統的實現》中 pmtest8.asm解析

  序言(廢話) : 在看書的過程中發現一開始不是很能理解pmtest8的目的,以及書上說得很抽象..於是在自己閱讀過源代碼后,將一些自己的心得寫在這裏。

  正文 : 

  講解順序依然按照書上貼代碼的順序來。但是是幾乎逐句解釋的。可能會稍微有點啰嗦。廢話就不多說了直接貼代碼。

LABEL_DESC_FLAT_C:  Descriptor 0,        0fffffh, DA_CR|DA_32|DA_LIMIT_4K; 0~4G
LABEL_DESC_FLAT_RW: Descriptor 0,        0fffffh, DA_DRW|DA_LIMIT_4K     ; 0~4G
SelectorFlatC       equ    LABEL_DESC_FLAT_C - LABEL_GDT                
SelectorFlatRW        equ    LABEL_DESC_FLAT_RW - LABEL_GDT

  顯然,兩個分別是 FLAT_C 和  FLAT_RW 的描述符和選擇子。

  問題 : 為什麼要有這兩個東西?

  解釋 : FLAT_C是用來執行的非一致性32位代碼段,粒度為4k,也就是 limit(段限長) = (0xfffff + 1)  * 4k = 4G,FLAT_RW 是用來修改數據的,因為需要利用這個描述符的權限(可寫)來將代碼寫入到目的地(這個目的地允許在 0 – 4G區間內)。之所以要分兩個選擇符,是防止在執行的時候修改代碼(所以FLAT_C不能給寫的權限),但是又必須在執行之前進行複製,所以一定要有一個入口能提供寫入的方式,於是設置兩個描述符來進行。這樣既安全又有章法。

 

SetupPaging:
    ; 根據內存大小計算應初始化多少PDE以及多少頁表
    xor    edx, edx
    mov    eax, [dwMemSize]
    mov    ebx, 400000h    ; 400000h = 4M = 4096 * 1024, 一個頁表對應的內存大小
    div    ebx
    mov    ecx, eax    ; 此時 ecx 為頁表的個數,也即 PDE 應該的個數
    test    edx, edx
    jz    .no_remainder
    inc    ecx        ; 如果餘數不為 0 就需增加一個頁表
.no_remainder:
    mov    [PageTableNumber], ecx    ; 暫存頁表個數

    ; 為簡化處理, 所有線性地址對應相等的物理地址. 並且不考慮內存空洞.

    ; 首先初始化頁目錄
    mov    ax, SelectorFlatRW
    mov    es, ax
    mov    edi, PageDirBase0    ; 此段首地址為 PageDirBase0
    xor    eax, eax
    mov    eax, PageTblBase0 | PG_P  | PG_USU | PG_RWW
.1:    ; es:edi 初始等於 PageDirBase0 (當前頁目錄表項), eax 初始基地址等於 PageTblBase0
    stosd
    add    eax, 4096        ; 為了簡化, 所有頁表在內存中是連續的.
    loop    .1

    ; 再初始化所有頁表
    mov    eax, [PageTableNumber]    ; 頁表個數
    mov    ebx, 1024        ; 每個頁表 1024 個 PTE
    mul    ebx
    mov    ecx, eax        ; PTE個數 = 頁表個數 * 1024
    mov    edi, PageTblBase0    ; 此段首地址為 PageTblBase0
    xor    eax, eax
    mov    eax, PG_P  | PG_USU | PG_RWW
.2:    ; es:edi 初始等於 PageTblBase0 (當前頁表項), eax = 0 (線性地址 = 物理地址)
    stosd
    add    eax, 4096        ; 每一頁指向 4K 的空間
    loop    .2

    mov    eax, PageDirBase0
    mov    cr3, eax
    mov    eax, cr0
    or    eax, 80000000h
    mov    cr0, eax
    jmp    short .3
.3:
    nop

    ret

 

  這段代碼我加註了兩句註釋 分別在 .1 和 .2 這兩個標籤那行,其實這裏和之前的setPaging並沒有很大的區別,需要注意的就是 這裏的 頁目錄表 的地址是  PageDirBase0, 頁表的地址是PageTblBase0,強調這點的原因在於之後的  PSwitch 這個函數中則是 PageDirBase1 和 PageTblBase1。也就是說實際上數據中有兩個頁面管理的數據結構(頁目錄表和頁表合起來相當於一個管理頁面的數據結構)。

 1 PagingDemo:
 2     mov    ax, cs
 3     mov    ds, ax
 4     mov    ax, SelectorFlatRW        ; 設置es為基地址為0的可讀寫的段(便於複製代碼)
 5     mov    es, ax
 6     
 7     push    LenFoo
 8     push    OffsetFoo
 9     push    ProcFoo            ; 00401000h
10     call    MemCpy        
11     add    esp, 12
12 
13     push    LenBar            ; 被複制代碼段(但是以ds為段基址)的長度 
14     push    OffsetBar        ; 被複制代碼段(但是以ds為段基址)的段偏移量
15     push    ProcBar            ; 目的代碼段的物理空間地址 00501000h
16     call    MemCpy
17     add    esp, 12
18 
19     push    LenPagingDemoAll
20     push    OffsetPagingDemoProc    
21     push    ProcPagingDemo            ; [es:ProcPagingDemo] = ProcPagingDemo = 00301000h
22     call    MemCpy
23     add    esp, 12
24 
25     mov    ax, SelectorData
26     mov    ds, ax            ; 數據段選擇子
27     mov    es, ax
28 
29     call    SetupPaging        ; 啟動分頁
30     ; 當前線性地址依然等於物理地址
31     call    SelectorFlatC:ProcPagingDemo    ; 訪問的線性地址為 00301000h,物理地址也是 00301000h
32     call    PSwitch            ; 切換頁目錄,改變地址映射關係
33     call    SelectorFlatC:ProcPagingDemo    ; 訪問的線性地址為 00301000h
34 
35     ret

  在這裏首先要說明的是 MemCpy函數,這個函數有三個參數分別表示 : 

   1)被複制段(但是以ds為段基址)的 長度 
   2)被複制段(但是以ds為段基址)的 段偏移量
   3)目的地的物理空間地址(之所以說是物理空間是因為當前線性地址等於物理地址,以es為段基址,但是es的段基址為0)
功能則是 將被複制段 的數據複製 參數1)的長度字節 去目的地去(簡單說就是利用三個參數複製數據)

我們可以知道的是在上面代碼中三次調用 MemCpy 都沒有進入分頁模式,也就是說當下線性地址等於物理地址。那麼根據我上面的註釋就可以知道三個代碼分別複製到哪裡去了。
之後就是恢複數據段(之前將ds = cs,是為了複製代碼),然後啟動分頁(上面已經講了),然後啟動分頁后當前線性地址依然等於物理地址。
這個時候第一次調用 call SelectorFlatC:ProcPagingDemo,也就是訪問的線性地址為 00301000h,物理地址也是 00301000h的代碼(之前移動過去的)。
 下面這段代碼就是被移動到00301000h的代碼,這段代碼只做了一件事那就是調用 [cs:LinearAddrDemo]的代碼,但請注意,由於 call SelectorFlatC:ProcPagingDemo
所以此時的 cs = SelectorFlatC,也就是說段基址等於0,於是實際上這段代碼的功能就是訪問 物理地址為00401000h處的代碼。
PagingDemoProc:
OffsetPagingDemoProc    equ    PagingDemoProc - $$
    mov    eax, LinearAddrDemo
    call    eax        ; 未開始PSwitch前, eax = ProcFoo = 00401000h (cs 的段基址 = 0)
    retf
LenPagingDemoAll    equ    $ - PagingDemoProc

  而物理地址00401000h處就是ProcFoo的代碼(第一次調用MemCpy拷貝的代碼)。被拷貝的代碼如下

foo:
OffsetFoo        equ    foo - $$
    mov    ah, 0Ch            ; 0000: 黑底    1100: 紅字
    mov    al, 'F'
    mov    [gs:((80 * 17 + 0) * 2)], ax    ; 屏幕第 17 行, 第 0 列。
    mov    al, 'o'
    mov    [gs:((80 * 17 + 1) * 2)], ax    ; 屏幕第 17 行, 第 1 列。
    mov    [gs:((80 * 17 + 2) * 2)], ax    ; 屏幕第 17 行, 第 2 列。
    ret
LenFoo            equ    $ - foo

  功能很明顯就是現實一個字符串 Foo而已。

總結第一次分頁后的動作:

  就是拷貝三份代碼分別到ProcFoo, ProcBar, ProcPagingDemo 處(這四個都是物理內存哦,並且後面因為段基址是0(FLAT_C 段基址)於是很容易地就訪問到了物理地址)。然後開啟分頁模式(其實幾乎沒什麼影響 因為仍然和分段一樣 線性地址 = 物理地址)。然後調用 被拷貝的函數 ProcPagingDemo ,ProcPagingDemo 函數調用 ProcFoo函數,显示字符 “Foo”然後兩次返回。

第二次分頁 : call PSwitch

被調用代碼如下 :

 1 PSwitch:
 2     ; 初始化頁目錄
 3     mov    ax, SelectorFlatRW
 4     mov    es, ax
 5     mov    edi, PageDirBase1    ; 此段首地址為 PageDirBase1
 6     xor    eax, eax
 7     mov    eax, PageTblBase1 | PG_P  | PG_USU | PG_RWW
 8     mov    ecx, [PageTableNumber]
 9 .1:    ; es:edi 初始等於 PageDirBase1 (當前頁目錄表項), eax 初始基地址等於 PageTblBase1
10     stosd
11     add    eax, 4096        ; 為了簡化, 所有頁表在內存中是連續的.
12     loop    .1
13 
14     ; 再初始化所有頁表
15     mov    eax, [PageTableNumber]    ; 頁表個數
16     mov    ebx, 1024        ; 每個頁表 1024 個 PTE
17     mul    ebx
18     mov    ecx, eax        ; PTE個數 = 頁表個數 * 1024
19     mov    edi, PageTblBase1    ; 此段首地址為 PageTblBase1
20     xor    eax, eax
21     mov    eax, PG_P  | PG_USU | PG_RWW
22 .2: ; es:edi 初始等於 PageTblBase1 (當前頁表項), eax 初始基地址等於 0(線性地址等於物理地址)
23     stosd
24     add    eax, 4096        ; 每一頁指向 4K 的空間
25     loop    .2
26 
27     ; 在此假設內存是大於 8M 的
28     ; 下列代碼將LinearAddrDemo所處的頁表的相對第一個頁表的偏移地址放入ecx中
29     mov    eax, LinearAddrDemo
30     shr    eax, 22
31     mov    ebx, 4096        ; (LinearAddrDemo / 4M)表示第幾個頁表
32     mul    ebx                ; 第幾個頁表 * 4k (1024(一個頁表項的數量) * 4(一個頁表項的字節))
33     mov    ecx, eax        ; 也就是對應頁表的偏移地址
34     
35     ; 下列代碼將LinearAddrDemo所處的頁表項相對第一個頁表項的偏移地址放入eax中
36     mov    eax, LinearAddrDemo
37     shr    eax, 12            ; LinearAddrDemo / 4k,表示第幾個頁表項
38     and    eax, 03FFh    ; 1111111111b (10 bits)    ; 取低10位,也就是餘下的零散頁表項(一個頁表有2^10個頁表項)
39     mov    ebx, 4                                
40     mul    ebx                                    ; * 4 表示的是具體偏移字節數
41     add    eax, ecx                            ; eax = (((LinearAddrDemo / 2^12) & 03FFh) * 4) + (4k * (LinearAddrDemo / 2^22))
42     
43     
44     add    eax, PageTblBase1                    ; 第一個頁表的第一個頁表項
45     mov    dword [es:eax], ProcBar | PG_P | PG_USU | PG_RWW
46 
47     mov    eax, PageDirBase1
48     mov    cr3, eax
49     jmp    short .3
50 .3:
51     nop
52 
53     ret

  在這裏我加了幾個比較重要的註釋分別在第 9, 22, 28,35處。

  這段代碼做了什麼?

  首先是設置頁面管理的數據結構(頁表和頁目錄表),但是需要注意的是,這裏設置頁表和頁目錄表除了不是之前的頁面管理結構之外,其實內容是差不多的,也就是說當前(第25行)這裏的狀態也是 線性地址 = 物理地址 !!!

 但是在第27行做了一個操作,就是將LinearAddrDemo對應的 頁表項的地址 換成了 ProcBar(00501000h) 的地址。(具體如何實現的請看27-45行我寫的註釋)。
  在做完這些之後就返回第二次執行 call SelectorFlatC:ProcPagingDemo 了,在這個時候 cs = SelectorFlatC (段基址等於0), eip = ProcPagingDemo = 00301000h,也就是說訪問了
線性地址 = 00301000h處,但是這裏已經被修改,除了這個頁面之外,其他頁面都是 線性地址 = 物理地址,但是這裏 線性地址 = 00301000h ,映射的物理地址是 ProcBar(00501000h)
於是便調用了 ProcBar 段的代碼,而這段的代碼是第二次調用MemCpy時候複製過去的。被複制的具體代碼是:
bar:
OffsetBar        equ    bar - $$
    mov    ah, 0Ch            ; 0000: 黑底    1100: 紅字
    mov    al, 'B'
    mov    [gs:((80 * 18 + 0) * 2)], ax    ; 屏幕第 18 行, 第 0 列。
    mov    al, 'a'
    mov    [gs:((80 * 18 + 1) * 2)], ax    ; 屏幕第 18 行, 第 1 列。
    mov    al, 'r'
    mov    [gs:((80 * 18 + 2) * 2)], ax    ; 屏幕第 18 行, 第 2 列。
    ret
LenBar            equ    $ - bar
也就是显示一個字符串 "Bar", 然後返回到PagingDemo的最後一句 ret,再次返回。於是這段代碼也就結束了。
第二次代碼是如何實現調用 ProcBar的?
  通過將線性地址 = ProcPaging(00301000h)對應的頁表項的地址值給修改成了 PaocBar(00501000h)的物理地址,於是從 00301000h 的線性地址 映射到 00501000h的物理地址上去了,
但是其實其他地方(除了這個頁之外)的線性地址 = 物理地址依然成立。也是上面這段代碼很小,一定是小於 4k(一頁的大小),於是只需要修改一個頁表項就可以了!
 

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

※想知道網站建置網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計後台網頁設計

※不管是台北網頁設計公司台中網頁設計公司,全省皆有專員為您服務

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

※帶您來看台北網站建置台北網頁設計,各種案例分享