从零构建内核:分页

进入C语言后,程序直接运行在物理地址之上。为了学习和利用x86提供的分页机制,今天为内核设置分页。设置流程大概是:

  • 检测内存大小,决定页表多少
  • 初始化页目录项和页表项
  • 配置cr0和cr3寄存器

检测内存方式有多种,我这里是用了中断15获得Address Range Descriptor,然后把各内存区的大小求和。

在这里我使用了二级页表:页目录表(Page Directory Table)和下级页表(Page Table)。cr3寄存器装载着页目录表的基址。页目录表可以用来索引下级页表。下级页表索引具体内存页。内存页+偏移量确定具体内存单元。

二级页表下的地址解析(gnu提供图)

页目录项结构

页表项结构

我设置两者属性均为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:一个操作系统的实现》)