歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux基礎 >> 關於Linux >> 一個操作系統的實現(8)-進一步體會分頁機制

一個操作系統的實現(8)-進一步體會分頁機制

日期:2017/3/1 11:49:01   编辑:關於Linux

上面的兩篇文章中,我們對可用內存進行了統計,並且合理的分配了頁表的大小。這節中,我們來看看分頁的好處

在此之前不知道你有沒有注意過一個細節,如果你寫一個程序(在Linux或Windows下均可),並改個名復制一份,然後同時調試,你會發現,從變量地址到寄存器的值,幾乎全部都是一樣的!而這些“一樣的”地址之間完全不會混淆起來,而是各自完成著自己的職責。這就是分頁機制的功勞,下面我們就來模擬一下這個效果。

線性地址到物理地址的映射

先執行某個線性地址處的模塊,然後通過改變cr3來轉換地址映射關系,再執行同一個線性地址處的模塊,由於地址映射已經改變,所以兩次得到的應該是不同的輸出。

映射關系轉換前的情形如下圖所示:

\

開始,我們讓ProcPagingDemo中的代碼實現向LinearAddrDemo這個線性地址的轉移,而LinearAddrDemo映射到物理地址空間中

的ProcFoo處。我們讓ProcFoo打印出紅色的字符串Foo,所以執行時我們應該可以看到紅色的Foo。隨後我們改變地址映射關系,變

化成下圖所示的情形。

\

頁目錄表和頁表的切換讓LinearAddrDemo映射到ProcBar(物理地址空間)處,所以當我們再一次調用過程ProcPagingDemo時,程序將轉移到ProcBar處執行,我們將看到紅色的字符串Bar。

接下來看看新增的代碼:

改變映射關系的代碼實現

首先,我們用到了另外一套頁目錄表和頁表,所以原先的頁目錄段和頁表段已經不再夠用了。事實上,前面的程序中我們用兩個段分別存放頁目錄表和頁表,是為了讓我們閱讀時更加直觀和形象。在接下來的程序中,我們把它們放到同一個段中,同時把增加的一套頁目錄和頁表也放到這個段中。

為了操作方便,我們新增加一個段,其線性地址空間為0~4GB。由於分頁機制啟動之前線性地址等同於物理地址,所以通過這個段可以方便地存取特定的物理地址。此段的代碼定義如下:

26 LABEL_DESC_FLAT_C: Descriptor 0, 0fffffh, DA_CR|DA_32|DA_LIMIT_4K; 0~4G

27 LABEL_DESC_FLAT_RW: Descriptor 0, 0fffffh, DA_DRW|DA_LIMIT_4K ; 0~4G

...

41 SelectorFlatC equ LABEL_DESC_FLAT_C - LABEL_GDT

42 SelectorFlatRW equ LABEL_DESC_FLAT_RW - LABEL_GDT

之所以用了兩個描述符來描述這個段,是因為我們不僅僅要讀寫這段內存,而且要執行其中的代碼,而這對描述符的屬性要求是不一樣的。這兩個段的段基址都是0,長度都是4GB。

下面我們就將啟動分頁的代碼做相應的修改,如下所示:

257 ; 啟動分頁機制 --------------------------------------------------------------

258 SetupPaging:

259 ; 根據內存大小計算應初始化多少PDE以及多少頁表

260 xor edx, edx

261 mov eax, [dwMemSize]

262 mov ebx, 400000h ; 400000h = 4M = 4096 * 1024, 一個頁表對應的內存大小

263 div ebx

264 mov ecx, eax ; 此時 ecx 為頁表的個數,也即 PDE 應該的個數

265 test edx, edx

266 jz .no_remainder

267 inc ecx ; 如果余數不為 0 就需增加一個頁表

268 .no_remainder:

269 mov [PageTableNumber], ecx ; 暫存頁表個數

270

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

272

273 ; 首先初始化頁目錄

274 mov ax, SelectorFlatRW

275 mov es, ax

276 mov edi, PageDirBase0 ; 此段首地址為 PageDirBase0

277 xor eax, eax

278 mov eax, PageTblBase0 | PG_P | PG_USU | PG_RWW

279 .1:

280 stosd

281 add eax, 4096 ; 為了簡化, 所有頁表在內存中是連續的.

282 loop .1

283

284 ; 再初始化所有頁表

285 mov eax, [PageTableNumber] ; 頁表個數

286 mov ebx, 1024 ; 每個頁表 1024 個 PTE

287 mul ebx

288 mov ecx, eax ; PTE個數 = 頁表個數 * 1024

289 mov edi, PageTblBase0 ; 此段首地址為 PageTblBase0

290 xor eax, eax

291 mov eax, PG_P | PG_USU | PG_RWW

292 .2:

293 stosd

294 add eax, 4096 ; 每一頁指向 4K 的空間

295 loop .2

296

297 mov eax, PageDirBase0

298 mov cr3, eax

299 mov eax, cr0

300 or eax, 80000000h

301 mov cr0, eax

302 jmp short .3

303 .3:

304 nop

305

306 ret

307 ; 分頁機制啟動完畢 ----------------------------------------------------------

我們原來並沒有把頁表個數保存起來,而現在情況發生了變化,我們不只有一個頁目錄和頁表,為了初始化另外的頁表時方便起見,在這裡增加了一個變量PageTableNumber,頁表的個數就存在裡面。

在整個初始化頁目錄和頁表的過程中,es始終為SelectorFlatRW。這樣,想存取物理地址的時候,只需將地址賦值給edi,那麼es:edi指向的就是相應物理地址。比如頁目錄物理地址為PageDirBase0,第276行將edi賦值為PageDirBase0,es:edi於是指向地址PageDirBase0處,賦值通過指令stosd來實現。初始化頁表也是同樣的道理。

這樣,頁目錄和頁表的准備工作就完成了。不過我們不再在原來的位置調用它,而是新建一個函數PagingDemo,把所有與分頁有關的內容全都放進裡面,這樣,程序看起來結構清晰一些。

根據上面兩幅圖,我們可以認為在這個程序的實現中有4個要關注的要素,分別是ProcPagingDemo、LinearAddrDemo、ProcFoo和ProcBar,我們把它們稱為F4。因為程序開始時LinearAddrDemo指向ProcFoo並且線性地址和物理地址是對等的,所以LinearAddrDemo應該等於ProcFoo。而ProcFoo和ProcBar應該是指定的物理地址,所以LinearAddrDemo也應該是指定的物理地址。也正因為如此,我們使用它們時應該確保使用的是FLAT段,即段選擇子應該是SelectorFlatC或者SelectorFlatRW。

為了將我們的代碼放置在ProcFoo和ProcBar這兩處地方,我們先寫兩個函數,在程序運行時將這兩個函數的執行碼復制過去就可以了。

ProcPagingDemo要調用FLAT段中的LinearAddrDemo,所以如果不想使用段間轉移,我們需要把ProcPagingDemo也放進FLAT段中。我們需要寫一個函數,然後把代碼復制到ProcPagingDemo處。

這樣看來,F4雖然都是當做函數來使用,但實際上卻都是內存中指定的地址。我們把它們定義為常量。如下:

13 LinearAddrDemo equ 00401000h

14 ProcFoo equ 00401000h

15 ProcBar equ 00501000h

16 ProcPagingDemo equ 00301000h

將代碼填充進這些內存地址的代碼就在上文我們提到的PagingDemo中,如下:

310 ; 測試分頁機制 --------------------------------------------------------------

311 PagingDemo:

312 mov ax, cs

313 mov ds, ax

314 mov ax, SelectorFlatRW

315 mov es, ax

316

317 push LenFoo

318 push OffsetFoo

319 push ProcFoo

320 call MemCpy

321 add esp, 12

322

323 push LenBar

324 push OffsetBar

325 push ProcBar

326 call MemCpy

327 add esp, 12

328

329 push LenPagingDemoAll

330 push OffsetPagingDemoProc

331 push ProcPagingDemo

332 call MemCpy

333 add esp, 12

334

335 mov ax, SelectorData

336 mov ds, ax ; 數據段選擇子

337 mov es, ax

338

339 call SetupPaging ; 啟動分頁

340

341 call SelectorFlatC:ProcPagingDemo

342 call PSwitch ; 切換頁目錄,改變地址映射關系

343 call SelectorFlatC:ProcPagingDemo

344

345 ret

346 ; ---------------------------------------------------------------------------

其中用到了名為MemCpy的函數,它復制三個過程到指定的內存地址,類似於C語言中的memcpy。但有一點不同,它假設源數據放在ds段中,而目的在es段中。所以在函數的開頭,你可以找到分別為ds和es賦值的語句。MemCpy代碼如下:

131 ; ------------------------------------------------------------------------

132 ; 內存拷貝,仿 memcpy

133 ; ------------------------------------------------------------------------

134 ; void* MemCpy(void* es:pDest, void* ds:pSrc, int iSize);

135 ; ------------------------------------------------------------------------

136 MemCpy:

137 push ebp

138 mov ebp, esp

139

140 push esi

141 push edi

142 push ecx

143

144 mov edi, [ebp + 8] ; Destination

145 mov esi, [ebp + 12] ; Source

146 mov ecx, [ebp + 16] ; Counter

147 .1:

148 cmp ecx, 0 ; 判斷計數器

149 jz .2 ; 計數器為零時跳出

150

151 mov al, [ds:esi] ; ┓

152 inc esi ; ┃

153 ; ┣ 逐字節移動

154 mov byte [es:edi], al ; ┃

155 inc edi ; ┛

156

157 dec ecx ; 計數器減一

158 jmp .1 ; 循環

159 .2:

160 mov eax, [ebp + 8] ; 返回值

161

162 pop ecx

163 pop edi

164 pop esi

165 mov esp, ebp

166 pop ebp

167

168 ret ; 函數結束,返回

169 ; MemCpy 結束-------------------------------------------------------------

被復制的三個過程如下:

402 PagingDemoProc:

403 OffsetPagingDemoProc equ PagingDemoProc - $$

404 mov eax, LinearAddrDemo

405 call eax

406 retf

407 LenPagingDemoAll equ $ - PagingDemoProc

408

409 foo:

410 OffsetFoo equ foo - $$

411 mov ah, 0Ch ; 0000: 黑底 1100: 紅字

412 mov al, 'F'

413 mov [gs:((80 * 17 + 0) * 2)], ax ; 屏幕第 17 行, 第 0 列。

414 mov al, 'o'

415 mov [gs:((80 * 17 + 1) * 2)], ax ; 屏幕第 17 行, 第 1 列。

416 mov [gs:((80 * 17 + 2) * 2)], ax ; 屏幕第 17 行, 第 2 列。

417 ret

418 LenFoo equ $ - foo

419

420 bar:

421 OffsetBar equ bar - $$

422 mov ah, 0Ch ; 0000: 黑底 1100: 紅字

423 mov al, 'B'

424 mov [gs:((80 * 18 + 0) * 2)], ax ; 屏幕第 18 行, 第 0 列。

425 mov al, 'a'

426 mov [gs:((80 * 18 + 1) * 2)], ax ; 屏幕第 18 行, 第 1 列。

427 mov al, 'r'

428 mov [gs:((80 * 18 + 2) * 2)], ax ; 屏幕第 18 行, 第 2 列。

429 ret

430 LenBar equ $ - bar

接下來繼續看PagingDemo中的代碼,這部分代碼最重要的部分是四個call語句,如下:

339 call SetupPaging ; 啟動分頁

340

341 call SelectorFlatC:ProcPagingDemo

342 call PSwitch ; 切換頁目錄,改變地址映射關系

343 call SelectorFlatC:ProcPagingDemo

首先啟動分頁機制,然後調用ProcPagingDemo,再切換頁目錄,最後又調用一遍ProcPagingDemo。

現在ProcPagingDemo、ProcFoo以及ProcBar的內容我們都已經知道了,由於LinearAddrDemo和ProcFoo相等,並且函數SetupPaging建立起來的是對等的映射關系,所以第一次對ProcPagingDemo的調用反映的就是開始時的內存映射關系圖。

接下來看看調用的PSwitch:

349 ; 切換頁表 ------------------------------------------------------------------

350 PSwitch:

351 ; 初始化頁目錄

352 mov ax, SelectorFlatRW

353 mov es, ax

354 mov edi, PageDirBase1 ; 此段首地址為 PageDirBase1

355 xor eax, eax

356 mov eax, PageTblBase1 | PG_P | PG_USU | PG_RWW

357 mov ecx, [PageTableNumber]

358 .1:

359 stosd

360 add eax, 4096 ; 為了簡化, 所有頁表在內存中是連續的.

361 loop .1

362

363 ; 再初始化所有頁表

364 mov eax, [PageTableNumber] ; 頁表個數

365 mov ebx, 1024 ; 每個頁表 1024 個 PTE

366 mul ebx

367 mov ecx, eax ; PTE個數 = 頁表個數 * 1024

368 mov edi, PageTblBase1 ; 此段首地址為 PageTblBase1

369 xor eax, eax

370 mov eax, PG_P | PG_USU | PG_RWW

371 .2:

372 stosd

373 add eax, 4096 ; 每一頁指向 4K 的空間

374 loop .2

375

376 ; 在此假設內存是大於 8M 的

377 mov eax, LinearAddrDemo

378 shr eax, 22

379 mov ebx, 4096

380 mul ebx

381 mov ecx, eax

382 mov eax, LinearAddrDemo

383 shr eax, 12

384 and eax, 03FFh ; 1111111111b (10 bits)

385 mov ebx, 4

386 mul ebx

387 add eax, ecx

388 add eax, PageTblBase1

389 mov dword [es:eax], ProcBar | PG_P | PG_USU | PG_RWW

390

391 mov eax, PageDirBase1

392 mov cr3, eax

393 jmp short .3

394 .3:

395 nop

396

397 ret

398 ; ---------------------------------------------------------------------------

這個函數前面初始化頁目錄表和頁表的過程與SetupPaging是差不多的,只是緊接著程序增加了改變線性地址LinearAddrDemo對應的物理地址的語句。改變後,LinearAddrDemo將不再對應ProcFoo,而是對應ProcBar。具體是如何改變的,不是很懂

所以,此函數調用完成之後,對ProcPagingDemo的調用就變成了後來的映射關系圖。

在代碼PSwitch的後半部分,我們把cr3的值改成了PageDirBase1,這個切換過程宣告完成。

程序的運行情況如下:

\

我們看到紅色的Foo和Bar,這說明我們的頁表切換起作用了。其實,我們先前提到的不同進程有相同的地址,原理跟本例是類似的,也是在任務切換時通過改變cr3的值來切換頁目錄,從而改變地址映射關系。

這就是分頁的妙處。其實,妙處還不僅僅如此。由於分頁機制的存在,程序使用的都是線性地址空間,而不再直接是物理地址。這好像操作系統為應用程序提供了一個不依賴於硬件(物理內存)的平台,應用程序不必關心實際上有多少物理內存,也不必關心正在使用的是哪一段內存,甚至不必關心某一個地址是在物理內存裡面還是在硬盤中。總之,操作系統全權負責了這其中的轉換工作。

源代碼

; ==========================================

; pmtest8.asm

; 編譯方法:nasm pmtest8.asm -o pmtest8.com

; ==========================================

%include "pm.inc" ; 常量, 宏, 以及一些說明

PageDirBase0 equ 200000h ; 頁目錄開始地址: 2M

PageTblBase0 equ 201000h ; 頁表開始地址: 2M + 4K

PageDirBase1 equ 210000h ; 頁目錄開始地址: 2M + 64K

PageTblBase1 equ 211000h ; 頁表開始地址: 2M + 64K + 4K

LinearAddrDemo equ 00401000h

ProcFoo equ 00401000h

ProcBar equ 00501000h

ProcPagingDemo equ 00301000h

org 0100h

jmp LABEL_BEGIN

[SECTION .gdt]

; GDT

; 段基址, 段界限, 屬性

LABEL_GDT: Descriptor 0, 0, 0 ; 空描述符

LABEL_DESC_NORMAL: Descriptor 0, 0ffffh, DA_DRW ; Normal 描述符

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

LABEL_DESC_CODE32: Descriptor 0, SegCode32Len-1, DA_CR|DA_32 ; 非一致代碼段, 32

LABEL_DESC_CODE16: Descriptor 0, 0ffffh, DA_C ; 非一致代碼段, 16

LABEL_DESC_DATA: Descriptor 0, DataLen-1, DA_DRW ; Data

LABEL_DESC_STACK: Descriptor 0, TopOfStack, DA_DRWA|DA_32 ; Stack, 32 位

LABEL_DESC_VIDEO: Descriptor 0B8000h, 0ffffh, DA_DRW ; 顯存首地址

; GDT 結束

GdtLen equ $ - LABEL_GDT ; GDT長度

GdtPtr dw GdtLen - 1 ; GDT界限

dd 0 ; GDT基地址

; GDT 選擇子

SelectorNormal equ LABEL_DESC_NORMAL - LABEL_GDT

SelectorFlatC equ LABEL_DESC_FLAT_C - LABEL_GDT

SelectorFlatRW equ LABEL_DESC_FLAT_RW - LABEL_GDT

SelectorCode32 equ LABEL_DESC_CODE32 - LABEL_GDT

SelectorCode16 equ LABEL_DESC_CODE16 - LABEL_GDT

SelectorData equ LABEL_DESC_DATA - LABEL_GDT

SelectorStack equ LABEL_DESC_STACK - LABEL_GDT

SelectorVideo equ LABEL_DESC_VIDEO - LABEL_GDT

; END of [SECTION .gdt]

[SECTION .data1] ; 數據段

ALIGN 32

[BITS 32]

LABEL_DATA:

; 實模式下使用這些符號

; 字符串

_szPMMessage: db "In Protect Mode now. ^-^", 0Ah, 0Ah, 0 ; 進入保護模式後顯示此字符串

_szMemChkTitle: db "BaseAddrL BaseAddrH LengthLow LengthHigh Type", 0Ah, 0 ; 進入保護模式後顯示此字符串

_szRAMSize db "RAM size:", 0

_szReturn db 0Ah, 0

; 變量

_wSPValueInRealMode dw 0

_dwMCRNumber: dd 0 ; Memory Check Result

_dwDispPos: dd (80 * 6 + 0) * 2 ; 屏幕第 6 行, 第 0 列。

_dwMemSize: dd 0

_ARDStruct: ; Address Range Descriptor Structure

_dwBaseAddrLow: dd 0

_dwBaseAddrHigh: dd 0

_dwLengthLow: dd 0

_dwLengthHigh: dd 0

_dwType: dd 0

_PageTableNumber dd 0

_MemChkBuf: times 256 db 0

; 保護模式下使用這些符號

szPMMessage equ _szPMMessage - $$

szMemChkTitle equ _szMemChkTitle - $$

szRAMSize equ _szRAMSize - $$

szReturn equ _szReturn - $$

dwDispPos equ _dwDispPos - $$

dwMemSize equ _dwMemSize - $$

dwMCRNumber equ _dwMCRNumber - $$

ARDStruct equ _ARDStruct - $$

dwBaseAddrLow equ _dwBaseAddrLow - $$

dwBaseAddrHigh equ _dwBaseAddrHigh - $$

dwLengthLow equ _dwLengthLow - $$

dwLengthHigh equ _dwLengthHigh - $$

dwType equ _dwType - $$

MemChkBuf equ _MemChkBuf - $$

PageTableNumber equ _PageTableNumber- $$

DataLen equ $ - LABEL_DATA

; END of [SECTION .data1]

; 全局堆棧段

[SECTION .gs]

ALIGN 32

[BITS 32]

LABEL_STACK:

times 512 db 0

TopOfStack equ $ - LABEL_STACK - 1

; END of [SECTION .gs]

[SECTION .s16]

[BITS 16]

LABEL_BEGIN:

mov ax, cs

mov ds, ax

mov es, ax

mov ss, ax

mov sp, 0100h

mov [LABEL_GO_BACK_TO_REAL+3], ax

mov [_wSPValueInRealMode], sp

; 得到內存數

mov ebx, 0

mov di, _MemChkBuf

.loop:

mov eax, 0E820h

mov ecx, 20

mov edx, 0534D4150h

int 15h

jc LABEL_MEM_CHK_FAIL

add di, 20

inc dword [_dwMCRNumber]

cmp ebx, 0

jne .loop

jmp LABEL_MEM_CHK_OK

LABEL_MEM_CHK_FAIL:

mov dword [_dwMCRNumber], 0

LABEL_MEM_CHK_OK:

; 初始化 16 位代碼段描述符

mov ax, cs

movzx eax, ax

shl eax, 4

add eax, LABEL_SEG_CODE16

mov word [LABEL_DESC_CODE16 + 2], ax

shr eax, 16

mov byte [LABEL_DESC_CODE16 + 4], al

mov byte [LABEL_DESC_CODE16 + 7], ah

; 初始化 32 位代碼段描述符

xor eax, eax

mov ax, cs

shl eax, 4

add eax, LABEL_SEG_CODE32

mov word [LABEL_DESC_CODE32 + 2], ax

shr eax, 16

mov byte [LABEL_DESC_CODE32 + 4], al

mov byte [LABEL_DESC_CODE32 + 7], ah

; 初始化數據段描述符

xor eax, eax

mov ax, ds

shl eax, 4

add eax, LABEL_DATA

mov word [LABEL_DESC_DATA + 2], ax

shr eax, 16

mov byte [LABEL_DESC_DATA + 4], al

mov byte [LABEL_DESC_DATA + 7], ah

; 初始化堆棧段描述符

xor eax, eax

mov ax, ds

shl eax, 4

add eax, LABEL_STACK

mov word [LABEL_DESC_STACK + 2], ax

shr eax, 16

mov byte [LABEL_DESC_STACK + 4], al

mov byte [LABEL_DESC_STACK + 7], ah

; 為加載 GDTR 作准備

xor eax, eax

mov ax, ds

shl eax, 4

add eax, LABEL_GDT ; eax <- gdt 基地址

mov dword [GdtPtr + 2], eax ; [GdtPtr + 2] <- gdt 基地址

; 加載 GDTR

lgdt [GdtPtr]

; 關中斷

cli

; 打開地址線A20

in al, 92h

or al, 00000010b

out 92h, al

; 准備切換到保護模式

mov eax, cr0

or eax, 1

mov cr0, eax

; 真正進入保護模式

jmp dword SelectorCode32:0

; 執行這一句會把 SelectorCode32 裝入 cs, 並跳轉到 Code32Selector:0 處

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

LABEL_REAL_ENTRY: ; 從保護模式跳回到實模式就到了這裡

mov ax, cs

mov ds, ax

mov es, ax

mov ss, ax

mov sp, [_wSPValueInRealMode]

in al, 92h ; ┓

and al, 11111101b ; ┣ 關閉 A20 地址線

out 92h, al ; ┛

sti ; 開中斷

mov ax, 4c00h ; ┓

int 21h ; ┛回到 DOS

; END of [SECTION .s16]

[SECTION .s32]; 32 位代碼段. 由實模式跳入.

[BITS 32]

LABEL_SEG_CODE32:

mov ax, SelectorData

mov ds, ax ; 數據段選擇子

mov es, ax

mov ax, SelectorVideo

mov gs, ax ; 視頻段選擇子

mov ax, SelectorStack

mov ss, ax ; 堆棧段選擇子

mov esp, TopOfStack

; 下面顯示一個字符串

push szPMMessage

call DispStr

add esp, 4

push szMemChkTitle

call DispStr

add esp, 4

call DispMemSize ; 顯示內存信息

call PagingDemo ; 演示改變頁目錄的效果

; 到此停止

jmp SelectorCode16:0

; 啟動分頁機制 --------------------------------------------------------------

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:

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:

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

; 分頁機制啟動完畢 ----------------------------------------------------------

; 測試分頁機制 --------------------------------------------------------------

PagingDemo:

mov ax, cs

mov ds, ax

mov ax, SelectorFlatRW

mov es, ax

push LenFoo

push OffsetFoo

push ProcFoo

call MemCpy

add esp, 12

push LenBar

push OffsetBar

push ProcBar

call MemCpy

add esp, 12

push LenPagingDemoAll

push OffsetPagingDemoProc

push ProcPagingDemo

call MemCpy

add esp, 12

mov ax, SelectorData

mov ds, ax ; 數據段選擇子

mov es, ax

call SetupPaging ; 啟動分頁

call SelectorFlatC:ProcPagingDemo

call PSwitch ; 切換頁目錄,改變地址映射關系

call SelectorFlatC:ProcPagingDemo

ret

; ---------------------------------------------------------------------------

; 切換頁表 ------------------------------------------------------------------

PSwitch:

; 初始化頁目錄

mov ax, SelectorFlatRW

mov es, ax

mov edi, PageDirBase1 ; 此段首地址為 PageDirBase1

xor eax, eax

mov eax, PageTblBase1 | PG_P | PG_USU | PG_RWW

mov ecx, [PageTableNumber]

.1:

stosd

add eax, 4096 ; 為了簡化, 所有頁表在內存中是連續的.

loop .1

; 再初始化所有頁表

mov eax, [PageTableNumber] ; 頁表個數

mov ebx, 1024 ; 每個頁表 1024 個 PTE

mul ebx

mov ecx, eax ; PTE個數 = 頁表個數 * 1024

mov edi, PageTblBase1 ; 此段首地址為 PageTblBase1

xor eax, eax

mov eax, PG_P | PG_USU | PG_RWW

.2:

stosd

add eax, 4096 ; 每一頁指向 4K 的空間

loop .2

; 在此假設內存是大於 8M 的

mov eax, LinearAddrDemo

shr eax, 22

mov ebx, 4096

mul ebx

mov ecx, eax

mov eax, LinearAddrDemo

shr eax, 12

and eax, 03FFh ; 1111111111b (10 bits)

mov ebx, 4

mul ebx

add eax, ecx

add eax, PageTblBase1

mov dword [es:eax], ProcBar | PG_P | PG_USU | PG_RWW

mov eax, PageDirBase1

mov cr3, eax

jmp short .3

.3:

nop

ret

; ---------------------------------------------------------------------------

PagingDemoProc:

OffsetPagingDemoProc equ PagingDemoProc - $$

mov eax, LinearAddrDemo

call eax

retf

LenPagingDemoAll equ $ - PagingDemoProc

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

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

; 顯示內存信息 --------------------------------------------------------------

DispMemSize:

push esi

push edi

push ecx

mov esi, MemChkBuf

mov ecx, [dwMCRNumber] ;for(int i=0;i<[MCRNumber];i++) // 每次得到一個ARDS(Address Range Descriptor Structure)結構

.loop: ;{

mov edx, 5 ; for(int j=0;j<5;j++) // 每次得到一個ARDS中的成員,共5個成員

mov edi, ARDStruct ; { // 依次顯示:BaseAddrLow,BaseAddrHigh,LengthLow,LengthHigh,Type

.1: ;

push dword [esi] ;

call DispInt ; DispInt(MemChkBuf[j*4]); // 顯示一個成員

pop eax ;

stosd ; ARDStruct[j*4] = MemChkBuf[j*4];

add esi, 4 ;

dec edx ;

cmp edx, 0 ;

jnz .1 ; }

call DispReturn ; printf("\n");

cmp dword [dwType], 1 ; if(Type == AddressRangeMemory) // AddressRangeMemory : 1, AddressRangeReserved : 2

jne .2 ; {

mov eax, [dwBaseAddrLow] ;

add eax, [dwLengthLow] ;

cmp eax, [dwMemSize] ; if(BaseAddrLow + LengthLow > MemSize)

jb .2 ;

mov [dwMemSize], eax ; MemSize = BaseAddrLow + LengthLow;

.2: ; }

loop .loop ;}

;

call DispReturn ;printf("\n");

push szRAMSize ;

call DispStr ;printf("RAM size:");

add esp, 4 ;

;

push dword [dwMemSize] ;

call DispInt ;DispInt(MemSize);

add esp, 4 ;

pop ecx

pop edi

pop esi

ret

; ---------------------------------------------------------------------------

%include "lib.inc" ; 庫函數

SegCode32Len equ $ - LABEL_SEG_CODE32

; END of [SECTION .s32]

; 16 位代碼段. 由 32 位代碼段跳入, 跳出後到實模式

[SECTION .s16code]

ALIGN 32

[BITS 16]

LABEL_SEG_CODE16:

; 跳回實模式:

mov ax, SelectorNormal

mov ds, ax

mov es, ax

mov fs, ax

mov gs, ax

mov ss, ax

mov eax, cr0

and eax, 7FFFFFFEh ; PE=0, PG=0

mov cr0, eax

LABEL_GO_BACK_TO_REAL:

jmp 0:LABEL_REAL_ENTRY ; 段地址會在程序開始處被設置成正確的值

Code16Len equ $ - LABEL_SEG_CODE16

; END of [SECTION .s16code]

Copyright © Linux教程網 All Rights Reserved