购买
下载掌阅APP,畅读海量书库
立即打开
畅读海量书库
扫码下载掌阅APP

2.7 保护模式

保护模式是从80286系列开始出现的一种新的运行模式。在实模式中,采用的是16位地址模式,最多能寻址10FFEFh的地址空间,同时,它的分段是针对所有的物理空间进行的,系统程序和用户程序都能访问所有的地址,如果某个存放了系统程序的内存空间被用户程序修改了,将会造成无法预料的错误。引入保护模式,就是为了解决上述两个缺陷。一方面,在保护模式中,采用的是32位地址模式,全部32条地址线都能使用,因此最多能寻址2 32 B=4GB内存空间,同时,段是通过一系列被称为“描述符表”的表所定义的,段寄存器存放的是指向这些表的指针。这些描述符表有两种:一种是全局描述符表(GDT),一种是局部描述符表(LDT)。另一方面,保护模式中还引入了段保护机制,使得物理内存不能再被直接访问,程序使用的都是虚拟地址,需要通过操作系统页表将这些虚拟地址转换为物理地址,才能被访问。

2.7.1 段描述符(Segment Descriptor)

段描述符是GDT或LDT中的数据结构,它为处理器提供段的描述信息,包括段基址、段大小、访问权限及状态信息。段描述符通常由编译器、链接器、装载器、操作系统或执行体创建。图2.4说明了段描述符的格式。

图2.4

其中英文含义如下:

· L:64位代码段(仅用于IA-32e模式)。

· AVL:可用于系统软件。

· D/B:默认操作大小(0=16位段;1=32位段)。

· DPL:描述符特权级。

· G:粒度。

· P:段存在。

· S:描述符类型(0=系统;1=代码或数据)。

· Type:段类型。

Intel手册上的大多数图总是让人摸不着头脑,其实图2.4应该这样看:从下面的长方形(从右到左)连接上面的长方形(从右到左),地址从低到高,共有8B,刚好是一个段描述符的长度。下面是自定义的段描述符宏结构:

于是,根据该宏我们可以定义如下一个段描述符:

那么,段描述符里面的各个参数代表什么意思呢?下面就来详细介绍。

段限长(Segment limit)

段限长主要用于指定段的大小。在图2.4中,我们可以看到有两个段限长,一个是“Segment limt 15:00”(16位),一个是“Segment limit 19:16”(4位),处理器用这两个值组合成为一个20位的值,所以这个域的值不会超过0FFFFFh。根据粒度标志G,处理器将按以下两种方式解释段限长:

· G=0,段大小的范围为1B到1MB,增量值为1B。这时,段描述符中组合成的20位的值加1就是段大小。如若当前的20位值为FFFFFh,那么段大小等于(FFFFFh+1)×1B=1MB。

· G=1,段大小的范围为4K到4GB,增量值为4KB。段大小应该为段描述符中组合成的的20位的值加1之后再乘以4KB。如若当前的20位值为FFFFFh,那么段大小等于(FFFFFh+1)×4KB=4GB。

由于粒度标识G决定了处理器如何解释段限长,它是“G|D/B|L|AVL|Segment limit 19:16”这一字节中的第7位,而编码操作的最小单元是字节,为了操作这一特定位,我们可以使用操作符“|”或“&”来完成。

G置位,用G_1表示,清位用G_0表示。

段基址(Base address)

段基址主要用来定义段的起始地址。在段描述符中,有3个Base Address,分别是“Base Address 15:00”(16位),“Base Address 23:16”(8位),“Base Address 31:24”(8位),处理器将这3个值组合成一个32位地址值。

如果段基址=0,段限长=0xffff,G=1,那么:段结束地址=段基址+(段限长+1)×4K-1=0+(0xFFFF+1)×4K-1=0xFFFFFFF。

S段描述符标识

当S=0,表示该段描述符是系统段描述符;当S=1,表示是代码段或数据段描述符,主要配合Type域使用。“P|DPL|S|Type”共用1B,S在第4位,代码或数据段描述符用S_1表示,系统段描述符用S_0表示。

类型(Type)

Type域是和S标识结合来解释该段的含义。当S=1时描述的是代码或数据段,根据Type域的不同组合值,描述当前段的信息。表2.1描述了每个位的含义。

表2.1

在表2.1中,11、10、9、8这4列代表Type域的4位,共有16种组合方式,Decimal就是对应的组合值,从0开始计数。

若11列等于0,描述的是数据段。10列描述的是E(expansion-direction,扩展方向),值为0表示向上扩展,值为1表示向下扩展。第9列描述的是W(write-enable,是否可读),值为0表示不可读,值为1表示可读。第8列描述的是A(accessed,是否访问过),值为0表示未被访问过,值为1表示被访问过。

若11列等于1,描述的是代码段。10列描述的是C(conforming,是否是一致代码段),值为0不是一致性代码,值为1则是一致性代码。第9列描述的是R(read enable,是否可读),值为0表示不可读,值为1表示可读。第8列描述的是A(accessed,是否访问过)。

数据和代码段描述符Type域的定义如下:

S=0时,是系统描述符。处理器可以识别以下系统描述符类型:

· 局部描述符表(LDT)段描述符。

· 任务状态段(TSS)描述符。

· 调用门描述符。

· 中断门描述符。

· 陷阱门描述符。

· 任务门描述符。

这些描述符又可以分为两类:系统段描述符和门描述符。系统段描述符指向系统段(LDT和TSS段)。门描述符对于调用门、中断门和陷阱门持有程序的入口点,对于任务门持有TSS的段选择符。表2.2给出了系统段描述符和门描述符类型字段的编码。

表2.2

续表

不同的Type域设置对应规定相应段应该实现的功能,Type域为0101的就只能是任务门描述符,而不能是其他段描述符。系统段描述符Type域的定义如下:

段存在标志(segment-present)

当P=1时表示该段当前存在于内存中,P=0表示不存在。当指向该段描述符的段选择符被装载进段寄存器时,若此时P=0,处理器将产生一个段不存在异常#NP。内存管理可以通过这个标识来控制在特定时间有哪些段是真正被装载进物理内存中的。

P在第7位,段存在用P_1表示,段不存在用P_0表示。

默认操作大小/默认栈指针大小和/或上界限

根据段描述符描述的是一个可执行代码段、向下扩展数据段还是堆栈段,该标识对应不同的功能。

若是可执行代码段,这个标识被称为D标志。若D=1,则默认使用32地址、32位或8位操作数;D=0,则默认使用16位地址、16位或8位操作数。不过,如果前面加了指令前缀66H,那么可以指定操作数的大小而不使用默认大小;如果加了指令前缀67H,那么可以指定地址大小而不使用默认大小。

若是堆栈段,这个标识被称为B标志,作为隐含栈操作(如push、pop和call)来指定栈指针的大小。B=1时,将使用32位栈指针,栈指针存放在32位esp寄存器中;B=0时,使用16位栈指针,栈指针存放在16位sp寄存器中。

若是向下扩展数据段,这个标识也称为B标志,指定了栈段上限。B=1,上限为FFFFFFFFH(4GB);B=0,上限为FFFFH(64KB)。

D/B在第6位。使用32位地址、32位栈指针、32位或8位操作数,上限为4GB时,用DB_32表示;使用16位地址、16位栈指针、16位或8位操作数,上限为64KB时,用DB_16表示。

L标志(64-bit code segment)

当L=1时,代码段的指令在64位模式下执行,且D位必须设为0。L=0时,代码段的指令在兼容模式下执行。所谓兼容模式指的是保护模式和实模式。

L在第5位,长模式用L_1表示,兼容模式用L_0表示。

AVL(Available and reserved bits),可用和保留位

AVL位主要为系统软件使用。

AVL在第4位,置位用AVL_1表示,清位用AVL_0表示。

至此,除了还有一个DPL(descriptor privilege level,描述符特权级)标识放在下面的特权级部分进行介绍外,其他段描述符的参数含义就具体介绍完了。读者在弄懂每个参数的含义之后,要将这些参数结合起来,看看由这些参数具体描述出来的一个段描述符是什么样的,要做到融会贯通,这样才能真正理解段描述符。

2.7.2 特权级(privilege level)

在保护模式中,处理器引入了一个段保护机制,用来识别4个特权级(0—3),值越大,级别越小。特权级0,保留给最高特权级的代码、数据和栈使用,通常是操作系统内核。特权级1和特权级2通常留给操作系统服务使用,而最外层的特权级3则留给不那么关键的应用程序使用。处理器使用特权级机制来防止较低特权级的进程或任务去访问较高特权级的段。当处理器检测到某个特权级违规时,会产生一个通用保护异常#GP。

处理器进行特权级检查的时候,通常需要考虑三个标识,一个是CPL(current privilege level,当前请求级),一个是DPL(descriptor privilege level,描述符特权级),还有一个是RPL(request privilege level,请求特权级)。

CPL是当前正在运行的进程或任务的特权级,存放在CS段寄存器的位0和位1中。通常,CPL与当前指令所在的代码段的特权级相等,当进程的控制流转到不同特权级的代码段时,处理器就会改变CPL。

DPL是一个段或门的特权级,存放在段或门描述符的DPL域中。如果当前执行的代码段试图访问另外一个段或门,那么处理器会将那个段或门的DPL与当前CPL和选择符的RPL进行比较。

· 数据段DPL=1,只有程序运行在CPL=1或CPL=0才能访问。

· 非一致代码段(不用调用门)DPL=0,只有程序运行在CPL=0才能访问。

· 一致代码段DPL=2,程序运行在CPL=0或CPL=1不能访问。

DPL占两位,在第5和第6位,最高特权级用DPL_0表示,供操作系统使用,最低特权级用DPL_3表示,供应用程序使用。

RPL是进程对段访问的请求权限,存放在进程所在段的段选择子的位0和位1。RPL可能会削弱CPL的能力,假如CPL=0,RPL=3,那么该进程也只有特级权为3的访问能力。

下面我们来看一下处理器是如何用这三个标识进行特权级检查的。

访问数据段时的特权级检查

为了访问数据段中的操作数,必须将该数据段的段选择子装载到数据段寄存器(DS、ES、FS或GS)或栈段寄存器(SS)中。但在载入前,处理器需要进行特权级检查,即对当前运行程序或任务的CPL,以及该数据段的RPL和DPL进行比较。若DPL在数值上比CPL和RPL都大或相等,那么处理器将该数据段的段选择子装载到数据段寄存器中;否则,会产生一个通用保护异常(#GP),并且不会将数据段的段选择子装载到数据段寄存器中。

装载SS寄存器时的特权级检查

当栈段的段选择子被装载到SS寄存器时,也会进行特权级检查。此时,所有与该栈段相关的特权级必须与CPL匹配。也就是说,CPL、栈段选择符的RPL和栈段描述符的DPL必须相等。如果RPL、DPL与CPL不相等,就会产生一个通用保护异常(#GP)。

访问非一致代码段

当访问非一致代码段时,调用例程的CPL必须与目标代码段的DPL相等,同时指向非一致代码段的段选择子的RPL必须在数值上小于或等于调用例程的CPL,以便成功进行控制转换,否则处理器会产生一个通用保护异常(#GP)。当非一致代码段的段选择符装载到CS寄存器时,特权级域不改变;即使该段选择符的RPL与CPL不同,CPL也不会改变。

2.7.3 段描述符表(segment descriptor table)

段描述符表是一个段描述符的数组。由于段寄存器(ES、CS、SS、DS、FS和GS)是16位,最大寻址2 16 =65 536,一个段描述符占据8B,所以一个段描述符表最多包含65 536÷8=8 192个段描述符。有两种类型的描述符表:全局描述符表(GDT)和局部描述符表(LDT)。

每个系统必须定义一个GDT,以备系统中的所有程序或任务使用。GDT本身不是一个段,而是线性地址空间中的一个数据结构。GDT的线性基地址和限长必须被装载入GDTR寄存器,处理器并不使用GDT中的第一个描述符。指向NULL描述符的段选择符被装载到数据段寄存器(DS、ES、FS或GS)时,处理器并不产生异常,但是如果访问内存,处理器就会产生一个通用保护异常(#GP)。GDT必须包含一个LDT段描述符,LDT段描述符可以放在GDT内的任何地方。在访问LDT时,为了避免地址翻译,LDT段选择符、线性基地址、段限长和访问权限都存放在LDTR寄存器中。下面是Windows NT定义的GDT数组,它在startup里,共128个,每个占据8B。

对于本书而言,只用到了内核代码段、内核数据段、用户代码段、用户数据段、内核TSS、内核PCR、线程环境块段、SU代码段和SU数据段。还有一些段暂时没用到,但在这里也一并保留下来,以便让读者知道如何设计GDT。

2.7.4 开启保护模式

GDT表已经在SU数据段中建立,但是我们得让处理器知道在哪里。GDTR寄存器就是用来专门存放GDT表的入口,使用lgdt指令加载进GDTR寄存器。CPU以后就根据GDTR寄存器来访问GDT表。GDTR是48位寄存器,前16位指明GDT表的大小,后32位指明GDT的地址。我们可以这样定义:

这里为什么要加上SU_MODULE_BASE呢?这是因为SU是在boot时被加载进以0x20000开始的物理地址空间的。定义好之后,使用指令lgdt[GDTregister]就能将GDT表地址加载进GDTR寄存器中了。另外,为了开启保护模式,还需要把CR0寄存器的第0位设为1。执行下面的代码就能进入保护模式。

进入保护模式后,我们访问的地址一般叫线性地址,段寄存器变成了段描述符的选择子,不再参与地址转译。 4N8MMdkMdf/w5xtAGk8qxnEt2MlufoLGoiXSx8/6JaHWgm7/YHs6Is89dMFHtQfu

点击中间区域
呼出菜单
上一章
目录
下一章
×