从零开始操作系统------探析保护模式

本文基于郑纲的《操作系统还原》,仅为个人学习笔记,前期的虚拟机配置等不再详细记录,其中不理解或者出错的地方还望提出意见!

从零开始操作系统------MBR直操硬盘、内核加载器

为什么有保护模式

(1)实模式下操作系统和用户程序属于同一特权级,没有区别对待;
(2)实模式下用户所引用的地址都是真实的物理地址;
(3)实模式下用户程序可以自由修改段基址,可以访问所有内存;
(4)实模式下共 20 条地址线,最大可用内存为 1MB;

寄存器扩展

CPU 发展到 32 位后,地址总线和数据总线也发展到 32 位,其寻址空间更达到了 2 的 32 次方, 4GB。除段寄存器外,通用寄存器、指令指针寄存器、标志寄存器都由原来的 16 位扩展到了 32 位。
在这里插入图片描述
寄存器中低 16 位的部分是为了兼容实模式,可以单独使用。高 16 位没办法单独使用,只能在用 32位寄存器时才有机会用到它们。

寻址方式与实模式下一样:“段基址:段内偏移地址”。其中段基址不再只是一个地址,还增加了许多对内存段的描述信息(安全性的体现),为此专门找了个数据结构—全局描述符表。既然叫表,就说明里面有表项,表中至少有一个表项,其中每一个表项称为段描述符,其大小为 64 字节,用来描述各个内存段的起始地址、大小、权限等信息。

这样,段寄存器中保存的再也不是段基址了,里面保存的内容叫“选择子”,selector,该选择子其实就是个数,用这个数来索引全局描述符表中的段描述符,把全局描述符表当成数组,选择子就像数组下标一样。

另外由于段描述符是保存在内存中,读取效率是十分低的,因此对段寄存器率先应用了缓存技术,将段信息用一个寄存器来缓存,这就是段描述符缓冲寄存器(Descriptor Cache Registers)。

寻址扩展

保护模式下,同样是内存寻址中,基址寄存器不再只是 bx、bp,而是所有 32 位的通用寄存器,变址寄存器也是一样,不再只是 si、 di,而是除 esp 之外的所有 32 位通用寄存器,偏移量由实模式的 16 位变成了 32 位。并且,还可以对变址寄存器乘以一个比例因子。比例因子只能是 1、 2、 4、 8。
在这里插入图片描述

运行模式反转

在实模式下,指令和操作数都是 16 位的,但我们也说过啦,它可以使用 32 位的资源。同样在保护模式下,指令和操作数都是 32 位的,它也可以使用 16 位的资源。也就是说,在某个模式下,可以使用另一模式下的资源。

CPU无法知道需要生成16位还是32位机器码,需要人为告知。编译器提供了伪指令 bits,用它来向编译器传达:我下面的指令都要编译成 xx位的,因为我知道下面的代码的运行环境是 xx 模式。
bits 的指令格式是[bits 16]或[bits 32]

  1. 操作数反转前缀0x66
    模式之间可以互相使用对方环境下的资源。比如, 16 位实模式下可以用 32 位保护模式下的寄存器。但这种福利的得来却是稍费功夫的,如果要用另一模式下的操作数大小,需要在指令前添加指令前缀 0x66,将当前模式临时改变成另一模式。
    在这里插入图片描述

  2. 寻址方式反转前缀 0x67
    不同模式之间不仅可以使用对方模式下的操作数,还可以使用对方模式下的寻址方式。
    在这里插入图片描述
    bits 伪指令用于指定运行模式,操作数大小反转前缀 0x66 和寻址方式反转前缀 0x67,用于临时将当前运行模式下的操作数大小和寻址方式转变成另外一种模式下的操作数大小及寻址方式。

全局描述符表

保护模式下,内存段(如数据段、代码段等)不再是简单地用段寄存器加载一下段基址就能用啦,段的信息增加了很多,需要提前把段定义好才能使用。
全局描述符表(Global Descriptor Table, GDT)是保护模式下内存段的登记表,这是不同于实模式的显著特征之一。

  1. 段描述符
    将描述内存段的属性放到了一个称为段描述符的结构中,该结构专门用来描述一个内段,存该结构是 8 字节大小(顺便说一句,在本书中提到的各种描述符大小都是 8 字节)。
    在这里插入图片描述
    在段描述符的高 32 位中,
    8~11 位是 type 字段,共 4 位,用来指定本描述符的类型,用于表示内存段或门的子类型。

    一个段描述符,在 CPU 眼里分为两大类,要么描述的是系统段,要么描述的是数据段,这是由段描述符中的 S 位决定的,用它指示是否是系统段。在 CPU 眼里,凡是硬件运行需要用到的东西都可称之为系统,凡是软件(操作系统也属于软件, CPU 眼中,它与用户程序无区别)需要的东西都称为数据,无论是代码,还是数据,甚至包括栈,它们都作为硬件的输入,都是给硬件的数据而已,所以代码段在段描述符中也属于数据段(非系统段)。 S 为 0 时表示系统段, S 为 1 时表示数据段。 type 字段是要和 S 字段配合在一起才能确定段描述符的确切类型。

    在这里插入图片描述
    表中的 A 位表示 Accessed 位,这是由 CPU 来设置的,每当该段被 CPU 访问过后, CPU 就将此位置 1。所以,创建一个新段描述符时,应该将此位置 0。

    段描述符的第 13~14 位是 DPL 字段, Descriptor Privilege Level,即描述符特权级。

    这两位能表示 4 种特权级,分别是 0、 1、 2、 3 级特权,数字越小,特权级越大。特权级是保护模式下才有的东西, CPU 由实模式进入保护模式后,特权级自动为 0。因为保护模式下的代码已经是操作系统的一部分啦,所以操作系统应该处于最高的 0 特权级。用户程序通常处于 3 特权级,权限最小。某些指令只能在 0 特权级下执行,从而保证了安全。

    段描述符的第 15 位是 P 字段, Present,即段是否存在。如果段存在于内存中, P 为 1,否则 P 为 0。P 字段是由 CPU 来检查的,如果为 0, CPU 将抛出异常,转到相应的异常处理程序,此异常处理程序是咱们来写的,在异常处理程序处理完成后要将 P 置 1。也就是说,对于 P 字段, CPU 只负责检查,咱们负责赋值。

    段描述符的第 20 位为 AVL 字段,从名字上看它是 AVaiLable,可用的。

    段描述符的第 21 位为 L 字段,用来设置是否是 64 位代码段。 L 为 1 表示 64 位代码段,否则表示 32位代码段。这目前属于保留位,在我们 32 位 CPU 下编程,将其置为 0 便可。

    段描述符的第 22 位是 D/B 字段,用来指示有效地址(段内偏移地址)及操作数的大小。

    对于代码段来说,此位是 D 位,若 D 为 0,表示指令中的有效地址和操作数是 16 位,指令有效地址用 IP 寄存器。若 D 为 1,表示指令中的有效地址及操作数是 32 位,指令有效地址用 EIP 寄存器。
    对于栈段来说,此位是 B 位,用来指定操作数大小,此操作数涉及到栈指针寄存器的选择及栈的地址上限。若 B 为 0,使用的是 sp 寄存器,也就是栈的起始地址是 16 位寄存器的最大寻址范围, 0xFFFF。若 B 为 1,使用的是 esp 寄存器,也就是栈的起始地址是 32 位寄存器的最大寻址范围, 0xFFFFFFFF。

    段描述符的第 23 位是 G 字段, Granularity,粒度,用来指定段界限的单位大小。所以此位是用来配合段界限的,它与段界限一起来决定段的大小。若 G 为 0,表示段界限的单位是 1 字节,这样段最大是 2的 20 次方1 字节,即 1MB。若 G 为 1,表示段界限的单位是 4KB,这样段最大是 2 的 20 次方4KB 字节,即 4GB。

  2. GDT
    全局描述符表 GDT 相当于是描述符的数组,数组中的每个元素都是8 字节的描述符。可以用选择子中提供的下标在 GDT 中索引描述符。

    全局描述符表位于内存中, 需要用专门的寄存器指向它后, CPU 才知道它在哪里。 这个专门的寄存器便是 GDTR,即 GDT Register,专门用来存储 GDT 的内存地址及大小。 GDTR 是个 48 位的寄存器。

    这 48 位内存数据划分为两部分,其中前 16 位是 GDT 以字节为单位的界限值,所以这 16 位相当于GDT 的字节大小减 1。后 32 位是 GDT 的起始地址。由于 GDT 的大小是 16 位二进制,其表示的范围是 2的16次方等于65536字节。每个描述符大小是8字节, 故, GDT中最多可容纳的描述符数量是65536/8=8192个,即 GDT 中可容纳 8192 个段或门。
    在这里插入图片描述

    GDTR有专门的指令lgdt进行初始化:
    lgdt 的指令格式是: lgdt48 位内存数据

    进入保护模式需要有 GDT,但进入保护模式后,还可以再重新换个 GDT 加载。在保护模式下重新换个 GDT 的原因是实模式下只能访问低端 1MB 空间,在这之前 GDT 只能位于 1MB 之内。

  3. 选择子
    段寄存器 CS、 DS、 ES、 FS、 GS、 SS,在实模式下时,段中存储的是段基地址,即内存段的起始地址。而在保护模式下时,由于段基址已经存入了段描述符中,所以段寄存器中再存放段基址是没有意义的,在段寄存器中存入的是一个叫作选择子的东西—selector。选择子“基本上”是个索引值。

    由于段寄存器是 16 位,所以选择子也是 16 位,在其低 2 位即第 0~1 位,用来存储 RPL,即请求特权级,可以表示 0、 1、 2、 3 四种特权级。
    选择子的第 2 位是 TI 位,即 Table Indicator,用来指示选择子是在 GDT 中,还是 LDT 中索引描述符。 TI为 0 表示在 GDT 中索引描述符, TI 为 1 表示在 LDT 中索引描述符。
    选择子的高 13 位,即第 3~15 位是描述符的索引值,用此值在 GDT 中索引描述符。选择子的索引值部分是 13 位,即 2 的 13 次方是 8192,故最多可以索引 8192 个段,这和 GDT中最多定义 8192 个描述符是吻合的。
    在这里插入图片描述
    保护模式下的段寄存器中已经是选择子,不再是直接的段基址。段基址在段描述符中,用给出的选择子索引到描述符后, CPU 自动从段描述符中取出段基址,这样再加上段内偏移地址,便凑成了“段基址:段内偏移地址”的形式。

    1、保护模式下后,由于已经是 32 位地址线和 32 位寄存器啦,任意一寄存器都能够提供 32 位地址,故不需要再将段基址乘以 16 后再与段内偏移地址相加,直接用选择子对应的“段描述符中的段基址”加上“段内偏移地址”就是要访问的内存地址;
    2、 GDT 中的第 0 个段描述符是不可用的, 原因是定义在 GDT 中的段描述符是要用选择子来访问的,如果使用的选择子忘记初始化,选择子的值便会是 0,这便会访问到第 0 个段描述符。为了避免出现这种因忘记初始化选择子而选择到第 0 个段描述符的情况, GDT 中的第 0 个段描述符不可用。也就是说,若选择到了 GDT 中的第 0 个描述符,处理器将发出异常;

进入保护模式

进入保护模式需要三个步骤。
(1)打开 A20。
(2)加载 gdt。
(3)将 cr0 的 pe 位置 1。

  1. 打开A20
    打开 A20Gate 的方式是极其简单的,将端口 0x92 的第 1 位置 1 就可以了,以下三个步骤就可以实现啦。

    in al, 0x92
    or al, 0000_0010B
    out 0x92, al
    
  2. 将 cr0 的 pe 位置 1
    控制寄存器系列 CRx。控制寄存器是 CPU 的窗口,既可以用来展示 CPU的内部状态,也可用于控制 CPU 的运行机制。这次我们要用到的是 CR0 寄存器。更准确地说,我们要用到 CR0寄存器的第 0 位,即 PE 位, Protection Enable,此位用于启用保护模式,是打开此位后, CPU 才真正进入保护模式。
    在这里插入图片描述
    PE 为 0 表示在实模式下运行, PE 为 1 表示在保护模式下运行。所以,我们的任务是将此位置 1。示例代码如下。

    mov eax, cr0
    or eax, 0x00000001
    mov cr0, eax
    

代码

  1. MBR更新
    由于 loader.bin 超过了 512 字节,所以我们要把 mbr.S 中加载 loader.bin 的读入扇区数增大,目前它是 1 扇区,为了避免将来再次修改,直接改成读入 4 扇区。
    在这里插入图片描述

  2. boot.inc更新

    ;----------------------- loader and kernel -----------------------
    
    LOADER_BASE_ADDR equ 0x900
    LOADER_START_SECTOR equ 0x2  
    ;--------------------------- gdt描述符属性 ------------------------
    DESC_G_4K equ  1_00000000000000000000000b     ; 23
    DESC_D_32 equ   1_0000000000000000000000b      ; 22
    DESC_L equ       0_000000000000000000000b       ; 21
    ; 64位代码标记,此处标记为0便可
    DESC_AVL equ      0_00000000000000000000b   ; cpu不用此位,暂置为0   ; 20
    DESC_LIMIT_CODE2 equ  1111_0000000000000000b                   ; 16-19
    DESC_LIMIT_DATA2 equ  DESC_LIMIT_CODE2
    DESC_LIMIT_VIDEO2 equ 0000_0000000000000000b                    ; 16-19
    DESC_P equ     1_000000000000000b                ; 15
    DESC_DPL_0 equ  00_0000000000000b             ; 13-14
    DESC_DPL_1 equ  01_0000000000000b 
    DESC_DPL_2 equ  10_0000000000000b 
    DESC_DPL_3 equ  11_0000000000000b 
    DESC_S_CODE equ   1_000000000000b            ; 12
    DESC_S_DATA equ  DESC_S_CODE
    DESC_S_sys equ    0_000000000000b               ; 12 
    DESC_TYPE_CODE equ 1000_00000000b            ; 8-11
    ; x=1,r=0,c=0,a=0 代码段是可执行的,非一致性,不可读,已访问位a清0
    
    DESC_TYPE_DATA equ 0010_00000000b            ; 8-11
    ; x=0,w=1,e=0,a=0 代码断不可执行,向上扩展的,可写,已访问为a清0
    
    DESC_CODE_HIGH4 equ  (0x00 << 24) + DESC_G_4K + DESC_D_32 + DESC_L + DESC_AVL + DESC_LIMIT_CODE2 + DESC_P + DESC_DPL_0 + DESC_S_CODE + DESC_TYPE_CODE + 0x00
    
    DESC_DATA_HIGH4 equ  (0x00 << 24) + DESC_G_4K + DESC_D_32 + DESC_L + DESC_AVL + DESC_LIMIT_DATA2 + DESC_P + DESC_DPL_0 + DESC_S_DATA + DESC_TYPE_DATA + 0x00
    
    DESC_VIDEO_HIGH4 equ (0x00 << 24) + DESC_G_4K + DESC_D_32 + DESC_L + DESC_AVL + DESC_LIMIT_VIDEO2 + DESC_P + DESC_DPL_0 + DESC_S_DATA + DESC_TYPE_DATA + 0x0B
    
  3. loader.s

    %include "boot.inc"
    section loader vstart=LOADER_BASE_ADDR
    LOADER_STACK_TOP equ LOADER_BASE_ADDR
    jmp loader_start 
    
    ; 构建gdt及其内部描述符
    GDT_BASE: dd 0x00000000
              dd 0x00000000
    
    CODE_DESC: dd 0x0000FFFF
               dd DESC_CODE_HIGH4
    
    DATA_STACK_DESC: dd 0x0000FFFF
                     dd DESC_DATA_HIGH4
    
    VIDEO_DESC: dd 0x80000007     ; limit=(0xbffff-0xb8000)/4k =0x7
                dd DESC_VIDEO_HIGH4 ; 此时dpl为0
    
    GDT_SIZE equ $ - GDT_BASE
    GDT_LIMIT equ GDT_SIZE - 1
    times 60 dq 0          ; 此处预留60个描述符的空位
    SELECTOR_CODE equ (0x0001 << 3) + TI_GDT + RPL0   ; 相当于(CODE_DESC - GDT_BASE)/8 + TI_GDT + RPL0
    SELECTOR_DATA equ (0x0002 << 3) + TI_GDT + RPL0
    SELECTOR_VIDEO equ (0x0003 << 3) + TI_GDT + RPL0
    
    ; 以下所gdt 的指针,前2个字节所gdt界限,后4字节的gdt起始地址
    
    gdt_ptr dw GDT_LIMIT
            dd GDT_BASE
    loadermsg db '2 loader in real.'
    
    loader_start:
    
    ;--------------------------------------------------------------------------------------------------
    ; INT 0x10   功能号:0x13   功能描述符:打印字母
    ;--------------------------------------------------------------------------------------------------
    ; 输入:
    ; AH 子功能号=13H
    ; BH = 页码
    ; BL = 属性(若 AL = 00H 或 01H)
    ; CX = 字符串长度
    ; (DH、DL) = 坐标(行、列)
    ; ES:BP = 字符串地址
    ; AL = 显示输出方式
    ;    0————字符串中只含显示字符,其显示属性在BL中
    ;         显示后,光标位置不变
    ;    1————字符串中
    ;
    ;    2————
    ;    3————
    ; 无返回值
    mov  sp, LOADER_BASE_ADDR
    mov  bp, loadermsg                           ; ES:BP = 字符串地址
    mov  cx, 17                                  ; CX = 字符串长度
    mov  ax, 0x1301                          ; AH=13, AL=01
    mov  bx, 0x001f     ; 页号为0 蓝底粉红字
    mov  dx, 0x1800
    int  0x10           ; 10h 号中断
    
    ;----------------------------------------准备进入保护模式-----------------------------------------------
    ; 1 打开A20
    ; 2 加载gdt
    ; 3 将cr0的pe位置1
    
    
    ;-----------------------打开A20---------------------------
    in al,0x92
    or al,0000_0010b
    out 0x92,al
    
    ;------------------------加载GDT--------------------------
    lgdt [gdt_ptr]
    
    
    ;----------------------将cr0第0位置1-----------------------
    mov eax, cr0
    or eax, 0x00000001
    mov cr0, eax
    
    ;mov byte [gs:160], 'P'
    
    jmp dword SELECTOR_CODE:p_mode_start      ; 刷新流水线
    
    
    [bits 32]
    p_mode_start:
        mov ax, SELECTOR_DATA
        mov ds, ax
        mov es, ax
        mov ss, ax
        mov esp, LOADER_STACK_TOP
        mov ax, SELECTOR_VIDEO
        mov gs, ax
    
        mov byte [gs:160], 'P'
    
        jmp $
    
    
    
  4. 结果
    在这里插入图片描述

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值