chap3.7_分页模式

为什么要分页

**1. 实模式分段: **

  • 原因: cpu段寄存器太小, 和cpu总线不匹配. ( cpu段寄存器16位,cpu总线20位.)

  • 解决方案:通过 16位段寄存器 , 16位指针寄存器 ,使用2个16位寄存器配合达到20位总线寻址.

  • 段寄存器:此时段寄存器是物理地址的偏移地址。

  • 段地址:此时段地址是实际的物理地址

**2. 保护模式分段: **

  • 原因: 实模式下20位总线,寻址只有1M,需要突破实模式1M内存寻址的限制。保护模式下使用32位总线,寻址可以达到4G,使用到更多内存。

  • 解决方法: 创建全局描述符表, 先根据全局描述符表定位到分段信息,然后根据分段信息定义的起始物理地址 和 当前偏移地址找到实际物理地址。

  • 段寄存器:此时段寄存器是全局描述符表的分段信息的选择子序号。

  • 段地址:此时段地址是实际的物理地址

3. 分页模式:

  • 原因: 实际物理地址存在范围比较小, 且不连续的问题. 碎片化后可使用内存减少。

  • 解决方法: 创建页表,建立虚拟地址和物理地址的映射,加载的是是虚拟地址,然后根据虚拟地址查到物理地址并执行代码

  • 段寄存器:此时段寄存器是全局描述符表的分段信息的选择子序号。

  • 段地址:此时段地址是映射的虚拟地址,需要查找页表转换成实际分配的物理地址。

不同架构的分页机制

32位下分页
32位系统,内存支持到4G,使用二级页表,就可以满足要求了、

64位下分页
64位系统,支持到128G内存,使用二级页表无法满足要求,此时需要使用三级,四级页表了。

Linux采用的四级页表目录的大小有所不同:对于i386而言,仅采用二级页表,即页上层目录和页中层目录长度为0;对于启用PAE的i386,采用了三级页表,即页上层目录长度为0;对于64位体系结构,可以采用三级或四级页表,具体选择由硬件决定。

从上述过程中,可以看出,对虚拟地址的分级解析过程,实际上就是不断深入页表层次,逐渐定位到最终地址的过程,所以这一过程被叫做page talbe walk

至于这种做法为什么能节省内存,举个更简单的例子更容易明白。比如要记录16个球场的使用情况,每张纸能记录4个场地的情况。采用4+4+4+4,共4张纸即可记录,但问题是球场使用得很少,有时候一整张纸记录的4个球场都没人使用。于是,采用4 x 4方案,即把16个球场分为4组,同样每张纸刚好能记录4组情况。这样,使用一张纸A来记录4个分组球场情况,当某个球场在使用时,只要额外使用多一张纸B来记录该球场,同时,在A上记录”某球场由纸B在记录”即可。这样在大部分球场使用很少的情况下,只要很少的纸即困记录,当有球场被使用,有需要再用额外的纸来记录,当不用就擦除。这里一个很重要的前提就是:局部性

页目录和页表

1. 一级页表

物理内存分块:
在分页的情况下,将物理内存划分为最小4K (即2^12寻址范围)的内存块。

页表:
4G内存下,使用4G/4K=1048576=1M个页。一个页地址占4个字节,需要1048576 x 页地址4字节 = 4M空间。

页地址:
一个页地址占4个字节,高20位为页号,低12位为物理内存分块的偏移地址

虚拟地址映射:
页地址:

  • 高20位:页号(1M)。
  • 低12位:物理内存分块的偏移地址(4K)。
    物理地址:
  • 虚拟地址 = 页号(高20位) + 物理内存分块的偏移地址(低12位)
  • 物理地址 = [ (页起始地址 + 页号 x 页表项大小) -> 物理内存开始地址 ] + 物理内存分块的偏移地址
  • 页表项大小 :4字节

一级页表存储结构:

一级页表
0页:高20位页号 0页:低12位偏移地址
1页:高20位页号 1页:低12位偏移地址
1048576页:高20位页号 1048576页:低12位偏移地址

2. 二级页表

一级页表大小是4M, 占用内存过大。如果把地址拆分开存放,用两个页表来存放地址,每个页表只需要1024K。(第一个表存页开始地址,第二个存物理内存开始地址)。

此时第一个称为页目录,第二个称为页表,页地址大小为4字节。

继续把一级的1M大页表 。拆分开1024 x 1024的页,每个页大小1K x 页地址4字节。

一级页表虚拟地址:1M页地址(20位) + 4K物理分块偏移地址(12位)
二级页表虚拟地址:1K页目录地址(10位) + 1K页地址(10位) + 4K物理分块偏移地址(12位)
页表项:

虚拟地址映射
页地址:

  • 高10位:页目录号(1K)

  • 中10位:页号(1K)

  • 低12位:物理内存分块的偏移地址(4K)。
    物理地址:

  • 虚拟地址 = 页目录序号(高10位) + 页序号(中10位) + 物理内存分块的偏移地址(低12位)

  • 物理地址 = [([页目录表起始地址 + 页目录号 x 页表大小) -> 页表起始地址] + 页号 x 页表项大小 ) -> 物理内存开始地址 ] + 物理内存分块的偏移地址

  • 页表大小:4K字节,页表项大小 :4字节。

页目录表
0目录:高20位页表起始地址
1目录:高20位页表起始地址
1024页:高20位页表起始地址
一级页表(按需分配)
0页:物理页起始地址
1页:物理页起始地址
1024页:物理页起始地址
  • 为什么要使用多级页表

    多级页表优势:

    1.可以离散存储页表。

    2.在某种意义上节省页表内存空间。

    多级页表劣势:

    增加寻址次数,从而延长访存时间。

    那么使用多级页表比使用以及页表有没有什么劣势呢?

    当然是有的。比如:使用以及页表时,读取内存中一页内容需要2次访问内存,第一次是访问页表项,第二次是访问要读取的一页数据。但如果是使用二级页表的话,就需要3次访问内存了,第一次访问页目录项,第二次访问页表项,第三次访问要读取的一页数据。访存次数的增加也就意味着访问数据所花费的总时间增加。

4. 动态分页

一个页表项,寻址范围为4M,16M需要4个页表项,4G需要1024个列表项。

进入分页模式

1. 进入分页模式

代码:

1
2
3
4
5
;------------------
;进入分页模式
mov eax, cr0
or eax, 0x80000000 ;设置第31位为1
mov cr0, eax

2.加载CR3

加载页目录基址到cr3寄存器

1
mov cr3, edx

头文件io.h

汇编代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
io_load_page:   ;void io_load_page(uint32 pde_address) 
;cr3
mov edx, [esp+4]
mov cr3, edx
ret

io_switch_page: ;void io_switch_page(void)
;cr0
mov eax, cr0
or eax, 0x80000000 ;设置第31位为1
mov cr0, eax
ret

页表到页表的寻址