1、修改loader.S程序
开启分页机制的三个步骤:
1)增加了创建页目录表和页表的代码
另外需要将GDT的基址加上0xc0000000,同时显存段描述符基址和我们的内核栈esp,也加上0xc0000000,这样映射到内核的虚拟地址上
2)页目录表地址赋值cr3
3)打开cr0的pg位(第31位)
%include "boot.inc"
section loader vstart=LOADER_BASE_ADDR
LOADER_STACK_TOP equ LOADER_BASE_ADDR
;构建GDT及其内部描述符
GDT_BASE: dd 0x00000000
dd 0x00000000
CODE_DESC: dd 0x0000FFFF
dd DESC_CODE_HIGH4
DATA_STACK_DESC: dd 0x0000FFFF
dd DESC_DATA_HIGH4
VIDEO_DESC: dd 0x80000007
dd DESC_VIDEO_HIGH4
GDT_SIZE equ $ - GDT_BASE
GDT_LIMIT equ GDT_SIZE - 1
times 60 dq 0
;构建选择子
SELECTOR_CODE equ (0x0001<<3) + TI_GDT + RPL0
SELECTOR_DATA equ (0x0002<<3) + TI_GDT + RPL0
SELECTOR_VIDEO equ (0x0003<<3) + TI_GDT + RPL0
total_mem_bytes dd 0 ;用于保存最终获得的内存容量
gdt_ptr dw GDT_LIMIT
dd GDT_BASE
;人工对齐
ards_buf times 244 db 0
ards_nr dw 0
loader_start:
;int 15h eax=0000e820h,edx=534D4150h获取内存布局
xor ebx,ebx
mov edx,0x534d4150
mov di,ards_buf
.e820_mem_get_loop:
mov eax,0x0000e820
mov ecx,20
int 0x15
jc .e820_failed_so_try_e801
add di,cx
inc word [ards_nr]
cmp ebx,0
jnz .e820_mem_get_loop
mov cx,[ards_nr]
mov ebx,ards_buf
xor edx,edx
.find_max_mem_area:
mov eax,[ebx]
add eax,[ebx+8]
add ebx,20
cmp edx,eax
jge .next_ards
mov edx,eax
.next_ards:
loop .find_max_mem_area
jmp .mem_get_ok
;int 15h ax=e801获取内存大小
.e820_failed_so_try_e801:
mov ax,0xe801
int 0x15
jc .e801_failed_so_try88
;算出低端15MB内存
mov cx,0x400
mul cx
shl edx,16
and eax,0x0000FFFF
or edx,eax
add edx,0x100000;加上不可探测的1MB
mov esi,edx
;16MB以上内存
xor eax,eax
mov ax,bx
mov ecx,0x10000
mul ecx
add esi,eax
mov edx,esi
jmp .mem_get_ok ;edx为总内存大小
;int 15h ah=0x88获取内存大小,只能获取64MB之内
.e801_failed_so_try88:
mov ah,0x88
int 0x15
jc .error_hlt
and eax,0x0000FFFF
mov cx,0x400
mul cx
shl edx,16
or edx,eax
add edx,0x100000;0x88只返回1MB以上内存
.mem_get_ok:
mov [total_mem_bytes],edx
;-------------------------准备进入保护模式-----------------------
;1 打开A20
;2 加载GDT
;3 将cr0的pe位置1
;-------------------------打开A20--------------------------------
in al,0x92
or al,0000_0010b
out 0x92,al
;-------------------------加载GDT--------------------------------
lgdt [gdt_ptr]
;------------------------cr0第0位置1-----------------------------
mov eax,cr0
or eax,0x00000001
mov cr0,eax
;刷新流水线
jmp dword SELECTOR_CODE:p_mode_start
.error_hlt:
hlt ;出错则挂起
[bits 32]
p_mode_start:
mov ax,SELECTOR_DATA
mov ds,ax
mov es,ax
mov ss,ax
mov esp,LOADER_STACK_TOP
mov ax,SELECTOR_VIDEO
mov gs,ax
;创建页母及页表并初始化页内存位图
call setup_page
sgdt [gdt_ptr] ;先备份描述符表地址,一会儿需要用新地址重新加载
;gdt中视频端描述符段基址+0xc0000000
mov ebx,[gdt_ptr+2]
or dword [ebx+0x18 +4],0xc0000000
;gdt的基址+0xc0000000
add dword [gdt_ptr+2],0xc0000000
;栈指针同样映射到内核地址
add esp,0xc0000000
;页目录地址赋值cr3
mov eax,PAGE_DIR_TABLE_POS
mov cr3,eax
;打开cr0的pg位(第31位)
mov eax,cr0
or eax,0x80000000
mov cr0,eax
;开启分页后,重新加载gdt
lgdt [gdt_ptr]
mov byte [gs:160],'V'
jmp $
;-----------------------------------创建页目录及页表----------------------------------
setup_page:
;页目录占用的空间逐字节清0
mov ecx,4096
mov esi,0
.clear_page_dir:
mov byte [PAGE_DIR_TABLE_POS + esi],0
inc esi
loop .clear_page_dir
;----------------------------页目录项----------
;创建页目录项(PDE)
;先创建低端4M内存的页目录项,其中第768项和第0项相同,第1023项指向页目录表自己
.create_pde:
mov eax,PAGE_DIR_TABLE_POS
add eax,0x1000 ;eax为第一个页表的位置及属性
mov ebx,eax
;将页目录项0和0xc00(第768个)都存为第一个页表的地址
or eax, PG_US_U | PG_RW_W | PG_P
mov [PAGE_DIR_TABLE_POS + 0x0],eax
mov [PAGE_DIR_TABLE_POS + 0xc00],eax
;使最后一个页目录项指向页目录表自己的地址
sub eax,0x1000
mov [PAGE_DIR_TABLE_POS + 4092],eax
;-------------------------------页表项----------
;下面创建页表项(PTE)
;低端1M内存页表项,1M/4K=256
mov ecx,256
mov esi,0
mov edx,PG_US_U | PG_RW_W | PG_P
.create_pte:
mov [ebx+esi*4],edx ;ebx=0x101000
add edx,4096
inc esi
loop .create_pte
;--------------------------其他内核页目录项----------
;创建内核其他页表的PDE
mov eax,PAGE_DIR_TABLE_POS
add eax,0x2000 ;eax为第二个页表位置
or eax,PG_US_U | PG_RW_W | PG_P
mov ebx,PAGE_DIR_TABLE_POS
mov ecx,254 ;769~1022
mov esi,769
.create_kernel_pde:
mov [ebx+esi*4],eax
inc esi
add eax,0x1000
loop .create_kernel_pde
ret
2、boot.inc配置文件修改
增加页目录表的物理地址,同时增加页表和页目录项的属性位
PAGE_DIR_TABLE_POS equ 0x100000
;---------------------------页表相关属性--------------------------------
PG_P equ 1b
PG_RW_R equ 00b
PG_RW_W equ 10b
PG_US_S equ 000b
PG_US_U equ 100b
3、上机测试运行
检查分页机制下的工作是否正常,mov byte [gs:160],'V',在第二行的第一个字符显示了我们大写字母'V'
分页之后,GDT的基地址已经是3GB之上的虚拟地址了,显存段基址也变成了3GB以上的虚拟地址,如图所示