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

从零开始操作系统------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清0DESC_TYPE_DATA equ 0010_00000000b            ; 8-11
    ; x=0,w=1,e=0,a=0 代码断不可执行,向上扩展的,可写,已访问为a清0DESC_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 + 0x00DESC_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 + 0x00DESC_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 0x00000000dd 0x00000000CODE_DESC: dd 0x0000FFFFdd DESC_CODE_HIGH4DATA_STACK_DESC: dd 0x0000FFFFdd DESC_DATA_HIGH4VIDEO_DESC: dd 0x80000007     ; limit=(0xbffff-0xb8000)/4k =0x7dd DESC_VIDEO_HIGH4 ; 此时dpl为0GDT_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_LIMITdd 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_DATAmov ds, axmov es, axmov ss, axmov esp, LOADER_STACK_TOPmov ax, SELECTOR_VIDEOmov gs, axmov byte [gs:160], 'P'jmp $
  4. 结果

从零开始操作系统------探析保护模式相关推荐

  1. 学习操作系统:进入保护模式,理解GDT(1):TEXT

    直接上代码,以及追加了自己对GDT的理解,为了理解GDT,翻遍了各种文章,但没有代码的支撑,凭空的理解很浪费时间. 下面的代码,稍微简化了原先的代码(于老师的代码^^).把pm.asm需要的代码从pm ...

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

    本文基于郑纲的<操作系统还原>,仅为个人学习笔记,前期的虚拟机配置等不再详细记录,其中不理解或者出错的地方还望提出意见! 从零开始操作系统------MBR直操显卡 硬盘 存储逻辑 为了更 ...

  3. poi 启用保护后取消_保护模式禁用怎么解除

    展开全部 具体操作步62616964757a686964616fe59b9ee7ad9431333365653762骤如下:打开IE浏览器,点击右上角的设置,点击"Internet选项&qu ...

  4. Freedos在运行时从实模式进入保护模式

    台风夜基本是不会睡觉的,写点有意思的.此时,2019年8月10日 2:20. 酷爱历史,于是在主音吉他手的推荐下,在历史的垃圾堆里找到了Freedos.这个从1994年开始的dos兼容开源操作系统.实 ...

  5. 《操作系统真象还原》第四章 ---- 剑指Loader 刃刺GDT 开启新纪元保护模式 解放32位

    文章目录 专栏博客链接 相关查阅博客链接 本书中错误勘误 看到第四章的一些很有趣的话 想记录下来 修改MBR.S 更新配置文件boot.inc 忽生疑惑(怎么是平坦模型?) 编写Loader.S 调用 ...

  6. 操作系统学习:实模式进入保护模式

    本文参考书籍 1.操作系统真相还原 2.Linux内核完全剖析:基于0.12内核 3.x86汇编语言 从实模式到保护模式 ps:基于x86硬件的pc系统 保护模式相关介绍 从实模式进入保护模式其实经历 ...

  7. 操作系统引导--从实模式到保护模式

    从开始到保护--系统开机引导 ------没有一个文档能写的通俗易懂,我希望写出来. 开机引导和实模式: 两个星期加上假期吐血整理,所述为计算机的开机引导,其中包括一系列计算机内存设置等等,由于没有老 ...

  8. 中国太阳能热水器市场营销模式探析与品牌格局调研报告2022版

    中国太阳能热水器市场营销模式探析与品牌格局调研报告2022版 HS--HS--HS--HS--HS--HS--HS--HS--HS--HS--HS--HS-- [修订日期]:2021年11月 [搜索鸿 ...

  9. 我是如何学习写一个操作系统(三):操作系统的启动之保护模式

    前言 上一篇其实已经说完了boot的大致工作,但是Linux在最后进入操作系统之前还有一些操作,比如进入保护模式.在我自己的FragileOS里进入保护模式是在引导程序结束后完成的. 实模式到保护模式 ...

最新文章

  1. python3 转码的函数_python基础3之文件操作、字符编码解码、函数介绍
  2. 【Groovy】自定义 Xml 生成器 BuilderSupport ( 创建 XmlNode 节点 | 管理 XmlNode 节点并将根节点转为 Xml 信息 | 完整代码示例 )
  3. 5 HBase命令行接口
  4. JMS学习(4):--Spring和ActiveMQ整合的完整实例
  5. 用ABAP代码读取S/4HANA生产订单工序明细 1
  6. 第18章 检测点模型
  7. 【codevs1116】四色问题
  8. zuc算法代码详解_最短路算法-dijkstra代码与案例详解
  9. C++读取和写入文件(fstream等)
  10. 获取synchronized锁中的阻塞队列中的线程是非公平的
  11. pragma预处理指令详解
  12. python GUI打开文本文件代码
  13. instantclient_11_2远程连接Oracle安装,绝对清晰易懂
  14. FileZilla Server与FileZilla Client
  15. navicat如何连接本地数据库
  16. XtraBackUp 全量备份
  17. MPG4 MP42 MP43: Microsoft MPEG-4 versions 1, 2, and 3
  18. Qt5.12 使用FFmpeg实时解码播放H264/H265摄像头记录(直传数据法)
  19. High Performance Visual Tracking with Siamese Region Proposal Network 阅读笔记
  20. springboot-grpc

热门文章

  1. 大数据时代如何通过区块链保证数据的稀缺性?
  2. 再看《西游记》——吴承恩眼中的现实社会是如何折射到《西游记》中的
  3. SpringBoot2 核心知识点
  4. react中引入echarts中国地图
  5. 常见乱码产生原因以及锟斤拷的产生过程
  6. 杭州php程序员工资一般多少,杭州Android基础一期大黑马强哥,完美收官~~欧巴,卡几嘛...
  7. Nginx系列教程(07) - Location正则表达式
  8. VLSI Basic2——OCV
  9. Gitea配置文件说明
  10. php 自定义生成短链工具