内存管理:算法及其c/c++实现 翻译六

来源:互联网 发布:全国网络教育排名 编辑:程序博客网 时间:2024/05/22 08:17
保护模式操作
 
保护模式提供了所有在实模式下没有的保护机制。奔腾处理器特地地设计成运行在保护模式下。它的内部设计使它运行32位指令比运行16位指令更加高效。装有奔腾处理器的机器启动时运行于实模式下,Intel工程师的这种设计方法能使操作系统的自启动程序方便的运行起来。
 
运行于保护模式的Intel处理器支持保护的分段机制,也支持分页机制,这意味着地址解析的方法将更加复杂。在实模式下,我们只需把段首地址加上偏移地址,它们的和就直接相应于物理内存地址。在保护模式下,处理器知道有一整套特殊目的的数据结构在特定的地方,另外,段地址和偏移地址之和不再直接与物理地址相对应,接着往下看,我们将慢慢道来。
 
保护模式分段机制
 
理解Intel处理器分段机制的最好方法是以图示的方法来显示它的实现过程。一副图抵得上千言万语的描述,此时真是千真万确J。所以,你得好好地、仔细地看看图1.9,并把它于图1.8做比较。最好把图1.9做好标签,以后好随时翻到此处来看看。
 
Figure1.9
 
首先要注意的是保护模式下使用了图1.2所示的所有奔腾处理器的寄存器。段寄存器不再存储16位段地址值,它存储了段选择器的值。
 
段选择器是一个16位的数据结构,它包含3个字段,它的组成如图1.10所示。实际比较重要的字段是索引字段,索引字段存储着描述符表的索引,索引值从0开始。
 
Figure1-10
 
……注意:段选择器的索引字段不是一个地址,它是一个索引,就像你在C语言中访问数组元素时使用的索引一样。处理器取出索引,做一些计算,以使索引与相应的线性地址相对应,注意此处所说的线性地址,不是物理地址,目前,线性地址与物理地址是一样的,但是,当我们使用分页机制时,它们就不一样了,这一点一定要记在脑筋了。………………………
 
描述符表是一个数据项的数组,其中的每一个数据项(称为所谓的段描述符)描述了特定内存段的属性,描述符里也包括它所描述的内存段的基地址。我们把32位的偏移地址加到段描述符的基地址就可以得到内存字节的地址。
 
有两个类型的描述符表:全局描述符表(Global Descriptor Table , GDT),局部描述符表(Local Descriptor Table, LDT)。每一个操作系统必须有一个GDT,但有一个还是有多个LDT结构是可以选择的。通常,如果使用了一个LDT,那么,它就用作代表属于一个特定进程的内存段。GDT的基地址存储在GDTR这个系统寄存器里面,同样,LDT的基地址存储在LDTR寄存器里面,由此自然可以想到必定有一套特定的系统指令来加载这些存储在寄存器里面的值(例如,LGDT和LLDT指令)。
……注意:本书所讨论的所有操作系统几乎都把重点放在GDT上,很少使用LDT(如果它确实使用了它的话)。……………………………………………………………………………
 
GDTR是48位的大小,GDTR不寻常的特征是它存储了两个不同的值。开始的16位存储了以字节为单位的GDT的大小,接着的32位存储了GDT物理地址的基地址(也可以说是线性地址)。如图1.11所示:
 
 
Figure1-11
 
 
……..现在提一个问题:处理器怎样通过段选择器里存储的索引找到相应的段描述符?
……………………………………………………………………………………………………….
…….答案如下:
……..处理器从段选择器里面取出索引的值,把此值乘以8(因为段描述符是64位的长度,所以要转变成8个字节的形式),然后,把得到的结果与GTDR或者LDTR中存储的值相加就的到了想要的值。………………………………………………………………………………..
 
…….注意:当你盯着图1.2看时,你可能感到疑惑,为什么另外两个内存管理寄存器IDTR和TR没有提及,是不是忘了它们!我没有忘记它们。只是它们对于我们所讨论的GDTR和LDTR没有什么多大的关系。IDTR和TR寄存器只是在管理硬件中断和多任务时使用。这本书主要讲述内存管理,所以我没有详细的讨论这两个寄存器。如果你对它们感兴趣,我推荐你阅读作为引用而写在本章末后面的Intel手册。…………………………………………….
 
早些时候我提到段描述符存储了它所描述的内存段的基线性地址,然而,它们也存储了许多其它的元素。段描述符里究竟存储了一些什么信息?图1.12能让你对此有一个更加清楚的认识。在此图中,我把64位的描述符分开成两个32位的部分,高位字节部分在低位字节部分的上面。
 
  
G = Granularity(粒度), 如果以字节为粒度则设为0,如果以4KB大小为粒度则设为1
DS = Default Size,当是16位的代码或数据段时设为0,当是32位的数据或代码段时设为1
第21位总是0,不要问我为什么,问Intel的工程师去J
S = System bit,被操作系统的常驻内存部分使用
P = Present在分页机制中如果段不在内存则为0,如果在内存则为1
PL = Privilege Level,特权等级(与段选择器里的特权等级相同)
SS = System Segment,如果是系统段则为0,如果是代码或数据段则为1
type = 段的类型
 
Figure1.12
 
有许多信息装在这64位的段描述符里。你可以清楚的看到,一些字段被分开来存放在段描述符的不同部分里面。在图1.11中,有两个字段不能明显地看出来,首先是SS标志,它显示了段是系统占用的段还是一般的代码段或者数据段。谈到系统占用的段你可能要搔后脑勺了,其实,它就是用作中断处理和多任务服务的段。我说过,我不会在本书中讨论这两个主题。
 
…….注意:你可能已经注意到段描述符的接下来的部门中有许多1位的标志,当某一位是1时,说明此位被设置了,当是0时,说明此位没有被设置。低级操作系统中对位的操作极其普遍,我们决不要回避它。高级语言的程序员倾向于轻视写低级代码的程序员,并把他们称为“劫持位的人”或“位旋弄者”。L………………………………………………………….
 
如果SS标志被设置,那么段描述符里的4位类型字段描述了特定的段特性。
 
Table1.1
 
被访问过的内存段是已经被访问过的段,所以第8位设为1,向下扩展的段用作创建堆栈,因为它们支持堆栈的创建,创建的堆栈是从高端内存向低端内存增长的。一致性代码段允许低特权的代码段跳进来,并且在其中执行更低特权的代码。
对安全比较敏感的系统工程师在执行一致性代码段时作出警告信息是比较明智的做法。
 
我的问题又来了,既然我们理解了段是怎样被使用的,也理解了段描述符里存放的一些信息,那么,这些内存段是怎样保护得到保护的呢?
 
…….答案是:段选择器和段描述符包含大量执行保护方案所需要的信息,处理器使用了很多位元去跟踪内存访问的违规现象。例如,段描述符里的段尺寸字段可以限定内存访问不会超过内存段的最后字节,另外,段描述符的类型字段可以确保指定为只读的段不会被写入。处理器使用段选择器和段描述符里的特权字段来保护程序不会非法地执行更高特权的代码和数据。…………………………………………………………………………………………….
 
……..注意:不要弄混淆了,虽然0x00的值最小,但是它有最高的特权等级。………………
 
特权等级是操作系统用作防止用户应用程序操作内核映象而危害系统安全的方法,在我们讨论的实模式下,你可以看到破坏系统或使其崩溃是多么的容易,我只要擦除中断向量表就可以轻而易举地做到这一点。在保护模式下,这种威胁就终结了。大量的数据结构和操作系统的代码能在硬件级就得到很好的保护。
 
Intel处理器支持4种不同特权的级别(从级别0到级别3),另一种说法是Intel处理器支持4个保护圈。这些保护圈如图1.13。就内存保护来说,这实际上是一种 恰如其分的保护方案。几十年前,当Control Data公司编写自己的操作系统NOSVE时,架构师当时想设计15层保护圈。奇怪的是当代的一些操作系统(比如Windows和Linux)仅仅实现了两层保护圈(一层为内核,另一层就是内核之外的所有东西),它们没有完全利用奔腾处理器提供的一些保护措施。
 
Figure1.13
 
当处理器访问内存时,它要做一系列的检查,这些检查是在内存地址解析成物理地址时进行的。由于所有检查发生在地址解析时钟周期内,所以没有任何额外时间开销。把内存管理让硬件去实现是一种漂亮的设计方法。
 
如果在检查时发现有一个保护违规现象,处理器就产生一个异常。异常是处理器发生了违规现象时发出的信号,这些异常由处理器不同的中断服务捕获并加以处理,具体的实现不在本书讨论的范围之内。通常情况下,处理器将使用称为中断描述符表(Interrupt Descriptor Table, IDT)的特定数据结构去处理操作系统所引发的异常,然后此数据结构决定具体做什么。当机器启动后操作系统的自运行程序运行时,实际上由操作系统代表处理器建立IDT,这样,操作系统就可以自由地在IDT中登记特定的处理函数,以便当发生内存违规时相应的函数可以被调用。
 
在Windows环境下,当发生内存异常时,一般会出现一个对话框,上面写着访问违规,并且Windows会终止发生违规的程序。为了让大家看到我所说的内容,在Windows下编译、运行下面的程序:
 
 
 
上面程序有一个明显的数组溢出,当运行此程序时,它将出问题,将出现如图1.14的对话框。如果你从没有看到过类似的对话框,好好瞧瞧它的模样,当你在Windows环境下进行指针密集型的开发时,你一定迟早会看到它的。
 
 
Figure1-14
 
我还没有对控制寄存器做详细说明,仅仅与目前所讨论的问题相关的控制寄存器是CR0控制寄存器。我们将在后面看到许多控制寄存器。CR0寄存器的第一位(最低顺序位)称为PE标志(所谓的开启保护机制,Protected Enable)。通过把PE位设为1,处理器转换进了保护模式,并且上面所讨论的所有分段保护机制都生效。下面是实现这个重要任务的汇编代码片断:
 
MOV   EAX, CR0
OR     AL, 1
MOV   CR0, EAX
 
另一种实现上述功能的方法是使用特殊目的的系统指令:SMSW、LMSW
 
SMSW   AX
OR      AL, 1
LMSW   AX
 
到目前为止,你可能被许多绕来绕去的术语搞蒙了,比如:选择器、描述符等等。如果你是第一次学习保护模式,你可能对上面的讲述还不知所云。下面是对上文一连续的术语的一个小小的总结,它有助于你记住这些词儿!
 
术语                                         用途
段选择器(Segment selector)                   在描述符表里选择一个描述符项。
段描述符(Segment descriptor)                 描述一个内存段(它有一些位元组成).
描述符表(Descriptor table )                   存储一个段描述符数组
描述符表寄存器(Descriptor table register)       存储描述符表的基地址
 
如果没有使用分页机制,图1.9所示的方案中产生的地址也是物理地址,也就是说它就是处理器放在32位地址线上的值。
 
    如果使用了分页机制,情况就不一样了。这就是我们下一部分将讲述的内容。