保护模式内存管理
内存管理概览
IA-32架构的内存管理设施分为两部分:分段和分页。
分段提供了一种隔离单个代码、数据和堆栈模块的机制,以便多个程序(或任务)可以在同一个处理器上运行而不互相干扰。分页提供了一种机制来实现传统的需求分页、虚拟内存系统,其中程序执行环境的部分被映射到物理内存中。分页也可以用来提供多个任务之间的隔离。
在保护模式下操作时,必须使用某种形式的分段。没有模式位可以禁用分段。然而,分页的使用是可选的。这两种机制(分段和分页)可以被配置为支持简单的单程序(或单任务)系统,或使用共享内存的多处理器系统。
如图所示,分段提供了一种机制来划分处理器的可寻址内存空间(称为线性地址空间),分成更小的受保护的地址空间,称为段。分段可以可以用来保存程序的代码、数据和堆栈,或者保存系统数据结构(如TSS或LDT)。
如果一个处理器上有多个程序(或任务)在运行,每个程序可以被分配到它自己的段组。然后,处理器强制执行这些段之间的界限,并确保一个程序不会因为写进另一个程序的段而干扰程序的执行。分段机制还允许对段进行打字。
段落机制也允许段的类型化,这样可以限制对特定类型的段进行的操作。一个系统中的所有段都包含在处理器的线性地址空间中。要在一个特定的段中找到一个字节必须提供一个逻辑地址(也称为远指针)。
一个逻辑地址包括一个段选择器和一个偏移量。段选择器是一个段的唯一标识符。在其他方面,它提供了一个
在描述符表(如全局描述符表,GDT)中的偏移量,该数据结构称为段描述符。每个网段都有一个网段描述符,它指定了网段的大小,网段的访问权限和段的大小,段的访问权限和特权级别,段的类型,以及段的第一个字节在线性地址空间中的位置(称为段的基址)。逻辑地址的偏移部分被添加到段的基地址中,以定位段的第一个字节。
基准地址加上偏移量就形成了处理器线性地址中的一个线性地址。
如果不使用分页,处理器的线性地址空间将直接被映射到处理器的物理地址空间。物理地址空间被定义为处理器可以在其地址总线上产生的地址范围。由于多任务计算系统通常定义的线性地址空间远远大于在物理内存中一次性包含的经济可行性,因此需要一些 “虚拟化 “线性地址空间的方法。这种线性地址空间的虚拟化是通过处理器的分页机制处理的。
分页支持 “虚拟内存 “环境,用少量的物理内存(RAM和ROM)和一些磁盘存储来模拟大的线性地址空间。当使用分页时,每个段被划分为若干页(通常每页大小为4KB),这些页存储在物理内存或磁盘上。操作系统或执行器维护一个页面目录和一组页面表,以跟踪这些页面。当一个程序(或任务)试图访问线性地址空间中的一个地址位置时,处理器会使用页目录和页表来将线性地址转换为物理地址,然后执行所要求的操作(读或写)在内存位置上。
如果被访问的页面当前不在物理内存中,处理器会中断程序的执行。(通过产生一个页面故障异常)。然后,操作系统或执行程序将该页从磁盘读入物理内存,并继续执行程序。
当分页在操作系统或执行系统中被正确实现时,物理内存和磁盘之间的换页对于程序的正确执行是透明的。即使是为16位IA32处理器编写的程序,在虚拟8086模式下运行时也可以进行分页(透明)。
linux下的内存管理
Linux系统中的物理存储空间和虚拟存储空间的地址范围分别都是从0x00000000到0xFFFFFFFF,共4GB,但物理存储空间与虚拟存储空间布局完全不同。Linux运行在虚拟存储空间,并负责把系统中实际存在的远小于4GB的物理内存根据不同需求映射到整个4GB的虚拟存储空间中。Linux主要工作在保护模式下。80X86从逻辑地址到物理地址变换中经过了两个阶段。第一阶段使用分段机制把程序的逻辑地址变换成处理器可寻址内存空间(称为线性地址空间)中的地址。第二阶段的分页机制把线性地址转换成物理地址。第一阶段的分段变换机制是必须使用的,但是第二阶段的分页机制是可选择的。如果没有开启分页机制,那么分段机制产生的线性地址空间就直接映射到处理器的物理地址空间上。
基本概念
物理地址: 放在寻址总线上的地址,用于内存芯片级内存单元寻址。放在寻址总线上,如果是读,电路根据这个地址每位的值就将相应地址的物理内存中的数据放到数据总线中传输。如果是写,电路根据这个地址每位的值就将相应地址的物理内存中放入数据总线上的内容。物理内存是以字节(8位)为单位编址的,是地址变换的最终结果地址,物理地址由32位或36位无符号整数表示。
逻辑地址:是指由程序产生的与段相关的偏移地址部分,每一个逻辑地址都由一个段和偏移量组成。在进行C语言指针编程中,可以读取指针变量本身值(&操作),实际上这个值就是逻辑地址,它是相对于你当前进程数据段的地址,不和绝对物理地址相干。只有在Intel实模式下,逻辑地址才和物理地址相等(因为实模式没有分段或分页机制,Cpu不进行自动地址转换);逻辑也就是在Intel 保护模式下程序执行代码段限长内的偏移地址(假定代码段、数据段如果完全一样)。
线性地址:是逻辑地址到物理地址变换之间的中间层。程序代码会产生逻辑地址,或者说是段中的偏移地址,加上相应段的基地址就生成了一个线性地址,是一个32位无符号整数,可以用来表示高达4GB的地址,也就是说,高达4294967296个内存单元,以十六进制表示,0x00000000到oxffffffff。如果启用了分页机制,那么线性地址可以再经变换以产生一个物理地址。若没有启用分页机制,那么线性地址直接就是物理地址。Intel 80386的线性地址空间容量为4G(2的32次方即32根地址总线寻址)。
虚拟内存:是指计算机呈现出要比实际拥有的内存大得多的内存量。因此它允许程序员编制并运行比实际系统拥有的内存大得多的程序。这使得许多大型项目也能够在具有有限内存资源的系统上实现。一个很恰当的比喻是:你不需要很长的轨道就可以让一列火车从上海开到北京。你只需要足够长的铁轨(比如说3公里)就可以完成这个任务。采取的方法是把后面的铁轨立刻铺到火车的前面,只要你的操作足够快并能满足要求,列车就能象在一条完整的轨道上运行。这也就是虚拟内存管理需要完成的任务。在现在操作系统中,都使用了MMU的存储管理技术,而MMU管理的地址是虚拟地址,虚拟内存是计算机系统内存管理的一种技术。它使得应用程序认为它拥有连续的可用的内存(一个连续完整的地址空间),而实际上,它通常是被分隔成多个物理内存碎片,还有部分暂时存储在外部磁盘存储器上,在需要时进行数据交换。与没有使用虚拟内存技术的系统相比,使用这种技术的系统使得大型程序的编写变得更容易,对真正的物理内存(例如RAM)的使用也更有效率。有时我们也把逻辑地址称为虚拟地址。因为和虚拟内存空间的概念类似,逻辑地址也是和实际物理内存容量无关的。
逻辑地址如何转换为线性地址
完整的内存管理,包含保护和地址变换两个关键部分。80386的工作模式包括实地址模式和虚地址模式(保护模式)。Linux主要工作在保护模式下。80X86从逻辑地址到物理地址变换中经过了两个阶段。第一阶段使用分段机制把程序的逻辑地址变换成处理器可寻址内存空间(称为线性地址空间)中的地址。第二阶段的分页机制把线性地址转换成物理地址。第一阶段的分段变换机制是必须使用的,但是第二阶段的分页机制是可选择的。如果没有开启分页机制,那么分段机制产生的线性地址空间就直接映射到处理器的物理地址空间上。
一个逻辑地址由两部份组成,段标识符: 段内偏移量。段标识符是由一个16位长的字段组成,称为段选择符。其中前13位是一个索引号。后面3位包含一些硬件细节,表示具体的是代码段寄存器还是栈段寄存器抑或是数据段寄存器,如图1所示。索引号就是“段描述符(segment descriptor)”的索引,段描述符具体地址描述了一个段。很多个段描述符,就组了一个数组,叫“段描述符表”,这样,可以通过段标识符的前13位,直接在段描述符表中找到一个具体的段描述符,这句话很关键,说明段标识符的具体作用,每一个段描述符由8个字节组成,如图2所示,与主题最密切的就是Base字段,她表示的是包含段的首字节的线性地址,也就是一个段的开始位置的线性地址。完全引用书中的一句话,一些全局的段描述符,就放在“全局段描述符表(GDT)”中,一些局部的,例如每个进程自己的,就放在所谓的“局部段描述符表(LDT)”中。那究竟什么时候该用GDT,什么时候该用LDT呢?这是由段选择符中的T1字段表示的,=0,表示用GDT,=1表示用LDT,GDT在内存中的地址和大小存放在CPU的GDTR控制寄存器中,而LDT则在IDTR寄存器中。这个过程中有几个基本的概念,一定要理清楚,如段选择符、段描述符、局部段描述符表、全局段描述符表。
上图显示了一个逻辑地址是怎样转换成相应线性地址的,给定一个完整的逻辑地址[段选择符:段内偏移地址]
1、看段选择符的T1=0还是1,即先检查段选择符中的TI字段,以决定段描述符保存在哪一个描述符表中,知道当前要转换是GDT中的段(在这种情况下,分段单元从GDTR寄存器中得到GDT的线性基地址),还是LDT中的段(在这种情况下,分段单元从LDTR寄存器中得到GDT的线性基地址),再根据相应寄存器,得到其地址和大小。
2、由于一个段描述符是8字节长,因此她在GDT或LDT内的相对地址是由段选择符的最高13位的值乘以8得到,拿出段选择符中前13位,可以在这个数组中,查找到对应的段描述符,这样,它的Offset,即偏移地址就知道了。
3、把Base + offset,就是要转换的线性地址了。
对于软件来讲,原则上就需要把硬件转换所需的信息准备好,就可以让硬件来完成这个转换了。下图逻辑地址转换为线性地址实例,段选择符为0x7B,指向用户数据段,段起始地址为0x00000000,逻辑偏移地址为0x80495B0,最终的线性地址为Base + offset=0x80495B0。
线性地址转物理地址
CPU通过地址来访问内存中的单元,地址有虚拟地址和物理地址之分,如果CPU没有MMU(Memory Management Unit,内存管理单元),或者有MMU但没有启用,CPU核在取指令或访问内存时发出的地址将直接传到CPU芯片的外部地址引脚上,直接被内存芯片(以下称为物理内存,以便与虚拟内存区分)接收,这称为物理地址(Physical Address,以下简称PA),如下图所示。
如果CPU启用了MMU,CPU核发出的地址将被MMU截获,从CPU到MMU的地址称为虚拟地址(Virtual Address,以下简称VA),而MMU将这个地址翻译成另一个地址发到CPU芯片的外部地址引脚上,也就是将虚拟地址映射成物理地址,如下图所示
虚拟内存地址和物理内存地址的分离,给进程带来便利性和安全性。虚拟地址必须和物理地址建立一一对应的关系,才可以正确的进行地址转换。
记录对应关系最简单的办法,就是把对应关系记录在一张表中。为了让翻译速度足够地快,这个表必须加载在内存中。不过,这种记录方式惊人地浪费。
因此,Linux采用了分页(paging)的方式来记录对应关系。所谓的分页,就是以更大尺寸的单位页(page)来管理内存。在Linux中,通常每页大小为4KB。
依据以下步骤进行转换:
- 从cr3中取出进程的页目录地址(操作系统负责在调度进程的时候,把这个地址装入对应寄存器);
- 根据线性地址前十位,在数组中,找到对应的索引项,因为引入了二级管理模式,页目录中的项,不再是页的地址,而是一个页表的地址。(又引入了一个数组),页的地址被放到页表中去了。
- 根据线性地址的中间十位,在页表(也是数组)中找到页的起始地址;
- 将页的起始地址与线性地址中最后12位相加,得到最终我们想要的;
分段机制
由IA-32体系结构支持的分段机制可以用来实现各种各样的系统设计。这些设计的范围很广,从只极少使用分段的平面模型到保护。这些设计从扁平模型到多分段模型,这些模型采用分段来创建一个强大的操作环境,在其中多个程序和任务可以可靠地执行。
Basic Flat Model(基本平坦模型)
一个系统最简单的内存模型是基本的 “平坦模型”,在这个模型中,操作系统和应用程序可以访问一个连续的、没有分割的地址空间。在最大程度上,这种基本的平坦模型对系统设计者和应用程序都隐藏了架构的分割机制。
程序员为了实现IA-32体系结构的基本平坦内存模型,至少要创建两个段描述符,一个用于引用代码段,一个用于引用数据段。这两个段都被映射到整个线性地址空间:也就是说,两个段描述符都有同样的基址值为0,同样的段限制为4GBytes。通过设置段限制为4GBytes,分段机制就不会因为超出限制的内存引用而产生异常,即使在一个特定的地址上没有物理内存存在。ROM(EPROM)通常位于物理地址空间的顶部。因为处理器在FFF_FFF0H开始执行。RAM(DRAM)被放在物理地址空间的底部,因为复位初始化后DS数据段的初始基址是0。
flat model
Protected Flat Model(受保护的平坦模型)
受保护的平坦模型与基本平坦模型类似,只是段的限制被设置为只包括物理内存实际存在的的地址范围(见下图)。一个一般保护的异常(#GP)会在任何访问不存在的内存的尝试中产生。这个模型提供了一个最低水平的
这种模式提供了最低水平的硬件保护,以防止某些类型的程序错误。
Protected Flat Model
更多的复杂性可以被添加到这个受保护的平坦模型中,以提供更多的保护。例如,对于分页机制要在用户和主管的代码和数据之间提供隔离,需要定义四个段。用户的代码和数据段的权限级别为3,而主管的代码和数据段的权限级别为0。通常,这些段都是相互重叠的,并从线性地址空间的地址0开始。这种平坦的分段模型和一个简单的分页结构可以保护操作系统不受应用程序的影响。通过为每个任务或进程增加一个单独的分页结构,它还可以保护应用程序之间的相互影响。类似的设计被几个流行的多任务操作系统所采用。
Multi-Segment Model(多段模型)
多段模型(如下图)使用分段机制的全部功能,对代码、数据结构、程序和任务提供硬件强制保护。在这里,每个
程序(或任务)都有自己的段描述符表和自己的段。这些段可以是对其分配的程序来说是完全私有的,或者在程序之间共享。对所有程序段的访问以及对运行在各个程序上的执行环境都由硬件控制。
访问检查不仅可以用来防止引用一个段限制之外的地址,还可以防止在段内进行不允许的操作。例如,由于代码段被指定为只读段,所以可以用硬件来防止向代码段写东西。为段创建的访问权限信息也可以用来设置保护环或保护级别。保护级别可以用来保护操作系统程序免受应用程序的未经授权的访问。
Multi-Segment Model
Segmentation in IA-32e Mode
在英特尔64架构的IA-32e模式下,分段的效果取决于处理器是在兼容模式还是64位模式下运行。在兼容模式下,分段的功能就像使用传统的16位或32位保护模式的语义。在64位模式下,分段功能通常被禁用(但不是完全禁用),创建一个平坦的64位线性地址空间。处理器将CS、DS、ES、SS的段基处理为零,创建一个线性地址,等于
有效地址。FS和GS段是个例外。这些段寄存器(存放段基)可以作为线性地址计算中的一个额外的基础寄存器。它们有助于寻址本地数据和某些操作系统的数据结构。注意,在64位模式下,处理器在运行时不执行段限制检查。
Paging and Segmentation(分页和分段)
分页可以与上图中描述的任何一种分段模式一起使用。处理器的分页机制将线性地址空间(段被映射到其中)分为若干页。然后这些线性地址空间的页被映射到物理地址空间的页中。分页机制提供了几个页级的保护设施,可以和段保护设施一起使用,也可以代替段保护设施。例如,它允许在逐页的基础上强制执行读写保护。分页 机制还提供了两级用户-监督者保护,也可以在每页的基础上指定。
逻辑地址和线性地址的转换
注:之前的内容为自己整理,以下为参考指导书翻译
在保护模式下的系统架构层面,处理器使用两个阶段的地址转换来获得物理地址:逻辑地址转换和线性地址空间分页。
物理地址:逻辑地址转换和线性地址空间分页。即使对段的使用降到最低,处理器地址空间中的每一个字节都可以用一个逻辑地址来访问。一个逻辑地址包括一个16位的段选择器和一个32位的偏移量。段选择器确定了字节所处的段,偏移量指定了字节在段中相对于该段的基址的位置。处理器将每个逻辑地址转化为一个线性地址。线性地址是处理器线性地址空间中的一个32位地址。像物理地址空间一样,线性地址空间是一个平面的(未分割的)。232字节的地址空间,地址范围从0到FFFFFFFH。线性地址空间包含所有段和为系统定义的系统表。为了将逻辑地址转换为线性地址,处理器做了以下工作:
- 使用段选择器中的偏移量来定位GDT或LDT中段的段描述符,并将其读入处理器。(只有当一个新的段选择器被加载到段寄存器中时才需要这个步骤。)
- 检查段描述符,以检查段的访问权限和范围,以确保段是可访问的,并且偏移量在段的限制范围内。
- 将段描述符中的段基地址加到偏移量上,形成一个线性地址。
如果不使用分页,处理器将线性地址直接映射到物理地址上。如果线性地址空间被分页,第二层的地址转换被用来将线性地址转换为物理地址。
Logical Address Translation in IA-32e Mode(IA-32e 模式下的逻辑地址转换)
在 IA-32e 模式下,Intel 64 处理器使用上述步骤将逻辑地址转换为线性地址。在64位模式下,段的偏移量和基地址是64位而不是32位。线性地址地址格式也是64位宽,并受制于典型形式的要求。每个代码段描述符都提供一个L位。这个位允许一个代码段执行64位代码或传统的32位代码的代码段。
Segment Selectors(段选择子)
段选择器是一个段的16位标识符(见下图)。它并不直接指向段,而是指向定义该段的段描述符。一个段选择器包含以下内容 :
Segment Selector
索引(第3至15位)- 在GDT或LDT中选择8192个描述符之一。处理器将索引值乘以8(段描述符中的字节数),并将结果加到GDT或LDT的基址上(分别来自GDTR或LDTR寄存器)。
TI(表指示器)标志
(Bit 2) - 指定要使用的描述符表:清除该标志选择GDT;设置该标志选择当前的LDT。
要求的权限级别(RPL)
(位0和1) - 指定选择器的权限级别。特权级别范围从0到3,其中0为最高权限级别。
GDT的第一个条目不被处理器使用。指向GDT这个条目的段选择器(即索引为0且TI标志设置为0的段选择器)被用作 “空段选择器”。就是说,一个索引为0且TI标志设置为0的段选择器被用作 “空段选择器”。当一个段寄存器(除了CS或SS寄存器)被载入空段选择器时,处理器不会产生一个异常。然而,当一个持有空段选择器的段寄存器被用来访问内存时,它会产生一个异常。空选择器可以用来初始化未使用的段寄存器。用一个空的段选择器加载CS或SS寄存器会导致一个通用的保护机制。段选择器加载CS或SS寄存器会产生一个通用保护异常(#GP)。段落选择器作为指针变量的一部分对应用程序是可见的,但是选择器的值通常是由链接编辑器分配或修改的,而不是应用程序。
Segment Registers(段寄存器)
为了减少地址转换的时间和编码的复杂性,处理器提供了最多容纳6个段选择器的寄存器。这些段寄存器中的每一个都支持一种特定的内存引用(代码、堆栈或数据)。对于几乎任何一种程序的执行,至少要有代码段(CS),数据段(DS)和堆栈段(SS)寄存器且必须被加载有效的段选择器。处理器还提供了三个额外的数据段寄存器(ES、FS和GS),这些寄存器可以用来为当前执行的程序(或任务)提供额外的数据段。
一个程序要访问一个段,该段的段选择器必须被加载到其中一个段寄存器中。因此,尽管一个系统可以定义数以千计的段,但只有6个可以立即使用。其他的段可以通过在程序执行期间将它们的段选择器加载到这些寄存器中来实现。
每个段寄存器都有一个 “可见 “部分和一个 “隐藏 “部分。(隐藏部分有时被称为”描述符缓存 “或 “阴影寄存器”)。当段选择器被加载到段寄存器的可见部分时,处理器也会在段寄存器的隐藏部分加载基础地址、段限制和段描述符的访问控制信息。这些信息来自段选择器所指向的段描述符。缓存在段寄存器中的信息(可见的和隐藏的)允许处理器翻译地址,而不需要花费额外的总线周期来读取基址和限制。
系统中,多个处理器可以访问相同的描述符表,当描述符表被修改时,软件有责任重新加载段寄存器。如果不这样做,缓存在段寄存器中的旧的段描述符就可能在其内存驻留版本被修改后被使用。
为加载段寄存器提供了两种类型的加载指令:
直接加载指令,如MOV, POP, LDS, LES, LSS, LGS和LFS指令。这些指令明确地引用段寄存器。
隐含的加载指令,如CALL、JMP和RET指令的远端指针版本,SYSENTER和SYSEXIT指令,以及IRET、INTn、INTO和INT3指令。
这些指令改变了CS寄存器的内容(有时也会改变其他段寄存器的内容),这是其操作的附带部分。MOV指令也可以用来将段寄存器的可见部分存储在通用寄存器中。
Segment Loading Instructions in IA-32e Mode(IA-32e模式下的段加载指令)
由于ES、DS和SS段寄存器在64位模式下不被使用,它们在段描述符寄存器中的字段(base, limit, and attribute)被忽略了。某些形式的段装载指令也是无效的(例如LDS, POP ES)。引用ES、DS或SS段的地址计算被视为段基
为零。
处理器检查所有线性地址引用都是典型的形式,而不是执行极限检查。模式切换并不改变段寄存器或相关描述符寄存器的内容。在64位模式执行过程中,这些寄存器也不会改变,除非执行显式段加载。
为了给应用程序设置兼容模式,段加载指令(MOV to Sreg, POP Sreg)在64位模式下正常工作。从系统描述符表(GDT或LDT)中读取一个条目,并加载到段描述符的隐藏部分。描述符寄存器的基数、极限和属性字段都被加载。然而,数据和堆栈段选择器以及描述符寄存器的内容被忽略。
当FS和GS段重写在64位模式下使用时,它们各自的基础地址被用于线性地址计算中使用。(FS或GS).base + index + displacement。然后,FS.base和GS.base会被扩展到整个实现所支持的线性地址大小。由此产生的有效地址计算可以跨越正负地址;产生的线性地址必须是规范的。
在64位模式下,使用FS段和GS段覆盖的内存访问不会被检查是否有运行时限制也不受属性检查的影响。正常的段加载(MOV to Sreg和POP Sreg)到FS和GS中加载一个在段描述符寄存器的隐藏部分加载一个标准的32位基础值。标准32位以上的基址位以上的基址位被清除为0,以保证使用少于64位的实现方式的一致性。
FS.base和GS.base的隐藏描述符寄存器字段被物理映射到MSR,以便加载64位实现支持的所有地址位。64位实现所支持的所有地址位。CPL=0的软件(特权软件)可以使用WRMSR将所有支持的线性地址位加载到FS.base或GS.base。写入64位FS.base和GS.base寄存器中的地址必须是典型的形式。如果WRMSR指令试图向这些寄存器写入非经典地址的WRMSR指令会导致#GP故障。
当处于兼容模式时,FS和GS的重写操作与32位模式行为的定义无关。值加载到隐藏描述符寄存器基字段的前32位线性地址位。兼容性模式在计算有效地址时忽略上面的32位。
一个新的64位模式指令,SWAPGS,可以用来加载GS base。SWAPGS将IA32_KernelGSbase MSR中的内核数据结构指针与GS base寄存器交换。然后,内核可以使用GS前缀对正常的内存引用来访问内核的数据结构。试图向IA32_KernelGSbase MSR写一个非正则的值(使用WRMSR)会导致一个#GP故障。
Segment Descriptors(段描述子)
段描述符是GDT或LDT中的一个数据结构,为处理器提供段的大小和位置以及访问控制和状态信息。段落描述符通常是由编译器、链接器、加载器、操作系统或执行器,但不是应用程序创建的。
段落描述符中的标志和字段如下:
分段限制字段
指定段的大小。处理器把两个段限制字段放在一起,形成一个 一个20位的值。处理器以两种方式之一解释段限制,这取决于G(粒度)标志的设置:
- 如果粒度标志是清除的,段的大小可以从1字节到1MByte,以字节为单位递增。
- 如果颗粒度标志被设置,段的大小可以从4KB字节到4GB字节,以4KB字节为增量。
处理器以两种不同的方式使用段的限制,取决于段是一个向上扩展的段或一个向下扩展的段。对于扩大的段,
在逻辑地址中的偏移量可以从0到段的极限范围。大于段限制的偏移量产生一般保护异常(#GP,用于SS以外的所有段)或堆栈故障异常(#SS用于SS段)。对于向下扩展的段,段限具有相反的功能。
偏移量可以从段限加1到FFFFFFFH或FFFFFFH,取决于B标志的设置。小于或等于段限制的偏移量产生一般保护异常或堆栈故障异常。减少一个扩展段的段限值字段的值,在段的地址空间的底部分配新的内存,而不是在顶部。
IA-32架构的堆栈总是向下增长,使得这种机制对于可扩展堆栈。
基准地址字段
定义段的第0字节在4-GByte线性地址空间中的位置。处理器将三个基础地址字段放在一起,形成一个32位的数值。段的基址应与16字节的边界对齐,尽管16字节对齐不是必须的。这种对齐方式允许程序通过在16字节边界上对齐代码和数据来最大限度地提高性能。
类型字段
表示段或门的类型,并指定可以对该段进行的访问类型和增长方向。这个字段的解释取决于描述符类型标志指定的是应用(代码或数据)描述符还是系统描述符。类型字段的类型字段的编码对代码、数据和系统描述符是不同的。
S(描述符类型)标志
指定段描述符是用于系统段(S标志为清除)还是用于代码或数据段(S标志为设置)。
DPL(描述符权限级别)字段
指定段的权限级别。特权级别的范围是0到3,其中0是最高的级别。
P(段存在)标志
指示段是否存在于内存中(设置)或不存在(清除)。如果这个标志是清除的,当指向段描述符的段选择器出现时,处理器会产生一个段不存在的异常(#NP)。内存管理软件可以使用这个标志来控制哪些段在给定的时间内被实际加载到物理内存中。它为管理虚拟内存提供了一个除分页之外的控制。当该标志被清除时,操作系统或执行程序可以自由地使用标记为 “可用 “的位置来存储自己的数据,例如关于丢失的段的位置的信息。
D/B(默认操作大小/默认堆栈指针大小和/或上界)标志
执行不同的功能,取决于段描述符是否是可执行的代码段、扩展的数据段、或者是其他的段,一个扩展的数据段,或者一个堆栈段。(对于32位的代码和数据段,这个标志应该总是设置为1,对于16位的代码和数据段,这个标志应该设置为0)。
- 可执行代码段。该标志被称为D标志,它指示了有效地址和操作数的默认长度。如果该标志被设置,32位的地址和32位或8位的操作数;如果它被清除,16位的地址和16位或8位操作数。指令前缀66H可以用来选择默认以外的操作数,而指令前缀67H可以用来选择一个非默认的地址大小。
- 堆栈段(由SS寄存器指向的数据段)。该标志被称为B(大)标志。它指定了用于隐式堆栈操作的堆栈指针的大小(如push, pops, and calls)。如果该标志被设置,则使用一个32位的堆栈指针,该指针被存储在32位的ESP寄存器中;如果该标志被清除,则使用16位的堆栈指针,该指针被存储在16位的SP寄存器中。如果堆栈段被设置为一个向下扩展的数据段(在下一段中描述下一段描述),B标志也指定了堆栈段的上界。
- 扩大-缩小数据段。该标志被称为B标志,它指定了段的上限。如果该标志被设置,上界是FFFFFFFH(4GB字节);如果该标志被清除,上限是FFFFFFFF(64KB)。
描述符的分类
段描述符分类
段描述符是GDT和LDT中的一个数据结构项,用于向处理器提供有关一个段的位置、大小以及访问控制的状态信息。每个段描述符的长度是8个字节,含有3个主要字段:
- 段基地址
- 段限长
- 段属性
段描述符通常由编译器,链接器,加载器或者操作系统来创建,但绝不是应用程序。
段描述符通用格式如下所示
系统段描述符中各个位的含义如下所示
存储段描述符
数据段描述符
当S=1且TYPE字段的最高位(第2个双字的位11)为0时,表明是一个数据段描述符。
下图是数据段描述符的格式。
代码段描述符
当S=1且TYPE字段的最高位(第2个双字的位11)为1时,表明是一个代码段描述符。
下图是代码段描述符的格式。
系统描述符类型
当段描述符中S标志位(描述符类型)是复位状态(0)的话,那么该描述符是一个系统描述符。处理器能够识别以下一些类型的系统段描述符:
- 局部描述符表(LDT)的段描述符
- 任务状态段(TSS)描述符
- 调用门描述符
- 中断门描述符
- 陷阱门描述符
- 任务门描述符
这些描述符类型可分为两大类: 系统段描述符和门描述符。系统段描述符指向系统段(如LDT或TSS段),门描述符也就是一个”门”,对应调用、中断或陷阱门,其中含有代码段的选择符和段中程序入口点的指针;对于任务门,其中含有TSS的段选择符。
系统段描述符和门描述符类型字段的编码如下所示: