保护模式
保护模式提供了实模式中所不具备的丰富多彩的内容。Pentium处理器是为保护模式而特别设计的。它内部的执行管道流水线,执行32位指令的效率优于执行16位指令。
电脑在启动时候,Pentium被设计运行在实模式下,是为了便于电脑在启动时候操作系统的启动引导程序的执行。
运行在保护模式下的Intel处理器支持受保护的分段机制,同样也支持分页机制。这意味着地址解析会变得更加复杂。在实模式中,我们只需要在段地址上添加一个偏移地址便获得一个直接与物理内存对应的地址值。在保护模式中,处理器要求在相应的位置加载特定的数据结构。此外段地址和偏移地址对,可能不再直接对应物理地址,好吧,让我们继续下面对内容...
保护模式下的分段机制
对Intel平台上的分段机制理解的最好方法是图片示例,来解释它是如何实现的。一幅好的图例,胜过长篇大论,对于这本问题,尤其如此。所以请认真仔细的看下图1.9,并跟图1.8做下比较。你也可以将图1.9收藏起来,当你想看的候可以随时看到它。
图1.9
首先注意到保护模式中,使用了图1.2中所有的完整Pentium寄存器。是的,我们又回到32位寄存器了。同样,段地址寄存器不再存储16位的段地址值。而是保存段选择器。段选择器是一个16位的数据结构,它包含三个字段。它的组成形式如图1.10所示。真正重要的字段是索引(index)字段。索引字段存储了一个描述符表的索引。索引值由0开始。
图1.10
注释 :索引字段在段选择器中不是地址值。它跟你在C语言中访问数据元素时使用的索引类似。处理器取得索引值,并通过内部必要的计算将该索引值与该索引值对应的线性地址匹配。注意我这里说的线性地址(linear address),不是指物理地址。此时(保护模式下的分段),线性地址和物理地址是一致的,当启用分页机 制以后,它们就不一致了,记住这一点。
描述符表是一个入口数组,数组中每个入口(称之为段描述符)描述对应内存段的相关属性。段描述符中包含它所描述内存段的基地值。32位的偏移地址加上段描述符的基地值用以指定内存字节的内存地址。有两种描述符表:全局描述符表(Global Descriptor Table -- GDT)和局部描述符表(Local Descriptor Table--LDT)。所有的操作系统必须具备一个GDT,但是具备一个可配置多个LDT。通常,如果一个LDT被使用,它将用于标识内段段所属的进程。GDT的基地址保存在GDTR的系统寄存器中。同样,LDT的基地值保存在LDTR寄存器中。当然,还有一些特定的系统指令用于装载这些值(例如,LGDT和LLDT指令)。
注意:本书中讨论的大部分操作系统都集中于使用GDT而且很少使用LDT(即使有使用LDT的地方)。
GDTR的大小为48位。GDTR有一个很明显的特点是,它存储两个不同的值。第一个大小为16位的值,用于保存GDT大小,单位是字节。另一个32位的值,用于存储GDT在物理内存中的线性基地址。如图1.11
图1.11
答案:处理器从段选择器取得其指定的索引值,将该值乘以8(因为段描述符是64位的缘故,所以该值要为8字节)然后将结果加上由GTDR或者LDTR提供的基地址值。
注释:当你在看图1.2时,或许对另外两个内存管理寄存器产生了疑惑,其实我并没有忘记它们。它们对于我
们的讨论话题来说并不如GDTR和LDTR这样重要。IDTR寄存器和IR寄存器是用于管理硬件中断和多任务的。本
书集中讨论内存管理,所以不会对此类寄存器做更深入的讨论。如果你对它们有兴趣,我建议你去选一本我
在本章末尾提供的相关Intel的手册资料
Bit 位 |
Type类型 |
Description 描述 |
|||
---|---|---|---|---|---|
11 |
10 |
9 |
8 |
||
0 |
0 |
0 |
0 |
data |
read-only |
0 |
0 |
0 |
1 |
data |
read-only, accessed |
0 |
0 |
1 |
0 |
data |
read-write |
0 |
0 |
1 |
1 |
data |
read-write, accessed |
0 |
1 |
0 |
0 |
data |
read-only, expand down |
0 |
1 |
0 |
1 |
data |
read-only, expand down, accessed |
0 |
1 |
1 |
0 |
data |
read-write, expand down |
0 |
1 |
1 |
1 |
data |
read-write, expand down, accessed |
1 |
0 |
0 |
0 |
code |
execute-only |
1 |
0 |
0 |
1 |
code |
execute-only, accessed |
1 |
0 |
1 |
0 |
code |
execute-read |
1 |
0 |
1 |
1 |
code |
execute-read, accessed |
1 |
1 |
0 |
0 |
code |
execute-only, conforming |
1 |
1 |
0 |
1 |
code |
execute-only, conforming, accessed |
1 |
1 |
1 |
0 |
code |
execute-read, conforming |
1 |
1 |
1 |
1 |
code |
execute-read, conforming, accessed |
图1.13
#include<stdio.h>
void main()
{
int array[4];
int i;
for(i=0;i<100;i++)
{
array[i]=i;
printf("set array[%d]=%d\n",i);
}
return;
}
上面的代码中有一个明目张胆的数组越界。当你运行这个程序时,程序会崩溃,Windows会显示一个如图1.14中所示的对话框。如果你以前还没有见过这样的对话框,好好看看吧。如果你在Windows上进行某种大量指针相关的程序开发,你迟早会看到它。
图1.14
我没有对控制寄存器说太多。跟目前这部分内容最相关的寄存器是是CR0寄存器。我们会在下一部分看到另一对寄存器。CR0寄存器的第一位(最低顺序位)被称为PE标志位(作为保护模式开启的标志)。通过将PE标志位置1,我们将处理器切换至保护模式开启所有我们前面讨论的段保护机制。下面是一个完成这个关键工作的汇编代码的片段
MOV EAX,CR0
OR AL,1
MOV CR0,EAX
另一个达到同样目的的方法是使用特定目的的SMSW和LMSW system 指令:
SMSW AX
OR AL,1
LMSW AX
在这里,你已经反复听到很多名词:选择器(selector),描述符(descriptor)等等。如果
这是你第一次听到关于保护模式的内容。你或许还在混淆它们到底是什么。下面是一个是一个简
的概要,有助于你记住这一些列的名词和相互关系:
Cast Member |
Purpose |
---|---|
段选择器 |
在描述符表中选择一个描述符 |
段描述符 |
描述一个内存段(元数据信息) |
描述符表 |
保护一个段描述符数组 |
描述符表寄存器 |
存储描述符表的基地址 |
如果分页还没有被启用,由图1.9中的结构所生成的最终地址同样也是物理地址,该地址值与处理器放置到它的32位地址线的值是相同的。如果启动了分页,情况不会再是这样,引导我们进入下一个部分:保护模式下的分页机制。