进入C语言后,程序直接运行在物理地址之上。为了学习和利用x86提供的分页机制,今天为内核设置分页。设置流程大概是:
- 检测内存大小,决定页表多少
- 初始化页目录项和页表项
- 配置cr0和cr3寄存器
检测内存方式有多种,我这里是用了中断15获得Address Range Descriptor
,然后把各内存区的大小求和。
在这里我使用了二级页表:页目录表(Page Directory Table)和下级页表(Page Table)。cr3寄存器装载着页目录表的基址。页目录表可以用来索引下级页表。下级页表索引具体内存页。内存页+偏移量确定具体内存单元。
我设置两者属性均为0x7可读写、用户使用、存在物理内存中(W|U|P)。基址是物理地址左移12bit的地址(所以两个表是4KB对齐的)。因为基址存在于结构的高20位,CR3也是用高20位存放基址,所以不需要在装入时做额外的移位。只要确保各表起始地址4KB对齐(后12bit为零)即可。
简单起见,我们两个表在内存中是这样排列的:
页目录表地址是0x100000
。空0x1000
,也就是4KB,也就是1024个页目录项的大小,也就是0x101000
是二级页表的首地址。页目录项中的页表基址,第一个是第一张页表的基址,第二个是第二张页表的基址…两个基址之间相差1,对应4KB的物理内存,也就是1024个页表项的大小。页表项中的页基址,第一个是第一页的基址,第二个是第二页的基址…两个基址之间相差1,对应4KB的物理内存。
最终,配置CR0、CR3,启用分页。这个需要使用汇编代码完成,最后链接到C语言的程序里调用。
(文中扫描图片来自于渊《Orange’s:一个操作系统的实现》)