注:本文配图来自:《64 ia 32 architectures software developer all manual》intel白皮书、《Orange‘s:一个操作系统的实现》、wiki.osdev.org

更多保护模式相关的知识可以翻阅intel白皮书 Volume3 Chapter 3 Protected-Mode Memory Management

什么是保护模式?

保护模式(Protected mode)是一种硬件资源保护方法,也包括内存保护,通过设置不同的级别来实现对应用程序操作限制,以保护计算机。同时在保护模式下,对内存的寻址能力也有了提升,从20位提升到了32位(注: 最大内存访问量受到硬件限制)。

进入保护模式

进入保护模式需要依次执行以下几个步骤:

  • 准备GDT(Global Descriptor Table 全局描述符)
  • 使用LGDT指令加载 GDT
  • 打开A20总线
    注:可以操作端口0x92h,来打开A20总线。0x92端口可能会被占用,可以通过BIOS提供的服务来开启A20总线,如果未成功则会返回错误。
    BIOS:int 15h, ax=2401h 开启A2总线,ax=2400h 关闭A20总线。
  • 设置CR0的PE位
  • 使用JMP加载CS段寄存器,计算机正式保护模式

注:推出保护模式的步骤较为繁琐,以下方法可作参考:

  • 准备16位模式的描述符(D/B=0,TYPE=2),描述符指向一段独立的16进制代码
  • 独立代码段内,将其余段寄存器设置为16位模式的描述符并且CR0的PE标志位置零
  • 跳回进入保护模式的段内,并恢复所有段寄存器在实模式中的值,然后关闭A20地址总线。

段描述符表

GDT表的格式实际上是段描述符的格式:
段描述符格式:

  • P位:存在(Present)位

    • P=1 表示段在内存中存在
    • P=0 表示段在内存中不存在
  • DPL描述符:特权级别(Descriptor Privilege Level)。特权级可以是0、1、2或者3。数字越小特权级越大。
  • S位:指明描述符类型

    • S=1 描述符是数据段/代码段描述符
    • S=0 还是系统段/门描述符
  • Type:描述符类型,根据描述符用法的不同,可以分为不同类型
    参考以下表格
    当描述符为数据段/代码段描述符时(S=1),描述符的定义如下:

    当门描述符为系统段/门描述符时(S=0),type的定义可参考后文的门描述符。
  • G位:段界限粒度(Granularity)位。

    • G=0时,段界限粒度为字节
    • G=1时,段界限粒度为4KB。
  • D/B位:这一位比较复杂,分以下三种情况

    • 在可执行代码段描述符中,这一位叫做D位。

      • D=1时,在默认情况下指令使用32位地址及32位或8位操作数
      • D=0时,在默认情况下使用16位地址及16位或8位操作数
    • 在向下扩展数据段的描述符中,这一位叫做B位。

      • B=1时,段的上部界限为4GB
      • B=0时,段的上部界限为64KB。
    • 在描述堆栈段(即描述符被SS所加载)的描述符中,这一位叫做B位。

      • B=1时,隐式的堆栈访问指令(如push、pop和call)使用32位堆栈指针寄存器esp;
      • D=0时,隐式的堆栈访问指令(如push、pop和call)使用16位堆栈指针寄存器sp。
  • AVL:保留,为操作系统使用
  • L:表示该描述符为ia-32e模式使用

GDTR

GDTR(Global Descriptor Table Registers 全局描述符寄存器),是一个专门用来控制GDT而存在的,通过lgdt指令可以加载数据。
,其内部的数据结构如下:

其中Size就是指定GDT表格的大小,而Offset则是GDT在内存中的位置,注意需要使用绝对地址(也就是段地址<<4+偏移值得出的物理地址)。

Selector

选择子,其主体结构如下:

  • TI位:表示加载的描述符类型

    • 0:GDT
    • 1:LDT
  • RPL:指定描述的权限级别,这需要与GDT描述中的DPL一判断,即那个权限小,那个生效。

CR0

Control Register0 控制寄存器0,这里不过多介绍其他位,只介绍PE位,指定CPU的运行状态,当置1时,表示CPU运行在32位保护模式下。

样例

程序执行成功后,会在屏幕的右下角显示一个红色的P字符。由于引导扇区最大不能超过512Bytes,所以以下只演示进入保护模式。
下文使用nasm程序进行编译,编译完成后可以使用qemu或bochs来运行尝试。

      org   0x7c00
      jmp   Label_start
; GDT表
GDT         dd    0,0
GDTCode32   dd    0xffff, 0x409800 ;Base=0,Limit=ffffh,G=0,D/B=1,P=1,DPL=0,S=1,type=8
GDTVideo    dd    0x80008000, 0x40920b ;Base=b8000h,Limit=8000h,G=0,D/B=1,P=1,DPL=0,S=1,type=2

GDTSize     equ   $ - GDT

GDTPtr:
      dw    GDTSize
      dd    0

; 由于nasm不支持对标签进行运算,所以这里使用0填充,上方的GDTCode32也是一样,后面使用代码来填补

SelectorCode32    equ   GDTCode32 - GDT
SelectorVideo     equ   GDTVideo - GDT

Label_start:
      mov   ax, cs
      mov   ds, ax
      mov   sp, 0x7c00
      mov   bp, sp

; 完善GDTPtr
      xor   eax, eax
      mov   ax, ds
      shl   ax, 4
      add   ax, GDT
      mov   [GDTPtr + 2], eax
; 完善GDTCode32
      xor   eax, eax
      mov   ax, cs
      shl   eax, 4
      add   eax, Label_Code32
      mov   [GDTCode32 + 2], ax
      shr   eax, 16
      mov   [GDTCode32 + 4], al
      mov   [GDTCode32 + 7], ah
; 着手进入保护模式
      cli
      in    al, 92h
      or    al, 010b
      out   92h, al
      lgdt  [GDTPtr]
      mov   eax, cr0
      or    al, 1
      mov   cr0, eax
      sti
; 加载描述符
      mov   ax, SelectorVideo
      mov   es, ax
; 由于CS段寄存器的特殊性,这里需要使用jmp命令来加载
      jmp   SelectorCode32:0

[BITS 32]
Label_Code32:
; 显示字符'P'
      mov   al, 'P'
      mov   ah, 0ch
      mov   [es:(80*24+79)*2], ax
      jmp   $

times 510 - ($ - $$) db 0
dw    0xaa55

本文经「原本」原创认证,作者乾坤盘,访问yuanben.io查询【VW0M1DC7】获取授权信息。

最后修改:2021 年 06 月 07 日 12 : 06 PM
如果觉得我的文章对你有用,请随意赞赏