虚拟地址保护模式.docx
- 文档编号:10472027
- 上传时间:2023-02-13
- 格式:DOCX
- 页数:21
- 大小:27.42KB
虚拟地址保护模式.docx
《虚拟地址保护模式.docx》由会员分享,可在线阅读,更多相关《虚拟地址保护模式.docx(21页珍藏版)》请在冰豆网上搜索。
虚拟地址保护模式
保护模式
(ProtectedMode,或有时简写为pmode)是一种80286系列和之后的x86兼容CPU操作模式。
保护模式有一些新的特色,设计用来增强多工和系统稳定度,像是内存保护,分页系统,以及
硬件支援的虚拟内存。
大部分的现今x86操作系统都在保护模式下运行,包含Linux、FreeBSD
、以及微软Windows2.0和之后版本。
另外一种286和其之后CPU的操作模式是真实模式,一种向前兼容且关闭这些特色的模式。
设
计用来让新的芯片可以执行旧的软件。
依照设计的规格,所有的x86CPU都是在真实模式下开机
来确保传统操作系统的向前兼容性。
在任何保护模式的特色可用前,他们必须要由某些程序手动地
切换到保护模式。
在现今的电脑,这种切换通常是由操作系统在开机时候必须完成的第一件工作
的一个。
它也可能当CPU在保护模式下运行时,使用虚拟86模式来执行设计给真实模式的程序
码。
尽管用软件的方式也有某些可能在真实模式的系统下使用多工,但保护模式下内存保护的特色,可
以避免有问题的程序破坏其他工作或是操作系统核心所拥有的内存。
保护模式也有中断正在执行
程序的硬件支援,可以把executioncontent交给其他工作,得以实现先占式多工。
大部分可以使用保护模式的CPU也拥有32位元暂存器的特色(例如80386系列和其后任何的
芯片),导入了融合保护模式而成为32位元处理的概念。
80286芯片虽有支援保护模式,但是仍
然只有16位元暂存器。
Windows2.0和之后版本中的保护模式增强称为"386增强模式",是因
为他们除了保护模式外,还需要32位元的暂存器,并且无法在286上面执行(即使286支援保
护模式)。
即使在32位元芯片上已经打开了保护模式,但是1MB以上的内存并无法存取,是由于一种仿照
IBMXT系统设计特性的memorywrap-around(内存连续)的因素。
这种限制可以由打开A20line
来回避。
在保护模式下,前面32个中断都是保留给CPU例外处理用。
举个例子,中断0D(十进制13)
是一般保护模式错物和中断00是除以零。
在8086/8088时代,处理器只存在一种操作模式(OperationMode),当时由于不存在其它操作模
式,因此这种模式也没有被命名。
自从80286到80386开始,处理器增加了另外两种操作模式——保
护模式PM(ProtectedMode)和系统管理模式SMM(SystemManagementMode),因此,
8086/8088的模式被命名为实地址模式RM(Real-addressMode)。
PM是处理器的native模式,在这种模式下,处理器支持所有的指令和所有的体系结构特性,提供最
高的性能和兼容性。
对于所有的新型应用程序和操作系统来说,建议都使用这种模式。
为了保证PM
的兼容性,处理器允许在受保护的,多任务的环境下执行RM程序。
这个特性被称做虚拟8086模式(
Virtual-8086Mode),尽管它并不是一个真正的处理器模式。
Virtual-8086模式实际上是一个PM
的属性,任何任务都可以使用它。
RM提供了Intel8086处理器的编程环境,另外有一些扩展(比如切换到PM或SMM的能力)。
当主机
被Power-up或Reset后,处理器处于RM下。
SMM是一个对所有Intel处理器都统一的标准体系结构特性。
出现于Intel386SL芯片。
这个模式为
OS实现平台指定的功能(比如电源管理或系统安全)提供了一种透明的机制。
当外部的SMM
interruptpin(SMI#)被激活或者从APIC(AdvancedProgrammingInterruptController)收到
一个SMI,处理器将进入SMM。
在SMM下,当保存当前正在运行程序的整个上下文(Context)时,处
理器切换到一个分离的地址空间。
然后SMM指定的代码或许被透明的执行。
当从SMM返回时,处理器
将回到被系统管理中断之前的状态。
由于机器在Power-up或Reset之后,处理器处于RM状态,而对于Intel80386以及其后的芯片,只有
使用PM才能发挥出最大的作用。
所以我们就面临着一个从RM切换到PM的问题。
本文不讨论SMM,本节的重点集中于在Booting阶段如何从RM切换到PM,这里不会过多的讨论PM的细
节,因为《IntelArchitectureSoftwareDeveloper’sManualVolume3:
SystemProgramming
》中有非常详尽和准确的介绍。
1.WhatisGDT
在ProtectedMode下,一个重要的必不可少的数据结构就是GDT(GlobalDescriptorTable)。
为什么要有GDT?
我们首先考虑一下在RealMode下的编程模型:
在RealMode下,我们对一个内存地址的访问是通过Segment:
Offset的方式来进行的,其中Segment
是一个段的BaseAddress,一个Segment的最大长度是64KB,这是16-bit系统所能表示的最大长度
。
而Offset则是相对于此SegmentBaseAddress的偏移量。
BaseAddress+Offset就是一个内存绝
对地址。
由此,我们可以看出,一个段具备两个因素:
BaseAddress和Limit(段的最大长度),
而对一个内存地址的访问,则是需要指出:
使用哪个段?
以及相对于这个段BaseAddress的Offset
,这个Offset应该小于此段的Limit。
当然对于16-bit系统,Limit不要指定,默认为最大长度64KB
,而16-bit的Offset也永远不可能大于此Limit。
我们在实际编程的时候,使用16-bit段寄存器CS
(CodeSegment),DS(DataSegment),SS(StackSegment)来指定Segment,CPU将段积存器
中的数值向左偏移4-bit,放到20-bit的地址线上就成为20-bit的BaseAddress。
到了ProtectedMode,内存的管理模式分为两种,段模式和页模式,其中页模式也是基于段模式的
。
也就是说,ProtectedMode的内存管理模式事实上是:
纯段模式和段页式。
进一步说,段模式是
必不可少的,而页模式则是可选的——如果使用页模式,则是段页式;否则这是纯段模式。
既然是这样,我们就先不去考虑页模式。
对于段模式来讲,访问一个内存地址仍然使用
Segment:
Offset的方式,这是很自然的。
由于ProtectedMode运行在32-bit系统上,那么Segment
的两个因素:
BaseAddress和Limit也都是32位的。
IA-32允许将一个段的BaseAddress设为32-bit
所能表示的任何值(Limit则可以被设为32-bit所能表示的,以2^12为倍数的任何指),而不象
RealMode下,一个段的BaseAddress只能是16的倍数(因为其低4-bit是通过左移运算得来的,只
能为0,从而达到使用16-bit段寄存器表示20-bitBaseAddress的目的),而一个段的Limit只能
为固定值64KB。
另外,ProtectedMode,顾名思义,又为段模式提供了保护机制,也就说一个段
的描述符需要规定对自身的访问权限(Access)。
所以,在ProtectedMode下,对一个段的描述则
包括3方面因素:
[BaseAddress,Limit,Access],它们加在一起被放在一个64-bit长的数据结构
中,被称为段描述符。
这种情况下,如果我们直接通过一个64-bit段描述符来引用一个段的时候,
就必须使用一个64-bit长的段积存器装入这个段描述符。
但Intel为了保持向后兼容,将段积存器
仍然规定为16-bit(尽管每个段积存器事实上有一个64-bit长的不可见部分,但对于程序员来说,
段积存器就是16-bit的),那么很明显,我们无法通过16-bit长度的段积存器来直接引用64-bit的
段描述符。
怎么办?
解决的方法就是把这些长度为64-bit的段描述符放入一个数组中,而将段寄存器中的值作
为下标索引来间接引用(事实上,是将段寄存器中的高13-bit的内容作为索引)。
这个全局的数
组就是GDT。
事实上,在GDT中存放的不仅仅是段描述符,还有其它描述符,它们都是64-bit长,我
们随后再讨论。
GDT可以被放在内存的任何位置,那么当程序员通过段寄存器来引用一个段描述符时,CPU必须知道
GDT的入口,也就是基地址放在哪里,所以Intel的设计者门提供了一个寄存器GDTR用来存放GDT的
入口地址,程序员将GDT设定在内存中某个位置之后,可以通过LGDT指令将GDT的入口地址装入此积
存器,从此以后,CPU就根据此积存器中的内容作为GDT的入口来访问GDT了。
GDT是ProtectedMode所必须的数据结构,也是唯一的——不应该,也不可能有多个。
另外,正象
它的名字(GlobalDescriptorTable)所揭示的,它是全局可见的,对任何一个任务而言都是这
样。
除了GDT之外,IA-32还允许程序员构建与GDT类似的数据结构,它们被称作LDT(LocalDescriptor
Table),但与GDT不同的是,LDT在系统中可以存在多个,并且从LDT的名字可以得知,LDT不是全
局可见的,它们只对引用它们的任务可见,每个任务最多可以拥有一个LDT。
另外,每一个LDT自身
作为一个段存在,它们的段描述符被放在GDT中。
IA-32为LDT的入口地址也提供了一个寄存器LDTR,因为在任何时刻只能有一个任务在运行,所以
LDT寄存器全局也只需要有一个。
如果一个任务拥有自身的LDT,那么当它需要引用自身的LDT时,
它需要通过LLDT将其LDT的段描述符装入此寄存器。
LLDT指令与LGDT指令不同的是,LGDT指令的操
作数是一个32-bit的内存地址,这个内存地址处存放的是一个32-bitGDT的入口地址,以及16-bit
的GDTLimit。
而LLDT指令的操作数是一个16-bit的选择子,这个选择子主要内容是:
被装入的LDT
的段描述符在GDT中的索引值——这一点和刚才所讨论的通过段积存器引用段的模式是一样的。
LDT只是一个可选的数据结构,你完全可以不用它。
使用它或许可以带来一些方便性,但同时也带
来复杂性,如果你想让你的OS内核保持简洁性,以及可移植性,则最好不要使用它。
引用GDT和LDT中的段描述符所描述的段,是通过一个16-bit的数据结构来实现的,这个数据结构
叫做SegmentSelector——段选择子。
它的高13位作为被引用的段描述符在GDT/LDT中的下标索引
,bit2用来指定被引用段描述符被放在GDT中还是到LDT中,bit0和bit1是RPL——请求特权等级
,被用来做保护目的,我们这里不详细讨论它。
前面所讨论的装入段寄存器中作为GDT/LDT索引的就是SegmentSelector,当需要引用一个内存地
址时,使用的仍然是Segment:
Offset模式,具体操作是:
在相应的段寄存器装入SegmentSelector
,按照这个SegmentSelector可以到GDT或LDT中找到相应的SegmentDescriptor,这个Segment
Descriptor中记录了此段的BaseAddress,然后加上Offset,就得到了最后的内存地址。
如下图所
示:
2.SetupGDT
由上一节的讨论得知,GDT是ProtectedMode所必须的数据结构,那么我们在进入ProtectedMode
之前,必须设定好GDT,并通过LGDT将其装入相应的寄存器。
尽管GDT允许被放在内存的任何位置,但由于GDT中的元素——描述符——都是64-bit长,也就是说
都是8个字节,所以为了让CPU对GDT的访问速度达到最快,我们应该将GDT的入口地址放在以8个字
节对齐,也就是说是8的倍数的地址位置。
GDT中第一个描述符必须是一个空描述符,也就是它的内容应该全部为0。
如果引用这个描述符进
行内存访问,则是产生GeneralProtection异常。
如果一个OS不使用虚拟内存,段模式会是一个不错的选择。
但现代OS没有不使用虚拟内存的,而实
现虚拟内存的比较方便和有效的内存管理方式是页式管理。
但是在IA-32上如果我们想使用页式管
理,我们只能使用段页式——没有方法可以完全禁止段模式。
但我们可以尽力让段的效果降低的最
小。
IA-32提供了一种被称作“BasicFlatModel”的分段模式可以达到这种效果。
这种模式要求在GDT
中至少要定义两个段描述符,一个用来引用DataSegment,另一个用来引用CodeSegment。
这2个
Segment都包含整个线性空间,即SegmentLimit=4GB,即使实际的物理内存远没有那么多,但
这个空间定义是为了将来由页式管理来实现虚拟内存。
在这里,我们只是处于Booting阶段,所以我们只需要初步设置一下GDT,等真正进入Protected
Mode,启动了OSKernel之后,具体OS打算如何设置GDT,使用何种内存管理模式,由Kernel自身来
设置,Booting只需要给Kernel的数据段和代码段设置全部线性空间就可以了。
段描述符的格式如下图所示:
具体到代码段和数据段,它们的格式如下图所示:
下面就是在Booting阶段为进入ProtectedMode而设置的临时的gdt。
这里定义了3个段描述符:
第
一个是系统规定的空描述符,第2个是引用4GB线性空间的代码段,第3个是引用4GB线性空间的数
据段。
这是"BasicFlatModel"所要求的最下GDT设置,但就booting阶段,只是为了进入
ProtectedMode,并为内核提供一个连续的,最大的线性空间这个目的而言,已经足够了。
#Descriptortables
gdt:
.word0,0,0,0#dummy
.word0xFFFF#4Gb-(0x100000*0x1000=4Gb)
.word0#baseaddress=0
.word0x9A00#coderead/exec
.word0x00CF#granularity=4096,386
#(+5thnibbleoflimit)
.word0xFFFF#4Gb-(0x100000*0x1000=4Gb)
.word0#baseaddress=0
.word0x9200#dataread/write
.word0x00CF#granularity=4096,386
#(+5thnibbleoflimit)
3.LoadGDT
设置好GDT之后,我们需要通过LGDT指令将设定的gdt的入口地址和gdt表的大小装入GDTR寄存器。
GDTR寄存器包括两部分:
32-bit的线性基地址,以及16-bit的GDT大小(以字节为单位)。
需要注
意的是,对于32-bit线性基地址,必须是32-bit绝对物理地址,而不是相对于某个段的偏移量。
而
我们在Booting阶段,在进入ProtectedMode之前,我们CS和DS设置很可能不是0,所以我们必须计
算出gdt的绝对物理地址。
为了执行LGDT指令,你需要把这两部分内容放在内存的某个位置,然后将这个位置的内存地址作为
操作数传递给LGDT指令。
然后LGDT指令会自动将保存在这个位置的这两部分值装入GDTR寄存器。
#这是存放GDTR所需的两部分内容的位置
gdt_48:
.word0x8000#gdtlimit=2048,
#256GDTentries
.word0,0#gdtbase(filledinlater)
#下面这段代码用来计算GDT的32-bit线性地址,并将其装入GDTR寄存器。
xorl%eax,%eax#Computegdt_base
movw%ds,%ax#(Convert%ds:
gdttoalinearptr)
shll,%eax
addl$gdt,%eax
movl%eax,(gdt_48+2)
lgdtgdt_48#loadgdtwithwhateverisappropriate
4.OtherPreparingStuff
在进入ProtectedMode之前,除了需要设置和装入GDT之外,还需要做如下一些事情:
屏蔽所有可屏蔽中断;
装入IDTR;
所有协处理器被正确的Reset。
由于在RealMode和ProtectedMode下的中断处理机制有一些不同,所以在进入ProtectedMode之
前,务必禁止所有可屏蔽中断,这可以通过下面两种方法之一:
使用CLI指令;
对8259A可编程中断控制器编程以屏蔽所有中断。
即使当我们进入ProtectedMode之后,也不能马上将中断打开,这时因为我们必须在OSKernel中
对相关的ProtectedMode中断处理所需的数据结构正确的初始化之后,才能打开中断,否则会产生
处理器异常。
在RealMode下,中断处理使用IVT(InterruptVectorTable),在ProtectedMode下,中断处理使
用IDT(InterruptDescriptorTable),所以,我们必须在进入ProtectedMode之前设置IDTR。
IDTR的格式和GDTR相同,IDTR的装入方式和GDTR也相同。
由于IDT中相关的中断处理程序需要让OS
Kernel来设定,所以在Booting阶段,我们只需要将IDTR中IDT的基地址和Size都设为0就可以了,
随后,等进入ProtectedMode之后,由OSKernel来真正设置它。
关于中断机制和中断处理,请参考Interrupt&Exception,这里不再赘述。
#
#这是存放IDTR所需的两部分内容的位置
#
idt_48:
.word0#idtlimit=0
.word0,0#idtbase=0L
#对于IDTR的处理,只需要这一条指令即可
lidtidt_48#loadidtwith0,0
#
#通过设置8259APIC,屏蔽所有可屏蔽中断
#
movbxFF,%al#maskallinterruptsfornow
outb%al,xA1
calldelay
movbxFB,%al#maskallirq'sbutirq2which
outb%al,x21#iscascaded
#保证所有的协处理都被正确的Reset
xorw%ax,%ax
outb%al,xf0
calldelay
outb%al,xf1
calldelay
#DelayisneededafterdoingI/O
delay:
outb%al,x80
ret
5.Let'sGo
好了,一切准备就绪,Fire!
:
)
进入ProtectedMode还是进入RealMode,完全靠CR0寄存器的PE标志位来控制:
如果PE=1,则
CPU切换到PM,否则,则进入RM。
设置CR0-PE位的方法有两种:
第一种是80286所使用的LMSW指令,后来的80386及更高型号的CPU为了保持向后兼容,都保留了这
个指令。
这个指令只能影响最低的4bit,即PE,MP,EM和TS,对其它的没有影响。
#
#通过LMSW指令进入ProtectedMode
#
movw,%ax#protectedmode(PE)bit
lmsw%ax#Thisisit!
第二种是Intel所建议的在80386以后的CPU上使用的进入PM的方式,即通过MOV指令。
MOV指令可以
设置CR0寄存器的所有域的值。
#
#通过MOV指令进入ProtectedMode
#
movl%cr0,%eax
xorb,%al#setPE=1
movl%eax,%cr0#go!
!
OK,现在已经进入ProtectedMode了。
很简单,right?
ButIt'snotoveryet!
6.StartKernel
我们已经从RealMode进入ProtectedMode,现在我们马上就要启动OSKernel了。
OSKernel运行在32-bit段模式,而当前我们却仍然处于16-bit段模式。
这是怎么回事?
为了了解
这个问题,我们需要仔细探讨一下IA-32的段模式的实现方法。
IA-32共提供了6个16-bit段寄存器:
CS,DS,SS,ES,FS,GS。
但事实上,这16-bit只是对程序员
可见的部分,但每个寄存器仍然包括64-bit的不可见部分。
可见部分是为了供程序员装载段寄存器,但一旦装载完成,CPU真正使用的就只是不可见部分,可
见部分就完全没有用了。
不可见部分存放的内容是什么?
具体格式我没有看到相关资料,但可以确定的是隐藏部分的内容和
段描述符的内容是一致的(请参考段描述的格式),只不过格式可能不完全相同。
但格式对我们理
解这一点并不重要,因为程序员不可能能够直接操作它。
我们以CS寄存器为例,对于其它寄存器也是一样的:
在RealMode下,当我们执行一个装载CS寄存器的指令的时候(jmp,call,ret等),相关的值会
被装入CS寄存器的可见部分,但同时CPU也会根据可见部分的内容来设置不可见部分。
比如我们执
行"ljmpx1234,$go"之后,CS寄存器的可见部分的内容就是1234h,同时,不可见部分的32-bit
BaseAddress域被设置为00001234h,20-bit的Limit域被设置为固定值10000h,也就是64KB,
AccessInformation部分的其它值我们不去考虑,只考虑其D/B位,由于执行此指令时处于Real
Mode模式,所以D/B被设置为0,表示此段是一个16-bit段。
当对CS寄存器的可见部分和不可见部分
的内容都被设置之后,CS寄存器的装载工作完成。
随后当CPU需要通过CS的内容进行地址运算的时
候
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 虚拟 地址 保护 模式
![提示](https://static.bdocx.com/images/bang_tan.gif)