第10课 - 实模式到保护模式(上)

从计算机的历史谈起

    以前的程序开发是 直接操作物理内存
    CPU指令的操作数 直接使用实地址(实际内存地址)
    任何一个程序员都拥有全部内存操作权力

拥有全部内存操作权力带来的问题

     难以重定位
        程序必须固定在内存的某个位置,因为加载地址必须明确指定
     给多个程序设计带来问题
        只要两个程序碰到内存重叠,程序就不能运行

CPU历史的里程碑-8086

     
    地址线宽度为 20位,可访问 1M内存空间
    引入[ 段地址偏移地址]的内存访问形式
        8086的段寄存器和通用寄存器为 16位
        单个寄存器寻址最多访问 64k的内存空间
        需要 两个寄存器配合来完成所有内存空间的访问

深入解析[段地址:偏移地址]

    硬件所做的工作
        段地址左移4位,构成20位的基地址(起始地址)
        基地址+偏移地址=实地址
    对于开发者的意义
        更有效的划分内存的功能(数据段,代码段,等)
        当出现程序地址冲突时,通过修改段地址解决冲突

示例

    mov ax, [0x1234]        ; 实地址 ==> (ds << 4) + 0x1234
    mov ax, [es:0x1234]    ; 实地址 ==> (es << 4) + 0x1234

有趣的问题

    [段地址:偏移地址]能访问的最大地址为 0xFFFF:0xFFFF,
    即: 0x10FFEF超过了1MB(0xFFFFF)的空间,CPU如何处理?

8086中的高端地址区(High Memory Area)

    0xFFFF:0xFFFF
        0xFFFF0+0xFFFF
            0xFFFF0+(0xF+0xFFF0)
                (0xFFFF0+0xF)+0xFFF0
                    0xFFFFF+ 0xFFF0(HMA)

8086的处理方式

    由于8086只有20位地址线,因此 最高位被丢弃(溢出),对内存地址的访问相当于取模1MB
    0xFFFF:0xFFFF
        100001111111111101111(0x10FFEF)
             回卷0xFFEF(舍弃最前面的1)

再谈8086历史

    8086在当时是非常成功的一款产品
    因此拥有一大批的开发者和应用程序
    各种基于8086程序设计的技术得到了发展
    不幸的是,各种奇技淫巧也应运而生

8086时期应用程序中的问题

    1M内存完全不够用
    开发者在程序中大量使用内存回卷技术
    应用程序之间没有界限,相互之间随意干扰
        A程序可以随意访问B程序的数据
        C程序可以修改系统调度程序的指令

80286的登场

    
    8086已经有那么多应用程序了,所以新CPU必须向下兼容
    加大内存容量,增加地址线数量(24位,16M ==> 0xFFFFFF)
    [段地址:偏移地址]的方式可以强化一下
        为每个段提供更多属性(如:范围,特权级,等)
        为每个段的定义提供固定方式

80286的兼容模式

    默认情况下完全兼容8086的运行模式(实模式)
        默认可直接访问1MB的内存空间
        通过特殊的方式访问1MB+的内存空间

80286之后的工作模式

    

初识保护模式

    每一段内存都拥有一个属性定义( 描述符Descriptor
    所有段的属性定义构成一张表( 描述符表Descriptor Table
    段寄存器保存的是属性定义在表中的索引( 选择子Selector

描述符(Descriptor)的内存结构

    
    因为一些历史原因,段基址被分成了几段

    GDT每个位置的意义如下:(引用于:https://www.cnblogs.com/Philip-Tell-Truth/p/5211248.html

  G位(Granularity,粒度)
    这个位用于解释界限的含义,当G=0,段界限是以字节为单位的,这个时候,段的拓展范围是1B-1MB(段描述符的段界限是20位的);如果G=1,则段界限是以4KB为单位的,范围4KB-4GB。
  D/B位(Default Operation Size,默认的操作数大小)
    对于代码段,当D=0表示指令的偏移地址或者操作数是16位的;D=1表示偏移地址或者操作数是32位的.
    对于栈段,当D=0,表示使用SP寄存器,栈段上界是0xFFFF;当D=1,表示使用ESP寄存器,栈段上界是0xFFFFFFFF。
  L位(64-bit Code Segement)
    这个位用于保留给64位处理器使用,当L=0,表示是32位处理器,当L=1,表示是64位处理器。
  AVL位(Available)
    通常由操作系统用,处理器并不会使用它。
  P位(Segement Present,存在位)
    P位用于描述描述符对应的段是否存在,一般来说,描述符所指示的段都是存在于内存中的,但是,当内存空间紧张的时候,有可能只是建立了描述符,对应的内存空间并不存在,这个时候就应该把P位清零。表示段并不 存在,另外,同样是在内从空间紧张的情况下,会把很少用到的段换出到硬盘中,腾出空间给当前急需内存的程序使用(当前正在执行的),这时,同样要把段的描述符P位清零,当再次轮到它执行时,P=1.
    P位是处理器负责检查的,每当通过描述符访问内存的段中时,如果P位是0,则处理器会产生一个异常中断,通常,这个中断处理过程是操作系统提供的。该处理过程的任务是负责将该段从硬盘中换回内存,并将P=1。在多用户,多任务的系统中,这是一种常用的虚拟内存调度策略。
  DPL位(Descriptor Privilege Level,DPL特权级)
    32位处理器的DPL位有四种,分别是0,1,2,3(就是特权级0123),不同特权级的程序是相互隔离的,其访问是严格限制的,而且有些处理器指令(特权指令)只能由0特权级的程序来执行。在这里,DPL指的是访问该段所必须拥有的最低特权级,如果这里的数值是2,那么特权级0,1,2可以访问这个段,特权级3访问这个段会被处理器阻止。
  S位(Descriptor Type,描述符类型)
    当S=0,说明这是一个系统段,当S=1,说明这是一个代码段或者是数据段(栈段是特殊的数据段)。
  TYPE位(描述符类别)
        
     X表示是否可执行(eXecutable)。数据段总是不可执行的,X=0,代码段可执行,X=1。
     E是对数据段而言,指示段的扩展方向,E=0表示向上拓展,E=1表示向下拓展。
     W段指示读写属性,W=0指示不可写入,否则会引发处理器异常中断,W=1表示允许写入。
    对代码段而言, C表示是否特权级依从(Conforming),C=0表示非依从的代码段,这样的代码段可以和他特权级相同的代码段调用。或者通过们调用,C=1表示允许从低特权级的程序转移到该段执行。代码段总是可以    执行,但是总是不允许被修改(如果要修改代码段,可以指定一个可以读写的数据段指向这个代码段)。至于能不能读出,由 R位决定,R=0表示不能读出,R=1表示可以读出。(相当于一个ROM)(R位不是指示处理器能否读取指令的,而是限制程序和指令的行为,比如使用超越前缀CS:访问代码段的内容)。
     A位是已访问位(Assessed),指示这个段最近有没有被访问过,在描述符创建后,这个位置应该被置零。之后,每当这个段被访问的时候,处理器都会将这个位变成1,对这个位清零是操作系统做的,通过定期监视该段  的位置,可以统计出该段的使用频率,当内存空间紧张的时候,可以不经常用的段退到硬盘上,从而实现虚拟内存管理。

描述符表(Descriptor Table)

    

选择子(Selector)的结构

    

进入保护模式的方法

    定义描述符表
    打开A20地址线
    加载描述符表
    通知CPU进入保护模式

小结

    [段地址:偏移地址]的寻址方式解决了早期程序重定位的难题
    8086实模式下的程序 无法保证安全性
    80286提出了 保护模式的概念,加强了内存段的安全性
    处于兼容性的考虑,80286之后的处理器都有2中保护模式
    处理器需要 特定的设置步骤才能进入保护模式,默认为 实模式

猜你喜欢

转载自www.cnblogs.com/Dua677/p/9333196.html