【自制操作系统04】从实模式到保护模式

通过前三章的努力,我们成功将控制权转交给了 loader.asm 这个程序。具体说就是 bios 通过加载并跳转到 0x7c00(IMB大叔们定的) 把控制权转交给了我们操作系统的第一个汇编程序 mbr.asm,然后 mbr.asm 里做的事就是通过加载 loader 程序并跳转到 0x900(这个是我们自己定的)把控制权转交给了 loader.asm 程序,目前这个程序里还只是向屏幕输出一行字符串“loader”,今天我们就将扩展它。并且今天我们要做的事,是操作系统中的第一个精彩之处,就是从实模式跨越到保护模式

一、实模式与保护模式鸟瞰

我这人喜欢直面问题,其实本章只需要搞明白三个主要问题就行了,什么是实模式和保护模式,实模式与保护模式的区别是什么,怎么进入保护模式。我先来简单阐述下这三个问题

什么是实模式和保护模式

Intel 8086 是一个由 Intel 于 1978 年所设计的 16 位微处理器芯片,是 x86 架构的鼻祖。紧接着 Intel 又推出了第一款 32 位的 cpu Intel 80286(很快被淘汰,80386更经典一些),这款 cpu 由于和之前有很多不同的“保护”特性,所以称为保护模式,也是与此同时,之前的 8086 这个 16 位 cpu 才有了实模式的叫法。

所以什么是实模式和保护模式,其实就是 Intel 给自己的处理器特性命的一个名字而已,具体有哪些特性那就是细节问题了,但最起码有一点刚刚已经有所透露,那就是保护模式至少是 32 位的,而实模式是 16 位的(即使一个 32 位的 cpu 也有实模式)

实模式与保护模式的区别是什么

  1. 实模式 16 位,保护模式 32 位
  2. 实模式下的地址是段寄存器地址偏移4位+偏移地址得到物理地址。保护模式下段寄存器存入了段选择子,在段描述符表中寻找段基址,再加上偏移地址得到物理地址(开启分页下为逻辑地址)
  3. 这个我觉得是个 1 的推论,就是实模式寻址空间是 1M,保护模式是 4G
  4. 这个我觉得是 2 的推论,就是段描述符表记录了段的权限,改变了实模式下可以随意访问所有内存的隐患(这也是保护这两个字的体现)

怎么进入保护模式

进入保护模式有三步:

  1. 打开 A20
  2. 加载 gdt
  3. 将 cr0 的 pe 位置 1

可以看出进入保护模式的操作是很简单的,但提前要做好准备工作,最重要的就是 gdt(Global Descriptor Table 全局描述表)的准备。

二、代码鸟瞰

loader.asm

section loader vstart=0x900

jmp protect_mode

gdt:
;0描述符
    dd  0x00000000
    dd  0x00000000
;1描述符(4GB代码段描述符)
    dd  0x0000ffff
    dd  0x00cf9800
;2描述符(4GB数据段描述符)
    dd  0x0000ffff
    dd  0x00cf9200
;3描述符(28Kb的视频段描述符)
    dd  0x80000007
    dd  0x00c0900b

lgdt_value:
    dw $-gdt-1  ;高16位表示表的最后一个字节的偏移(表的大小-1)
    dd gdt      ;低32位表示起始位置(GDT的物理地址)

SELECTOR_CODE   equ 0x0001<<3
SELECTOR_DATA   equ 0x0002<<3
SELECTOR_VIDEO  equ 0x0003<<3

protect_mode:
;进入32位
    lgdt [lgdt_value]
    in al,0x92
    or al,0000_0010b
    out 0x92,al
    cli
    mov eax,cr0
    or eax,1
    mov cr0,eax

    jmp dword SELECTOR_CODE:main

[bits 32]
;正式进入32位
main:
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:0xa0],'3'
mov byte [gs:0xa2],'2'
mov byte [gs:0xa4],'m'
mov byte [gs:0xa6],'o'
mov byte [gs:0xa8],'d'

jmp $

这里说说我的心得体会,现在看整段的代码虽不能说每一行让我自己写能写出来,但现在看起来极为清晰。我现在其实已经想不起来当时为什么理解了好久好久就是理解不了,调试了好半天也老是有各种问题。不过这个代码是我去掉了一些可有可无影响理解的部分,只留下了最精华的部分,我不知道如果我一开始接触的是这样的代码是否能够理解到位。

鸟瞰整段代码,大概分为三块。

  • 第一块用二进制方式网内存中写了数据(四个段描述符),并定义了三个常量
  • 第二块其实仔细观察会发现就是进入保护模式的步骤(打开A20、加载gdt、将cr0的pe位置1)
  • 第三块还是一个在屏幕上输出“32mod”字符串,与之前不同的是这是在保护模式下的输出

三、代码第一块解读:全局段描述符表(GDT)

cpu 与操作系统打配合的方式

有件事现在说可能体会不大,写到后面好多地方你会发现,像加载 gdt 这种操作模式好多地方都是通用的,咱先不用管 gdt 是什么,总之 cpu 会有很多与操作系统相互打配合的地方,这个就是其中之一。配合怎么打呢,那就是 cpu 定义好一个数据结构,再给你一个寄存器。操作系统一般负责做三件事情

  1. 负责在内存中某位置按照这个数据结构写一堆数据(如本讲的段描述符表gdt,以及之后要说的页表)
  2. 然后再把你写在内存的哪个位置这个信息(起始地址),存在 cpu 给你预留的一个寄存器里,这一般会有一条专门的指令,比如本讲的 lgdt,不会说让你用 mov 操作的
  3. 操作系统将 cpu 某寄存器中的某位置 1

然后就开启了这个功能,段描述符表如此,页表如此,TSS亦是如此,这个之后讲到会深有体会。我现在已经有所体会了,但还没整理出全部的这种打配合的地方,等我再深入些再给大家整理一份。

先说说什么是段描述符

直接上干货,还记不记得第一节课说的内容

在你开机的一瞬间,CPU 的 PC 寄存器被强制初始化为 0xFFFF0。如果再说具体些,CPU 将段基址寄存器 cs 初始化为 0xF000,将偏移地址寄存器 IP 初始化为 0xFFF0,根据实模式下的最终地址计算规则,将段基址左移 4 位,加上偏移地址,得到最终的物理地址也就是抽象出来的 PC 寄存器地址为 0xFFFF0。

这种段基址左移 4 位,加上偏移地址,得到物理地址的方式,就是实模式下的地址转换方式。

然而保护模式下不一样了

在保护模式下,段基址寄存器中存的数据,被理解为段选择子,根据这个值去我们自己在内存中写好的段描述符表中找,找到对应的段描述符,从中取出段基址。用这个段基址加上偏移地址,最终得到物理地址(逻辑地址和页表的事以后再说,不冲突)。

就这么点区别

那自然就有两个问题,一个是段描述符表长什么样子呀?决定了我们往内存中写的数据结构是什么。另一个就是去哪找段描述符表压,这个就需要告诉 cpu 为我们提前预留好的寄存器,也就是 lgdt 指令。下面我们就分别看着两个问题

段描述符表长什么样子

首先段描述符表是一张表,在内存中也就是个数组,是一个个的段描述符一个个紧挨着的结果。所以我们要了解段描述符长什么样就好了

这里我顺便把选择子GDTR 寄存器的结构也列出来了,这些就是全部的需要我们自己写数据的地方了,也是 cpu 和操作系统配合中需要约定的全部事情

;0描述符
    dd  0x00000000
    dd  0x00000000
;1描述符(4GB代码段描述符)
    dd  0x0000ffff
    dd  0x00cf9800
;2描述符(4GB数据段描述符)
    dd  0x0000ffff
    dd  0x00cf9200
;3描述符(28Kb的视频段描述符)
    dd  0x80000007
    dd  0x00c0900b

我们看看这些直接在内存中写死的常量,就是按照段描述符的数据结构写的

代码段描述符转化为二进制是 00000000_00000000_11111111_11111111_00000000_11001111_10011000_00000000
数据段描述符转为为二进制是 00000000_00000000_11111111_11111111_00000000_11001111_10010010_00000000
视频段描述符转化为二进制是 10000000_00000000_00000000_00000111_00000000_11000000_10010000_00000000

这里我们拿视频段描述符来分析,提取(拼凑)出段基址的数据,00000000_00000000_10000000_00000000,转换为十六进制是 0x80000。怎么样熟不熟悉,这恰好是显卡黑白模式在内存中的映射的起始地址。可以看下第一章的内容,不过我这里还是把图贴出来。

接下来的几个常量定义,很容易明白它们的意思

lgdt_value:
    dw $-gdt-1  ;高16位表示表的最后一个字节的偏移(表的大小-1)
    dd gdt      ;低32位表示起始位置(GDT的物理地址)

SELECTOR_CODE   equ 0x0001<<3
SELECTOR_DATA   equ 0x0002<<3
SELECTOR_VIDEO  equ 0x0003<<3

lgdt_value 就是按照 lgdt 寄存器规定的数据结构拼凑出来的,下面的三个常量其实就是对应上面定义的三个段描述符的偏移量,由于每个描述符占 64 位,也就是占 8 个地址单元,所以索引下标的计算就是第几个描述符 * 8就好了,相信这个不难理解。

四、代码第二块解读:进入保护模式三步走

代码直接对应上面的三步

加载 gdt

lgdt [lgdt_value]

打开 A20

in al,0x92
or al,0000_0010b
out 0x92,al
cli    ;禁止中断,先不用管

将 cr0 的 pe 位置 1

mov eax,cr0
or eax,1
mov cr0,eax

此时已经进入保护模式了,段基址寄存器的意义已经变了,所以跳转指令变成了

jmp dword SELECTOR_CODE:main

五、代码第三块解读:保护模式下的简单代码

前面就是将数据段寄存器赋值给一些段基址寄存器用于访问数据段,然后将栈基址赋值位本次加载到的内存位置,重点是下面几句

mov ax,SELECTOR_VIDEO
mov gs,ax
mov byte [gs:0xa0],'3'
...

这段将我们刚刚写好的常量 SELECTOR_VIDEO 写入了段基址寄存器 gs,并在其后用了这个基址寄存器去进行 mov 操作。通过这个段选择子,在段描述符表里寻找出来的段基址是我们写好的显卡的内存映射的起始地址,所以同前几章在实模式下的输出就一样了。

六、运行代码

我们并没有增加新文件,所以Makefile和上一篇一样,不用变,直接运行看效果,make brun

可以看到,我们的段基址寄存器没有直接写显卡的起始地址,而是通过段选择子索引的,但依然正常输出了 "32mod" 字符串,说明成功了

写在最后:开源项目和课程规划

如果你对自制一个操作系统感兴趣,不妨跟随这个系列课程看下去,甚至加入我们,一起来开发。

参考书籍

《操作系统真相还原》这本书真的赞!强烈推荐

项目开源

项目开源地址:https://gitee.com/sunym1993/flashos

当你看到该文章时,代码可能已经比文章中的又多写了一些部分了。你可以通过提交记录历史来查看历史的代码,我会慢慢梳理提交历史以及项目说明文档,争取给每一课都准备一个可执行的代码。当然文章中的代码也是全的,采用复制粘贴的方式也是完全可以的。

如果你有兴趣加入这个自制操作系统的大军,也可以在留言区留下您的联系方式,或者在 gitee 私信我您的联系方式。

课程规划

本课程打算出系列课程,我写到哪觉得可以写成一篇文章了就写出来分享给大家,最终会完成一个功能全面的操作系统,我觉得这是最好的学习操作系统的方式了。所以中间遇到的各种坎也会写进去,如果你能持续跟进,跟着我一块写,必然会有很好的收货。即使没有,交个朋友也是好的哈哈。

目前的系列包括

原文地址:https://www.cnblogs.com/flashsun/p/12233407.html

时间: 2024-12-15 04:34:53

【自制操作系统04】从实模式到保护模式的相关文章

【OS】实模式和保护模式区别及寻址方式

实模式和保护模式区别及寻址方式 转载请注明出处:http://blog.csdn.NET/rosetta 64KB-4GB-64TB? 我记得大学的汇编课程.组成原理课里老师讲过实模式和保护模式的区别,在很多书本上也有谈及,无奈本人理解和感悟能力实在太差,在很长一段时间里都没真正的明白它们的内含,更别说为什么实模式下最大寻址空间为1MB?段的最大长度不超过64KB?而保护模式下为啥最大寻址能力就变成了64TB?每个段最大也达4GB? 更甚者分段和分页这两个高深的概念像我这种菜鸟怎么也理解不了啊!

Linux下的实模式和保护模式

实模式:(即实地址访问模式)它是Intel公司80286及以后的x86(80386,80486和80586等)兼容处理器(CPU)的一种操作模式.实模式被特殊定义为20位地址内存可访问空间上,这就意味着它的容量是2的20次幂(1M)的可访问内存空间(物理内存和BIOS-ROM),软件可通过这些地址直接访问BIOS程序和外围硬件.实模式下处理器没有硬件级的内存保护概念和多道任务的工作模式.但是为了向下兼容,所以80286及以后的x86系列兼容处理器仍然是开机启动时工作在实模式下.80186和早期的

《80X86汇编语言程序设计教程》十 实模式与保护模式的切换实例

1.  再次声明,需要纯DOS系统才能看到满意测试效果.内容是演示实模式与保护模式切换实例,实现功能是16进制显示从110000H开始的256个字节的值 2.  源代码如下: 1 ;功能:演示实模式与保护模式的切换,16进制显示从110000H开始的256个字节的值 2 ;16位偏移的段间直接转移指令的宏定义,这是一个JMP指令到所描述的地址 3 4 JUMP macro selector,offsetv 5 db 0eah ;操作码 6 dw offsetv ;16位偏移 7 dw selec

实模式与保护模式

1. 实模式,又叫实地址模式,CPU完全按照8086的实际寻址方法访问从00000h--FFFFFh(1MB大小)的地址范围的内存,在这种模式下,CPU只能做单任务运行:寻址公式为:物理地址=左移4位的段地址+偏移地址,即:物理地址是由16位的段地址和16位的段内偏移地址组成的. 2.保护模式,又叫内存保护模式,寻址采用32位段和偏移量,最大寻址空间4GB,在这种模式下,系统运行于多任务,设计这种模式的原因和好处是:保护模式增加了寻址空间,增加了对多任务的支持,增加了段页式寻址机制的内存管理(分

《X86汇编语言 从实模式到保护模式》bochs 配置教程(详细)

本文是写给<X86汇编语言 从实模式到保护模式>读者的一份Bochs配置指南. 我们要做的有: 1.下载并安装bochs 2.配置bochs 3.通过bochs调试虚拟硬盘 bochs的官方网址:http://bochs.sourceforge.net/ bochs的下载地址:http://sourceforge.net/projects/bochs/files/bochs/ 本书附带文件下载地址:https://files-cdn.cnblogs.com/files/leec/booktoo

实模式与保护模式下的分段分页机制

1. 实模式 在实模式下,CPU不会为任务提供任务的保护机制,代码任意运行.8086处理器是学习实模式的常用例子.它内部大致有以下寄存器: 8个16位的通用寄存器: AX (可以拆分成两个AH/AL的8位寄存器) BX (BH,BL) CX (CH,CL) DX (DH,DL) SI (source index, 源索引寄存器) DI (destination index, 目的索引寄存器) BP (base pointer, 基数指针寄存器) SP (stack pointer, 堆栈指针寄存

程序的加载和执行(六)——《x86汇编语言:从实模式到保护模式》读书笔记26

程序的加载和执行(六)--<x86汇编语言:从实模式到保护模式>读书笔记26 通过本文能学到什么? NASM的条件汇编 用NASM编译的时候,通过命令行选项定义宏 Makefile的条件语句 在make命令行中覆盖Makefile中的变量值 第13章习题解答 复习如何构造栈段描述符 我们接着上篇博文说. 在我修改后的文件中,用到了条件汇编. 比如: %ifdef DEBUG put_core_salt: ;打印内核的符号 ... ... put_usr_salt: ;打印用户的符号 ... .

Linux进程5——实模式和保护模式

早期的Inter芯片只支持1MB内存,采用实模式,采用16bit地址.后来随着技术进步,出现可以访问更多内存的 保护模式芯片,采用32bit地址.为了保持对前面芯片的兼容,Inter支持这两种模式.当芯片启动时,默认处于实模式, 然后OS控制进入保护模式. 实模式和保护模式的最大区别: 实模式下,程序地址为真实的物理地址,可以访问任意地址空间,这样不同进程可能访问到其它进程程序,造成 严重错误. 保护模式下,程序地址为虚拟地址,然后由OS系统管理内存访问权限,这样每个进程只能访问分配给自己的物理

Linux-0.11源代码阅读二 实模式到保护模式

bootsect部分已经执行完成,程序也跳转到setup部分: start: ! ok, the read went well so we get current cursor position and save it for ! posterity. mov ax,#INITSEG ! this is done in bootsect already, but... mov ds,ax mov ah,#0x03 ! read cursor pos xor bh,bh int 0x10 ! sa