实验 : 将同一个虚拟地址映射到不同的物理地址
说明:此处所讲的 <将同一个虚拟地址映射到不同的物理地址> 是指 将 同一个虚拟地址 在不同的页表中进行映射,并直接指定映射的物理地址,此处直接指定的物理地址 依然是某个物理页的起始地址,特点式目标物理地址 低12位为0,即目标物理地址是4K的整数倍 说明是某个物理页面(一页4K大小)的起始地址。所以最终的映射方式不变,依然是虚拟地址高10位 作为 子页表在页目录的位置,中间10位是 目标物理页号地址 在子页表中的位置,最后低12位是 页内偏移地址。
%include "inc.asm"
;两个页目录 子页表 起始地址 对应两个不同的任务
;简单的分页构建方法,所有的子页表顺序的连续排列
;页目录0起始地址
PageDirBase0 equ 0x200000
;子页表0起始地址
PageTblBase0 equ 0x201000
;页目录1起始地址
PageDirBase1 equ 0x300000
;子页表1起始地址
PageTblBase1 equ 0x301000
;用处:在不同的页表中 将目标虚地址X 分别映射到 Y Z 两个目标物理地址
;目标虚地址X 4K字节对齐
ObjectAddrX equ 0x401000
;目标物理地址Y 低12位0 4K的整数倍 说明是某个物理页面的起始地址
TargetAddrY equ 0x501000
;目标物理地址Z 低12位0 4K的整数倍 说明是某个物理页面的起始地址
TargetAddrZ equ 0x601000
org 0x9000
jmp ENTRY_SEGMENT
[section .gdt]
; GDT definition
; 段基址, 段界限, 段属性
GDT_ENTRY : Descriptor 0, 0, 0
CODE32_DESC : Descriptor 0, Code32SegLen - 1, DA_C + DA_32
VIDEO_DESC : Descriptor 0xB8000, 0x07FFF, DA_DRWA + DA_32
DATA32_DESC : Descriptor 0, Data32SegLen - 1, DA_DRW + DA_32
STACK32_DESC : Descriptor 0, TopOfStack32, DA_DRW + DA_32
;页目录段0 段描述符 最大4096 可读可写,32位保护模式下的段
PAGE_DIR_DESC0 : Descriptor PageDirBase0, 4095, DA_DRW + DA_32
;子页表段0 段描述符 最大1024(0--1023)个子页表 可读可写,属性表明 段界限的单位是页(DA_LIMIT_4K) 4K字节,32位保护模式下的段
;默认单位是字节 这里显式的指明了单位是 4K字节
PAGE_TBL_DESC0 : Descriptor PageTblBase0, 1023, DA_DRW + DA_LIMIT_4K + DA_32
;页目录段1 段描述符
PAGE_DIR_DESC1 : Descriptor PageDirBase1, 4095, DA_DRW + DA_32
;子页表段1 段描述
PAGE_TBL_DESC1 : Descriptor PageTblBase1, 1023, DA_DRW + DA_LIMIT_4K + DA_32
;平坦内存模型 段描述符 段基地址是0 段界限4G(DA_LIMIT_4K表示单位是4K) 可读可写
FLAT_MODE_RW_DESC : Descriptor 0, 0xFFFFF, DA_DRW + DA_LIMIT_4K + DA_32
; GDT end
GdtLen equ $ - GDT_ENTRY
GdtPtr:
dw GdtLen - 1
dd 0
; GDT Selector
Code32Selector equ (0x0001 << 3) + SA_TIG + SA_RPL0
VideoSelector equ (0x0002 << 3) + SA_TIG + SA_RPL0
Data32Selector equ (0x0003 << 3) + SA_TIG + SA_RPL0
Stack32Selector equ (0x0004 << 3) + SA_TIG + SA_RPL0
;页目录段0 选择子
PageDirSelector0 equ (0x0005 << 3) + SA_TIG + SA_RPL0
;;子页表段0 选择子
PageTblSelector0 equ (0x0006 << 3) + SA_TIG + SA_RPL0
;页目录段1 选择子
PageDirSelector1 equ (0x0007 << 3) + SA_TIG + SA_RPL0
;子页表段1 选择子
PageTblSelector1 equ (0x0008 << 3) + SA_TIG + SA_RPL0
;平坦内存模型选择子
FlatModeRWSelector equ (0x0009 << 3) + SA_TIG + SA_RPL0
; end of [section .gdt]
TopOfStack16 equ 0x7c00
[section .dat]
[bits 32]
DATA32_SEGMENT:
DTOS db "D.T.OS!", 0
DTOS_LEN equ $ - DTOS
DTOS_OFFSET equ DTOS - $$
HELLO_WORLD db "Hello World!", 0
HELLO_WORLD_LEN equ $ - HELLO_WORLD
HELLO_WORLD_OFFSET equ HELLO_WORLD - $$
Data32SegLen equ $ - DATA32_SEGMENT
[section .s16]
[bits 16]
ENTRY_SEGMENT:
mov ax, cs
mov ds, ax
mov es, ax
mov ss, ax
mov sp, TopOfStack16
; initialize GDT for 32 bits code segment
mov esi, CODE32_SEGMENT
mov edi, CODE32_DESC
call InitDescItem
mov esi, DATA32_SEGMENT
mov edi, DATA32_DESC
call InitDescItem
mov esi, STACK32_SEGMENT
mov edi, STACK32_DESC
call InitDescItem
; initialize GDT pointer struct
mov eax, 0
mov ax, ds
shl eax, 4
add eax, GDT_ENTRY
mov dword [GdtPtr + 2], eax
; 1. load GDT
lgdt [GdtPtr]
; 2. close interrupt
cli
; 3. open A20
in al, 0x92
or al, 00000010b
out 0x92, al
; 4. enter protect mode
mov eax, cr0
or eax, 0x01
mov cr0, eax
; 5. jump to 32 bits code
jmp dword Code32Selector : 0
; esi --> code segment label
; edi --> descriptor label
InitDescItem:
push eax
mov eax, 0
mov ax, cs
shl eax, 4
add eax, esi
mov word [edi + 2], ax
shr eax, 16
mov byte [edi + 4], al
mov byte [edi + 7], ah
pop eax
ret
[section .s32]
[bits 32]
CODE32_SEGMENT:
mov ax, VideoSelector
mov gs, ax
mov ax, Stack32Selector
mov ss, ax
mov eax, TopOfStack32
mov esp, eax
mov ax, Data32Selector
mov ds, ax
;加载平坦内存模型选择子 使用平坦内存模型
mov ax, FlatModeRWSelector
mov es, ax
;将 DTOS_OFFSET位置处字符串 ”D.T.OS!“ 拷贝到指定物理内存TargetAddrY中 长度为DTOS_LEN
mov esi, DTOS_OFFSET ;source
mov edi, TargetAddrY ;destination
mov ecx, DTOS_LEN
;拷贝
call MemCpy32
;将 HELLO_WORLD_OFFSET位置处字符串 Hello World!" 拷贝到指定物理内存TargetAddrY中 长度为 HELLO_WORLD_LEN
mov esi, HELLO_WORLD_OFFSET ;source
mov edi, TargetAddrZ ;destination
mov ecx, HELLO_WORLD_LEN
;拷贝
call MemCpy32
;初始化 页表0 内存布局
mov eax, PageDirSelector0 ;页目录段0 选择子
mov ebx, PageTblSelector0 ;子页表段0 选择子
mov ecx, PageTblBase0 ;子页表0起始地址
call InitPageTable
;初始化 页表1 内存布局
mov eax, PageDirSelector1
mov ebx, PageTblSelector1
mov ecx, PageTblBase1
call InitPageTable
;在页表0中将虚地址映射到目标物理地址
mov eax, ObjectAddrX ; 0x401000 目标虚地址
mov ebx, TargetAddrY ; 0x501000 对应物理地址
mov ecx, PageDirBase0 ; 在0号页目录PageDirBase0中进行映射
call MapAddress
;在页表1中将虚地址映射到目标物理地址
mov eax, ObjectAddrX ; 0x401000
mov ebx, TargetAddrZ ; 0x601000
mov ecx, PageDirBase1
call MapAddress
; mov eax, PageDirBase0
; call SwitchPageTable
; mov eax, PageDirBase1
; call SwitchPageTable
jmp $
;功能:在页表中将虚地址映射到目标物理地址(需要工作在平坦内存模型下)
;直接将物理地址 存储到 虚拟地址所对应的子页表的偏移位置处。此时映射就是直接的物理地址 不再是物理页面的页号
; es --> flat mode
; eax --> virtual address 虚地址
; ebx --> target address 目标物理地址
; ecx --> page directory base 页目录,即改写该页目录中某个虚地址所映射到的目标物理地址
MapAddress:
push edi
push esi
push eax ; [esp + 8]
push ebx ; [esp + 4]
push ecx ; [esp] 当前栈顶
; 1. 取虚地址高 10 位, 计算子页表在页目录中的位置
mov eax, [esp + 8] ;获取虚地址
shr eax, 22 ;右移22位 取高10位
and eax, 1111111111b
shl eax, 2 ; *4 = 子页表在页目录中的位置
; 2. 取虚地址中间 10 位, 计算物理地址在子页表中的位置
mov ebx, [esp + 8] ;获取虚地址
shr ebx, 12 ;右移21位 取中间10位
and ebx, 1111111111b
shl ebx, 2 ; *4 = 物理地址在子页表中的位置
; 3. 取子页表起始地址
mov esi, [esp] ;获取页目录起始地址 栈顶
add esi, eax ;获取子页表偏移地址(平坦内存模型) = *(页目录地址+子页表偏移地址)
mov edi, [es:esi] ;获取子页表起始地址(平坦内存模型)
and edi, 0xFFFFF000 ;低12位清零
; 4. 将目标地址写入子页表的对应位置
add edi, ebx ;目标物理地址所在地址 = 子页表中地址 + 物理地址存储偏移位置
mov ecx, [esp + 4] ;获取 目标物理地址
and ecx, 0xFFFFF000 ;低12位清零 4k字节对齐
or ecx, PG_P | PG_USU | PG_RWW ;目标物理地址加属性
mov [es:edi], ecx ;将 目标物理地址 存入子页表对应的位置中
pop ecx
pop ebx
pop eax
pop esi
pop edi
ret
; es --> flat mode selector
; ds:esi --> source
; es:edi --> destination
; ecx --> length
MemCpy32:
push esi
push edi
push ecx
push ax
cmp esi, edi
ja btoe
add esi, ecx
add edi, ecx
dec esi
dec edi
jmp etob
btoe:
cmp ecx, 0
jz done
mov al, [ds:esi]
mov byte [es:edi], al
inc esi
inc edi
dec ecx
jmp btoe
etob:
cmp ecx, 0
jz done
mov al, [ds:esi]
mov byte [es:edi], al
dec esi
dec edi
dec ecx
jmp etob
done:
pop ax
pop ecx
pop edi
pop esi
ret
;初始化页表
; eax --> page dir base selector 页目录选择子
; ebx --> page table base selector 子页表选择子
; ecx --> page table base 子页表起始地址
InitPageTable:
push es
push eax ; [esp + 12]
push ebx ; [esp + 8]
push ecx ; [esp + 4]
push edi ; [esp] 栈顶
;设置目录项
;页目录基地址
mov es, ax ;获取页目录基地址
mov ecx, 1024 ; 1K sub page tables 有1024个子页表 循环1024次
mov edi, 0
mov eax, [esp + 4] ;获取子页表起始地址
;子页表基地址 + 属性:对应页已经在内存中 是用户级别的页 可读可写
or eax, PG_P | PG_USU | PG_RWW
cld
stdir:
;将 eax中的值存储到[es:edi]指向的内存单元中,edi+4(页目录中下一项子页表)
stosd
;填充页目录下一项 子页表地址
add eax, 4096
loop stdir
;设置子页表
;子页表基地址
mov ax, [esp + 8] ;获取 子页表选择子
mov es, ax
mov ecx, 1024 * 1024 ; 1M pages, 1024个子页表*每个子页表有1024个物理页面条目
mov edi, 0
;第一个物理页面地址 接上面累加结果,属性:对应页已经在内存中 是用户级别的页 可读可写
mov eax, PG_P | PG_USU | PG_RWW
cld
sttbl:
;将 eax中的值存储到[es:edi]指向的内存单元中,edi+4(页目录中下一项子页表)
stosd
;填充子页表中记录的下一项 物理页面地址信息
add eax, 4096
loop sttbl
pop edi
pop ecx
pop ebx
pop eax
pop es
ret
; eax --> page directory base
SwitchPageTable:
push eax
mov eax, cr0
and eax, 0x7FFFFFFF
mov cr0, eax
mov eax, [esp]
mov cr3, eax
mov eax, cr0
or eax, 0x80000000
mov cr0, eax
pop eax
ret
; ds:ebp --> string address
; bx --> attribute
; dx --> dh : row, dl : col
PrintString:
push ebp
push eax
push edi
push cx
push dx
print:
mov cl, [ds:ebp]
cmp cl, 0
je end
mov eax, 80
mul dh
add al, dl
shl eax, 1
mov edi, eax
mov ah, bl
mov al, cl
mov [gs:edi], ax
inc ebp
inc dl
jmp print
end:
pop dx
pop cx
pop edi
pop eax
pop ebp
ret
Code32SegLen equ $ - CODE32_SEGMENT
[section .gs]
[bits 32]
STACK32_SEGMENT:
times 1024 * 4 db 0
Stack32SegLen equ $ - STACK32_SEGMENT
TopOfStack32 equ Stack32SegLen - 1
验证:
0x401000虚拟地址所对应的各项信息:
100 0000 0001 0000 0000 0000
低12 : 0000 0000 0000
中10位 : 00 0000 0001 = j ; 所以 子页表地址 = 页目录地址+1*4
高10位 :1 = k 所以目标物理内存页号存储地址是 子页表地址 + 1*4
.
页表0验证
1 先反编译 : ndisasm -b 32 -o 0x9000 loader > loader.txt
2 找到关键代码指令所对应的地址 : 0x919b
0000919B EBFE jmp short 0x919b
3 break 0x9132
4 info break
5 c
// x命令读取内存值
//1:代表度一个单位 w:代表4个字节 x:以16进制方式打印结果
x /1wx 地址
6 首先读取 页目录中目标子页表地址 (页目录0起始地址0x20000 + 子页表地址 偏移4)
//x /1wx 0x200004
<bochs:4> x /1wx 0x200004
[bochs]:
0x00200004 <bogus+ 0>: 0x00202007 //低12位是属性位
<bochs:5>
7 再根据子页表地址 查看目标物理内存地址 (子页表地址高20位 + 物理内存地址偏移地址4)
//x /1wx 0x00202004
<bochs:5> x /1wx 0x00202004
[bochs]:
0x00202004 <bogus+ 0>: 0x00501007 //读取出来的值 正好是我们写进去的映射地址!!!
<bochs:6>
页表1验证类似…
代码中 初始化页表的 内存结果如图所示: