读内存管理(整理)

来源:互联网 发布:数据结构与算法考点 编辑:程序博客网 时间:2024/06/11 09:26

Figure1.15
GDTR寄存器里存放着GDT数据结构的基线性地址,运行于使用分页机制的保护模式下的代码交替地使用线性地址,这样的代码使用的是“假的”32位的地址,但对代码来说,这个地址是足够真实,掩藏在其中的具体实现是处理器把假的(或者说线性的)地址解析成实际物理存储地址。


线性地址的最后10位,是页目录里某一项的偏移地址,页目录是一个由32位的项组成的数组,它的基地址存储在CR3控制寄存器里,页目录项里包括页表的基地址等信息。

假定页目录项指定了某个页表的基地址,则线性地址的中间10位作为页表内的索引地址,此索引地址确定了一个32位的页表项,32位的页表项里包括实际的4KB内存页的基地址等信息。

假定页目录项指定了某个页表的基地址,则线性地址的中间10位作为页表内的索引地址,此索引地址确定了一个32位的页表项,32位的页表项里包括实际的4KB内存页的基地址等信息。
现在该是线性地址的头12位起作用的时候了,这个12位的偏移地址加到页表项的基地址上,其和就是虚拟内存中字节的实际物理地址,这个地址所指的字节或者在DRAM中,或者已经被换页到了磁盘上.

存储在页表项里的基地址是20位的大小,处理器设定这20位是32位基地址中最有意义的,换句话说就是另外的12位是默认设置的,它们默认都是零。
例如,基地址为:0XCAEEB 实际是 0xCAEEB [0] [0] [0]。


大多数操作系统为每一个进程分配了一个属于自己的页目录,以便程序的线性地址空间映射到不同的物理存储部分,这样,只要页结构项不相同,一个进程就不能访问另外一个进程的物理地址。


 页目录项
   
AVL = 操作系统使用,
PS = 页大小(如果为0,则页大小为4KB)
O   = 总是0
G   = 全局标志(可忽略)
A   = 是否被访问的标志(如果这个页最近被方位过,则设为1)
CD = 是否使用了缓存(如果要阻止页表进入缓存,则设为1)
WT = 写通(当使用缓存、并且缓存被增强时使用这一位)
US   = 用户或监管(如果此位为0,则页表有监管特权)
RW   = 读或写(如果此位为0,则页表属性为只读)
P     = 如果为1,则被指向的页表目前在物理内存之中

figure1.16

AVL = 操作系统使用,
G   = 全局标志(可忽略)
O   = 总是0
D   = 是否是脏页(当设为1时,页已经被写过)
A   = 是否被访问的标志(如果这个页最近被方位过,则设为1)
CD = 是否使用了缓存(如果要阻止页表进入缓存,则设为1)
WT = 写通(当使用缓存、并且缓存被增强时使用这一位)
US   = 用户或监管(如果此位为0,则页表有监管特权)
RW   = 读或写(如果此位为0,则页表属性为只读)
P     = 如果为1,则4Kb的页目前在物理内存之中

figure 1.17
Figure 1.18


CR0用作控制处理器的模式和状态。CR0中另一个重要的标志是位于其最高位的PG,当PG标志被设置时,处理器将使用分页机制,当PG位被清除时,线性地址就被当作物理地址。PE处理器模式状态位。
CR2寄存器用来存储发生页错误的线性地址。
CR3寄存器在分页机制里物理地址的解析过程中扮演着重要的角色,这个寄存器存放着页目录的基地址。

CR4寄存器在一些高级机制中使用,例如,设置PAE标志可以使另外四根地址线被使用,这样,地址线将达到36根。要注意的是为了使PAE生效,CR0里的PG标志也必须被设置。如果标志位PSE被设置位0,则页大小为4KB,如果PSE被设为1,则页大小为4MB,如果PAE和PSE都设置为1,则页大小为2MB。

GDT中的第一项由于一些原因没有被使用,所以,在某种意义上它只是一个占位符,第二和第三项用作代码段和数据段,此处的技巧是使两者的描述符都指向同一个段,这个段地址是0x00000000,大小是4GB。大小被限制到最大的可能值(0xFFFFFFFF)是为了阻止抛出“超出范围”的例外。
DS、ES或者CS段寄存器,必须使用一个段选择器来索引找出GDT中两个有效的描述符中的其中一个。记住有三个可能的段选择器,其中的两个段选择器指向代码和数据段描述符,一个段选择器指向空的段描述符,此选择器就称为空选择器。如果使用了0特权,那么这三个选择器将如下所示:
               段描述符                    段选择器
0             0(空选择器)
1     0x08
2             0x10

figure 1.19


把操作系统占用的页放在监管模式,把用户程序占用的页放在用户模式,这样,内核能受到保护而不会被恶意的程序所侵害。如果应用程序试图访问监管模式下的页,处理器可以产生页错误信息。
读写标志可以用来加强用户/监管模式的分离。当处理器在监管页里执行代码时,它能任意的读写,换句话说,内存页的只读标志被忽略了。然而,如果处理器在用户页里执行代码,那么它只能读写到其它的用户级别的页,如果它竭力访问监管状态的页,那么,处理器将产生一个页错误。用户页间的访问依赖于页的读写标志,用户级别的代码必须遵守其它用户页是否允许读写的规则。


figure 1.20
 
物理地址是DRAM里的字节的地址,它是处理器放置在地址线上的值,此值用来访问芯片上的内存。
逻辑地址是由段寄存器和通用寄存器指定的地址,只有在实模式下逻辑地址才与物理地址一一对应,这是因为实模式没有使用分段机制或分页机制,图1.19描述的方法也没有被使用。你可能要记住的是逻辑地址中的偏移部分不必被存储在通用寄存器里,我只是为了表述的方便才说保存在通用寄存器里。
线性地址是一个32位的值,它是由段描述符里的基地址和通用寄存器里的偏移值指定的。如果没有使用分页机制的话,线性地址就与实际的物理地址一一对应。
如果使用了分页机制,那么,线性地址将被分解为三个部分,并且,为了得到物理地址,必须进行页目录和页表的相关操作。


六个步骤可以转换到保护模式下:
1.       创建GDT
2.       关中断
3.       使A20地址线可以使用
4.       加载GDTR寄存器
5.       设计CR0寄存器里的PE标志位
6.       执行远程跳转


闭中断是许多处理器使用旗语作为信号量来实现原子操作的方法.
一旦通过LDTR指令加载了GDTR寄存器,并且在CR0中设置了PE标志位,FAR远程跳转就必须被执行。在Intel的汇编器中,FAR跳转指令被用作实现段间跳转,这导致新的值被加载到代码段寄存器CS和指令指针寄存器IP中,执行FAR远程跳转的目的是在CS中加入段选择器的值。

FAR远程跳转中有窍门的地方是它必须以二进制进行编码。在跳转到保护模式前我们还在实模式下,这意味着汇编器把指令看作传统的16位的方式,如果我们竭力在汇编语言中使用32位的FAR远程跳转来编码,那么,汇编器将或者发出错误或者错误的执行跳转指令。这样,困难的活就留给我们去做了。


BIOS (它被烧进ROM中)开始行动起来,它寻找一个可以启动的设备。记住PC是以实模式启动的。为了简化,我们假设BIOS从驱动器A中加载可以启动的磁盘(译者注:软盘)。BIOS将把磁盘(译者注:软盘)的启动扇区加载到DRAM的0000[0]:7c00地址(也就是物理地址0x07c00),这是第一步操作。
一旦BIOS把启动扇区加载进了DRAM,它就把控制权交给了位于DRAM中地址为0000[0]:7c00的指令。然而,还有许多BIOS服务可以通过中断访问,这是BIOS扮演重要角色的地方,因为还有一些简单的磁盘I/O中断是用来加载内核的。
磁盘扇区的大小为512个字节,这样大小的代码只够把内核从磁盘(译者注:硬盘)加载到内存中(第二步操作)并且转换到保护模式。在转换到保护模式快结束时,16位的启动扇区代码执行一个我们编写好的FAR远程跳转,跳转到内核的入口点(第三步操作)。内核接管了以后的事情,这样,系统现就运行在保护模式下,并且执行的是32位的指令(第四步操作)。

一个启动扇区,为了节省空间,你应该使用汇编代码写启动扇区,其次,你的启动代码应该只使用BIOS中断与硬件打交道,你不应该调用DOS中断与硬件打交道,启动时DOS还不知道在哪呢!
另外,你应该编译和连接启动扇区的代码成为16位的.COM二进制文件形式。PC启动时处在实模式下,并且它只能运行16位的指令集。启动扇区里必须是纯粹的代码,这意味着在最后编译好的可执行文件中不能有额外的数据格式存在,这就是为什么我们要编译成.COM的文件形式,因为此文件中是纯粹的二进制代码,没有其它的信息在里面。


 : / code / boot > debug boot.com
- l
- w cs : 0100 0 0 1
- q
C : / code / boot >
 
-1命令把boot.com文件加载到内存,默认情况下boot.com文件被debug命令加载到地址CS:0x0100。下一条命令把那个地址开始的指令写到驱动器A(也就是驱动去0)的逻辑扇区0上,一个512字节的扇区被写入。下面是W命令的通用格式,这样更容易理解:
w    startAddr   driverLetter    startSector    nSectors
   做完上面的事后,剩下的事情就是写内核,并把内核调到磁盘(译者注:硬盘)上了

Figure1.9
 

段选择器是一个16位的数据结构,它包含3个字段,它的组成如图1.10所示。实际比较重要的字段是索引字段,索引字段存储着描述符表的索引,索引值从0开始。

Figure1-10 (段选择器)
 

.段选择器的索引字段不是一个地址,它是一个索引,就像你在C语言中访问数组元素时使用的索引一样。
 

全局描述符表(Global Descriptor Table , GDT),局部描述符表(Local Descriptor Table, LDT)。每一个操作系统必须有一个GDT,但有一个还是有多个LDT结构是可以选择的。如果使用了一个LDT,那么,它就用作代表属于一个特定进程的内存段。
GDTR是48位的大小,GDTR不寻常的特征是它存储了两个不同的值。开始的16位存储了以字节为单位的GDT的大小,接着的32位存储了GDT物理地址的基地址.

处理器怎样通过段选择器里存储的索引找到相应的段描述符?
……………………………………………………………………………………………………….
…….答案如下:
……..处理器从段选择器里面取出索引的值,把此值乘以8(因为段描述符是64位的长度,所以要转变成8个字节的形式),然后,把得到的结果与GTDR或者LDTR中存储的值相加就的到了想要的值。………………………………………………………………………………..

64位的描述符分开成两个32位的部分,高位字节部分在低位字节部分的上面。
 

 
Figure1.12
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 = 段的类型
两个字段不能明显地看出来,首先是SS标志,它显示了段是系统占用的段还是一般的代码段或者数据段。谈到系统占用的段你可能要搔后脑勺了,其实,它就是用作中断处理和多任务服务的段.
如果SS标志被设置,那么段描述符里的4位类型字段描述了特定的段特性。
 
这些内存段是怎样保护得到保护的呢?
 
…….答案是:段选择器和段描述符包含大量执行保护方案所需要的信息,处理器使用了很多位元去跟踪内存访问的违规现象。例如,段描述符里的段尺寸字段可以限定内存访问不会超过内存段的最后字节,另外,段描述符的类型字段可以确保指定为只读的段不会被写入。处理器使用段选择器和段描述符里的特权字段来保护程序不会非法地执行更高特权的代码和数.

段选择器(Segment selector)                   在描述符表里选择一个描述符项。
段描述符(Segment descriptor)                 描述一个内存段(它有一些位元组成).
描述符表(Descriptor table )                   存储一个段描述符数组
描述符表寄存器(Descriptor table register)       存储描述符表的基地址
 
在具有内存保护的分段方案中,每一个应用程序被分配了至少一个段的空间。大的应用程序经常有几个段空间

数据页被存储在物理内存时(例如:DRAM),它们被放在一个页尺寸大小的内存块中,这个内存块就叫做页框(Page Frame)。除了跟踪单个页面使用情况外,大多数操作系统也跟踪页框的使用情况。页框的数目通常远小于页的数目,所以,操作系统必须小心翼翼地管理这些珍贵的资源。

原创粉丝点击