您的位置:首页 > 其它

保护模式下寻址

2010-05-31 17:33 183 查看
保护模式下寻址
2009年08月25日 星期二 10:20

段机制轻松体验
内存寻址:
实模式下的内存寻址:
让我们首先来回顾实模式下的寻址方式
段首地址×16+偏移量 = 物理地址
为什么要×16?因为在8086CPU中,地址线是20位,但寄存器是16位的,最高寻址64KB,它无法寻址到1M内存。于是,Intel设计了这种寻址方式,先缩小4位成16位放入到段寄存器,用到时候,再将其扩大到20位,这也造成了段的首地址必须是16的倍数的限制。

公式:xxxx:yyyy
保护模式下分段机制的内存寻址:
分段机制是利用一个称作段选择符的偏移量,从而到描述符表找到需要的段描述符,而这个段描述符中就存放着真正的段的物理首地址,再加上偏移量

一段话,出现了三个新名词:
段选择子
描述符表
段描述符
================================
我们现在可以这样来理解这段话:
有一个结构体类型,它有三个成员变量:
段物理首地址
段界限
段属性

内存中,维护一个该结构体类型的数组。
而分段机制就是利用一个索引,找到该数组对应的结构体,从而得到段的物理首地址,然后加上偏移量,得到真正的物理地址。
公式:xxxx:yyyyyyyy
其中,xxxx也就是索引,yyyyyyyy是偏移量(因为32位寄存器,所以8个16进制)xxxx存放在段寄存器中。
================================
现在,我们来到过来分析一下那三个新名词:
段描述符:一个结构体,它有三个成员变量:
段物理首地址
段界限
段属性

描述符表:也就是一个数组,什么样的数组呢?是一个段描述符组成的数组。
段选择子:      也就是数组的索引,但这时候的索引不在是高级语言中数组的下标,而是我们将要找的那个段描述符相对于数组首地址(也就是全局描述表的首地址)偏移位置。
就这么简单,如图:



图中,通过Selector(段选择子)找到存储在Descriptor Table(描述符表)中某个Descriptor(段描述符),该段描述符中存放有该段的物理首地址,所以就可以找到内存中真正的物理段首地址Segment
Offset(偏移量):就是相对该段的偏移量
物理首地址 + 偏移量 就得到了物理地址    本图就是DATA
但这时,心细的朋友就发现了一个GDTR这个家伙还没有提到!
我们来看一下什么是GDTR
Global Descriptor Table Register(全局描述符表寄存器)
但是这个寄存器有什么用呢 ?
大家想一下,段描述符表现在是存放在内存中,那CPU是如何知道它在哪里呢?所以,Iterl公司设计了一个全局描述符表寄存器,专门用来存放段描述符表的首地址,以便找到内存中段描述符表。
这时,段描述符表地址被存到GDTR寄存器中了。
=================================

 
 

好了,分析就到这,我们来看一下正式的定义:
当x86 CPU 工作在保护模式时,可以使用全部32根地址线访问4GB的内存,因为80386的所有通用寄存器都是32位的,所以用任何一个通用寄存器来间接寻址,不比分段就可以访问4G空间中任意的内存地址。
但这并不意味着,此时段寄存器就不再有用了。实际上,段寄存器更加有用了,虽然再寻址上没有分段的限制了,但在保护模式下,一个地址空间是否可以被写入,可以被多少优先级的代码写入,是不是允许执行等等涉及保护的问题就出来了。要解决这些问题,必须对一个地址空间定义一些安全上的属性。段寄存器这时就派上了用场。但是设计属性和保护模式下段的参数,要表示的信息太多了,要用64位长的数据才能表示。我们把着64位的属性数据叫做段描述符,上面说过,它包含3个变量:
段物理首地址、段界限、段属性
80386的段寄存器是16位(注意:通用寄存器在保护模式下都是32位,但段寄存器没有被改变)的,无法放下保护模式下64位的段描述符。如何解决这个问题呢?方法是把所有段的段描述符顺序存放在内存中的指定位置,组成一个段描述符表(Descriptor Table);而段寄存器中的16位用来做索引信息,这时,段寄存器中的信息不再是段地址了,而是段选择子(Selector)。可以通过它在段描述符表中“选择”一个项目已得到段的全部信息。
那么段描述符表存放在哪里呢?80386引入了两个新的寄存器来管理段描述符,就是GDTR和LDTR,(LDTR大家先忘记它,随着学习的深入,我们会在以后学习)。
这样,用以下几步来总体体验下保护模式下寻址的机制
1、段寄存器中存放段选择子Selector
2、GDTR中存放着段描述符表的首地址
3、通过选择子根据GDTR中的首地址,就能找到对应的段描述符
4、段描述符中有段的物理首地址,就得到段在内存中的首地址
5、加上偏移量,就找到在这个段中存放的数据的真正物理地址。
=================================

好的,那我们开始编码,看看如何实现先前描述的内容

首先,既然我们需要一个数组,全局描述符表,那我们就定义一块连续的结构体:
[SECTION .gdt]    ;为了代码可读性,我们将这个数组放到一个节中
;由一块连续的地址组成的,不就是一个数组吗?看下面代码,^_^
段基地址    段界限 段属性
GDT_BEGIN: Descriptor 0,       0,    0  
GDT_CODE32: Descriptor 0,    0,    DA_C

;上面,我定义了二个连续地址的结构体,大家先认为Descriptor就是一个结构体类型,我们会在以后详细讲述
;第一个结构体,全部是0,是为了遵循Interl规范,先记得就OK
;第二个定义了一个代码段,段基地址和段界限我们暂且还不知道,先初始化为0,但是因为是个代码段,代码段具备执行的属性,那么DA_C就代表是一个可执行代码段,DA_C是一个预先定义好的常量,我们会在详细讲解段描述符中讲解。


=================================
我们继续来实现,那么下面,我们就需要设计段选择子了,因为上面代码已经包含了段描述符和全局描述符表
还记得选择子是个什么东西吗 ?
段选择子:      也就是数组的索引,但这时候的索引不在是高级语言中数组的下标,而是我们将要找的那个段描述符相对于数组首地址(也就是全局描述表的首地址)偏移位置。
看我代码怎么实现,包含以上代码不再说明:
[SECTION .gdt]
GDT_BEGIN: Descriptor 0, 0, 0
GDT_CODE32: Descriptor 0, 0, DA_C

;下面是定义代码段选择子,它就是相对数组首地址的偏移量
SelectorCode32 equ    GDT_CODE32 - GDT_BEGIN
;因为第一个段描述符,不被使用,所以就不比设置段选择子了。
=================================

 

偏移地址:
注意一点,我们在程序中使用的都是偏移地址,相对于段的偏移地址,用上面的例子来说,象 GDT_CODE32 GDT_BEGIN 这些结构体的首地址都是相对于数据段的偏移量。什么意思呢 ?
因为我们的程序到底加载到内存的哪个地方是不固定,不知道的,只需使用偏移地址操作就行了,如:
SelectorCode32 ,它本身就是一个偏移地址
但是SelectorCode32    equ GDT_CODE32 - GDT_BEGIN
怎么解释呢 ?
GDT_CODE32是相对于数据段的偏移量,
GDT_BEGIN也是相对于数据段的偏移量,虽然它是数组的首地址,说的罗索一些,GDT_BEGIN是数组的首地址,但是它是相对于数据段的偏移量
那么两个偏移量相减就是GDT_CODE32 相对于GDT_BEGIN的偏移量

所以,我们要时时刻刻记得,在程序中,我们永远使用的是偏移量,因为我们不知道程序将要被加载内存那块地方。
好了,基础也学的差不多了,下面我们要自己动手写一段程序,实现实模式到保护模式之间的跳转

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息