Linux操作系统基础(四)保护模式内存管理(2)【转】

转自:http://blog.csdn.net/rosetta/article/details/8570681

Linux操作系统基础(四)保护模式内存管理(2)

转载请注明出处:http://blog.csdn.net/rosetta

          本节主要讲:保护模式内存管理相关的物理地址空间,逻辑和线性地址空间,段选择符,段寄存器,段描述符。

物理地址空间

         保护模式下,IA-32架构提供了一个4GBytes(2^32bytes)正常大小的物理寻址空间。处理器可以使用地址总线录址这些地址空间。这些地址空间是平坦的,范围从0到FFFFFFFFH,这些物理地址空间可以映射到读写内存、只读内存和I/O内存。以下描述内存映射机制的内容可以分为分段和分页。

逻辑和线性地址空间

         在系统保护模式中,处理器需要经过两步完成到物理地址空间的转换:逻辑地址转换和线性地址空间分页。

         即使最小程度使用分段,处理器地址空间中的每一个字节都需要通过逻辑地址访问。一个逻辑地址由16位段选择符和32位偏移地址组成,如图10所示。段选择符确定该字节位于哪个段,偏移地址确定字节相对于段基地址的段内位置。

         处理器转换逻辑地址到线性地址。线性地址是处理器线性地址空间中的32位地址。和物理地址类似,线性地址空间也是平坦的(不分段),大小2^32bytes,范围从0到FFFFFFFFH。

线性地址空间包含所有的段和系统定义的系统表。

         从逻辑地址转换到线性地址,处理器需要做如下操作:

1.        使用段选择符在GDT或LDT中定位段的段描述符,然后读取到处理器中。(这步仅当一个新的段选择符被段寄存器加载时才需要)。

2.        检查段描述符的访问权限和段的范围以确保段是可以接受的,并且确保偏移在段限长内。

3.        偏移值和段描述符表中的段基地址相加最终形成线性地址。

         假如分页不启用,处理器直接把线性地址映射到物理地址(也就是说,线性地址可以直接送到处理器的总线上)。假如启用分页,第二步地址转换将使用,这一步将把线性地址转换为物理地址。

图10 逻辑地址到线性地址的转换

段选择符

         段选择符为16位用来标识一个段,如图11所示。它不直接直向段,而是通过指向段描述符,段描述符再定义段的信息。一个段选择符包含以下元素:

         索引:位3和位15-共13位可以在GDT或LDT中选择8192个段描述符表。处理器把索引值乘以8(一个段描述符的大小为8字节)再加上GDT或LDT中的基地址就可以定位一个描述符的位置,GDT和LDT分别存放在GDTR和LDTR寄存器中。

         TI(tableindicator)flag:位2-用来指定使用的是哪个表:TI=0,表示选择GDT;TI=1表示选择LDT。

         Requested Privilege Level(RPL):位0和位1-指定选择符的特权级。特权级从0到3,0的特权限最高。

         GDT表中的第一个描述符不能被处理器使用。指向GDT第一项(也就是TI=0,索引为0时)的段选择符叫做空段选择符。当一个段寄存器(如CS,SS)加载一个空选择符时处理器不会产生异常。但是当使用加载了空选择符的段寄存器访问内存时将会产生异常。空选择符可以用来初始化不使用的段寄存器。加载带有空选择符的段寄存器CS或SS将会产生一个一般保护性异常。

         段选择符作为指针变量的一部分对应用层程序员是可见的,但是选择符的值通过由链接程序指定或者修改,而不是应用程序开发者。

 

图11 段选择符

段寄存器

    为了减少转换时间和代码的复杂性,处理器提供6个可以存放段选择符的寄存器,如图12所示。每个段寄存器支持一个特定的内存引用(代码,数据和堆栈)。对于实际上任何程序执行,至少需要加载可用的段选择符到CS,DS和SS寄存器中。处理器还提供三个额外数据段寄存器:ES,FS和GS,它们可以给当前执行的任务(或程序)提供额外的可用数据段。

    对于访问一个段的程序,这个段的段选择符必须加载到一个段寄存器中。所以,虽然一个系统可以定义成千的段,但只有6个段可被立即使用。其它段可以在程序执行过程中通过加载段对应的段选择符而使其生效。

图12 段寄存器

         每个段寄存器都有可见部分和隐藏部分(隐藏部分也称“描述符缓冲”或“影子寄存器”)。当一个段选择符被加载到段寄存器的可见部分时,处理器也把由段选择符指向的段描述中的基地址,段限长和访问控制信息加载到段寄存器的隐藏部分。缓冲在段寄存器中(可见部分和隐藏部分)的信息使得处理器在进行地址转换时不需要花费额外的总线周期(周期指从段描述符中读取基地址和段限长的时间)。在一个多处理器系统中,处理器访问同一个描述符表,当描述符表被修改后,软件有职责重新加载到段寄存器。

         以下两种方法提供加载段寄存器:

1,直接使用MOV,POP,LDS,LES,LSS,LGS和LFS 等指令加载。这些指令显示的引用段寄存器。

2,隐式的加载,比如使用长指针方法的CALL,JMP和RET指令,SYSENTER和SYSEXIT指令,IRET,INT n,INTO和INT3指令。这些指令执行的时候会伴随着CS寄存器内容的改变(有时其它寄存也会改变)。

    MOV指令也可用来把段寄存器的可见部分存储到通用寄存器中。

段描述符

         段描述符是GDT或LDT表中的一个数据结构项,它提供一个段的大小、位置、控制权限和状态信息。段描述符通常由编译器、链接器、加载器或操作系统创建,而不是应用程序创建。图13表示了所有类似的段描述符的通用格式。

 

图13 段描述符

段描述符中的标志表示的含义如下:

段限长(Segment Limint)

         段限长定义了段的大小。物理器会把两个段限长位域连起来形成一个20位长的段限长值。处理器依据颗粒性标志G有不同值有两种解释段限长:

l  假如G=0,段限长范围1字节到1MByte,以字节为单位增长。

l  假如G=1,段限长范围从4KBytes到4GBytes,以4KByte为单位增加。

         处理器使用段限长有两种不同的方式,依据type域中的段扩展方向标志E来决定是上扩段还是下扩段(E=0,为上扩段;E=1为下扩段,在“代码和数据段描述类型”一节会详细描述)。

         对于上扩段,逻辑地址中的偏移值范围从0到段限长,当偏移值大于段限长时会产生通用保护异常;对于下扩段,段限长的功能刚好相反,根据下扩数据标志B(在描述D/B时会有具体解释),偏移值从段限长到FFFFFFFFH(4GB,B=1)或FFFFH(64KB, B=0),小于段限长的偏移值将会产生通用保护异常。对于下扩段,减少段限长值允许在段地址空间底部分配内存,而不是顶部。因为IA-32架构使用的栈总是向下增长的,所以这种机制方便于栈的扩展。

基地址(Base address)

         确定一个段的0字节在4GByte线性地址空间中的位置。物理器会把三个基地址位域连起来形成一个32位长的段基地址值。段基地址应该16字节边界对齐。虽然16字节对齐不是必须的,但是这种对齐允许程序通过使数据段和代码段16字节边界对齐而达到最高性能

类型(type)

         指明段或门的类型,定义段的访问类型和段的扩展方向。这个域根据描述符的类型有两种解释(数据代码段描述符和系统段描述符)。这个域的值会根据代码段、数据段还是系统段还不同。

S描述符类型(descriptortype)

         指明段的类型是系统段(S=0)或数据段、代码段(S=1)。

DPL 描述符特权级(descriptorprivilege level)

         指明段的特权级。特权限从0到3,0的特权级最高。DPL用来控制段的访问。

P 段存在标志(segment-present)

         指明段是否在内存中(P=1,在内存;P=0,不在内存)。假如P=0,当一个段描述符的段选择符加载进段寄存器时处理器产生段不存在异常。内存管理软件可以使用此标志控制在某一给定时间内把哪个段加载进物理内存中,这为管理虚拟内存除分页以外的控制。如图14显示P=0时一个段描述符的格式。当这个标志被清除,操作系统可以自由使用标为“可用”(Available)的位置来存储它自己的数据,例如不存在的段实际在什么地方的信息。

图14 当段存在位P=0时的段描述符

D/B(默认操作大小/默认栈指针大小/上界限)标示(defaultoperation size/default stack pointer size and/or upper bound)

         根据段描述符是一个可执行代码段、下扩数据段、或堆栈段完成不同的功能。(在32位代码和数据段中必须设置为1;在16位数据和代码段中必须设置为0。)

l  可执行代码段。此时这个标志叫D标志,它指明段中的指令引用有效地址和操作数的默认长度。假如D=1,则默认是32位地址和32位或8位操作数;假如D=0,则默认是16位地址和16位或8 位操作数。指令前缀66H可用来选择非默值的操作数大小,指令67H可用来选择非默认值的操作地址大小。

l  堆栈段(数据段指针由SS寄存器指向)。此时这个标志叫B标志,它指明用于隐含栈操作(如pushes,pops,calls)时栈指针的大小。假如B=1,一个32位的栈指针被使用,它存储在32位ESP寄存器中;假如B=0,一个16位的栈指针被使用,它存储在16的SP寄存器中。假如堆栈段被设置成为下扩数据段,那么B标志也用来指定栈段的上界。

l  下扩段(Expand-downdata segment)。此时标志叫作B标志,它指明段的上界。假如B=1,上界为FFFFFFFFH(4Gbytes);B=0,上界为FFFFH(64KBytes)。

G颗粒性标志(granularity)

         决定段限长域值(segment limit)的单位。当G=0时,段限长的单位为字节;当G=1时,段限长的单位为4-KByt(这个标志不会影响基地址的颗粒性,基地址的单位总是字节。)。

当G=1时使用段限长检查偏移值时,不会检查偏移值的低12位有效值。例如,当G=1时,段限长为0可以表示偏移值从0到4095。

可用和保留位(Available and reserved bits)

         段描述符的第二个双字位20是给操作系统使用的;位21是64位代码段标志,在这里不作讨论,只把它设为0就行。

数据和代码段描述符类型

         当段描述符中的S置位时,描述符用于代码或数据段描述符。类型(type)的最高有效位(第二个双字的位11)用于决定是数据段描述符(复位)还是代码段描述符(置位)。

         对于数据段描述符,类型的低三个有效位(位8,9和10)解释为已访问(accessed A)、可写(write-enable W)和扩展方向(expansion-direction E),如图15所示。根据读取标志位(位9),决定数据段是只读还是可读写。

图15 数据和代码段类型

    堆栈段必须是可读写数据段,如果SS寄存器加载一个不可写的数据段选择符将会产生通用保护异常。如果栈的大小需要动态改变,栈段段可以设置为下扩段(type域中的E标志为1)。在这里,动态改变栈段的段限长会使栈空间增加在栈的底部。如果栈段的大小需要保持静态,那么栈段可以是上扩段也可以是下扩段。

         访问位指明自从操作系统复位些位以来该段是否被访问过。当段寄存器加载段的段选择符时处理器都使此位置位(假设包含此段描述符的内存类型是可写的)。此位一直保持直到需要被明确清除。此位可用来管理虚拟内存管理和调试。

         对于代码段,类型(type)域的低三位从低到高依次解释为访问位(accessed A),可读位(read enable R),和一致性位(conforming C)。代码段依据可读位(R)的不同可分为仅执行或可读/可执行。当常量或其它静态数据以及代码指令放在ROM中时一个可读/可执行段可被使用。这里,代码段中的数据可被带CS前缀的指令或者数据段寄存器中(指DS,ES,FS和GS寄存器)的代码段的段选择符加载。在保护模式下,代码段永不能写。

    代码段可以是一致性和非一致性的。允许执行在当前特权级时向更高权级的一致性代码段进行执行转移。当向不同特权级的非一致性代码段进行执行转移时,会产生一个通用保护异常,除非使用调用门(call gate)或任务门(task gate)(相关内容可看后面文章)。不访问受保护的和处理某些异常类型(如除出错,溢出)的系统功能可以放在一致性代码段。不能被更低特权级程序访问的需要被保护的程序功能应该放在非一致性代码段。

注意

不能通过call或jump转入更低优先级的代码段执行,不管目标段是一致性或非一致代码段。如果尝试这样转变将会引起一个通过保护异常。

         所有的数据段都是非一致性的,这意味着它们不能被更低特权级的程序访问。不像代码段,数据段总是能被更高特权级的程序访问而不需要使用特别的访问门。

         如果段描述符位于ROM中的GDT或LDT中,假如软件或处理器尝试往ROM中的段描述符更新(写入)时会进入无限循环。为了防止这种情况发生,把ROM中的所有段描述符的访问位都置位。并且,从操作系统代码中移除那些试图修改ROM中段描述符的代码。

 

参考:《Intel SystemProgramming Guide》

          《Linux内核完全剖析》赵炯 编著

          《新版汇编语言程序设计》钱晓捷 主编

posted @ 2016-03-15 16:01  Sky&Zhang  阅读(568)  评论(0编辑  收藏  举报