因为 所有的物理页的起始地址都必须是按照4K字节来对齐的,也就是被4096除尽,从二进制角度来看 就是低12位全为0,也就是说 不论在页目录中(存储子页表物理地址) 还是页表中(存储目标数据物理地址),所存储的那些物理页的起始地址低12位都是0。所以低12位就没必要作为地址位了 所以用于存储属性。
实验:x86处理器 设置页目录 设置子页表 启用处理器分页机制
开启分页机制后,效果就是:当我们访问一个地址的时候 要访问的地址 就是虚地址了,虚地址会通过页映射得到实际的物理地址
inc.asm
; Segment Attribute
DA_32 equ 0x4000
DA_LIMIT_4K EQU 0x8000
DA_DR equ 0x90
DA_DRW equ 0x92
DA_DRWA equ 0x93
DA_C equ 0x98
DA_CR equ 0x9A
DA_CCO equ 0x9C
DA_CCOR equ 0x9E
; Segment Privilege
DA_DPL0 equ 0x00 ; DPL = 0
DA_DPL1 equ 0x20 ; DPL = 1
DA_DPL2 equ 0x40 ; DPL = 2
DA_DPL3 equ 0x60 ; DPL = 3
; Special Attribute
DA_LDT equ 0x82
DA_TaskGate equ 0x85 ; 任务门类型值
DA_386TSS equ 0x89 ; 可用 386 任务状态段类型值
DA_386CGate equ 0x8C ; 386 调用门类型值
DA_386IGate equ 0x8E ; 386 中断门类型值
DA_386TGate equ 0x8F ; 386 陷阱门类型值
; Selector Attribute
SA_RPL0 equ 0
SA_RPL1 equ 1
SA_RPL2 equ 2
SA_RPL3 equ 3
SA_TIG equ 0
SA_TIL equ 4
PG_P equ 1 ; 页存在属性位
PG_RWR equ 0 ; R/W 属性位值, 读/执行
PG_RWW equ 2 ; R/W 属性位值, 读/写/执行
PG_USS equ 0 ; U/S 属性位值, 系统级
PG_USU equ 4 ; U/S 属性位值, 用户级
; 描述符
; usage: Descriptor Base, Limit, Attr
; Base: dd
; Limit: dd (low 20 bits available)
; Attr: dw (lower 4 bits of higher byte are always 0)
%macro Descriptor 3 ; 段基址, 段界限, 段属性
dw %2 & 0xFFFF ; 段界限1
dw %1 & 0xFFFF ; 段基址1
db (%1 >> 16) & 0xFF ; 段基址2
dw ((%2 >> 8) & 0xF00) | (%3 & 0xF0FF) ; 属性1 + 段界限2 + 属性2
db (%1 >> 24) & 0xFF ; 段基址3
%endmacro ; 共 8 字节
; 门
; usage: Gate Selector, Offset, DCount, Attr
; Selector: dw
; Offset: dd
; DCount: db
; Attr: db
%macro Gate 4
dw (%2 & 0xFFFF) ; 偏移地址1
dw %1 ; 选择子
dw (%3 & 0x1F) | ((%4 << 8) & 0xFF00) ; 属性
dw ((%2 >> 16) & 0xFFFF) ; 偏移地址2
%endmacro
loader.asm
%include "inc.asm"
;简单的分页构建方法,所有的子页表顺序的连续排列
;页目录起始地址
PageDirBase equ 0x200000
;子页表起始地址
PageTblBase equ 0x201000
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
;页目录段 段描述符 最大4096 可读可写,32位保护模式下的段
PAGE_DIR_DESC : Descriptor PageDirBase, 4095, DA_DRW + DA_32
;子页表段 段描述符 最大1024个子页表 可读可写,属性表明 段界限的单位是页(DA_LIMIT_4K) 4K字节,32位保护模式下的段
;默认单位是字节 这里显式的指明了单位是 4K字节
PAGE_TBL_DESC : Descriptor PageTblBase, 1023, 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
;页目录段 选择子
PageDirSelector equ (0x0005 << 3) + SA_TIG + SA_RPL0
;子页表段 选择子
PageTblSelector equ (0x0006 << 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_OFFSET equ DTOS - $$
HELLO_WORLD db "Hello World!", 0
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 ebp, DTOS_OFFSET
mov bx, 0x0C
mov dh, 12
mov dl, 33
call PrintString
mov ebp, HELLO_WORLD_OFFSET
mov bx, 0x0C
mov dh, 13
mov dl, 31
call PrintString
;设置目录项信息 设置子页表信息 启用处理器分页机制
call SetupPage
;...开启分页机制后,效果就是:当我们访问一个地址的时候 要访问的地址 就是虚地址了,虚地址会通过页映射得到实际的物理地址
jmp $
;设置目录项信息 设置子页表信息 启用处理器分页机制
SetupPage:
;相关寄存器入栈备份
push eax
push ecx
push edi
push es
;设置目录项
;页目录基地址
mov ax, PageDirSelector
mov es, ax
mov ecx, 1024 ; 1K sub page tables 有1024个子页表 循环1024次
mov edi, 0
;页表基地址 属性:对应页已经在内存中 是用户级别的页 可读可写
mov eax, PageTblBase | PG_P | PG_USU | PG_RWW
cld
stdir:
;将 eax中的值存储到edi指向的内存单元中,edi+4(页目录中下一项子页表)
stosd
;填充页目录下一项 子页表地址
add eax, 4096
loop stdir
;设置子页表
;子页表基地址
mov ax, PageTblSelector
mov es, ax
mov ecx, 1024 * 1024 ; 1M pages 1024个子页表*每个子页表有1024个物理页面条目
mov edi, 0
;第一个物理页面地址 0,属性:对应页已经在内存中 是用户级别的页 可读可写
mov eax, PG_P | PG_USU | PG_RWW
cld
sttbl:
;将 eax中的值存储到edi指向的内存单元中,edi+4(页目录中下一项子页表)
stosd
;填充子页表中记录的下一项 物理页面地址信息
add eax, 4096
loop sttbl
;启动处理器分页机制
;cr3寄存器指向页目录地址(可以切换不同的页目录)
mov eax, PageDirBase
mov cr3, eax
;将 cr0寄存器最高位置1,硬件级开启分页机制 !!!
mov eax, cr0
or eax, 0x80000000
mov cr0, eax
;相关寄存器出栈还原
pop es
pop edi
pop ecx
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