首先:在未开启分页机制的情况下,逻辑地址在分段管理机制作用下,直接转换成线性地址(物理地址);在开启分页机制的情况下,逻辑地址在分段管理机制下,转换成线性地址,然后在分页机制管理下,再从线性地址转换成物理地址。
然后给大家分享一下网上分页管理机制的示意图:
上图转换使用两级页表,第一级叫做页目录,大小为4KB,存储在一个物理页中,每个表项4字节长,所以共有1024 个表项(4KB/4B)。每个表项对应第二级的一个页表,第二级的每一个页表也有1024个表项,每一个表项对应一个物理页。
线性地址转换物理地址的具体步骤是:
(1)先是从寄存器cr3指定的页目录中根据线性地址的高10位得到页表。
(2)在页表中,根据线性地址的第12—21位得到物理页首地址。
(3)将这个首地址,加上线性地址的低12位便得到了物理地址。
接下来看一下分页机制管理的代码
_init_paging: ; 初始化页目录
mov ax, SELECTOR_PAGE_DIR
mov es, ax
mov ecx, 1024 ;页目录表项个数为1024,也等于页表个数
xor edi, edi ;将edi清零,即存放页目录表首地址
xor eax, eax ;将eax清零
mov eax, PAGE_TBL_BASE | PG_P | PG_USU | PG_RWW ;定义初始化页目录表项结构
.loop_PDE: ;设置PDE(页目录表项)
Stosd ; mov [es:edi], eax; edi = edi + 4 即将eax的内容复制到edi的内存空间,为四个字节,填充页目录表项结构,并将edi加4个字节
add eax, 4096 ; 所有页目录表项在内存中连续,每次循环页目录表项的页表地址增加4096B(4KB),映射下一个页表内存地址
loop .loop_PDE ;直到ecx为0时才退出循环,ecx是需要的PDE个数(内存大小/4MB)
;设置cr3寄存器和cr0开启分页机制
mov eax, PAGE_DIR_BASE ;页目录表的首地址传给eax。
mov cr3, eax ;将CR3寄存器指向页目录表的首地址
mov eax, cr0 ;取CR0寄存器的地址给eax。
or eax, 0x80000000 ;将CR0的PG位(分页机制)置位。
mov cr0, eax ;将eax更新的值存放到CR0寄存器。
ret ;跳转指令使其生效。
.loop_PTE: ;设置PTE(页表项)
stosd ;mov [es:edi], eax; edi = edi + 4 即将eax的内容复制到edi的内存空间,为四个字节,填充页表项结构,并将edi加4个字节
add eax, 4096 ;每次循环PTE基地址增加4096(4KB),每一页指向4KB的内存空间
loop .loop_PTE ;直到ecx为0时才退出循环,ecx是需要的PTE个数
mov ax, SELECTOR_PAGE_TABLE ;初始化页表,页表的首地址
mov es, ax ;将ax寄存器的内容给es寄存器。
mov ecx, 1024 * 1024 ;共计1M个页表项,即PTE个数,在ecx中开始循环设置PTE
xor edi, edi ;将edi清零,即edi存放页表首地址。
xor eax, eax ;将eax清零,页表从物理地址0开始映射
mov eax, PG_P | PG_USU | PG_RWW ;初始化页表项结构,PTE 存在 | 用户级别 | 可读可写
由以上的代码可以看出启用分页机制需要:
1.初始化页目录表和页表
2.将页目录表的首地址传给CR3寄存器,并且将CR0寄存器上的PG位
关于控制寄存器可见:控制寄存器cr0-4
关于PDE,PTE结构与分页机制管理可见:开启分页机制
本文以上部分参考:
自己动手写操作系统
GitHub