x86 - 分段与分页详解

系列文章

x86 - CPU架构/寄存器详解 (一)x86、8086、i386、IA-32 是什么?
x86 - CPU架构/寄存器详解 (二) 实模式(8086模式)
x86 - CPU架构/寄存器详解 (三) 保护模式
x86 - 分段与分页详解
x86 - 特权级别 CPL / RPL / DPL / IOPL
x86 - 操作系统:中断、陷阱、异常、故障、终止
x86 - 描述符详解:存储/系统段描述符、门描述符

本文内容:

    • 系列文章
    • 分段机制详解
    • 分页机制详解
      • 分页机制起源
      • 什么是线性地址
      • 从简单分页到层次结构
      • 地址转换过程
      • 处理页缺失
      • 页表项和页目录项

分段机制详解

  每个程序都有属于自己的内存空间。在 16 位模式下,一个程序可以自由地访问不属于它的内存位置,甚至可以对那些地方的内容进行修改。这当然是不安全的,也不合法,但却没有任何机制来限制这种行为。在 32 位模式下,处理器要求在加载程序时,先定义该程序所拥有的段,然后允许使用这些段。定义段时,除了基地址(起始地址)外,还附加了段界限、特权级别、类型等属性。当程序访问一个段时,处理器将用固件实施各种检查工作,以防止对内存的违规访问。
  在 32 位模式下,传统的段寄存器,如 CS、SS、DS、ES,保存的不再是 16位段基地址,而是段的选择子,即,用于选择所要访问的段,因此,严格地说,它的新名字叫做段选择器。段选择子由三部分组成,第一部分是描述符的索引号,用来在描述符表中选择一个段描述符。TI 是描述符表指示器(Table Indicator),TI=0 时,表示描述符在 GDT (全局描述符表)中;TI=1 时,描述符在 LDT (局部描述符表)中。RPL(Request Privilege Level) 是请求特权级,表示给出当前选择子的那个程序的特权级别,正是该程序要求访问这个内存段:
  段选择子:
x86 - 分段与分页详解_第1张图片
  段描述符:
x86 - 分段与分页详解_第2张图片
  除了段选择器之外,每个段寄存器还包括一个 64 位的不可见部分,称为描述符高速缓存器,里面有段的基地址和各种访问属性。这部分内容程序不可访问,由处理器自动使用:
x86 - 分段与分页详解_第3张图片
  每当引用一个段时,处理器自动将段地址左移 4 位,并传送到描述符高速缓存器。此后,就一直使用描述符高速缓存器的内容做为段地址。实际上,保护模式下CPU在从描述符表中向高速缓存中加载数据时要做大量的保护性检查,大致如下:

  1. 段寄存器的值不能是0。根据规范,描述符表中的第一个描述符必须是空描述符,所以段寄存器值为0是不合法的。如果为0,产生异常中断13.
  2. 段寄存器中的值是否大于或等于描述符表的长度(存在GDTR中),如果大于或等于描述符表的长度,产生异常中断13.
  3. 如果段寄存器是CS,检查描述符表中的段类型是否为代码段,如果不是,产生异常中断13.
  4. 如果段寄存器CS要求装入的段是代码段,检查描述符表该段是否存在,如果不存在产生异常中断11
  5. 如果段寄存器CS通过了3、4的检查,还要检查IP是否超越了该段的边界,如果越界,产生异常中断13
  6. 如果段寄存器CS通过了3、4、5的检查,则把相应的描述符装入高速缓存
  7. 如果段寄存器不是CS,检查描述符表中的段类型为数据段,如果不是产生异常中断13
  8. 如果段寄存器不是CS,该段为数据段,检查其是否存在,如果不存在产生异常中断13
  9. 如果段寄存器不是CS,且通过了7、8检查,则把相应的描述符装入高速缓存

  在x86体系中,关于地址的定义如下:
x86 - 分段与分页详解_第4张图片
  虚拟地址(Virtual Address) 是由两部分组成,一个是段选择子(segment selector),另一个是段内偏移(segment offset);
  线性地址(Linear Address) 指的是通过段地址转换机构把虚拟地址进行转换之后得到的地址;
  物理地址(Physical Addresses) 是分页地址转换机构把线性地址进行转换之后得到的真实的内存地址,这个地址将会最终送到芯片的地址总线上。

  比如:我们所编写的C语言程序中的指针的值是虚拟地址中段内偏移部分的值。

  处理器把逻辑地址转化成一个线性地址的过程:
x86 - 分段与分页详解_第5张图片

  1. 使用段选择符中的偏移值在GDT(全局描述符表) 或 LDT(局部描述符表)中定位相应的段描述符。(仅当一个新的段选择符加载到段寄存器中时才需要 ,否则直接使用段寄存器中的高速缓存内容)
  2. 利用段描述符校验段的访问权限和范围,以确保该段是可以访问的并且偏移量位于段界限内。
  3. 利用段描述符中取得的段基地址加上偏移量,形成一个线性地址。

分页机制详解

分页机制起源

  为 IA-32 处理器编程,访问内存时,需要在程序中给出段地址和偏移量,因为分段是 IA-32 架构的基本特征之一,任何时候Intel处理器都无法关闭段管理机制。传统上,段地址和偏移地址称为逻辑地址,偏移地址叫做有效地址 (Effective Address,EA),在指令中给出有效地址的方式叫做寻址方式(Addressing Mode)。
  IA-32 处理器支持多任务。在多任务环境下,任务的创建需要分配内存空间;当任务终止后,还要回收它所占用的内存空间。在分段模型下,内存的分配是不定长的,程序大时,就分配一大块内存;程序小时,就分配一小块。时间长了,内存空间就会碎片化,就有可能出现一种情况:内存空间是有的,但都是小块,无法分配给某个任务。为了解决这个问题,IA-32 处理器支持分页功能,分页功能将物理内存空间划分成逻辑上的页。页的大小是固定的,一般为 4KB,通过使用页,可以简化内存管理。
  总的来说,分页机制是为了解决内存的外部碎片问题,即使没有开启分页机制,分段也一样能够实现虚拟内存。

什么是线性地址

  让我们再来回顾一下从逻辑地址到物理地址的转换过程:
x86 - 分段与分页详解_第6张图片
  段机制实现虚拟地址到线性地址的转换;分页机制实现线性地址到物理地址的转换。
  如果不启用分页,那么线性就是物理地址;如果启用分页,那么线性地址需要通过页部件转换得到物理地址。
  如图所示,当页功能开启时,段部件产生的地址就不再是物理地址了,而是线性地址(Linear Address),线性地址还要经页部件转换后,才是物理地址:
x86 - 分段与分页详解_第7张图片
  线性地址的概念用来描述任务的地址空间。IA-32 处理器上的每个任务都拥有 4GB 的虚拟内存空间,这是一段长 4GB 的平坦空间,就像一段平直的线段,因此叫线性地址空间。相应地,由段部件产生的地址,就对应着线性地址空间上的每一个点,这就是线性地址。
  4GB虚拟内存空间不可能用来保存任何数据,因为它是虚拟的,只是用来指示内存的使用情况。当操作系统加载和一个程序并创建为任务时,操作系统在虚拟内存空间寻找空闲的段,并映射到空闲的页。当真正开始加载程序时,再把原本属于段的数据按页的尺寸拆开,分开写入对应的页中。

从简单分页到层次结构

  为了完成从虚拟地址从物理地址,操作系统应该为每个任务准备一张页映射表,因为任务的虚拟地址空间为4GB,可以分出1048576个页,所以映射表需要1048576个表项用于存放页的物理地址,又因为每个表项占4字节,映射表总大小为4MB,这是一个不小的值。在实践中,没有哪个任务真的会用到所有表象,因此造成了很严重的内存浪费。
x86 - 分段与分页详解_第8张图片
  为了解决这个问题,处理器设计了层次化的分页结构。分页结构层次化的主要手段是不采用单一的映射表,取而代之的是页目录表和页表。
  首先,将4GB虚拟内存空间对应的1048576个4KB的页组织在1024个页表内,每个页表可以容纳1024个页。页表内的每个项目叫做页表项(PTE,Page Table Entry),占4字节,存放的是页的物理地址,故每个页表大小为4KB,正好是一个标准页的大小。
  在将1048576个页归拢到1024个页表之后,再用一个表来指向1024个页表,这就是页目录表(Page Direction Table,PDT),和页表一样,页目录项(PTE)的长度为4字节,填写的是页表的物理地址,共指向1024个表页,所以页目录表的大小也是4KB,正好是一个标准页的长度。
x86 - 分段与分页详解_第9张图片
  这样的层次化分页结构是每个任务都有的,或者说每个任务都有自己的页目录。在处理器内部,有一个控制寄存器叫 CR3,存放着当前任务的页目录的物理地址,故 CR3 又叫做页目录基址寄存器(Page Directory Base Register,PDBR)
  每个任务都有自己的 TSS(Task-State Segment ,任务状态段),其中就包括了 CR3 寄存器域,存放着任务自己的页目录的物理地址。当任务切换时,CR3 寄存器的内容也会被更新,更新为新任务的页目录的物理地址。
  页目录和页表也是普通的页,混迹于全部的物理页中。它们和普通的页没有什么区别,无非就是功能不一样。当任务被操作系统撤销后,它们和任务所占用的普通的物理页一样会被回收。页目录总是在物理内存中,页表可以在需要时再分配,这样就大大节省了物理内存。

地址转换过程

  32bit下分页机制的线性地址是由32bit组成的,分成3个域:

Direction(目录) Table(页表) Offset(页内偏移)
10 bit 10 bit 12 bit

  依据以下步骤进行转换:
x86 - 分段与分页详解_第10张图片

  1. 从CR3中取出进程的页目录地址(操作系统负责在调度进程的时候,把这个地址装入CR3);
  2. 根据线性地址高十位,在页目录中找到对应的页目录项(PDE),因为引入了二级管理模式,页目录中的项,不再是页的地址,而是一个页表的地址,指向目标页表项所在的页表。
  3. 根据线性地址的中间十位,在页表中找到目标页的页表项(PTE),获取页表项内的物理地址(高20位),也就是目标页的起始地址;
  4. 将目标页的起始地址与线性地址中最后12位相加,得到完整的32位物理地址。

处理页缺失

  页目录表中每个表项(PDE)也有一个存在(Present)属性,类似于页表中的表项(PTE)。页目录表项中的存在属性指明对应的二级页表是否存在。如果目录表项指明对应的二级页表存在,那么通过访问二级表,表查找过程将同如上描述继续下去。如果存在位表明对应的二级表不存在,那么处理器就会产生一个异常来通知操作系统。页目录表项中的存在属性使得操作系统可以根据实际使用的线性地址范围来分配二级页表页面。
  目录表项中的存在位还可以用于在虚拟内存中存放二级页表。这意味着在任何时候只有部分二级页表需要存放在物理内存中,而其余的可保存在磁盘上。处于物理内存中页表对应的页目录项将被标注为存在,以表明可用它们进行分页转换;处于磁盘上的页表对应的页目录项将被标注为不存在。由于二级页表不存在而引发的异常(Page Fault)会通知操作系统把缺少的页表从磁盘上加载进物理内存,把页表存储在虚拟内存中减少了保存分页转换表所需要的物理内存量。

页表项和页目录项

x86 - 分段与分页详解_第11张图片x86 - 分段与分页详解_第12张图片
  上图就是页目录项和页表项的格式。可以看出,由于页表或者页的物理地址都是4KB对齐的(低12位全是零),所以上图中只保留了物理基地址的高20位(bit[31:12]),低12位可以安排其他用途:
  【P】:存在位。为1表示页表或者页位于内存中;否则,表示不在内存中,必须先予以创建或者从磁盘调入内存后方可使用。
  【R/W】:读写标志。为1表示页面可以被读写,为0表示只读。当处理器运行在0、1、2特权级时,此位不起作用。页目录中的这个位对其所映射的所有页面起作用。
  【U/S】:用户/超级用户标志。为1时,允许所有特权级别的程序访问;为0时,仅允许特权级为0、1、2的程序访问。页目录中的这个位对其所映射的所有页面起作用。
  【PWT】:Page级的Write-Through标志位。为1时使用Write-Through的Cache类型;为0时使用Write-Back的Cache类型。当CR0.CD=1时(Cache被Disable掉),此标志被忽略。
  【PCD】:Page级的Cache Disable标志位。为1时,物理页面是不能被Cache的;为0时允许Cache。当CR0.CD=1时,此标志被忽略。对于我们的实验,此位清零。
  【A】:访问位。该位由处理器固件设置,用来指示此表项所指向的页是否已被访问(读或写),一旦置位,处理器从不清这个标志位。这个位可以被操作系统用来监视页的使用频率。
  【D】:脏位。该位由处理器固件设置,用来指示此表项所指向的页是否写过数据。
  【PS】:Page Size位。为0时,页的大小是4KB;为1时,页的大小是4MB(for normal 32-bit addressing )或者2MB(if extended physical addressing is enabled).
  【G】:全局位。如果页是全局的,那么它将在高速缓存中一直保存。当CR4.PGE=1时,可以设置此位为1,指示Page是全局Page,在CR3被更新时,TLB内的全局Page不会被刷新。
  【AVL】:被处理器忽略,软件可以使用。

你可能感兴趣的:(操作系统,#,x86,操作系统,cpu,内核,intel)