一般, *** 作系统负责整个计算机软,硬件管理,它做任何事情都是可以的。但,用户程序却应有所限制。只允许它访问属于自己的数据,即使是转移,也只允许在自己的各个代码段之间进行。
问题在于,实模式下,用户程序对内存访问无限制。
在多用户,多任务时代,内存有多个用户程序同时运行,为了使它们彼此隔离,防止某个程序的编写错误或崩溃而影响 *** 作系统和其他用户程序,用保护模式非常有必要。
本章学习目标:
1.了解x86处理器的保护模式需先定义全局描述符表GDT,认识段描述符的各个组成部分及它们的含义和作用。
2.认识32位处理器的全局描述符表寄存器GDTR,段寄存器【由段选择器和描述符高速缓存器组成】、控制寄存器CR0和段选择子。
3.了解进入32位保护模式的方法和步骤
4.学习保护模式下的一些程序调试技术,如察看全局描述符表GDT,段寄存器和控制寄存器等。
5.学习一条x86处理器的新指令lgdt。
11.1.代码清单11-1 11-2.全局描述符表我们知道,为了让程序在内存自由浮动而不影响它的正常执行,处理器将内存划分成逻辑上的段,并在指令中使用段内偏移地址。保护模式下,对内存的访问仍然使用段地址和偏移地址。但是,每个段能访问前,需先进行登记。
和一个段有关的信息需8个字节来描述,所以称为段描述符,每个段都需要一个描述符。为存放这些描述符,需在内存开辟一段空间。这段空间里,所有的描述符都挨着一起,集中存放,这就构成了一个描述符表。
最主要的描述符表是全局描述符表,所谓全局,意味着该表是为整个软硬件系统服务的。进入保护模式前,需定义全局描述符表。
为跟踪全局描述符表,处理器内部有一个48位的寄存器,称为全局描述符表寄存器。该寄存器分为两个部分,分别是32位的线性地址和16位的边界。32位的处理器有32根地址线,可访问地址范围是0x00000000~0xFFFFFFFF。所以,GDTR的32位线性基地址部分保存的是全局描述符表在内存的起始线性地址,16位边界部分保存的是全局描述符表的边界【界限】,其在数值上等于表的大小减去1.
换句话说,全局描述符表的界限值就是表内最后1字节的偏移量。
因为GDT的界限占16位,所以,该表最大是2^16字节。又因为一个描述符占8字节,故最多可定义8192个描述符。
理论上,全局描述符表可以位于内存中的任何地方。但是,由于,进入保护模式后,处理器立即要按新的内存访问模式工作,所以,必须在进入保护模式之前定义GDT。但,由于在实模式下只能访问1MB的内存,故GDT通常定义在1MB以下的内存范围中。当然, 允许进入保护模式后换个位置重新定义GDT。
11.3.存储器的段描述符在程序的开始部分要初始化段寄存器。
保护模式下,内存的访问机制完全不同,即,必须通过描述符来进行。所以,这些段必须重新在GDT中定义。
先是确定GDT的起始线性地址。实模式下,主引导程序的加载位置是0x0000:0x7c00。
实模式和保护模式在内存访问上是有区别的,在保护模式下,你不能说访问那个段就访问那个。访问前,需先在GDT内定义要访问的内存段。
描述符不是由用户程序自己建立的,而是加载时,由 *** 作系统根据你的程序结构建立的。用户程序通常无法建立和修改GDT。这种情况下, *** 作系统为你的程序建立了几个段,你就只能在这些段工作,超出此范围,或未按预定方法访问这些段,都将被处理器阻止。
一旦确定了GDT在内存的起始位置,下一步就是确定要访问的段,并在GDT中为这些段创建各自的描述符。
每个描述符在GDT中占8字节。
描述符中指定了32位的段起始地址,及20位的段边界。在实模式下,段地址并非真实的物理地址。计算物理地址时需左移4位。和实模式不同,在32位保护模式下,段地址是32位的线性地址,如未开启分页功能,该线性地址就是物理地址。开启分页功能需做很多准备工作。
描述符中的段基地址和段界限不是连续的。之所以,分离的,是为了和80286中16位保护模式兼容。
段基地址可以是0~4GB范围内的任意地址,不过,还是建议应选取那些16字节对齐的地址。尽管对Intel处理器来说,允许不对齐的地址,但,对齐能使程序在访问代码和数据时的性能最大化。
20位的段界限用来限制段的扩展范围。因为访问内存的方法是用段基地址加上偏移量,所以,对向上扩展的段,如代码段和数据段来说,偏移量从0开始递增,段界限决定了偏移量的最大值。对向下扩展的段,如栈段来说,段界限决定了偏移量的最小值。
G位是粒度位,用于解释段界限的含义。当G位是"0"时,段界限以字节为单位。此时,段的扩展范围是从1字节到1兆字节【1B~1M】。因为段界限值是20位的。相反,如果该位是"1",则,段界限是以4KB为单位的。这样,段的扩展范围从4KB到4GB。
S位用于指定描述符的类型。当该位是"0"时,表示是一个系统段。为"1",表示是一个代码段或数据段【栈段也是特殊的数据段】。
DPL表示描述符的特权级。共有4种处理器支持的特权级,分别是0,1,2,3,其中0是最高级别特权级,3是最低。刚进入保护模式时执行的代码有最高特权级,这些代码通常是 *** 作系统代码。每当 *** 作系统加载一个用户程序时,它通常会指定一个稍低的特权级,比如3。不同特权级的程序是相互隔离的,其互访是严格限制的,且有些处理器指令只能由0特权级的程序来执行。
这里,描述符的特权级用于指定要访问该段所必须具有的最低特权级。如这里的数值是2,则只有特权级0,1,2的程序才能访问该段。而特权级3的程序访问该段时,处理器会予以阻止。
P是段存在位。P位用于指示描述符所对应的段是否存在。一般,描述符所指示的段都位于内存。但是,当内存空间紧张时,有可能只建立了描述符,对应的内存空间并不存在。另外,同样是在内存空间紧张下,会把很少用到的段换出到硬盘,腾出空间给当前急需内存的程序使用【当前正执行的】,这时,同样要把段描述符的P位清零。当再次轮到它执行时,再装入内存。然后将P位置1。
P位是由处理器负责检查的。每当通过描述符访问内存中的段时,如P位是"0",处理器就会产生一个异常中断。通常, 该中断处理过程由 *** 作系统提供,该处理过程的任务是负责将该段从硬盘换回内存,并将P位置1。在多用户,多任务的系统中,这是一个常用的虚拟内存调度策略。
当内存很小,运行程序很多时,如计算机运行速度慢,并伴随频繁硬盘 *** 作时,说明这种情况正发生。
D/B位是"默认的 *** 作数大小"或者"默认的栈指针大小",又或者"上部边界"标志。
设立该标志位,主要是为了能在32位处理器 上兼容运行16位保护模式的程序。
该标志位对不同的段有不同的效果。对于代码段,此位被称作"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。
L位是64位代码段标志,保留此位给64位处理器使用。
TYPE字段共4位,用于指示描述符的子类型,或者说是类别。对于数据段来说,这4位分别是X,E,W,A位。而对于代码段来说,这4位分别是X,C,R,A位。
表中X表示是否可执行。数据段总是不可执行的。代码段总是可以执行的。
对数据段来说,E指示段的扩展方向。E=0是向上扩展,也就是向高地址方向扩展的,是普通的数据段;E=1是向下扩展的,也就是向低地址方向扩展的,通常是栈段。W位指示段的读写属性,或者说段是否可写,W=0的段不允许写入,否则会引起处理器异常中断。W=1的段可正常写入。
对代码段来说,C位指示段是否为特权级依从的。C=0表示非依从的代码段,这样的代码段可从与它特权级相同的代码段调用,或者通过门调用;C=1表示允许低特权级的程序转移到该段执行。R位指示代码段是否允许读出。代码段总是可以执行的,但,为防止程序被破坏,它是不能写入的。至于是否有读出的可能,由R位决定。R=0,不可读出。R=1,可以读出。即可把这个段的内容当成ROM一样使用。
数据段和代码段的A位是已访问位,用于指示它所指向的段最近是否被访问过。在描述符创建时候,应清零。之后,每当该段被访问时,处理器自动将该位置1.对该位的清零由软件【 *** 作系统】负责的。内存空间紧张时,可把不经常使用的段退避到硬盘上,从而实现虚拟内存管理。
AVL是软件可以使用的位,通常由 *** 作系统来用,处理器不使用。
11.4.安装存储器的段描述符并加载GDTR实模式下,在GDT中安装描述符,必须将GDT的线性地址【物理地址】转换成逻辑段地址和偏移地址。
GDT的线性地址是我们直接给出的。
处理器规定,GDT中的第一个描述符必须是空描述符,或者叫哑描述符或NULL描述符。
进入保护模式后必然要从一个代码段开始执行。
加载描述符表的线性基地址和界限到GDTR寄存器,这要使用lgdt指令,该指令格式为:
lgdt m48
这就是说,该指令的 *** 作数是一个48位【6字节】的内存区域。在16位模式下,该地址是16位的;在32位模式下,该地址是32位的。该指令在实模式和保护模式下都可执行。
在这6字节内存区域中,要求前【低】16位是GDT界限值,后【高】32位是GDT的基地址。在初始状态下【计算机启动后】,GDTR的基地址被初始化为0x00000000;界限值为0xFFFF。
GDT表的界限值是表的总字节数减去1,所以是31。
11.5.关于第21条地址线A20的问题在即将进入保护模式前,还涉及一个历史遗留问题,就是处理器的第21根地址线,编号为A20。"A"是Address的首字符,就是地址。A0是第一根地址线,A31是第32根地址线,所以,A20就是第21根地址线。在8086处理器上运行程序无A20问题,因为它只有20根地址线。
到了80286时代,处理器有24根地址线,为了和8086兼容。IBM公司使用一个与门来控制第21根地址线A20,并把这个与门的控制阀门放在键盘控制器内,端口号是0x60。向该端口写入数据时,如第1位是"1",那么,键盘控制器通向与门的输出就为"1",与门的输出就取决于处理器A20是"0"还是"1"。
这种做法非常繁琐,要访问键盘控制器,需要先判断状态,要等到键盘控制器不忙,至少需十几个步骤。从80486处理器开始,处理器本身就有了A20M#引脚,意思是A20屏蔽,它是低电位有效的。
输入输出控制器集中芯片ICH的处理器接口部分,有一个用于兼容老式设备的端口0x92,第7~2位保留未用,第0位叫做INIT_NOW。用于初始化处理器,当它从0过渡到1时,ICH芯片会使处理器INIT#引脚的电平变低【有效】,并保持至少16个PCI时钟周期。通俗说,向这个端口写1,会使处理器复位,导致计算机重新启动。
端口0x92的位1用于控制A20,叫做替代的A20门控制,它和来自键盘控制器的A20控制线一起,通过或门连接到处理器的A20M#引脚。和使用键盘控制器的端口不同,通过0x92端口非常迅速,方便。
当INIT_NOW从0过渡到1时,ALT_A20_GATE将被置"1"。即,计算机启动时,第21根地址线是自动启用的。A20M#信号仅用于单处理器系统,多核处理器一般不用。
端口0x92是可读写的。从0x92读出数据,将第二位置"1",在写回。以便打开A20。不然,为了兼容20根地址线,21根地址线始终为0。
11.6.保护模式下的内存访问控制这两种模式切换的开关原是一个叫CR0的寄存器。
CR0是处理器内部的控制寄存器。
CR0是32位的寄存器,包含一系列用于控制处理器 *** 作模式和运行状态的标志位。它的第1位是保护模式允许位,是开启保护模式大门的把手。如把该位置"1",则处理器进入保护模式,按保护模式规则运行。
保护模式下的中断机制和实模式不同,原有的中断向量表不再适用。保护模式下,BIOS中断都不能再用,因为它们是实模式下的代码。在重新设置保护模式下的中断环境前,需关中断。
在32位处理器内,段寄存器新增了FS,GS。
32位处理器的这6个段寄存器又分为两部分,前16位和8086相同,实模式下,它们用于按传统方式寻址1MB内存,使用方法也没变化。使得8086程序可继续在32位处理器上运行。每个段寄存器还包括一个不可见部分,称为描述符高速缓存器,用来存放段的线性基地址,段界限,段属性。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)