汇编网首页登录博客注册
goodboy的学习博客
博客首页博客互动【做检测题】论坛求助

我的博客

个人首页 |  我的文章 |  我的相册 |  我的好友 |  最新访客 |  文章收藏 |  论坛提问 |  友情链接 |  给我留言  
图片载入中
学习动态
好友圈
文章收藏
友情链接

[2008-11-21 23:05] 保护模式基础,强力推荐

推荐给和我一样在学习保护模式下汇编的菜鸟。
关于GDTR LDTR IDTR等概念,纠结了半天,我看的《windows深如刨析》上面太简略了,看不大理解。在csdn在逛了半天,最后我用百度搜索出这盘文章,才恍然大悟。另外有没有朋友知道学习保护模式的QQ群,大家一起交流一下。这个是一个asm程序设计基础系列,
http://blog.programfan.com/blog.asp?blogid=970&columnid=889这里有全的
整理说明 要深入理解32位编程的奥秘,必须理解保护模式.这是李彦昌的保护模式教程,基本是清华出版社80X86汇编语言程序设计的保护模式部分的电子版,但修改了原书的一些错误,也加入了李先生自己的一些体会.很好的资料,保护模式的资料本来就少,中文的的资料更少,谨以此与Asm爱好者共享Hume[Afo:hume.longcity.net2001年10月22日
下载地址
http://download.csdn.net/source/280715





虽然80386处理器要较以前的处理器的功能大大增强,但这些功能只能在保护模式下才能全部得到发挥。在实模式下最大寻址空间只有1M,但在保护模式最大寻址空间可达4G,可以访问到所有的物理内存。同时由于引入虚拟内存的概念,在程序设计中可使用的地址空间为64TB。80386处理器采用了可扩充的分段管理和可选的分页管理机制,这两个存储管理机制由MMU(Memory Management Unit)部件来实现。因此,如果在80386下进行实模式编程,这时的80386处理器相当于一功能更强大,运行速度更快的8086处理器。80386提供对虚拟存储器的支持,虚拟存储器的理论基础就是:速度非常快的内存储器和海量的外存储器,所以它是一种软硬件结合的技术,它能够提供比物理内存大得多的存储空间。
   80386下的段具有三个属性:段基址,段界限,段属性,通常描述段的称作段描述符(Segment Descriptor),而描述符通常放在一个线性表中,这种线性表又分为:GDT(Global Descriptor Table),LDT(Local Descriptor Table),IDT(Interrupt Descriptor Table),通常用一个叫做选择子的东西去确定使用上述三个线性表中哪一个描述符。程序中使用的地址空间就是虚拟地址空间,上面已经说过80386下虚拟地址空间可达到64TB(后面将解释为什么可以达到64TB),虚拟地址空间由一个选择子和段内偏移组成,这是因为通过段的选择子我们可以得到该段的描述符,而在描述符中又说明了段的基址,段的界限及段的属性,再加上段的偏移就可以得到虚拟地址空间。不过请注意,这里并没有将段基址乘以16再加上偏移地址,这是保护模式与实式模式的区别之一。很明显,任何数据都必须装入到物理内存才能够被存储器处理,所以二维的虚拟地址空间必须转换成一维的物理地址。同时,由于每个任务都有自已的虚拟地址空间,为了防止多个并行任务将虚拟地址空间映射同一物理地址空间采用线性地址空间隔离虚拟地址和物理地址,线性地址空间由一维的线性地址构成,线性地址空间与物理地址空间对等,线性地址为32位,可寻址空间为4GB(物理地址空间最大也可以达到4GB,址址为32位,所以说线性地址空间与物理地址空间对等)。下面是80386虚拟地址空间与物理址空间的转换示意图:
   
        |----------|              |------------|       |--------|        |------------------|       |--------|
        | 虚拟地址 |------>|分段管理部件|------>|线性地址|---|--->|可选的分页管理部件|---|-->|物理地址| 
        |----|-----|       |------------|       |--------|   |    |------------------|   |   |--------|
      |------|-------|                                       |                           |
      |              |                                       |---------------------------|   
 |----------|    |---------|   
 |  选择子  |    | 段内偏移|
 |----------|    |---------|

   地址映射过程中,通过分段管理部件将虚拟地址空间转换成线性地址,这一步是必然存在的。如果在程序中启用了分页管理机制,那么线性地址还要经过分页管理部件的处理才得到最后的物理地址。如果没有采用分页管理机制,那么得到的线性地址就是物理地址。分页管理部件的主要的工作机制在于将线性地址和物理地址划分成大小相同的块,通过在建立两者之间的页表来建立对应关系。分段管理机制使用大小可变的存储块,使用分段管理机制适合处理复杂系统的逻辑分段。分页管理机制使用固定大小的块,所以它适合管理物理存储器,分页管理机制能够更有效地使用虚拟地址空间。
   80386支持多任务,因此对各个任务进行保护是非常必要的,对任务的保护可分为:同一任务内的保护,不同任务之间的保护。
   a.同一任务内的保护,在同一任务内定义有四种特权级别(Previlege Level),将这些特权级别分配给段中的代码和数据,把最高的特权级别分配给最重要的数据和最可信任的代码,将较低级别的特权分给一般的代码和不重要的数据。特权级别用0~3来表示,用数字0表示最高特权级别,用数字3表示最低特权级别,在比较特权级别时不使用大于或小于,而是使用外层或里层来比较,很明显特权级别为0表示最里层,特别级别为3表示最外层。任何一个存储段(程序直接进行访问的代码段和数据段)都有一个特权级别,在一个程序试图访问这个存储时,就会进行特权级别的比较,如果小于或等于(如果等于表明同级,小于则表明是内层)处该存储段的特权级别就可以对该存储段进行访问。任务在特定时刻下的特权级别称为CPL(Current Previlege Level),看一简单的结构示意图:
                   
                   |---------|-------|   
                   |  CodeA  | DataA | 特权级别为0                              
                   |---------|-------|
                   |---------|-------|   
                   |  CodeB  | DataB | 特权级别为1                              
                   |---------|-------|
                   |---------|-------|   
                   |  CodeC  | DataC | 特权级别为2                              
                   |---------|-------|
                   |---------|-------|   
                   |  CodeD  | DataD | 特权级别为3                              
                   |---------|-------|

     CodeA可以访问DataA,CodeB,DataB,CodeC,DataC,CodeD,DataD
     CodeB可以访问Datab,CodeC,DataC,CodeD,DataD,但不可以访问CodeA,DataA
     CodeC可以访问DataC,CodeD,DataD,但不可以访问CodeA,DataA,CodeB,DataB
     CodeD处在最外层,只能访问同级的DataD,不可以访问CodeA,DataA,CodeB,DataB,CodeC,DataC
     通常应用程序放在最外层,但由于每个应用程序的虚拟地址空间不同,因此它们被隔离保护。这种特权级别的典型用法就是:将操作系统的核心放在0层,操作系统的其余部分放在1级,2级留给中间软件使用,3级放应用程序,这样的安排的好处在于:操作系统的核心因为放在0层,因此它可以访问任务中所有的存储段,而1级的部分操作系统可以访问除0级以外的所有存储段,应用程序只能访问自身的存储段。
   b.不同任务间的保护,通过把每个任务放在不同的虚拟地址空间来实现隔离保护,虚拟地址到物理地址之间的映射由每个任务中的映射函数来决定,随着任务切换,映射函数也跟着切换,这样可以保证任务A映射到物理内存中的区域与任务B映射到内存中的区域是不同的,尽管有可能它们的虚拟地址空间相同,但它们最终在物理内存中的位置是不同的,从而起到了保护作用。
主要介绍段描述符,段选择子
   在保护模式下,段是实现虚拟地址到线性地址转换的基础。在保护方下,每个段有三个参数:段基址,段界限,段属性。段基址规定了线性地址空间中段的开始地址,段基址长度为32位,所以任何一个段都可以从32位线性地址空间中的任何一个字节开始,这一点和实式方式不同,实式方式下要求段的边界必须被16整除。段界限规定段的大小,段界限用20位表示,而且段界限可以是字节或4K为单位,这个称为段的粒度。当段界限以字节为单位时,那么段的范围是1字节至1M字节;当段界限是以4K字节为单位时,那么段的范围是4K至4G。段的界限同时也是用来校验偏移地址的合法性,比如说段A的基址为00123456H,段界限为1000H,如果段界限以字节为单位,那么段的范围是00123456H-00124456H;如果段界限以4K字节为单位,那么段的范围是00123456H-00223456H。事实上,段的界限也可以用来校验偏移地址的合法性,上面的例子中界限为1000H,那么偏移地址的范围就是0-1000H,如果偏移地址不在这个范围内那就会引起异常。需要说明的是,数据段有点特殊,因为数据段的偏移范围不仅仅是由段界限来决定,还要由段的扩展方向(Extension Direction)来决定,因为要照顾到堆栈段(堆栈段是一种特殊的数据段,它是向低端地址扩展的),如果段界限为Limit,段的扩展方向为向高端地址扩展的话,那么我们可以断定它是一普通的数据段,0-Limit是有效的偏移范围,而Limit以上属于无效的偏移范围;如果段界限为Limit,段的扩展方向为向低端地址扩展的话,那么可以断定它是一堆栈段,此时0-Limit是无效的偏移范围,Limit以上则属于有效的偏移范围,正好和向高端地址扩展的普通数据段相反。除了堆栈段以外,其它的段均是自然向高端扩展。
   段基址,段界限及段属性这三个参数在保护模式下用描述符来描述,每个描述符的长度为8个字节,每个段都有一个对应的描述符。在保护模式下有三种描述符:存储段描述符,系统段描述符,门描述符。
   A.存储段描述符:存储段是指程序直接执行的代码段和数据段,存储段描述符是用来描述存储段的,也可以说是用来描述代码和数据段的,它的长度为8个字节,该描述符结构示意图:
    
    第7字节  第6字节  第5字节     第4字节  第3字节  第2字节   第1字节  第0字节
   |--------|------------------|-----------------------------|-----------------|
   |段基址的|                  |                             |                 |
   |高8位   |Segment Attributes|       段基址的低24位        | 段界限的低16位  |
   | 24~31  |   段属性,占用两 |         0~23                |      0~15       |
   |        |   个字节         |                             |                 |
   |--------|------------------|-----------------------------|-----------------|
            |                  | 
            |                  | 
   _________|                  |_____________________________
   | 15  14 13  12 11            8 7 6     5   3           0|
   |---|---|---|---|-------------|---|--- -|---|------------|
   | G | D |0  |AVL|段界限的高4位| P | DPL |DT |    TYPE    |
   |---|---|---|---|--- ---------|---|-----|---|------------|
   
   段基址和段界限都被安排在描述符的两个域中,主要是来看段的属性:
   a.G(第15位),这是段界限粒度,即是说段界限到底是以字节为单还是以4K字节为单位。G=0表示段界限是字节,G=1表示段界限为4K字节。
   b.D(第14位),D是一个很特殊的位,在描述可执行段,向低扩展数据段或者由SS寄存器寻址的段。在描述可执行段的描述符中,D位决定了指令使用的地址及操作数据默认的大小,D=1表示默认情况下使用32位地址及32位或8位操作数,这样的代码段称为32位代码段;D=0表示默认情况下使用16位地址及16位操作数或8位操作数,这样的代码段称为16位代码段;在向低扩展的数据段中,D=1表示段的上部界限为4G,D=0表示段的上部界限为64K;在描述由SS寄存器寻址的段中,该位决定使用隐式的堆栈访问指令使用何种堆栈指针寄存器。D=1表示使用32位堆栈指针寄存器ESP,D=0表示使用16位堆栈指针寄存器SP,隐式的堆栈访问指令指的是那些指令中没有明显对SP或ESP进行操作的指令,比如说PUSH,POP,PUSHA,POPA,PUSHAD,POPAD都属于隐式的堆栈访问指令。
   c.0(第13位),这一位恒为0,为80386以后的处理器保留的。
   d.AVL(第12位),软件可利用位,主要是为了保持和以后的处理兼容。
   e.第11位到第8位是段界限的高4位。
   f.P(第7位),存在位,P=1表示描述符对转换地址有效。P=0表示描述符对转换地址无效,如果使用该描述符将会引起异常。    
   g.DPL(Descriptor Privelege Level)描述符特权级,共2位,它规定了所述段的特权级别,用于特权检查,以决定是否能对该段进行访问。
   h.DT(Descriptor Type)描述符的类型,DT=0表示存储段描述符,DT=0表示系统段描述符和门描述符。
   i.TYPE,共4位,说明存储段的具体属性:
     TYPE0:指示描述符是否被访问,用A标记,A=0表示描述符未被访问,A=1表示描述符已被访问。
     TYPE1:根据TYPE3来确定。
     TYPE2:根据TYPE3来确定。
     TYPE3:指示描述符所描述的段是数据段还是代码段,用E标记。E=0表示是不可执行段,是数据段,对应的描述符也就是数据段描述符。E=1表示是可执行段,也就是代码段,对就的描述符也就是代码段描述符。 
     如果TYPE3=0,也就是说描述符是数据段描述符,那么TYPE1指示该数据段是否可写,用W标记。W=0表示对应的数据段不可写,只读。W=1表示对应的数据段可写。TYPE2则指示数据段的扩展方向,用ED标记。ED=0表示向高端扩展,ED=1表示向低端扩展。
     如果TYPE3=1,也就是说描述符是代码段描述符,那么TYPE1指示该代码段是否可读,用符号R标记。R=0表示对应的代码段不可读,只能执行,R=1表示对应的代码可读可执行。TYPE2则指示所描述的代码段是否是一致代码段,用C表示。C=0表示代码段不是一致代码段,C=1表示是一致代码段。
     TYPE3-TYPE0这四位可以列成一个表:
     ___________________________________________________________________________________ 
    |0000 |只读                                                                        |
    |_____|____________________________________________________________________________| 
    |0001 |只读,已访问                                                                |
    |_____|____________________________________________________________________________|
    |0010 |可读,可写                                                                  |
    |_____|____________________________________________________________________________|
    |0011 |读写,已访问                                                                |
    |_____|____________________________________________________________________________|
    |0100 |只读,向低扩展                                                              |
    |_____|____________________________________________________________________________|
    |0101 |只读,向低扩展                                                              |
    |_____|____________________________________________________________________________|
    |0110 |读/写,向低扩展                                                             |
    |_____|____________________________________________________________________________|
    |0111 |读/写,向低扩展,已访问                                                     |   
    |_____|____________________________________________________________________________|
    |1000 |只执行                                                                      |
    |_____|____________________________________________________________________________|
    |1001 |只执行,已访问                                                              |
    |_____|____________________________________________________________________________|
    |1010 |可执行,可读                                                                |
    |_____|____________________________________________________________________________|
    |1011 |可执行,可读,已访问                                                        |
    |_____|____________________________________________________________________________|
    |1100 |只执行,一致代码段                                                          |
    |_____|____________________________________________________________________________|
    |1101 |只执行,一致代码段,已访问                                                  |
    |_____|____________________________________________________________________________|
    |1110 |可执行,可读,一致代码段                                                    |
    |_____|____________________________________________________________________________|
    |1111 |可执行,可读,一致代码段,已访问                                            |
    |_____|____________________________________________________________________________|                                         
    存储段描述符的结构可以这样定义:
    DESCRIPTOR STRUCT
    Segment_LimitL16 DW 0;段界限的低16位    
    Segment_BaseL16 DW 0;段基址的低16位
    Segment_BaseM8 DB 0;段基址的中间8位
    Segment_BaseH8 DB 0;段基址的高8位
    Segment_Attributes DW 0;段属性
    DESCRIPTOR ENDS
    一个任务有多个段,每个段都有一个描述符。因此在80386下,为了方便管理这些段描述符,将描述符组成一个线性表,称之为描述符表。在80386下有三种描述符表:GDT(Global Descriptor Table),LDT(Local Descriptor Table),IDT(Interrupt Descriptor Table)。在整个系统中全局描述符表GDT和中断描述符表只有一张,局部描述符表可以由若干张。每个描述符表都形成一个特殊的16位数据段,这样的特殊数据段最多可以有8192个描述符,具体使用哪一个段描述符,由段的选择子来确定。每个任务都有自已的局部描述符表LDT,它包含自已的代码段,数据段,堆栈段,也包含该任务使用的一些门描述符。随着任务的切换,LDT也跟着切换。GDT包含每一个任务都可能或可以访问的段的描述符,通常包含描述操作系统所用的代码段,数据段以及堆栈段的描述符,也包含描述任务LDT的描述符。在任务切换时,并不切换GDT。一个任务的整个虚拟地址空间可以分为相等的两半,一半空间的描述符在全局描述符表GDT中,一半空的描述符在局部描述符表LDT中。由于全局描述符表和局部描述符表都可以包含最多为8192个描述符,而每个描述符所描述的段的最大长度为4G,因此最大的虚拟地址空间为:8192*4G*2=64TB。
   段选择子用来确定使用描述符表中的哪一个描述符。实式模式下逻辑地址由段地址*16再加上段内偏移地址;保护模式下虚拟地址空间由段选择子和段内偏移来确定,和实式模式比较,段选择子代替了段值,实际上通过段选择子就可以确定了段基址。选择子的高13位是描述符表中的索引号,用来确定描述符,因为是13位,所以说最多可以有2的13次方8192个描述符,索引号:0-8191。标记TI指示是从全局描述符中读取描述符还是从局部描述符表中读取描述符。TI=0指示是从全局描述符表中读取描述符,TI=1指示从局部描述符表读取描述符。RPL表示请求特权级,用于特权检查。假设段选择子为88H,则表示请求的特权级别是0,从全局描述表中读取描述表,描述符的索引号为11H。有一个特殊的选择子称为空选择子,它的Index=0(即高13位为0),TI=0,RPL则可以为任意值。当用空选择子对存储器进行访问,会出现异常。空选择子对应于全局描述表中的第0个描述符,因此全局描述符表中的第0个描述符总是不会被访问。如果TI=1,那么就不是空选择子,它指定的是当前局部描述符表中的第0个描述符。为了更快地从段选择子中获得段的基本信息(段基址,段界限,段属性),从80386开始为每个段寄存器在硬件上配备了段描述符高速缓冲存储器,对我们写程序的人来讲,它是不可编程的。有了这种高速缓冲寄存器后,每当将选择子装入段寄存器后,处理器将自动装入描述符表中相应的描述符,并将描述表的信息装入到高速缓冲寄存器,这样可以加快访问速度,以下是段选择子的结构示意图:
   
   15________________________________________________________________3__2__1_____0 
  |                                                                |TI |  RPL   |
   |________________________________________________________________|___|________|
评论次数(1)  |  浏览次数(1247)  |  类型(保护模式汇编基础) |  收藏此文  | 

[  acool   发表于  2008-11-22 12:27  ]

不错,踩踩;)

 
 请输入验证码  (提示:点击验证码输入框,以获取验证码