QQ登录

只需一步,快速开始

登录 | 立即注册 | 找回密码

主题

帖子

荣誉

VIP至尊会员

Rank: 15Rank: 15Rank: 15

积分
752
查看: 72|回复: 0

[技术交流] X86汇编语言-从实模式到保护模式—学习笔记(十八)

[复制链接]
最佳答案
24 
累计签到:346 天
连续签到:13 天
兰陵月 发表于 2017-11-14 20:18:55 720 | 显示全部楼层 |阅读模式

马上注册加入鱼C,享用更多服务吧^_^

您需要 登录 才可以下载或查看,没有帐号?立即注册

x
第11章  进入保护模式

11.2  全局描述符表

在保护模式下,对内存的访问仍然使用段地址和偏移地址,但是,在每个段能够访问之前,必须先进行登记。当访问的偏移地址超出段的界限时,处理器就会阻止这种访问,并产生一个叫做内部异常的中断。


和一个段有关的信息需要8个字节(64位)来描述,所以称为段描述符(Segment Descript),每个段都需要一个描述符。为了存放这些描述符,需要在内存中开辟出一段空间。在这段空间里,所有的描述符都是挨在一起,集中存放的,这就构成一个描述符表。
最主要的描述符表是全局描述符表(Global Descript Table,GDT),,所谓全局,意味着该表是为整个软硬件系统服务的。在进入保护模式前,必须要定义全局描述符表。


003_BOOK_P186_全局描述符表寄存器GDTR.jpg

为了跟踪全局描述符表,处理器内部有一个48位的寄存器,称为全局描述符表寄存器(GDTR),如上图。该寄存器分为两部分,分别是32位的线性地址和16位的边界。32位的处理器具有32根地址线,可以访问的地址范围是0x00000000到0xFFFFFFFF,共232字节的内存,即4GB内存。所以,GDTR的32位线性基地址部分保存的是全局描述符表在内存中的起始线性地址,16位边界部分保存的是全局描述符表的边界(界限),其在数值上等于表的大小(总字节数)减一。


全局描述表的界限值就是表内最后一字节的偏移量。第1字节的偏移量是0,最后1字节的偏移量是表大小减一。如果界限值为0,表示表的大小是1字节。
因为GDT的界限是16位的,所以,该表最大字节是216字节,也就是65536字节(64KB)。又因为一个描述符占8字节,故最多可以定义8192个描述符。实际上,不一定非得这么多,到底有多少,视需要而定,但最多不能超过8192个。


理论上,全局描述表可以位于内存中的任何位置。但是,由于在进入保护模式之后,处理器立即要按新的内存访问模式工作,所以,必须在进入保护模式之前定义        GDT(即在实模式的时候定义GDT)。但是,由于实模式下只能访问1MB的内存,故GDT通常都定义在1MB以下的内存范围中。当然,允许在进入保护模式之后换个位置重新定义GDT。



11.3  存储器的段描述符

005_PROG_6_9.jpg

和以前一样,程序的开始我们必须初始化段寄存器,第7行~第9行用于初始化栈,使栈段的逻辑段地址和代码段地址相同,并使栈指针

寄存器SP指向0x7C00。这是个分界线,从这里,代码向上扩展,栈向下扩展。


下面就要开始定义[我们现在只是定义,而并没有开始使用,这个概念要分清,以免傻傻弄不清楚]主引导扇区代码所使用的数据段、代码段和栈段。在保护模式下,内存的访问机制完全不同,即,必须通过描述符来进行。所以,这些段必须重新在GDT中定义。[在没有进入到保护模式之前,我们正在编写的这个主引导程序同样在使用数据段、代码段和栈段,但这是实模式下的使用。既然要进入到保护模式,那我们就要为运行保护模式做好准备工作,在这里重新定义这些段,这样进入保护模式之后,处理器就能够通过描述符方式来访问这些段]


首先是确定GDT的起始线性地址[现在我们还没有学习分页机制,所以本程序没有引入分页机制的内容,没有开启页功能。因此,这里讲的GDT起始线性地址实际上就是内存中的真实物理地址]。如下图,我们在程序第96行,声明了标号gdt_base并初始化了一个双字0x00007E00,我们决定从这个地方开始创建全局描述符表(GDT)。


006_PROG_95_96.jpg

为什么要在这个地方创建?这是我们有意为之。首先看下图。


004_BOOK_P187_进入保护模式前的内存映像.jpg

上图中,实模式下,主引导程序的加载位置是0x0000:0x7C00。因为现在的地址是32位的,所以它现在对应着物理地址0x00007C00。主引导扇区程序共512字节(0x200),所以,我们决定把GDT设在主引导程序之后,也就是物理地址0x00007E00处[0x00007C00+0x200=0x00007E00]。因为GDT最大可以为64KB,所以,理论上,它的尺寸可以扩展到物理地址0x00017DFF处[0x00007E00+0xFFFF=0x00017DFF]。


相应地,因为栈指针寄存器SP被初始化为0x7C00,和CS一样,栈段寄存器SS被初始化为0x0000,而且栈是向下扩展的[即压栈的时候,是从高地址往低地址方向],所以,从0x00007C00往下的区域是实际上可用的栈区域。只不过,该区域包含了很多BIOS数据,包括实模式下的中断向量表,所以一定要小心。这是没有办法的事情,在实模式下,处理器不会为此负责,只能靠我们自己。


一旦确定GDT在内存中的起始位置,下一步的工作就是确定要访问的段,并在GDT中为这些段创建各自的描述符。每个描述符在GDT中占8字节,也就是2个双字,或者说是64位。下图中下面是低32位,上面是高32位。


007_BOOK_P188_存储器的段描述符格式.jpg

1、段基址


该字段有32位,由于历史原因被分为3部分存放在段描述符表中。分别是段基地址15~00、段基地址23~16、段基地址31~24。


段基址可以是0~4GB范围内的任意地址,不过,还是建议应当选取那些16字节对齐的地址。尽管对于Intel处理器来说,允许不对齐的地址,但是,对齐能够使程序在访问代码和数据时的性能最大化。这一点,对于那些学过计算机原理,特别是了解内存芯片组织的人来说,是最清楚不过的。


2、段界限字段


段界限字段,用来表示一个段的大小,结合段基址就可以知道段的起始地址和结束地址。“段基址:偏移量”中当偏移量超过段界限时CPU会触发相应的异常。段界限只有20位,可以表示段的大小,用来限制段的扩展范围。段界限分为两部分存放,分别是段界限15~00、段界限19~16。


对于向上扩展的段,如代码段和数据段来说,偏移量是从0开始递增,段界限决定了偏移量的最大值;对于向下扩展的段,如栈段来说,段界限决定了偏移量的最小值。


3、G字段


G位是粒度(Granularity)位,用于解释段界限的含义。段界限字段与G字段配合使用:
(1)、G=0时,段限长的20位为实际段限长,最大限长为220=1MB,段界限可表示1B~1MB的大小。
(2)、G=1时,则实际段限长为20位段限长乘以212=4KB,最大限长达到4GB,段界限可表示4KB~4GB的大小。


4、S字段


S位用于指定描述符的类型(Descriptor Type),S字段占一个位。当S为0,表系统段。对CPU而言凡是硬件需要用到的都属于系统段。当S为1,表数据段(栈段也是特殊的数据段)。软件用到的东西都放在数据段中。注意操作系统也算软件。


5、DPL字段


表示描述符的特权级(Descriptor Privilege Level,DPL),这两位用于指定段的特权级。


这里表示访问该段时CPU所需最低特权级。描述符特权级(Descriptor Privilege Level),分0、1、2、3这4种特权级,特权级,0为最高特权级,3为最低。如果这里的数值是2,那么,只有特权级0、1、2的程序才能访问该段,而特权级为3的程序范文该段时,处理器会予以阻止。


6、P字段(Segment Present)


表示段是否存在于内存中,P为1表示存在。一般来说,描述符所指示的段都位于内存中。但是,当内存空间紧张时,有可能只是建立了描述符,对应的内存空间并不存在,这时,就应当把描述符的P位清零,表示段并不存在。另外,同样是在内存空间非常紧张的情况下,会把很少用到的段换出到硬盘中,腾出空间给当前急需内存的程序使用(当前正在执行的),这是,同样要把段描述符的P位清零。当再次轮到它执行时,再装入内存,然后将P位置1。


P位是由处理器负责检查的。每当通过描述符访问内存中的段时,如果P位是“0”,处理器就会产生一个异常中断。通常,该中断处理过程是由操作系统提供的,该处理过程的任务是负责将该段从硬盘换回内存,并将P位置1。在多用户、多任务的系统中,这是一种常用的虚拟内存调度策略。当内存很小,运行的程序很多时,如果计算机的运行速度变慢,并伴随着繁忙的硬盘操作时,说明这种情况正在发生。如果开启了分页模式,那么分页模式本身就支持内存置换。就不需要这个P字段了。


7、D/B位


D/B位是“默认的操作数大小”(Default Operation Size)或者“默认的栈指针大小”(Default Stack Pointer Size),又或者“上部边界”(Upper Bound)标志。


设立该标志位,主要是为了能够在32位处理器上兼容运行16位保护模式的程序。尽管这种程序现在已经非常罕见了,但它毕竟存在过,兼容,这是Intel公司能够兴旺发达的重要因素。


该标志位对不同的段有不同的效果。对于代码段,此位称作“D”位,用于指示指令中默认的偏移地址和操作数尺寸。D=0表示指令中的偏移地址或者操作数是16位的;D=1,指示32位的偏移地址和操作数。举个例子来说,如果代码段描述符的D位是0,那么,当处理器在这个段上执行时,将使用16位的指令指针寄存器IP来取指令,否则使用32位的EIP。


对栈段来说,该位被叫做“B”位,用于在进行隐式的栈操作时,是使用SP寄存器还是使用ESP寄存器。隐式的栈操作指令包括push、pop和call等。如果该位是“0”,在访问那个段时,使用SP寄存器,否则就是使用ESP寄存器。同时,B位的值也决定了栈的上部边界。如果B=0,那么栈段的上部边界(也就是SP寄存器的最大值)为0xFFFF;如果B=1,那么栈段的上部边界(也就是ESP寄存器的最大值)为0xFFFFFFFF。对于本书来说,它应当为1。


8、TYPE 字段


TYPE字段共4位,用于指示描述符的子类型。或者说类别。


(1)type<8时:数据段


007.jpg

上图中“E”前面那个位为“X”位,对于数据段,它总是不可执行的,所以X总是等于“0”。


对于数据段来说,“E”位指示段的扩展方向。E=0是向上扩展的,也就是向高地址方向扩展的,是普通的数据段;E=1        是向下扩展的,也就是向低地址方向扩展的,通常是栈段。W位指示段的读写属性,或者说段是否可写,W=0的段是不允许写入的,否则会引发处理器异常中断;W=1的段是可以正常写入的。


(2)type>=8时:代码段


008.jpg

上图中“C”前面那个位为“X”位,对于代码段,它是可执行的,所以X总是等于“1”。


对于代码段来说,C位指示段是否为特权级依从的(Conforming)。C=0表示非依从的代码段,这样的代码段可以从与它特权级相同的代码段调用,或者通过门调用;C=1表示允许从低特权级的程序转移到该段执行。R位指示代码段是否允许读出。代码段总是可以执行的,但是,为了防止程序被破坏,它是不能写入的。至于是否有读出的可能,由R位指定。R=0表示不能读出,如果企图去读一个R=0的代码,会引发处理器异常中断;如果R=1,则代码段是可以读出的,即可以把这个段的内容当成ROM一样使用。这里的R位属性并非用来限制处理器,而是用来限制程序和指令的行为。一个典型的例子是使用段超越前缀“CS:”来访问代码段中的内容。


数据段和代码段的“A”位是已访问位(Accessed),用于指示它所指向的段最近是否被访问过。在描述符创建的时候,应该清零。之后,每当该段被访问时,处理器自动将该位置1。对该位的清零是由软件(操作系统)负责的,通过定期监视该位的状态,就可以统计出该段的使用频率。当内存空间紧张时,可以把不经常使用的段退避到硬盘上,从而实现虚拟内存管理。


9、L位


L位是64位代码段标志(64-bit Code Segment),保留此位给64位处理器使用。目前,我们将此位置“0”即可。


10、AVL位


AVL是软件可以使用的位(Available),通常由操作系统来用,处理器并不使用它。如果你把它理解成“好吧,该安排的都安排了,最后多出这么一位,不知道干什么用好,就给软件用吧”。大概就是这个意思。




本帖被以下淘专辑推荐:

楼层
跳转到指定楼层

发表回复

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

关闭

小甲鱼强烈推荐 上一条 /1 下一条

    移动客户端下载(未启用)
    微信公众号

小黑屋|手机版|Archiver|鱼C工作室 ( 粤ICP备11014136号

Copyright 2018 鱼C论坛 版权所有 All Rights Reserved.

Powered by Discuz! X3.1 Copyright
© 2001-2018 Comsenz Inc.    All Rights Reserved.

小黑屋|手机版|Archiver|鱼C工作室 ( 粤公网安备 44051102000370号 | 粤ICP备11014136号

GMT+8, 2017-11-25 19:22

快速回复 返回顶部 返回列表