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内核完全剖析》赵炯 编著

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

时间: 2024-12-21 18:21:25

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

十天学Linux内核之第三天---内存管理方式

原文:十天学Linux内核之第三天---内存管理方式 昨天分析的进程的代码让自己还在头昏目眩,脑子中这几天都是关于Linux内核的,对于自己出现的一些问题我会继续改正,希望和大家好好分享,共同进步.今天将会讲诉Linux如何追踪和管理用户空间进程的可用内存和内核的可用内存,还会讲到内核对内存分类的方式以及如何决定分配和释放内存,内存管理是应用程序通过软硬件协助来访问内存的一种方式,这里我们主要是介绍操作系统正常运行对内存的管理.插个话题,刚才和姐姐聊天,她快结婚了,说起了自己的初恋,可能是一句很

Linux操作系统基础知识之二:内存寻址

Q1.        什么是物理地址?什么是虚地址?什么是线性地址? A: 1)        将主板上的物理内存条所提供的内存空间定义为物理内存空间,其中每个内存单元的实际地址就是物理地址: 2)        将应用程序员看到的内存空间定义为虚拟地址空间(或地址空间),其中的地址就叫做虚拟地址(或虚地址),一般用"段:偏移量"的形式来描述,如A815:CF2D: 3)        线性地址空间是指一段连续的.不分段的.范围为0~4GB的地址空间,一个线性地址就是线性地址空间的一个

Linux操作系统基础知识之一:Linux操作系统概述

 Q1.        什么是GNU?Linux与GNU有什么关系? A: 1)        GNU是GNU is Not Unix的递归缩写,是自由软件基金会(Free Software Foundation,FSF)的一个项目,该项目已经开发了许多高质量的编程工具,包括emacs编辑器.著名的GNU C和C++编译器(gcc和g++): 2)        Linux的开发使用了许多GNU工具,Linux系统上用于实现POSIX.2标准的工具几乎都是由GNU项目开发的:Linux内核.GN

Linux操作系统基础知识之三:进程

Q1.        程序与进程的概念分别是什么?为什么要引入"进程"的概念? A: 1)        程序是一个普通文件,是机器代码指令和数据的集合,这些指令和数据存储在磁盘上的一个可执行映像中,可执行映像(executable image)就是一个可执行文件的内容: 2)        进程代表程序的执行过程,它是一个动态的实体,随着程序中指令的执行而不断地变化,在某个时刻进程的内容被称为进程映像(process image): 3)        程序的执行过程可以说是一个执行

Linux操作系统基础知识之八:文件系统

Q1.        Linux目录树结构是怎样的?它与Windows的目录树结构有什么区别?为什么Linux的文件系统采用固定的目录形式? A:文件是一个抽象的概念,它是存放一切数据或信息的仓库: 1)        Linux的目录树结构为:根目录(/)在上,其它的平行在下: 2)        Windows操作系统也是采用树型结构,但其树型结构的根是磁盘分区的盘符,有几个分区就有几个树型结构,它们之间的关系式并列的:而在Linux中,无论操作系统管理几个磁盘分区,这样的目录树只有一个:

Linux操作系统基础知识之九:设备驱动

Q1.        为什么把设备分为"块设备"和"字符设备"两大类? A: 1)        Linux将设备看成文件,具有三方面的含义:第一,每个设备都对应一个文件名,在内核中也就对应一个索引节点:第二,对文件操作的系统调用大都适用于设备文件:第三,从应用程序的角度看,设备文件的逻辑空间是一个线性空间:对于同一个具体的设备而言,文件操作和设备驱动是同一个事物的不同层次,概念上可以将一个系统划分为应用.文件系统和设备驱动三个层次: 2)        Linux

Linux操作系统安全必要保护措施实例

系统安全记录文件 操作系统内部的记录文件是检测是否有网络入侵的重要线索.如果你的系统是直接连到Internet,你发现有很多人对你的系统做Telnet/FTP登录尝试,可以运行"#more /var/log/secure | grep refused"来检查系统所受到的攻击,以便采取相应的对策,如使用SSH来替换Telnet/rlogin等. 启动和登录安全性 1. BIOS安全 设置BIOS密码且修改引导次序禁止从软盘启动系统. 2. 用户口令 用户口令是Linux安全的一个基本起点

Linux操作系统基础知识之七:内核中的同步

Q1.        什么是临界区?什么是竞争状态?什么是同步? A: 1)        临界区(critical regions)就是访问和操作共享数据的代码段,多个内核任务并发访问同一个资源通常是不安全的: 2)        如果两个内核任务可能处于同一个临界区,就是一种错误现象:如果确实发生了这种情况,就称它为竞争状态: 3)        避免并发和防止竞争状态称为同步(synchronization).   Q2.        简要介绍一下死锁及避免死锁的方法. 答:死锁包括自死

Linux操作系统基础知识之六:系统调用

Q1.        什么是系统调用?为什么要引入系统调用? A: 1)        操作系统为用户态的进程与硬件设备(如CPU.磁盘和打印机等)之间的交互提供了一组接口,这些接口使得程序更具有可移植性,因为不同的操作系统只要所提供的一组接口相同,那么在这些操作系统之上就可以正确地编译和执行相同的程序,这组接口就是所谓的"系统调用": 2)        引入系统调用的原因有: A.      这使得编程更加容易: B.       这极大地提高了系统的安全性: C.      最重