硬件基础
1.IA-32(英特尔32位体系架构)内存模型
1.1内存平面模型
在此情况下,线性地址等同于物理地址,但不总是如此,当拥有完善的内存保护机制时,线性地址就处于过渡状态,所以也就完全不等于物理地址了。
1.2内存分段模型
在分段内存模型中,内存呈现在段的不同区域,某段中某字节由逻辑地址指定,逻辑地址由段选择器和偏移地址(有效地址)组成。段选择器指定被引用的段,偏移地址用于指定段中字节的位置。
2.操作模式:
2.1实模式
实模式为分段模型的一个实例,在实模式中,内存中一个字节的逻辑地址由一个16位的段选择器和一个16位的有效地址组成,段选择器中存储一个64KB大小的内存基地址(注意:此处为字节寻址,一个内存单元为一个字节,16位则表示2^16个字节,也就是64KB),有效地址指定被访问内存段的段内偏移,有效地址和段选择器中的内容相加构成字节的物理地址。
在8086处理器中,20位地址总线被16位值访问的方法如上,通过这个隐式0来解决。
注意:右边为物理地址空间
2.2保护模式
在IA-32体系中,保护模式也是分段内存模型的实列,区别在于物理地址的解析过程不仅由处理器独自完成,操作系统(windows,linux等)还必须通过维护大量帮助处理器完成工作的特殊表来与处理器合作,这些特殊表为内存保护,按需分页等等附属功能提供了支持。
在保护模式中,还需要大量额外的寄存器来管理执行环境,包括5个控制寄存器(CR0-CR4),全局描述符表寄存器GDTR,本地描述符表寄存器LDTR,中断描述符表寄存器IDTR。
在保护模式中,IA-32处理器使用分段和分页两种机制实现内存保护。
2.2.1保护模式分段
分页是可选的,但保护模式分段是强制的,在此情况下,段选择器的大小是16位,有效地址的值是32位。保护模式中段选择器不是用来存储物理内存中的物理地址(注意是物理地址),而是索引了一个二进制结构,该结构包含了线性地址(注意是线性地址)空间中段的细节,该结构称为描述符表,里面的项被称为段描述符。
描述符表分为两类:全局描述符表(GDT)和局部描述符表(LDT),GDT是强制存在的,每一个运行在IA-32上面的操作系统在启动时必须创建一个GDT,而且整个系统只有一个被任务共享的GDT,与之对应的LDT则是可选的,他能被单一任务使用。
图中GDTR是一个特殊寄存器,用来保存GDT基地址,GDTR寄存器大小为48位,低16位决定GDT大小,其余的32位存储GDT的起始线性地址。
段选择器(SegmentSelector)
1=指定LDT中的一个描述符,0=指定GDT中的一个描述符。
Requested Prlvilege Level(RPL):请求特权级。0代表最高,3最低。
64位段描述符(SegmentDescriptor)
Descriptor Prlvilege Level(DPL):被引用段的特权级。
Type:类型域,段类型,访问和增长方向。
S:S标志,S标志位定义了两类段描述符:代码和数据段描述符(S=1),系统段描述符(S=0)。
重要的域有三个:基地址域,DPL域,界限域(Segment Limit)。
2.2.2保护模式分页
如果操作系统不使用分页,那么被引用的线性地址空间直接和物理空间内存相对应,如果启用分页,则通过分段产生的线性地址变为第二阶段的起点。也就是说如果采用分页机制,则该线性地址通过分页机制被映射成物理地址。如果不采用分页机制,则该线性地址就是物理地址。线性地址现在就是一个被分成3个子域的换算结构。
0-11位表示物理内存的字节偏移量。
12-21位指定了页表中的一个特定项。(PTE(页表项))
22-31位指定了页目录的数组结构的项。(PDE(页目录项))存储了页表的一个字节的物理地址
CR3寄存器存放页目录的第一字节的物理地址。
首先,把线性地址的最高10位(即位22至位31)作为页目录表的索引,对应表项所包含的页码指定页表;然后,再把线性地址的中间10位(即位12至位21)作为所指定的页目录表中的页表项的索引,对应表项所包含的页码指定物理地址空间中的一页;最后,把所指定的物理页的页码作为高20位,把线性地址的低12位不加改变地作为32位物理地址的低12位。
扩展分页
和以前一样,一切开始于CR3寄存器,但是这次CR3指定PDPT的物理地址(这里CR3存放PDPT的第一字节的物理地址)然后才是PDE。
页表
U/S和W标志位对内存保护很重要,U/S标志位定义两个特权级,用户和超级用户,如果该标志位清零,则PTE指向的页被分配为超级用户,W标志位指明一个页或者一组页是只读还是可写。
控制寄存器
控制寄存器(CR0-CR4),前面已经说明了CR3的作用,除CR3之外,还有一个比较重要的是CR0,CR0第16位为写保护位WP,当WP置位时,超级用户不能写入只读用户级内存页面。CR0第31位PG,置位时启用分页,第1位PE,置位时启用保护模式。还有在CR4中,PSE标志位置位时启用更大的页面(默认4k,可以更大,如2MB,4MB)。另外,CR1保留不用,CR2用于处理页故障。
3.中断
3.1实模式中断
在实模式中,内存的第一个千字节由中断向量表(IVT)占用。IVT把中断映射到处理中断的ISR(中断服务进程)中进行处理。IVT通过顺序地存储每个ISR的逻辑地址来定位ISR。在内存的底部(地址0x00000)是第一个ISR的有效地址,有效地址的后面是ISR的段选择器。需要注意的是,对于这两个值来说,地址的低字节放在低地址中,这就是中断类型0的中断向量。内存中接下来的4字节(从0x00004到0x00007)存储着中断类型1的中断向量,以此类推。因为每个中断占用4个字节,所以IVT能够容纳256个向量(由值0~255指定)。当实模式中发生中断时,处理器使用存储在相应中断向量中的地址来定位和执行必要的过程。
3.2保护模式中断
在保护模式下,IVT被中断描述符表(Interrupt Descriptor Table,IDT)代替。IDT中存储一个64位的门描述符数组,这些门描述符可能是中断门描述符、陷阱门描述符和任务门描述符。 与IVT不同的是,IDT可能驻留在线性地址空间的任何地方。32位的IDT基地址存储在48位的IDTR寄存器中(位16~位47)。IDT字节单位的大小受存储在IDTR寄存器的低序字中(位0~位15)的限制。指令LIDT可以用来设置IDTR寄存器中的值,指令SIDT可用来读取IDTR寄存器中的值。 大小限制和你想象的并不一样。它实际上是IDT的基地址到最后一个表项的字节偏移量,这样一来,有N项的IDT的大小限制为(8(N-1))。如果引用一个超过大小限制的向量,处理器将产生通用保护(#GP)异常。 与实模式类似,保护模式也有256个可能的中断向量。在保护模式中,IA-32处理器保留0~31号向量用于处理机器特有的异常和中断,其余的向量服务于用户定义的中断。
4.内存保护
不提供任何保护的内存管理方案
4.1分段实现内存保护
IA-32平台上基于分段的内存保护主要有一下几点:
- 界限检查 就是所谓的分段描述符的20位限制域,确保程序不会访问不存在的内存。同时GDTR的大小限制以至于段选择器不会访问位于GDT之外的项。
- 段类型检查 段描述符的S标志和类型域确保程序不会以不合适的方式访问内存段。比如CS寄存器只能加载代码段的选择器,在代码段上不能写入任何指令。远调用或远转移只能访问另一个代码段或调用门的段描述符,如果程序试图为CS或SS段寄存器加载指向GDT第一项的选择器(空描述符),就会产生通用保护异常。
- 特权级检查
基于IA-32处理器认可的四个特权级。这些特权级从0级(表示最高特权级)到3级(表示最低特权级)。最内环是环0,对应特权级0级。特权级检查是为了阻止在外环运行的进程随意访问存在于内环的段。
为了实现特权级检查,需要使用3个不同的特权指示符:CPL、RPL和DPL。当前特权级(Current Privilege Level,CPL)实质上是选择器的RPL值,这些选择器当前存储于正在执行进程的CS和SS寄存器中。程序的CPL通常是当前代码段的特权级,当执行远转移或远调用时CPL会随之改变。
当与段描述符相关的段选择器被加载到处理器的一个段寄存器中时,就会进行特权级检查。当一个程序试图访问另一个代码段的数据,或是通过段间转移而转移程序控制时就会发生这种情况。如果处理器发现了特权级违例,那么就会产生通用保护异常(#GP)。
为了访问另一个数据段中的数据,数据段的选择器必须加载到栈段寄存器(Stack-Segment register,SS)或数据段寄存器(例如DS、ES、FS、或GS)中。对于要转移到另一个代码段的程序控制,目标代码段的段选择器必须加载到代码段寄存器(Code-Segment register,CS)中。CS寄存器无法显式地修改,只能通过JMP、CALL、RET、INT、IRET、SYSENTER和SYSEXIT之类的指令来隐式地修改。
当访问另一个段内的数据时,处理器检查必须确保DPL的值大于或等于RPL和CPL。如果这样的话,处理器将把数据段的段选择器加载到数据段寄存器中。切记,试图访问另一个段中数据的进程可以控制该数据段的段选择器的RPL值。
当试图向栈段寄存器中装载一个新栈段的段选择器时,栈段的DPL以及对应段选择器的RPL都必须与CPL相匹配。 非一致代码段(nonconforming code segment)指的是以较低特权级执行的程序无法访问的代码段(即有较高的CPL)。当把控制转移到非一致代码段时,调用例程的CPL必须等于目标段的DPL(即篱笆两侧的特权级必须一样)。此外,与目标代码段相对应的段选择器的RPL必须小于或等于CPL。
当把控制转移到一致代码段时,调用例程的CPL必须大约或等于目标段的DPL(即DPL定义的可以执行并仍可成功进行转移的调用例程的CPL最小值)。在这种情况下,就不再检查目标段的段选择器的RPL值。 - 受限指令检查
为了验证程序并没有试图使用受限指令,这些指令仅适用于较低CPL值的代码。下面这个示例列举了一些指令,当CPL为0(最高特权级)时,这些指令才能执行。如LGDT和LIDT,都用于建立和维持用户应用程序不能访问的系统数据结构,其他指令用于管理系统事件以及执行影响整个机器的操作。
LGDT:加载GDTR寄存器
LIDT:加载LDTR寄存器
MOV:把值装入控制寄存器
HLT:暂停处理器
WRMSR:写入模型特有的寄存器
所有的检查都在内存访问开始之前进行。
4.2门描述符
门描述符为程序提供了一种受控访问具有不同特权级代码段的方式。
门描述符的三个种类:
- 调用门描述符(call-gate descriptor)
- 中断门描述符(interrupt-gate descriptor)
- 陷阱门描述符(trap-gate descriptor)
由段描述符(Segment Descriptor)的类型域来标识:
调用门描述符存在于GDT中,和段描述符很类似,调用门描述符不是存储32位起始线性地址(如代码或数据段描述符),而是存储16位段选择器和32位偏移地址。
存储于调用门描述符中的段选择器引用了GDT中的一个代码段描述符。调用门描述符中的偏移地址与代码段描述符中的基地址相加来指定目标代码段中例程的线性地址。原始逻辑地址的有效地址未被使用。所以,本质上你所拥有的是GDT中的一个描述符,它指向GDT中的另一个描述符,所指向的描述符接着指向一个代码段。
中断门描述符和陷阱门描述符外观和功能都类似于调用门描述符,区别在于它们驻留在中断描述符表(Interrupt Descriptor Table, IDT)中。中断门和陷阱门描述符都存储段选择器和有效地址。段选择器指定了GDT中的一个代码段描述符。有效地址与存储于代码段描述符中的基地址相加,来为线性地址空间中的中断/陷阱指定处理例程。因此,尽管中断门和陷阱门描述符存在于IDT中,它们最终都使用GDT中的项来指定代码段。
中断门描述符和陷阱门描述符之间仅有的真正区别在于处理器如何操作EFLAGS寄存器里的IF位。具体而言,当使用中断门描述符访问中断处理例程时,处理器会清空IF,相反,陷阱门并不要求改变IF。
而对于中断和陷阱处理例程的特权级检查来说,调用处理例程的程序的CPL必须小于或等于中断或陷阱门的DPL,这种情况只有当处理例程由软件(例如INT指令)调用时才会发生。此外,对于调用门来说,指向处理例程代码段的段描述符的DPL必须小于或等于CPL。
注意:此处逻辑地址的有效地址未被使用。4.3分页保护
对于基于段的保护来说,页级检查发生在内存访问周期启动之前,页级检查和地址解析过程同时进行,所以不会产生性能开销。如果已经启用分页,可以简单地通过把CR0中的WP标志位清零,并将PDE和PTE中的R/W和U/S标志置位来禁用页级内存保护。这可以使得所有内存页都可写,同时为所有页分配用户特权级权限,并允许超级用户级代码向标记为只读的用户级页面写入信息。 如果分段和分页都用于实现内存保护,那么将首先执行基于段的检查,然后才是页检查。基于段的违例将会产生一个通用保护异常(#GP),基于页的违例会产生一个页故障异常(#PF)。此外,段级的保护设置不能被页级的保护设置覆盖,例如,页表中的R/W位与代码段在内存中的页相对应,仅通过设置页表的R/W位并不会使该页可写。
虽然分段是强制性的,但可以把段级保护的影响降到最低,并主要依赖与页相关的保护设施(windows的内存保护机制)。具体地说,你可以实现一个平面分段模型,其GDT由五项组成:一个空描述符和两组代码和数据描述符。一组代码和数据描述符的DPL为0,另一组相应的DPL是3。所有描述符都是从地址0x00000000开始并跨越全部线性地址空间。这样一来,每个人都共享同一个地址空间,实际上不存在分段。
5.总结