x86 分段机制

目录

视频教学

1.段的定义

2.段描述符表

3.段选择符

4.段描述符

5.LDTR是什么

6.直达底部

视频教学

x86分段机制 – 段的定义
X86分段机制–段描述符表,段描述符,段选择子
X86分段机制 — LDTR机制,LDT访问寻址过程

段的定义

段的介绍

分段机制就是把虚拟地址空间中的虚拟内存组织成一些长度可变的称为段的内存单元。 80386虚拟地址空间中的虚拟地址(逻辑地址)由一个段部分和一个偏移部分构成。段是虚拟地址到线性地址转化的基础。每个段有三个参数定义:

  1. 段基地址,指定段在线性地址空间中的开始地址。基地址是线性地址对应于段中偏移 0 处。
  2. 段限长,是虚拟地址空间中段内最大可用偏移地址。定义了段的长度。
  3. 段属性,指定段的特性。如该段是否可读、可写或可作为一个程序执行,段的特权级等。

多个段映射到线性地址中的范围可以部分重叠或覆盖,甚至完全重叠,如下图所示
x86 分段机制_第1张图片

相关的数据结构

段的基地址、段限长以及段的保护属性存储在一个称为段描述符的结构项中。在逻辑地址到线性地址的转换映射过程中会使用这个段描述符。段描述符保存在内存中的段描述符表中。

段描述符表是包含段描述符项的一个简单数组。 使用段选择符来指定段描述符表中一个段描述符的位置来指定相应的段。

虚拟地址到线性地址转化

即使是使用段的最小功能,使用逻辑地址也能访问处理器地址空间中的每一个字节。逻辑地址由 16 位的段选择符和 32 位的偏移量组成。

当需要访问处理器地址空间中的某个字节时。段选择符指定了该字节所在的段,偏移量指定了该字节在段中相对于段基址的位置。处理器会吧每个逻辑地址转换成线性地址。线性地址是处理器线性地址空间中的 32 位地址。也是平坦的 4GB 地址空间,地址范围从 0 到 0xFFFFFFFF。线性地址空间中含有系统定义的所有段和系统表。

处理器把逻辑地址转化成一个线性地址的过程:

  1. 使用段选择符中的偏移值(段索引,我感觉这个偏移值应该是相对于段描述符表其实地址的偏移)在GDT(全局描述符表) 或 LDT(局部描述符表)中定位相应的段描述符。(仅当一个新的段选择符加载到段寄存器中时才需要 ??)

  2. 利用段描述符校验段的访问权限和范围,以确保该段是可以访问的并且偏移量位于段界限内。

  3. 利用段描述符中取得的段基地址加上偏移量,形成一个线性地址。

    x86 分段机制_第2张图片

返回目录

段描述符表

段描述符表示存放段描述符的一个数组。 它的长度可变,最多可以包含 8192 个段描述符,每个段描述符长度为 8 个字节。段描述符表有两种:全局描述符表GDT和局部描述符表LDT。段描述符表结构如下图所示。
x86 分段机制_第3张图片

段描述符表存储在由操作系统维护着的特殊数据结构中,由处理器的内存管理硬件来引用。 这些特殊的数据结构保存在只有操作系统能够访问的受保护的内存区域,防止被应用程序修改。

虚拟地址空间被分割成大小相等的两半。一半由GDT来映射变换到线性地址,另一半由LDT来映射。整个虚拟地址空间共含有 2^14 个段: 一半空间(2^13)是由GDT映射的全局虚拟地址空间,另一半是由LDT映射的局部虚拟地址空间。 指定一个描述符表(GDT或LDT)和表中的描述符号,就可以定位到一个段描述符(通过段描述符就可以定位到段)。

当任务切换时,LDT会更换成新任务的LDT,GDT不会改变,因为GDT所映射的一半虚拟地址空间是系统中所有任务公有的,LDT所映射的另一半则在任务切换时被改变。系统中所有任务共享的段有GDT来映射。

系统中每个应用程序对应一个任务,并且每个任务都有自己的LDT,如下图所示,应用程序A在任务A中运行,拥有LDTa ,用来映射段Codea和Dataa。类似地,应用程序B在任务B中运行,使用LDTb来映射Codeb和Datab。操作系统内核的两个段Codeos和Dataos 使用GDT来映射,这样它可以被像个人物所共享。 LDTa和LDTb两个段也使用 GDT来映射。
x86 分段机制_第4张图片

当任务A在运行时,可以访问 LDTa映射的Codea 和Dataa段,以及GDT映射的操作系统段COdeos和Dataos。当任务B在运行时,可以访问的段包括LDTb映射的Codeb和Datab段,以及GDT映射的操作系统段COdeos和Dataos。

通过让每个任务使用不同的LDT,演示了虚拟地址空间如何隔离每个任务。当任务A在运行时,任务B的段不是虚拟地址空间的部分。因此,任务A无法访问任务B的内存。同样的,当任务B运行时,任务A的段也不能被任务B访问。这种使用LDT来隔离每个应用程序任务的方法,正式关键保护的需求之一。

每个系统必须定义一个GDT,并可用于系统中所有程序或任务。可以选定义一个或多个LDT。可以为每个运行任务定义一个LDT,或者某些任务共享一个LDT。

GDT本身并不是一个段,而是线性地址空间的一个数据结构。GDT的基线性地址和长度必须加载进 GDTR 寄存器中。处理器并不使用 GDT中的第一个描述符。把这个"空描述符"加载到段寄存器中并不会产生一个异常。但是,如果使用这些加载了空描述符的段选择符来访问内存就会引发一般保护性异常。通过使用这个段选择符初始化段寄存器,就会引发异常。

LDT表存放在LDT类型的系统段中。此时GDT必须含有LDT的段描述符。如果系统支持多LDT,那么每个LDT都必须在GDT中有一个段描述和段选择符。一个LDT的段描述符可以存放在GDT表的任何地方。 访问LDT需使用其段选择符。为了在访问LDT是减少地址转换次数,LDT的段选择符、段基址、段限长以及访问权限需要存放在LDTR寄存器中。

返回目录

段选择符

段选择符(或称段选择子)是段的一个十六位标志符,如下图所示。段选择符并不直接指向段,而是指向段描述符表中定义段的段描述符。 段选择符包括 3 个字段的内容:

  • 请求特权级RPL([0:1])
  • 表指引标志TI([2])TI = 0 ,表示描述符在GDT中,TI = 1,表示描述符在LDT中。
  • 索引值,给出了描述符在GDT或LDT表中的索引项号。

下面是一些段选择符的示例:
x86 分段机制_第5张图片

为了减少地址转换时间和编程复杂性,处理器提供可存放最多 6 个段选择符的寄存器。即段寄存器。每个段寄存器支持特定类型的内存引用(代码、数据或栈)。原则上执行每个代码都需要把有效的段选择符加载到代码段(CS)、数据段(DS)和堆栈段(SS)寄存器中。

段描述符

每个段描述符长度是 8 字节,含有三个主要字段:段基地址、段限长和段属性。段描述符通常由编译器。链接器、加载器或者操作系统来创建,绝不可能由应用程序来创建。

段描述符通用格式如下:

x86 分段机制_第6张图片

段描述符中各字段和标志的含义如下:

  • 段限长字段 LIMIT

  • 基地址字段 BASE

  • 段类型字段 TYPE

  • 段描述符类型标识 S

  • 段描述符特权级字段 DPL

  • 段存在标志 P

  • D/B 标志(默认操作大小/默认栈指针大小/或上界限)

  • 颗粒度标志 G

  • 可用和保留比特位。

    段描述符第 2 个双字的位 20 可供系统软件使用,位 21 是保留并应该总是设置为 0

返回目录

LDTR是什么

IA-32为LDT的入口地址也提供了一个寄存器LDTR,因为在任何时刻只能有一个任务在运行,所以LDT寄存器全局也只需要有一个。如果一个任务拥有自身的LDT,那么当它需要引用自身的LDT时,它需要通过lldt指令将其LDT的段描述符装入此寄存器。lldt指令与lgdt指令不同的时,lgdt指令的操作数是一个32-bit的内存地址,这个内存地址处存放的是一个32-bit GDT的入口地址,以及16-bit的GDT Limit。而lldt指令的操作数是一个16-bit的选择子,这个选择子主要内容是:被装入的LDT的段描述符在GDT中的索引值

至此,我们可以这样理解GDT和LDT:GDT为一级描述符表,LDT为二级描述符表。如图:
x86 分段机制_第7张图片

例如:如果我们想在表LDT2中选择第三个描述符所描述的段的地址12345678h。

  1. 首先需要装载LDTR使它指向LDT2:使用指令lldt将Select2装载到LDTR。
  2. 通过逻辑地址(SEL:OFFSET)访问时SEL的index=3代表选择第三个描述符;TI=1代表选择子是在LDT选择,此时LDTR指向的是LDT2,所以是在LDT2中选择,此时的SEL值为1C h(二进制为11 1 00b),OFFSET=12345678h。逻辑地址为1C:12345678h。
    这里需要说明一下,程序使用虚拟地址(SEL:OFFSET)访问内存时,会看到SEL中T1=1,说明要使用LDT表,LDT表由LDTR指向,而LDTR是在任务切换的时候,系统通过lldt selector2加载的,selector2里面有LDT在GDT中的偏移。而不是使用逻辑地址中SEL里面的index,SEL里面的index是用来在LDT中进行索引偏移的
  3. 由SEL选择出描述符,由描述符中的基址(Base)加上OFFSET可得到线性地址,例如基址是11111111h,则线性地址=11111111h+12345678h=23456789h。
  4. 此时若再想访问LDT1中的第三个描述符,只要使用lldt指令将选择子Selector 1装入再执行2、3两步就可以了(因为此时LDTR又指向了LDT1)。

由于每个进程都有自己的一套程序段、数据段、堆栈段,有了局部描述符表则可以将每个进程的程序段、数据段、堆栈段封装在一起,只要改变LDTR就可以实现对不同进程的段进行访问。

返回目录

你可能感兴趣的:(硬件知识,内核,虚拟化)