手写操作系统--进入保护模式的开篇

之前我们讲的主引导扇区以及内核加载器等内容。都是在实模式下运行的。在实模式下寻址范围仅有1M,是远远不够我们用的。我们想要更大的内存空间,就得进入保护模式,实模式是一个历史遗留问题,本身是没有这个名字的。是因为有了保护模式才对原先的8086模式起个名字叫实模式。为何叫保护模式呢,顾名思义,是有些东西想要保护起来。我们回顾实模式,内核和用户程序都跑在同一个内存空间中。地址都是实际的物理地址。分分钟可以修改别人的程序甚至是内核程序。这样做是非常危险的。因此保护模式想在根源上杜绝这些问题因此就有了更多的保护措施。下面我们一一道来。

(1)首先,寄存器的扩展

 如上图所示,实模式下的16位寄存器都变成了32位(除了段寄存器)并且在原先的寄存器名字上多了个E,表示扩展。如果是64位的话扩展名就是R比如说RAX。我们只讨论32位的,64位只是顺带提一句。

下面我们介绍下段寄存器。在原先的实模式下,想访问超过64KB的内存就需要借助段寄存器才能把20跟地址线充分利用,也就是访问1M的内存,公式我们前面介绍过(段寄存器*16+偏移地址)

进入保护模式后,32位的CPU拥有着访问4GB内存的能力,远不是1M能比的,此时不再需要段寄存器来辅助寻址,那这么说段寄存器岂不是没用了?然而并不是,段寄存器在进入保护模式后,叫段选择子,386之后还扩展了两个段寄存器(fs,gs)段选择子如图所示:

这就是段选择子也就是我们实模式下的段寄存器。它的0-1比特位是表示特权级,就是访问权限。保护模式嘛必然加了很多安全的东西。我们知道在intel的架构提供了几种特权级别:

 这样的圈我们也称为环。比如最里面的level 0也叫0环。是权限最高的一个 给操作系统使用的。往外还有1环,2环,3环。但是在现代操作系统(windows,Linux)均没有使用1环2环权限。只使用了0环和3环也就是我们教科书上对应的内核态和用户态,这是权限级别。

讲完了特权级我们回到段选择子中,看到第三个比特位TI,这是表示访问的是GDT还是LDT,如果为1访问的是GDT,如果为0访问的是LDT。至于什么是GDT,LDT后面我们会介绍。,最后就是3-15位比特位。这是一个索引值。用于检索GDT表项。你可以把它理解为是数组的下标索引。GDT也称为全局描述符表,是一个类似于数组的这么一个表。这个表有多大呢,就是我们索引的最大值也就是13个1,也就是8192个(0也算,真实情况下第一个项是全0,能用的其实是8191个):

GDT中的每一项也叫段描述符。段选择子的最大功能就是索引到对应的段描述符。什么是段描述符呢?

 图示就是一个段描述符。这是一个64位的段描述符。里面记录了很多有用的信息。我们现在只需要关注一些重点就可以了。毕竟这么描述符挺复杂的。低32位的16-31比特位与高32位的0-7和24-31共同组成了一个32位的线性地址(这个地址不是真实的物理地址是需要转换的)。低32位的0-15位和高32位的16-19位组成一个20位的界限,记录了这个段的长度。这是最基本的信息,有了这些信息我们就能找到对应的段在什么位置以及它有多长。其次高32位的8-11这4个比特位标识一个类型。会把找到的段标识为数据段还是代码段还是系统段。先介绍这么多。以后有些位我们用到在细说。

可能会很好奇为什么这个段描述符长的这么怪,一个段地址分为三份。那是因为Intel能走到今天这么一个硬件大帝国。靠的就是它的兼容性。为了兼容之前的产品(中间出过一个80286,地址总线24根)那么它的描述符长的就很规范:

 

 这是80386的段描述符:

 段描述符就是这么进化而来的。我们写操作系统的时候用的是80386的段描述符,也就是最后一个图。

x86使用一个寄存器来指GDT表的首地址这个寄存器是GDTR:

我们能操作的指令是:

lgdt  [指针] ;加载GDT
sgdt  [指针] ;保存GDT

下面我们在讲讲A20线:

为了兼容8086,A20线默认是关闭的,想要访问超过实模式下1M的内存,我们必须把这个A20线打开,有兴趣的可以查查这A20的历史,我也不是很懂。

下面上代码:

memory_base equ 0   ;   内存基址=0
memory_limit equ ((1024*1024*1024*4)/(1024*4))-1  ;4G/4K -1
;第一步构建段描述符,相当于C语言定义一个结构体
gdt_base:              ;第1一个 也就是索引为0的是不可用的 全为0dd 0, 0
gdt_code:             ;构建代码段,数据段段描述符dw memory_limit & 0xffff ;段界限0-15位dw memory_base & 0xffff ;基地址的0-16位db (memory_base>>16)& 0xff ;基地址16-23位db 0b_1_00_1_1_0_1_0 ;存在 - dlp 0 - S _ 代码 - 非依从 - 可读 - 没有被访问过; 4k - 32 位 - 不是 64 位 - 段界限 16 ~ 19db 0b1_1_0_0_0000 | (memory_limit >> 16) & 0xf;db (memory_base >> 24) & 0xff; 基地址 24 ~ 31 位
gdt_data:dw memory_limit & 0xffff; 段界限 0 ~ 15 位dw memory_base & 0xffff; 基地址 0 ~ 15 位db (memory_base >> 16) & 0xff; 基地址 16 ~ 23 位; 存在 - dlp 0 - S _ 数据 - 向上 - 可写 - 没有被访问过db 0b_1_00_1_0_0_1_0;; 4k - 32 位 - 不是 64 位 - 段界限 16 ~ 19db 0b1_1_0_0_0000 | (memory_limit >> 16) & 0xf;db (memory_base >> 24) & 0xff; 基地址 24 ~ 31 位
gdt_end:
;第二步 定义好gdtr寄存器 参考上图gdtr寄存器的比特位gdt_ptr:dw (gdt_end - gdt_base)- 1dd gdt_base
;第三步 定义好段选择子 参考选择子的比特位code_selector equ (1 << 3) ;这里很巧妙 将变成8 也就是1000 访问索引为1 ,访问的是GDT ,访问的权限是系统级
data_selector equ (2 << 3)   ;2左移3位是10000 10表示索引2 访问GDT , 访问权限是系统级
;第四步 打开A20 启动保护模式xchg bx, bx  ; 断点cli; 关闭中断; 打开 A20 线in al,  0x92or al, 0b10out 0x92, allgdt [gdt_ptr]; 加载 gdt; 启动保护模式mov eax, cr0or eax, 1mov cr0, eax; 用跳转来刷新缓存,启用保护模式jmp dword code_selector:protect_mode
;第五步 进入保护模式 用保护模式输入超过1M的内存空间[bits 32] ;告诉编译器进入32位
protect_mode:xchg bx, bx; 断点mov ax, data_selectormov ds, axmov es, axmov fs, axmov gs, axmov ss, ax; 初始化段寄存器mov esp, 0x10000; 修改栈顶mov byte [0xb8000], "P"mov byte [0x200000], "P"

这些代码全部在内核加载器中实现,我们看看实际效果:

实模式下即将进入保护模式

打开A20 然后加载gdtr寄存器:

 已经进入保护模式:

 开始执行保护模式下的代码:

初始化段寄存器然后屏幕输出:

 原先的L已经被改为了P

接着往0x200000的地址写入一个P:

 我们的实验完成。

 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/13196.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Keil环境下CANopenNode移植到STM32问题记录(一)---printf重定向问题

文章目录 问题描述问题结决思考&#xff1a;相关文章 在直接将CANopenSTM32的示例工程直接移植到Keil环境下。 如果移植工程未实现printf函数重定向&#xff0c;则要注释掉log_printf下面的printf函数&#xff0c;使日志打印失效 /* Printf function of CanOpen app */ #define…

vue3的getCurrentInstance()方法拿到的实例对象中的proxy

getCurrentInstance方法拿到的是当前组件的实例对象 实例对象中的成员proxy是一个代理对象&#xff0c;可以通过访问代理对象来间接访问当前组件的实例对象 这样就不需要this&#xff0c;也可以操作当前组件的实例对象了 proxy对象就相当于当前组件的实例对象 proxy对象会对…

FPGA实验五:信号发生器设计

目录 一、实验目的 二、设计要求 三、实验代码 1.代码原理分析 2.代码设计思路 3.IP核的设计与配置 四、实验结果及分析 1、引脚锁定 2、仿真波形及分析 &#xff08;1&#xff09;关于波形一些指标的介绍 &#xff08;2&#xff09;对波形转换功能的验证 &#xf…

【CPU】关于x86、x86_64/x64、amd64和arm64/aarch64

为什么叫x86和x86_64和AMD64? 为什么大家叫x86为32位系统&#xff1f; 为什么软件版本会注明 for amd64版本&#xff0c;不是intel64呢&#xff1f; x86是指intel的开发的一种32位指令集&#xff0c;从386开始时代开始的&#xff0c;一直沿用至今&#xff0c;是一种cisc指令…

Haskell 入门学习(一)之安装试用 Haskell

Haskell 入门学习&#xff08;一&#xff09;之安装试用 Haskell 文章目录 Haskell 入门学习&#xff08;一&#xff09;之安装试用 Haskell前言&#xff1a;安装Windows 安装Linux、MacOs 使用 VSCode 进行代码编写创建一个简单的项目使用 Cabal 管理项目项目大致结构运行项目…

python验证公网ip与内网ip

公网IP和内网IP都是用于标识网络设备的地址&#xff0c;但它们有着不同的作用和特点。 公网IP是由互联网服务提供商&#xff08;ISP&#xff09;分配给用户设备的唯一标识符。它是全球范围内唯一的&#xff0c;并且可以被其他网络设备使用来寻找和连接特定的设备。公网IP通常用…

python_day2

猜数字-while循环 import randomnum random.randint(1, 10) while True:x int(input("输入&#xff1a;"))if x > num:print("大了")elif x < num:print("小了")else:print("猜对了")break打印九九乘法表-while循环 i 1 wh…

MySQL索引优化整合案例实现

目录 1 JOIN优化1.1 JOIN算法原理1.2 in和exists函数 2 order by优化2.1 索引排序2.2 额外排序2.3 排序优化 3 索引单表优化案例3.1. 建表3.2. 单表索引分析3.1.1 需求3.1.2 优化 4 索引多表优化案例 1 JOIN优化 1.1 JOIN算法原理 1) JOIN回顾 JOIN 是 MySQL 用来进行联表操作…

Spring Boot中的Hibernate是什么,如何使用

Spring Boot中的Hibernate是什么&#xff0c;如何使用 Hibernate是一个流行的Java ORM框架&#xff0c;它提供了一种将Java对象映射到关系数据库表的方法。Spring Boot集成了Hibernate&#xff0c;使得在开发Web应用程序时可以轻松地使用Hibernate操作数据库。本文将介绍Sprin…

插值算法

插值法在较少的数据模型的基础上模拟产生新的靠谱数值&#xff0c;可以用来预测。 利用已知的点建立合适的插值函数 f(x) ,未知点 x_i 由插值函数 f(x) 可以求出函数值 f(x_i) &#xff0c;用求得的 (x_i,f(x_i))近似代替未知点。 基本概念&#xff1a; yf(x)在[a,b]上有定义 x…

机器学习基础之《特征工程(2)—特征工程介绍、特征抽取》

一、什么是特征工程 机器学习领域的大神Andrew Ng(吴恩达)老师说“Coming up with features is difficult, time-consuming, requires expert knowledge. “Applied machine learning” is basically feature engineering. ” 注&#xff1a;业界广泛流传&#xff1a;数据和特…

vue创建项目报错npm install --loglevel error --legacy-peer-deps

vue创建项目报错npm install --loglevel error --legacy-peer-deps 如图所示&#xff1a; 话不多说&#xff0c;直接上解决方法&#xff1a; 1、找到这两个文件&#xff08;每个人的位置不一样&#xff0c;像我是安装在D盘&#xff09; 2、分别点进去&#xff0c;右键—属性…