cpu是如何将物理内存分段成虚拟内存,并且应用分页机制

来源:互联网 发布:mac安装win8.1详细过程 编辑:程序博客网 时间:2024/05/23 11:04

因为段的长度不定, 在分配内存时, 可能会发生内存中的空闲区域小于要加载的段, 或者空闲区域远远大于要加载的段. 在前一种情况下, 需要另外寻找合适的空闲区域; 在后一种情况下, 分配会成功, 但太过于浪费. 为了解决这个问题, 从80386处理器开始, 引入了分页机制. 分页功能从总体上来说, 是用长度固定的页来代替长度不一定的段, 藉此解决因段长度不同而带来的内存空间管理问题. 尽管操作系统也可以用软件来实施固定长度的内存分配, 但太过于复杂, 由处理器固件来做这件事, 可以使速度和效率最大化.

分页机制概述

简单的分页模型

处理器中有负责分段管理的段部件, 每个程序或任务都有自己的段, 这些段都用段描述符定义. 随着程序的执行, 当要访问内存, 就用段地址加上偏移量, 段部件就会输出一个线性地址. 在单纯的分段模式下, 线性地址就是物理地址.

一旦决定采用页式内存管理, 就应当把4GB内存分成大小相同的页. 页的最小单位是4KB, 也就是4096字节, 用十六进制表示就是0x1000. 因此, 第一个页的物理地址就是0x00000000, 第2个页的物理地址是0x00001000, 第3个页的物理地址是0x00002000....最后一个页的物理地址是0xfffff000. 这样,  4GB内存划分为1048576(0x100000)个页. 很显然, 页的物理地址, 其低12位始终为0.

段管理机制对于Intel处理器来说是最基本的, 任何时候都无法关闭. 也就是说, 即使启用页管理功能, 分段机制依然是起作用的, 段部件依然工作.


如上图所示, 内存的分配设计段空间的分配和页分配. 左边是虚幻的, 或者说虚拟的4GB内存空间, 称为虚拟内存; 右边是实实在在的内存, 被分成1048576个4KB的页面(每个方框4KB, 灰色代表已分配).

在分页模式下, 操作系统可以创建一个为所有任务公用的4GB虚拟内存空间, 也可以为每一个任务创建独立的4GB虚拟内存空间, 这都是可行的. 当一个程序加载时, 操作系统既要在左边的虚拟内存中分配段空间, 又要在右边的物理内存中分配相应的页面. 因此, 第一步骤是寻找空闲的段空间, 该段空间既没有被其他程序使用, 也没有被同一程序内的其他段使用. 比如上图, 假设已经成功找到并分配了一个段空间, 基地址为0x00200000, 长度为8200字节.

页的最小尺寸是4KB, 也就是4096字节, 因此, 8200字节的段, 需要占用3个页面, 其中最后一个页面只用了8个字节, 其余都是浪费着, 但这无关紧要, 如果允许页共享, 多个段或多个程序可以用同一个页来存放各自的数据. 在分段之后, 操作系统的任务就是把段拆开, 并分别映射到物理页. 注意, 段必须是连续的, 但不要求所分配的页都是连续的, 挨在一起的.

就上图中的列子来说, 该段有8200字节, 需要分配3个页面. 操作系统在物理内存中搜索可用的空闲页, 接下来, 要建立线性地址和也之间的对应关系, 在图中, 0x200000~0x00200FFF对应着物理地址为0x00002000的页, 0x00201000~0x00201FFF对应着0x00004000, 0x00202000~0x00202007对应着0x00007000的页, 当然, 这里只是示例, 线性地址区间和页的对应关系可以随意.

4GB虚拟内存空间不可能用来保存任何数据, 因为它是虚拟的, 它只是用来指示内存的使用情况. 当操作系统加载一个程序并创建为任务时, 操作系统在虚拟内存空间寻找空闲的段, 并映射到空闲的页, 然后, 到真正开始加载程序时, 再把原本属于段的数据按页的尺寸拆开, 分开写入对应的页中.

从段部件输出的是线性地址, 或者叫做虚拟地址. 为了根据线性地址找到页的物理地址, 操作系统必须维护一张表, 把线性地址转换成物理地址, 这是一个反过程.

如上图所示, 因为有1048576个页, 故转换表有1048576个表项. 这是个一维表格, 每个表项占4字节, 内容为页的物理地址. 这个表格的用法是这样的: 因为页的尺寸是4KB, 故, 线性地址的低12位可用于访问页内偏移, 高20位可用于指定一个物理页. 因此, 把线性地址的高20位当成索引, 乘以4, 作为表内偏移量, 从表中取出一个双字, 那就是该线性地址做对应的页的物理地址. 举个例子: mov edx, [0x0002]    执行这条指令, 段部件用段地址0x00200000加上指令中给出的偏移量0x2002, 得到线性地址0x00200002. 线性地址的高20位是表格索引, 即0x00200, 将索引乘以4, 得到0x00800, 这就是表内偏移, 看图, 从该单元可以取出一个双字0x00007000, 这就是页物理地址. 线性地址的低12位是页内偏移, 用页物理地址加上页内偏移量, 就是最终的物理内存地址. 0x00007000加上0x0002, 得到0x00007002, 这就是实际要访问的物理内存地址. 这里有个问题, 为什么表内表内偏移为0x000800的地方, 会恰好是物理地址0x00007000, 而不是其他页地址呢? 当程序加载时, 操作系统会首先在虚拟内存中分配段, 然后, 根据段需要分成多少页, 来搜索空闲页面. 当段较大时, 要按页的尺寸分成好几个地址区段, 操作系统用每个区段的首地址, 取高20位, 乘以4, 作为偏移量访问表格, 并将分配给区段的页的物理地址写入该表项. 最后, 把原本需要写入每个区段的程序数据, 写到对应的页中. 注意了, 在页式内存管理中, 页面的管理和分配是独立的, 和分段以及段地址没有关系.操作系统所要做的, 就是寻找空闲页面, 把它分配给需要的段, 并将页的物理地址填写到映射表内. 很显然, 也很重要的结论是, 线性地址, 包括线性地址空间, 和页面分配机制没有关系.

基于以上特点, 同时为了充分挖掘分页内存管理的潜力, 一般来说, 每个任务都可以拥有4GB的虚拟内存空间; 同时, 每个任务都有自己的4GB虚拟内存空间, 但是, 很重要的是, 在整个系统中, 物理页面是统一调配的. 考虑这样一种情景: 任务A有一个段, 基地址为0x00050000, 长度为3000自己, 系统为它分配了物理地址0x08001000的页. 过了一会, 任务B加载了, 它也有一个段, 基地址也是0x00050000, 长度为4096字节, 此时, 操作系统为它分配了另外一个不同的, 物理地址为0x00700000的页. 在这种情况下, 在任务A内访问线性地址0x00050006, 访问的其实是物理地址0x08001006; 在任务B内访问同样的线性地址时, 访问的其实是物理地址0x00700006.

另一个问题是, 每个任务都有4GB虚拟内存空间, 而物理内存只有一个, 最大也才4GB, 根本不够分的. 事实上, 确实不够分, 但是操作系统可以暂时将不用页退避到磁盘, 调入马上要使用的页, 通过这种手段来实现分页内存管理.

以上, 就是基本的段页式内存管理机制. 基本的段页式内存管理示意图:

页目录, 页表和页

我们知道, 为了完成从虚拟地址(线性地址)到物理地址的转换, 操作系统应当为每个任务准备一张页映射表. 因为任务的虚拟地址空间为4GB, 可以分出1048576个页, 所以, 映射表需要1048756个表项, 又因为每个表项4字节, 故映射表总大小为4MB. 没错, 这张表很大, 要占用相当一部分空间, 考虑到在实践中, 没有哪个任务会真的用到所有表项, 充其量只是很小一部分, 这就很浪费了.  为了解决这个问题, 处理器设计了层次化的分页结构.

分页结构层次化的主要手段是不采用单一的映射表, 取而代之的是页目录表和页表. 如下图所示:

首先, 因为4GB的虚拟内存空间对应着1048576个4KB页, 可以随机的抽取这些页, 将它们组织在1024个页表内, 每个页表可以容纳1024个页. 页表内的每个项目叫做页表项, 占4字节, 存放的是页的物理地址, 故每个页表的大小是4KB, 正好是一个标准页的长度. 注意, 页在页表内的分布是随机的, 哪个页位于哪个页表中, 这是没有规律的.

如图所示, 在将1048576个页归拢到1024个页表之后, 接着, 再用一个表来指向1024个页表, 这就是页目录表(Page Directory Table: PDT), 和页表一样, 页目录项的长度为4字节, 填写的是页表的物理地址, 共指向1024个页表, 所以页目录表的大小是4KB, 正好一个标准页的长度.

这样的层次化分页结构是每个任务都拥有的, 或者说, 每个任务都有自己的页目录和页表. 如下图所示, 在处理器中有个控制寄存器CR3, 存放着当前任务页目录的物理地址, 故又叫做页目录基址寄存器(Page Directory Base Register: PDBR). 每个任务都有自己的TSS, 其中就包括了CR3寄存器域, 存放了任务自己的页目录物理地址. 当任务切换时, 处理器切换到新任务开始执行, 而CR3寄存器的内容也被更新, 以指向新任务的页目录位置. 相应的, 页目录又指向一个个的页表, 这就使得每个任务都只在自己的地址空间内运行. 从下图可以看出, 页目录和页表也是普通的页, 混迹于全部的物理页中. 它们和普通页的不同支持仅仅在于功能不一样. 当任务撤销之后, 它们和任务所占用的普通页一样会被回收, 并分配给其他任务.

地址变换的具体过程

对于Intel处理器来说, 有关分页, 最简单和最基本的机制就是这些; CR3寄存器给出了页目录的物理地址; 页目录给出了所有页表的物理地址, 而每个页表给出了它所包含的页的物理地址. 好了, 该清楚的都清楚了, 唯一还不明白的, 应该是如何用这种层次性的分页结构把线性地址转换成物理地址? 这里举个例子, 某任务加载后, 在4GB虚拟地址空间创建了一个段, 起始地址为0x00800000, 段界限为0x5000, 字节粒度. 当前任务执行时, 段寄存器DS指向该段. 又假设执行了下面一条指令

  1. mov edx, [0x1050]  
此时, 段部件会输出线性地址0x00801050. 在没有开启分页机制时, 这就是要访问的物理地址. 但现在开启了分页机制, 所以这是一个下虚拟地址, 要经过页部件转换, 才能得到物理地址.

如下图所示, 处理器的页部件专门负责线性地址到物理地址的转换工作. 它首先将段部件送来的32位线性地址分为3段, 分别是高10位, 中间10位, 低12位. 高10位是页目录的索引, 中间10位是页表的索引, 低12位则作为页内偏移量来用.


当前任务页目录的物理地址在处理器的CR3寄存器中, 假设它的内容为0x00005000. 段管理部件输出的线性地址是0x00801050, 其二进制的形式如图中给出. 高10位是十六进制的0x002, 它是页目录表内的索引, 处理器将它乘以4(因为每个目录项4字节), 作为偏移量访问页目录. 最终处理器从物理地址00005008处取得页表的物理地址0x08001000.

线性地址的中间10位为0x001, 处理器用它作为页表索引取得页的物理地址. 将该值乘以4, 作为偏移量访问页表. 最终, 处理器又从物理地址08001004处取得页的物理地址, 这就是我们一直努力寻找的那个页.

页的物理地址是0x0000c000, 而线性地址的低12位是数据所在的业内偏移量. 故处理器将它们相加, 得到物理地址0x0000C050, 这就是线性地址0x00801050所对应的物理地址, 要访问的数据就在这里.

注意, 这种变换不是无缘无故的, 而是事先安排好的. 当任务加载时, 操作系统先创建虚拟的段, 并根据段地址的高20位决定它要用到哪些页目录项和页表项. 然后, 寻找空闲的也, 将原本应该写入段中的数据写到一个或者多个页中, 并将页的物理地址填写到相对应的页表项中. 只有这样做了, 当程序运行的时候, 才能以相反的顺序进行地址变换, 并找到正确的数据.

页目录项, 页表项, CR3和打开分页

页目录项和页表项

页目录和页表中分别存放为页目录项和页表项, 它们的格式如下:

可以看出, 在页目录和页表中, 只保存了页表或者页物理地址的高20位. 原因很简单, 页表或者页的物理地址, 都要求必须是4KB对齐的, 以便于放在一个页内, 故其低12位全是0. 在这种情况下, 可以只关心其高20位, 低12位安排其他用途.

  • P 是存在位, 为1时, 表示页表或者页位于内存中. 否则, 表示页表或者页不在内存中, 必须先予以创建, 或者从磁盘调入内存后方可使用.
  • RW 是读/写位. 为0时表示这样的页只能读取, 为1时可读可写
  • US 是用户/管理位. 为1时, 允许所有特权级别的程序访问; 为0时, 只允许特权级别为0, 1和2的程序访问.
  • PWT(Page-level Write-Through) 是页级通写位, 和高速缓存有关. "通写"是处理器高速缓存的一种工作方式, 这一位用来间接决定是否采用此种方式来改善页面的访问效率.
  • PCD(Page-level Cache Disable)是页级高速缓存禁止位, 用来间接决定该表项所指向的那个页是否使用高速缓存策略.
  • A 是访问位. 该位由处理器固件设置, 用来指示此表项所指向的页是否被访问过.
  • D(Dirty) 是脏位. 该位由处理器固件设置, 用来指示此表项所指向的页是否写过数据
  • PAT(Page Attribute Table) 页属性表支持位. 此位涉及更复杂的分页系统, 和页高速缓存有关, 可以不予理会, 在普通的4KB分页机制中, 处理器建议将其置0.
  • G 是全局位. 用来指示该表项所指向的页是否为全局性质的. 如果页是全局的, 那么, 它将在高速缓存中一直保存(也就意味着地址转换速度会很快). 因为页高速缓存容量有限, 只能存放频繁使用的那些表项. 而且, 当因任务切换等原因改变CR3寄存器的内容时, 整个页高速缓存的内容都会被刷新.
  • AVL位卑处理器忽略, 软件可以使用.

CR3(PDBR)和开分页机制

控制寄存器CR3, 也就是页目录表基地址寄存器PDBR, 该寄存器如上图所示. 

由于页目录表必须位于一个自然页内(4KB对齐), 故其物理地址的低12位是全0. 低12位除了PCD和PWT外, 都没有使用. 这两位用于控制页目录的高速缓存特性, 参见上面解释.

控制寄存器CR0的最高位PG位, 用于开启分页或者关闭页功能. 当该位清0时, 页功能关闭, 从段部件来的线性地址就是物理地址. 当它置位时, 页功能开启. 只能在保护模式下才能开启分页功能, 当PE位清0时(实模式), 设置PG位将导致处理器产生一个异常中断.

0 0
原创粉丝点击