逻辑地址,线性地址,物理地址

程序编译后,程序内部使用的地址是段内偏移量,这个地址是逻辑地址。

程序加载运行时,操作系统负责多个程序的调度。程序使用的段内偏移量(逻辑地址),加上段内偏移量,就是线性地址。

如果没有分页机制,那线性地址=物理地址。启用分页机制之后,线性地址通过转换,才能对应为物理地址。

因此,逻辑地址经过分段机制转换后,得到线性地址;再经过分页机制转换后,得到物理地址。

分页

为什么要分页

段的长度不定,在分配内存时,可能会发生内存中的空闲区域小于要加载的段,或者空闲区域远远大于要加载的段。在前一种情况下,需要另外寻找合适的空闲区域;在后一种情况下,分配会成功,但太过于浪费。为了解决这个问题,从386开始,引入了分页机制。分页功能从总体上来说,是用长度固定的页来代替长度不一定的段,藉此解决因段长度不同而带来的内存空间管理问题。

页框,页表,页目录表

操作系统将线性地址按固定大小组织,每一份称之为一页(page)。页大小可以为4K,1M等,一般为4KB。物理地址按照同样大小进行划分,称为页框(frame)。操作系统需要记录线性地址与物理地址的映射关系,这个关系就记录在页表(page table)中。页表中的每一项称为PTE。

实际中,为了节省内存,会使用多级页表,即为页表再创建一个页目录表(page direcotry table)。页目录表中每的一项,称之为PDE,都指向不同的页表。

分页寻址

逻辑地址经过分段后,得到32位的线性地址。启用分页机制后,32位的线性地址会再次划分为3部分,分别表示

  • 22~31位:指向页目录表中的某一项PDE
  • 12~21位:指向具体某个页表中的某一项
  • 0~11位:页内偏移量

假设我们采用2级页表,我们可以计算一下:

  • 每个页大小4KB,12位的业内偏移量恰好可以寻址4KB偏移地址。
  • 22~31位共10位,最多可以表示1024个不同值,因此页目录表最多只能有1024个PDE
  • 同理,12~21位共10位,对应页表最多页也只能有1024个PTE
  • 1024个PDE * 1024个PTE * 4KB = 4GB,分页之后能够寻址的最大空间还是4GB

启用分页机制

启用分页机制,需要进行如下3步操作:

  • 创建页表
  • 将cr3寄存器设置为页目录表地址
  • 将cr0寄存器pg位置1

内存布局设计

现在的内存布局是我从其它地方抄来的。

截图中显示了页目录表,及第1页表。

  • 页目录表中的第0项及第768项,均对应第1页表。
  • 页目录表中第1023项(即最后一项),对应页目录表本身。
  • 第1页表,只填写了0~255项,对应1M(4K*256)物理内存。

页目录表第0项表示线性地址最开端的4M地址,页目录表第768项对应线性地址3G开始的4M内存。均对应第1页表。当前我们在第1页表只填充了1M内存。所以线性地址开始的1M内存(0~1M)与3G开始的1M内存(3G~3G+1M),对应同一块物理内存。

这样划分,是因为之前所有的代码都是在线性地址的开始(0~1M)内存实现的。但我们会将4G内存空间划分为用户空间(0~3G)和内核空间(3G~4G)。通过将页目录表第0项与第768项指向同一块内存,则可以在使用线性地址3G以上的内核空间地址继续访问之前的1M内存。

测试

测试方法和以前相同,通过在屏幕打日志的方式判断是否执行成功。