实战分页机制实现 — 通过实际内存大小动态调整页表个数
1. 引言
上一篇文章中,我们详细讲解了 32 位保护模式下的分页机制,最终,我们将 4GB 的内存区域划分为了连续的 1023 个分页,页表保存在 4MB 的空间中。
详解操作系统分页机制与实战
但是我们的内存大小到底是多少呢?如果内存总共只要 8MB,那上面的分页程序执行完,光是页表就占用了 4MB,空间已经所剩无几,可见,按需使用内存,合理规划页表的大小是非常重要的,而这一切的前提是必须要搞清楚内存总共有多少。
本文我们就来通过一个程序获取计算机的内存信息。
2. 通过 BIOS 中断获取内存信息
我们曾经通过 BIOS 的 10H 硬件中断实现向显示器输出一行文字。
计算机是如何启动的?如何制作自己的操作系统
通过 15H 号硬件终端,我们可以获取系统多个不同方面的信息,这其中就包括内存信息的获取。
2.1. 原理
用于获取内存信息的 10H 中断将内存信息拼装为一个 20 个字节的数据结构 — 地址范围描述符结构,写入到指定的内存中。
每一次中断生成一个描述符结构,用来表示一段连续可用的内存,经过若干次中断调用,即可获取整个内存中若干段的可用内存。
2.2. 地址范围描述符 ARDS
ARDS 是 Address Range Descriptor Structure 的简称,也就是地址范围描述符。
他描述了内存中连续可用的一段,共有 20 个字节:
ARDS 结构
偏移 | 名称 | 说明 |
---|---|---|
0 | BaseAddrLow | 初始物理地址低 32 位 |
4 | BaseAddrHigh | 初始物理地址高 32 位 |
8 | LengthLow | 内存段长度字节数低 32 位 |
12 | LengthHigh | 内存段长度字节数高 32 位 |
16 | Type | 地址范围类型,位 1 表示可以被 OS 使用,其他值表示 OS 不可用 |
2.3. 准备工作
利用 BIOS INT 15H 获取内存信息前,需要填充以下寄存器:
- EAX — 设置为 0E820h,表示获取内存信息
- EBX — 设置为 0
- ES:DI — 信息写入的内存区域首地址
- ECX — 内存区域大小字节数,通常系统需要写入的数据是 20 字节,如果 ECX 值小于 20,那么 BIOS 会写入 ECX 字节,但有些实现中 BIOS 没有考虑 ECX 的值,总是写入 20 字节
- EDX — 0534D4150h,用于校验的固定值,中断执行后会被填充到 EAX
2.4. INT 15H 中断完成后寄存器的值
- EFLAGS — EFLAGS 的 CF 位表示中断执行是否出错,位 0 表示没有出错,为 1 表示出错
- EAX — 0534D4150h
- ECX — BIOS 实际写入字节数
- EBX — 剩余需要写入的地址描述符数量
3. 获取内存信息
下面,我们就在实地址模式下通过 INT 15H 获取内存信息保存在内存上,然后到保护模式下,通过 8025 彩色字符模式打印出内存的信息。
3.1. 分配 ARDS 存储空间
我们需要在数据段中开辟出一块内存用来存储若干个 ARDS 结构,同时为了能够在保护模式下使用,需要创建一个存储偏移的指针。
与之对应的,我们还需要一个变量,用于存储 ARDS 结构的个数,以及保护模式下使用的段内偏移指针变量。
_MemChkBuf: times 256 db 0 ; ARDS 缓冲区
_dwMCRNumber: dd 0 ; ARDS 个数
MemChkBuf equ _MemChkBuf - $$
dwMCRNumber equ _dwMCRNumber - $$
3.2. 在实模式下循环获取 ARDS
; 循环获取 ARDS
xor ebx, ebx ; 清零 EBX
mov di, _MemChkBuf ; DI 寄存器中保存写入地址偏移
.loop:
mov eax, 0E820h ; 初始化 EAX 为固定值
mov ecx, 20 ; ARDS 字节数
mov edx, 0534D4150h ; 初始化 EDX 为固定值
int 15h ; 触发 15H 中断
jc LABEL_MEM_CHK_FAIL ; EFLAGS 寄存器 CF 位为 1 则跳转,表示失败
add di, 20 ; DI 寄存器指向下一个待写入位置偏移
inc dword [_dwMCRNumber] ; 计数变量 + 1
cmp ebx, 0 ; 比较 EBX 判断是否完成 ARDS 获取
jne .loop
jmp LABEL_MEM_CHK_OK
LABEL_MEM_CHK_FAIL:
mov dword [_dwMCRNumber], 0
LABEL_MEM_CHK_OK:
4. 打印 ARDS 并获取最大连续内存
4.1. 内存变量分配
为了打印 ARDS 并获取最大连续内存,除了上面我们已经定义并填充的内存缓冲区,我们还需要定义连续内存大小值的存储变量。
同时,需要一个 ARDS 结构变量来进行比较。
_dwMemSize: dd 0 ; 最大连续内存大小
_ARDStruct:
_dwBaseAddrLow: dd 0
_dwBaseAddrHigh: dd 0
_dwLengthLow: dd 0
_dwLengthHigh: dd 0
_dwType: dd 0
dwMemSize equ _dwMemSize - $$
ARDStruct equ _ARDStruct - $$ ; 用于存储 ARDS 结构的临时变量
dwBaseAddrLow equ _dwBaseAddrLow - $$
dwBaseAddrHigh equ _dwBaseAddrHigh - $$
dwLengthLow equ _dwLengthLow - $$
dwLengthHigh equ _dwLengthHigh - $$
dwType equ _dwType - $$
RAMSize db "RAM size:", 0
OffsetMemSize equ RAMSize - $$
4.2. 打印函数定义
随着我们的程序越来越长,我们必须进行函数的封装和拆分,甚至进行文件的拆分,来让我们的程序可读性更强。
4.2.1. 打印字符串
; ------------------------------------------------------------------------
; 显示一个字符串
;
; params:
; db property
; dw display_index
; dw string_offset
; ------------------------------------------------------------------------
DisplayString:
push ebp
mov ebp, esp
push eax
push edi
push esi
mov ah, [ebp + 16]
mov edi, [ebp + 12]
mov esi, [ebp + 8]
cld
.loop_label:
lodsb
test al, al
jz .over_print
mov [gs:edi], ax
add edi, 2
jmp .loop_label
.over_print:
pop esi
pop edi
pop eax
pop ebp
ret
4.2.2. 打印一个字节中的数字
; ------------------------------------------------------------------------
; 显示一个字节中的数字
;
; params:
; db property
; dw display_index
; db number
;
; return:
; eax display_index
; ------------------------------------------------------------------------
DisplayByteNumber:
push ebp
mov ebp, esp
push edx
push edi
push esi
mov ah, [ebp + 16]
mov edi, [ebp + 12]
mov al, [ebp + 8]
mov dl, al
shr al, 4
mov ecx, 2
.begin:
and al, 01111b
cmp al, 9
ja .letter
add al, '0'
jmp .number
.letter:
sub al, 0Ah
add al, 'A'
.number:
mov [gs:edi], ax
add edi, 2
mov al, dl
loop .begin
mov eax, edi
pop esi
pop edi
pop edx
pop ebp
ret
4.2.3. 打印一个 dword 中的数字
在 32 位系统中,打印一个 32 位的数字是最为常用的功能,也是我们本次程序中所必须使用的。
; ------------------------------------------------------------------------
; 显示一个整形数
;
; params:
; db property
; dw display_index
; db number
;
; return:
; eax display_index
; ------------------------------------------------------------------------
DisplayInt:
push ebp
mov ebp, esp
push edx
push edi
push esi
push ebx
push ecx
mov ah, [ebp + 16]
mov edi, [ebp + 12]
mov ecx, 4
mov ebx, 24
push ah
.loop:
mov eax, [ebp + 8]
shr eax, ebx
push edi
push eax
call DisplayByteNumber
add esp, 8
add edi, 2
sub ebx, 8
loop .loop
mov al, 'h'
push edi
mov [gs:edi], ax
add edi, 4
mv eax, edi
pop ecx
pop ebx
pop esi
pop edi
pop edx
pop ebp
ret
可以看到,他通过循环调用 DisplayByteNumber 实现了对32位数字的打印。
4.3. 显示内存信息并获取最大连续内存
; ---------------------- 显示内存信息 ---------------------------
DispMemSize:
push esi
push edi
push ebx
push ecx
push edx
mov esi, MemChkBuf ; esi 指向 ARDS 缓冲器首地址
mov ecx, [dwMCRNumber] ; 循环次数复制
mov ebx, (80 * 3) * 2
push ebx
.dispmemsizeloop:
mov edx, 5 ; 依次打印 ARDS 中的 5 个成员
mov edi, ARDStruct
.printloop:
mov eax, 0Fh
push eax
mov eax, ebx
push eax
push dword [esi]
call DisplayInt
add esp, 12
mov ebx, eax
add ebx, 4
add esi, 4
dec edx
cmp edx, 0
jnz .printloop
pop ebx
add ebx, 160
push ebx
cmp dword [dwType], 1 ; dwType 不为 1 表示操作系统不可用
jne .finishgetmem
mov eax, [dwBaseAddrLow]
add eax, [dwLengthLow] ; 获取当前结构对应的连续内存长度
cmp eax, [dwMemSize] ; 与已获取到的最大内存大小比较,如果大于则更新
jb .finishgetmem
mov [dwMemSize], eax
.finishgetmem:
loop .dispmemsizeloop
pop ebx
add ebx, 160
mov eax, 0Fh
push eax
mov eax, ebx
push eax
push OffsetMemSize
call DisplayString
add esp, 12
mov eax, 0Fh
push eax
mov eax, ebx
push eax
push dword [dwMemSize]
call DisplayInt
add esp, 12
pop edx
pop ecx
pop ebx
pop edi
pop esi
ret
5. 改造分页机制
接下来,我们就要对上一篇文章中的分页机制进行改造,实现在有限的最大连续内存中分配我们的页目录表和页表。
5.1. 变量分配
我们需要动态计算页表个数,因此需要一个变量来存储页表个数。
_PageTableNumber dd 0 ; 页表个数
PageTableNumber equ _PageTableNumber - $$
5.2. 启动分页机制
下面,我们就让我们的程序通过上面计算出的最大可用连续内存来动态决定页表个数,分配可用内存。
; ---------------------- 分页机制启动 ---------------------------
SetupPaging:
; 根据内存大小计算应初始化多少PDE以及多少页表
xor edx, edx
mov eax, [dwMemSize]
mov ebx, 400000h ; 一个页表对应的内存 4MB
div ebx
mov ecx, eax ; ecx 保存页表个数
test edx, edx
jz .no_remainder
inc ecx ; 余数不为 0 则增加一个页表
.no_remainder:
mov [PageTableNumber], ecx ; 暂存页表个数
; 为简化处理, 所有线性地址对应相等的物理地址. 并且不考虑内存空洞.
; 初始化页目录
mov ax, SelectorFlatRW
mov es, ax
mov edi, PageDirBase ; 此段首地址为 PageDirBase
xor eax, eax
mov eax, PageTblBase0 | 7 ; 用户级,存在于内存,可读写
.filter_pde:
stosd
add eax, 4096 ; 为了简化, 所有页表在内存中是连续的.
loop .filter_pde
; 再初始化所有页表
mov eax, [PageTableNumber] ; 页表个数
mov ebx, 1024 ; 每个页表 1024 个 PTE
mul ebx
mov ecx, eax ; PTE 个数 = 页表个数 * 1024
mov edi, PageTblBase0
xor eax, eax
mov eax, 7 ; 用户级、存在于内存、可读写
.filter_pte:
stosd
add eax, 4096 ; 每一页指向 4K 的空间
loop .filter_pte
; 设置页目录表起始地址
mov eax, PageDirBase
mov cr3, eax
; 开启分页机制
mov eax, cr0
or eax, 80000000h
mov cr0, eax
ret
6. 运行效果
经过一系列的工作,我们终于完成了我们的程序,让我们的“操作系统”可以获取实际可用的连续内存大小,并在其中分配页表来启动我们的程序,那接下来就让我们执行看看:
7. 微信公众号
欢迎关注微信公众号,以技术为主,涉及历史、人文等多领域的学习与感悟,每周三到七篇推文,只有全部原创,只有干货没有鸡汤。
此处有图片 3
8. 附录 — 完整代码
8.1. 函数封装 — lib.asm
; ------------------------------------------------------------------------
; 显示一个字节中的数字
;
; params:
; db property
; dw display_index
; db number
;
; return:
; eax display_index
; ------------------------------------------------------------------------
DisplayByteNumber:
push ebp
mov ebp, esp
push edx
push edi
push esi
mov ah, [ebp + 16]
mov edi, [ebp + 12]
mov al, [ebp + 8]
mov dl, al
shr al, 4
mov ecx, 2
.begin:
and al, 01111b
cmp al, 9
ja .letter
add al, '0'
jmp .number
.letter:
sub al, 0Ah
add al, 'A'
.number:
mov [gs:edi], ax
add edi, 2
mov al, dl
loop .begin
mov eax, edi
pop esi
pop edi
pop edx
pop ebp
ret
; ------------------------------------------------------------------------
; 显示一个整形数
;
; params:
; db property
; dw display_index
; db number
;
; return:
; eax display_index
; ------------------------------------------------------------------------
DisplayInt:
push ebp
mov ebp, esp
push edx
push edi
push esi
push ebx
push ecx
mov ah, [ebp + 16]
mov edi, [ebp + 12]
mov ecx, 4
mov ebx, 24
push ah
.loop:
mov eax, [ebp + 8]
shr eax, ebx
push edi
push eax
call DisplayByteNumber
add esp, 8
add edi, 2
sub ebx, 8
loop .loop
mov al, 'h'
push edi
mov [gs:edi], ax
add edi, 4
mv eax, edi
pop ecx
pop ebx
pop esi
pop edi
pop edx
pop ebp
ret
; ------------------------------------------------------------------------
; 显示一个字符串
;
; params:
; db property
; dw display_index
; dw string_offset
; ------------------------------------------------------------------------
DisplayString:
push ebp
mov ebp, esp
push eax
push edi
push esi
mov ah, [ebp + 16]
mov edi, [ebp + 12]
mov esi, [ebp + 8]
cld
.loop_label:
lodsb
test al, al
jz .over_print
mov [gs:edi], ax
add edi, 2
jmp .loop_label
.over_print:
pop esi
pop edi
pop eax
pop ebp
ret
8.2. 主程序 — protect.asm
; ---------------- 内存段描述符宏 -------------
; 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 & 0FFFFh ; 段界限1
dw %1 & 0FFFFh ; 段基址1
db (%1 >> 16) & 0FFh ; 段基址2
dw ((%2 >> 8) & 0F00h) | (%3 & 0F0FFh) ; 属性1 + 段界限2 + 属性2
db (%1 >> 24) & 0FFh ; 段基址3
%endmacro
PageDirBase equ 200000h ; 页目录开始地址: 2M
PageTblBase equ 201000h ; 页表开始地址: 2M+4K
; ------------ DOS 加载初始内存地址 -----------
org 0100h
jmp LABEL_BEGIN
; ------------------- GDT ---------------------
[SECTION .gdt]
; GDT
; 段基址, 段界限, 属性
LABEL_GDT: Descriptor 0, 0, 0 ; 空描述符
LABEL_DESC_NORMAL: Descriptor 0, 0ffffh, 92h ; Normal 描述符
LABEL_DESC_PAGE_DIR: Descriptor PageDirBase, 4095, 92h ; Page Directory,可读写
LABEL_DESC_PAGE_TBL: Descriptor PageTblBase, 1023, 8092h ; Page Tables,段界限为 1023 * 4096 字节
LABEL_DESC_CODE32: Descriptor 0, SegCode32Len - 1, 4098h ; 非一致代码段
LABEL_DESC_CODE16: Descriptor 0, 0ffffh, 98h ; 非一致代码段, 用于跳回 16 BITS 模式
LABEL_DESC_DATA: Descriptor 0, DataLen-1, 92h ; 可读写数据段,界限 64KB
LABEL_DESC_STACK: Descriptor 0, TopOfStack, 4093h ; 32 位全局堆栈段,可读写数据段,且栈指针默认使用 esp 寄存器
LABEL_DESC_VIDEO: Descriptor 0B8000h, 0ffffh, 92h ; 显存首地址
; ------------------ END OF GDT ----------------
GdtLen equ $ - LABEL_GDT ; GDT长度
GdtPtr dw GdtLen - 1 ; GDT界限
dd 0 ; GDT基地址
; ------------------ GDT 选择子 -----------------
SelectorNormal equ LABEL_DESC_NORMAL - LABEL_GDT
SelectorPageDir equ LABEL_DESC_PAGE_DIR - LABEL_GDT
SelectorPageTbl equ LABEL_DESC_PAGE_TBL - 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 .data1] ; 数据段
ALIGN 32
[BITS 32]
LABEL_DATA:
SPValueInRealMode dw 0
BootMessage: db "Hello World my OS, techlog.cn!", 0
OffsetBootMessage equ BootMessage - $$
RAMSize db "RAM size:", 0
OffsetMemSize equ RAMSize - $$
_MemChkBuf: times 256 db 0 ; ARDS 缓冲区
_dwMCRNumber: dd 0 ; ARDS 个数
_PageTableNumber dd 0 ; 页表个数
_dwMemSize: dd 0 ; 最大连续内存大小
_ARDStruct:
_dwBaseAddrLow: dd 0
_dwBaseAddrHigh: dd 0
_dwLengthLow: dd 0
_dwLengthHigh: dd 0
_dwType: dd 0
MemChkBuf equ _MemChkBuf - $$
dwMCRNumber equ _dwMCRNumber - $$
PageTableNumber equ _PageTableNumber - $$
dwMemSize equ _dwMemSize - $$
ARDStruct equ _ARDStruct - $$ ; 用于存储 ARDS 结构的临时变量
dwBaseAddrLow equ _dwBaseAddrLow - $$
dwBaseAddrHigh equ _dwBaseAddrHigh - $$
dwLengthLow equ _dwLengthLow - $$
dwLengthHigh equ _dwLengthHigh - $$
dwType equ _dwType - $$
DataLen equ $ - LABEL_DATA
; 全局堆栈段
[SECTION .gs]
ALIGN 32
[BITS 32]
LABEL_STACK:
times 512 db 0
TopOfStack equ $ - LABEL_STACK - 1
[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 [SPValueInRealMode], sp
; 循环获取 ARDS
xor ebx, ebx ; 清零 EBX
mov di, _MemChkBuf ; DI 寄存器中保存写入地址偏移
.loop:
mov eax, 0E820h ; 初始化 EAX 为固定值
mov ecx, 20 ; ARDS 字节数
mov edx, 0534D4150h ; 初始化 EDX 为固定值
int 15h ; 触发 15H 中断
jc LABEL_MEM_CHK_FAIL ; EFLAGS 寄存器 CF 位为 1 则跳转,表示失败
add di, 20 ; DI 寄存器指向下一个待写入位置偏移
inc dword [_dwMCRNumber] ; 计数变量 + 1
cmp ebx, 0 ; 比较 EBX 判断是否完成 ARDS 获取
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
; 初始化非一致代码段描述符
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 ; 清空 eax 寄存器
mov ax, ds
shl eax, 4
add eax, LABEL_GDT ; 计算出 GDT 基地址的物理地址
mov dword [GdtPtr + 2], eax ; [GdtPtr + 2] <- gdt 基地址
; 加载 GDTR
lgdt [GdtPtr]
; 关闭硬件中断
cli
; 打开 A20 地址总线
in al, 92h
or al, 00000010b
out 92h, al
; 置位 PE 标志位,打开保护模式
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, [SPValueInRealMode]
; 关闭 A20 地址线
in al, 92h
and al, 0fdh
out 92h, al
; 打开硬件中断
sti
; 触发 BIOS int 21h 中断,回到实地址模式
mov ax, 4c00h
int 21h
[SECTION .s32] ; 32 位代码段. 由实模式跳入.
[BITS 32]
LABEL_SEG_CODE32:
mov ax, SelectorData
mov ds, ax ; 数据段选择子
mov ax, SelectorVideo
mov gs, ax ; 赋值视频段选择子
mov ax, SelectorStack
mov ss, ax ; 堆栈段选择子
mov esp, TopOfStack
mov eax, 0Fh
push eax
mov eax, 80 * 2 * 2
push eax
push OffsetBootMessage
call DisplayString
add esp 12
call DispMemSize ; 显示内存信息
call SetupPaging ; 启动分页机制
jmp SelectorCode16:0
; ---------------------- 显示内存信息 ---------------------------
DispMemSize:
push esi
push edi
push ebx
push ecx
push edx
mov esi, MemChkBuf ; esi 指向 ARDS 缓冲器首地址
mov ecx, [dwMCRNumber] ; 循环次数复制
mov ebx, (80 * 3) * 2
push ebx
.dispmemsizeloop:
mov edx, 5 ; 依次打印 ARDS 中的 5 个成员
mov edi, ARDStruct
.printloop:
mov eax, 0Fh
push eax
mov eax, ebx
push eax
push dword [esi]
call DisplayInt
add esp, 12
mov ebx, eax
add ebx, 4
add esi, 4
dec edx
cmp edx, 0
jnz .printloop
pop ebx
add ebx, 160
push ebx
cmp dword [dwType], 1 ; dwType 不为 1 表示操作系统不可用
jne .finishgetmem
mov eax, [dwBaseAddrLow]
add eax, [dwLengthLow] ; 获取当前结构对应的连续内存长度
cmp eax, [dwMemSize] ; 与已获取到的最大内存大小比较,如果大于则更新
jb .finishgetmem
mov [dwMemSize], eax
.finishgetmem:
loop .dispmemsizeloop
pop ebx
add ebx, 160
mov eax, 0Fh
push eax
mov eax, ebx
push eax
push OffsetMemSize
call DisplayString
add esp, 12
mov eax, 0Fh
push eax
mov eax, ebx
push eax
push dword [dwMemSize]
call DisplayInt
add esp, 12
pop edx
pop ecx
pop ebx
pop edi
pop esi
ret
; ---------------------- 分页机制启动 ---------------------------
SetupPaging:
; 根据内存大小计算应初始化多少PDE以及多少页表
xor edx, edx
mov eax, [dwMemSize]
mov ebx, 400000h ; 一个页表对应的内存 4MB
div ebx
mov ecx, eax ; ecx 保存页表个数
test edx, edx
jz .no_remainder
inc ecx ; 余数不为 0 则增加一个页表
.no_remainder:
mov [PageTableNumber], ecx ; 暂存页表个数
; 为简化处理, 所有线性地址对应相等的物理地址. 并且不考虑内存空洞.
; 初始化页目录
mov ax, SelectorFlatRW
mov es, ax
mov edi, PageDirBase ; 此段首地址为 PageDirBase
xor eax, eax
mov eax, PageTblBase0 | 7 ; 用户级,存在于内存,可读写
.filter_pde:
stosd
add eax, 4096 ; 为了简化, 所有页表在内存中是连续的.
loop .filter_pde
; 再初始化所有页表
mov eax, [PageTableNumber] ; 页表个数
mov ebx, 1024 ; 每个页表 1024 个 PTE
mul ebx
mov ecx, eax ; PTE 个数 = 页表个数 * 1024
mov edi, PageTblBase0
xor eax, eax
mov eax, 7 ; 用户级、存在于内存、可读写
.filter_pte:
stosd
add eax, 4096 ; 每一页指向 4K 的空间
loop .filter_pte
; 设置页目录表起始地址
mov eax, PageDirBase
mov cr3, eax
; 开启分页机制
mov eax, cr0
or eax, 80000000h
mov cr0, eax
ret
%include "lib.asm" ; 库函数
SegCode32Len equ $ - LABEL_SEG_CODE32
; 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 word 0:LABEL_REAL_ENTRY ; 段地址会在程序开始处被设置成正确的值
Code16Len equ $ - LABEL_SEG_CODE16
; END of [SECTION .s16code]
9. 参考资料
http://www.uruk.org/orig-grub/mem64mb.html。
https://en.wikipedia.org/wiki/BIOS\_interrupt\_call。
阅读原文
《实战分页机制实现 — 通过实际内存大小动态调整页表个数》来自互联网,仅为收藏学习,如侵权请联系删除。本文URL:http://www.bookhoes.com/4025.html