操作系统真象还原-开启分页模式

操作系统真象还原-开启分页模式

0x00 代码

​ boot.inc 更新条目:

...
PAGE_DIR_TABLE_POS equ 0x100000;PDT在1MB内核以外
...

;---页表相关
PG_P equ 1b
PG_RW_R equ 00b
PG_RW_W equ 10b
PG_US_S equ 000b
PG_US_U equ 100b

​ loader.s:

%include "boot.inc"
LOADER_STACK_TOP equ LOADER_BASE_ADDR
SECTION loaders vstart=LOADER_BASE_ADDR
jmp near loader_start

;---GDT
;省略了gdt条目 详见上一章博客

;以下是物理内存检测的缓冲结构
ards_buf times 240 db 0;缓冲区 保存返回结构
ards_nr dw 0;统计返回多少个结构
mem_total_size dd 0;32位 下 最大32位
loader_msg db "2 loader in real.",0

loader_start:

mov ax,0
mov es,ax

;以下检测内存大小,必须在实模式下进行,调用bios int 0x15
xor ebx,ebx;清0
mov edx,0x534d4150 ;签名
mov di,ards_buf;es:di 为返回结构的缓冲地址
.e820_loop:
mov eax,0xe820
mov ecx,20;返回的结构大小
int 0x15
jc .check_end;如果cf为1 则读取失败
add di,20;更新缓冲地址
inc word [ards_nr];统计结构个数
cmp ebx,0
jne .e820_loop;cf为0 ebx非0 说明还有结构没读 如果 cd为0 ebx也为0 那么说明读完了

;这个结构返回的是物理内存的布局 每个区域的基地址+长度,所以最大容量 是 地址的最大值 即 基地址+长度的最大值 以字节为单位,因此要用32为寄存器保存
;遍历所有结构 找到最大的;  (32位平台下只考虑 结构的0字节偏移(低32位基地址)和8字节偏移(长度的低32位))
xor ebx,ebx;用ebx保存最大值
mov di,ards_buf;es di 是结构数组的起始地址
mov cx,[es:ards_nr];循环次数
.max_loop:
xor eax,eax
mov eax,[es:di];
add eax,[es:di+8];
cmp eax,ebx
ja .above
add di,20
loop .max_loop
jmp .check_end
.above:
mov ebx,eax;更新最大值
add di,20
loop .max_loop

.check_end:

;以下进入保护模式 
mov ax,0
mov es,ax
mov ds,ax
mov ss,ax
mov sp,LOADER_BASE_ADDR

mov [mem_total_size],ebx;写入内存大小

mov bp,loader_msg;es:bp 待显示字符串
mov cx,17
mov ax,0x1301;设置功能号 及显示方式
mov bx,0x001f;设置颜色
mov dx,0x1800;设置行列
int 0x10;

;--进入保护模式
;打开A20
in al,0x92
or al,0000_0010b
out 0x92,al

;---加载gdt
lgdt [gdt_ptr]

;---打开cr0 pe位
mov eax,cr0
or eax,0x01
mov cr0,eax

jmp dword SELECTOR_CODE:p_mode_start


[bits 32]
;进入保护模式立马开启分页机制
p_mode_start:
mov ax,SELECTOR_DATA
mov ss,ax
mov ds,ax
mov es,ax
mov fs,ax
mov ax,SELECTOR_VIDEO
mov gs,ax;gs指向video段

call setup_page
sgdt [gdt_ptr];重新写回gdtr的值
mov ebx,[gdt_ptr+2];获取gdt基地址
or dword [ebx+0x18+4],0xc0000000;将视频段描述符中的基地址+0xc0000000 实际上,只需要将高32位中的最高8位中的最高2位置1
;另外两个段描述符不用改,因为它们是0-4GB不是针对物理地址建立的,而视频段则是针对物理地址0x000b8000建立的

or dword [gdt_ptr+2],0xc0000000;修改gdt的基地址
or esp,0xc0000000;同样的,要修改esp ,因为开启分页机制后,全部会使用虚拟地址

mov eax,PAGE_DIR_TABLE_POS
mov cr3,eax;cr3中高20位是物理地址 低12位是属性 但是实际上 属性全0,因此直接使用eax

mov eax,cr0
or eax,0x80000000
mov cr0,eax;开启分页机制

lgdt [gdt_ptr];开启分页机制后,重新加载gdt

mov byte [gs:320],'V'
jmp $



;----创建页目录及页表

setup_page:
mov ecx,4096
mov esi,0
.clear_page_dir:
mov byte [PAGE_DIR_TABLE_POS+esi],0
inc esi
loop .clear_page_dir

.create_pde:
mov eax,PAGE_DIR_TABLE_POS
add eax,0x1000 ;第一个页表
mov ebx,eax

or eax,PG_US_U|PG_RW_W|PG_P
mov [PAGE_DIR_TABLE_POS+0x00],eax ;给第一个PDE写入对应页表地址及属性
mov [PAGE_DIR_TABLE_POS+0xc00],eax;将这个页表也挂到内核地址空间的第一个PDE (内核是3GB以上) 1100 0000 00_00 0000 0000 0000 0000 0000
;内核所在的虚拟地址空间第一个pde对应的索引是 0x300,因此第一个pde的偏移是0xc00
sub eax,0x1000
mov [PAGE_DIR_TABLE_POS+4092],eax ;eax是PDT的物理地址 将PDT的最后一个PDE(1023*4)指向自己,方便以后用虚拟地址访问PDT

;以下对第一个PDE对应的页表,建立页表项
mov ecx,256 ;只建立前256个,因为我们只用到了1MB地址空间,只分配256个
mov esi,0;分配的物理页的初始地址0x00
mov edx,PG_US_U|PG_RW_W|PG_P
.create_pte:
mov [ebx+esi*4],edx
add edx,0x1000;分配的物理页地址,每次增加4096
inc esi
loop .create_pte

;创建内核其他的 所有的 PDE 为什么现在就分配,就创建?
;因为内核是共享的,高于3GB以上的线性地址空间和物理地址的映射
;在所有进程里都要相同,因此 如果不一开始就分配好,以后分配
;就需要修改所有进程的PDE

;虽然分配了页表,但是每个页表都是空的
mov eax,PAGE_DIR_TABLE_POS
add eax,0x2000
or eax,PG_US_U|PG_RW_W|PG_P
mov ebx,PAGE_DIR_TABLE_POS
mov ecx,254;一共有256个 但是最高的PDE用来指向自己 第一个内核PDE已经创建了
mov esi,769
.create_kernel_pde:
mov [ebx+esi*4],eax
inc esi
add eax,0x1000
loop .create_kernel_pde
ret

以上代码编译通过,运行正确