操作系统真象还原:保护模式入门

第4章-保护模式入门

这是一个网站有所有小节的代码实现,同时也包含了Bochs等文件

Intel8086CPU由于自身设计存在诸多缺点,最致命的有两条:1、仅能寻址1MB内存空间;2、用户程序可以通过自由修改段基址来访问所有内存空间而引出的安全问题。所以后来的CPU自然就要解决以上的两个问题,CPU厂商为了凸显出自己新的CPU的安全性,将新开发出的CPU命名为工作在保护模式下——也就是提供了一种保护机制让程序不能随意访问所有内存空间,同时CPU的寻址范围也达到了4GB。而8086的那种工作模式由于保护模式的出现而被命名为实模式。这种具有新的工作模式的CPU被叫做IA32(工作在32位环境下)体系架构CPU,这是从80836CPU开始的一种架构。

但由于8086CPU在当时取得了非凡的市场成功,所以后来的IA32体系架构CPU必须兼容8086的那种工作模式,所以IA32体系架构CPU也必须可以运行在实模式下。8086只能运行实模式,它使用[段基址:偏移]这种寻址方式,所以IA32体系架构CPU为了兼容8086上开发的程序,也得用[段基址:偏移]这种模式,但是又同时为了能够寻址更大的地址空间以及获得安全性检查,所以就采用了将段寄存器提供的值(16位)不再作为段基址,而是作为一个选择子去GDT表中找到对应的表项,然后从这个表项中得到段基址(32位)与进行安全性检查。

由于IA32体系架构的CPU有两种主要工作模式,而BIOS加载MBR,MBR加载Loader的时候工作在实模式下,此后为了获得更大的地址空间,就必须从实模式切换到保护模式。模式的切换就意味着寻址方式的切换,所以在由实模式切换到保护模式的时候,就必须要在内存中初始化GDT表,也就是初始化GDT表中的表项(也叫段描述符)。

原文链接:https://blog.csdn.net/kanshanxd/article/details/130749718

保护模式下的被扩展的32位寄存器:
在这里插入图片描述
在这里插入图片描述

进入保护模式的三个步骤:

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

4.3全局描述符表

4.3.1段描述符

在这里插入图片描述

type:是用来指定本描述符的类型,配合S字段使用

S:判断当前是一个什么段描述符,0是系统段,1是数据段

DPL:描述符特权级字段,分别为0,1,2,3级特权,数字越小,特权级越大

P:判断段是否存在,存在为1,否则为0

AVL:可用字段

L:用来设置是否是64位代码段。L为1表示是64为代码段,否则为32为代码段

D/B:用来指示有效地址及操作数的大小,对于代码段来说此位是D位,若为0,表示指令中的有效地址和操作数为16位,指令有效地址用IP寄存器。为1,表示指令中的有效地址及操作数为32位,指令有效地址为EIP寄存器。 对于栈段来说,此位为B位,用来指定操作数大小,若为0,使用sp寄存器,也就是16为寄存器,为1,使用esp寄存器,也就是32为寄存器。

G:粒度,用来表示段界限的单位大小,为0表示段界限的单位为1,若为1,表示段界限的单位是4kb。

段界限边界值计算公式:

(描述符中段界限+1) * (段界限的粒度大小: 4KB或1) -1 。

这里我们使用的是平坦模式,G为1,段界限为0xFFFFF,段界限边界值为0xFFFFFFFF;

在这里插入图片描述

4.3.2全局描述符表GDT、局部描述符表LDT及选择子

在这里插入图片描述

在这里插入图片描述

GDT 中最多可容纳的描述符数量是 65536/8=8192个,即 GDT 中可容纳 8192 个段或门。

全局描述符表:GDT(对应的是GDTR寄存器指向这个表的内存,专有指令加载GDT,即lgdt),局部描述符表: LDT(对应的是LDTR寄存器指向这个表的内存,专有指令加载LDT,即lldt),TI位判断是存储在GDT(0)还是LDT(1)

选择子的作用主要是确定段描述符,确定描述符的目的,一是为了特权级、界限等安全考虑,最主要的还是要确定段的基地址。

虽然到了保护模式,但 IA32 架构始终脱离不了内存分段,即访问内存必须要用“段基址:段内偏移地址”的形式。保护模式下的段寄存器中已经是选择子,不再是直接的段基址。段基址在段描述符中,用给出的选择子索引到描述符后, CPU 了“段基址:段内偏移地址”的形式。 自动从段描述符中取出段基址,这样再加上段内偏移地址,便凑成了“段基址:段内偏移地址”的形式。

例子:

在这里插入图片描述

4.3.3打开A20地址线

打开A20是因为现在CPU为了兼容以前的版本在实时模式下当访问 0x100000~0x10FFEF之间的地址时, CPU 将采用 8086/8088 的地址回绕 。所以现在我们在保护模式下我们要突破第20条地址线去访问更大的内存空间。所以我们要关闭地址回绕,也就是打开A20 Gate,以下是代码实现:

#打开 A20Gate 的方式是极其简单的,将端口 Ox92 的第 1 位置 1 就可以了
in al,0x92
or al,0000_0010B
out 0x92,al
4.3.4保护模式的开关,CR0寄存器的PE位

控制寄存器是 CPU 的窗口,既可以用来展示 CPU的内部状态,也可用于控制 CPU 的运行机制 。

CRO寄存器的第 0 位,即 PE 位, Protection Enable,此位用于启用保护模式,是保护模式的开关。PE为0表示在实时模式下运行,PE为1表示在保护模式下运行。
在这里插入图片描述

mov eax,cr0
or	eax,0x00000001
mov cr0,eax
4.3.5进入保护模式

boot.inc

;--------------------------loader和kernel----------------

LOADER_BASE_ADDR equ 0x900
LOADER_START_SECTOR equ 0x2

;--------------------------gdt描述符属性-----------------
DESC_G_4k	equ	1_00000000000000000000000b
DESC_D_32	equ	1_0000000000000000000000b
DESC_L		equ	0_000000000000000000000b	;64位代码标记,此处标记为0便可是32位
DESC_AVL	equ	0_00000000000000000000b		;CPU不用此位,暂为0
DESC_LIMIT_CODE2	equ	1111_0000000000000000b
DESC_LIMIT_DATA2	equ	DESC_LIMIT_CODE2
DESC_LIMIT_VIDEO2	equ	0000_000000000000000b
DESC_P		equ	1_000000000000000b
DESC_DPL_0	equ	00_0000000000000b
DESC_DPL_1	equ	01_0000000000000b
DESC_DPL_2	equ	10_0000000000000b
DESC_DPL_3	equ	11_0000000000000b
DESC_S_CODE	equ	1_000000000000b
DESC_S_DATA	equ	DESC_S_CODE
DESC_S_sys	equ	0_000000000000b
DESC_TYPE_CODE	equ	1000_00000000b	;x=1,c=0,r=0,a=0代码段是可执行的,非一致性,不可读,以访问位a清0
DESC_TYPE_DATA	equ	0010_00000000b	;x=0,e=0,w=1,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

;----------------------------------选择子属性----------------------------
RPL0	equ	00b
RPL1	equ	01b
RPL2	equ	10b
RPL3	equ	11b
TI_GDT	equ	000b
TI_LDT	equ	100b

loader.s

%include "boot.inc"
section loader vstart=LOADER_BASE_ADDR
LOADER_STACK_TOP equ LOADER_BASE_ADDR
	jmp loader_start	;z主要是跳过一段gdt段描述符数据

;构建gdt及其内部的描述符
GDT_BASE: 
	dd	0x00000000	;GDT低32位 
	dd	0x00000000	;GDT高32位

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	;此时dp1=0
	    
	GDT_SIZE equ $-GDT_BASE
	GDT_LIMIT equ GDT_SIZE-1
	times 60 dq 0	;预留60个描述符的空位置 dp表示8字节
	SELECTOR_CODE	equ	(0x0001<<3)+TI_GDT+RPL0	;相当于( CODE_DESC - GDT_BASE) / 8 + TI_GDT + RPLO
	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:
	mov sp, LOADER_BASE_ADDR
	mov bp, loadermsg ;ES:BP = 字符串地址,承接上面的是显存文字段区域
	mov cx, 17 ;ex =字符事长度
	mov ax, 0x1301 ;AH = 13, AL = Olh
	mov bx, 0x001f ;页号为 0 (BH = 0 )蓝底粉红字( BL = lfh)
	mov dx, 0x1800
	int 0x10 ;INT 0x10    功能号:0x13    功能描述:打印字符串

;一一一一一一一一一一 准备进入保护模式 一一一一一一一一一一一
;1.打开A20
;加载gdt
;将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

	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 $

这里我们的操作系统是在平坦模式下的,所以整个4GB 32位系统就只有一个段,所以他的代码段和数据段,都一样;显示段就是显示器映射的那一部分

4.4处理器架构简介

4.4.1流水线

CPU 是按照程序中指令顺序来填充流水线的,也就是说按照程序计数器 PC(x86中是 CS: ip)中的值来装载流水线的,当前指令和下一条指令在空间上是挨着的 。如果当前执行的指令是jmp,下一条指令已经被送上流水线译码了,第三条指令已经被送上流水线取指啦 仔细想想看,其实这个流水线没用了,因为 CPU 早已经跳到别处去执行了,第二、三条指令用不上了,所以 CPU 在遇到无条件转移指令 jmp 时,会清空流水线

4.4.2乱序执行

乱序执行,是指在 CPU 中运行的指令并不按照代码中的顺序执行,而是按照一定的策略打乱顺序执
行,也许后面的指令先执行,当然,得保证指令之间不具备相关性 。

乱序执行的好处:就是后面的操作可以放到前面来做,利于装载到流水线上提高效率 。

4.4.3缓存

低速存储设备是整个系统的瓶颈,缓存用来缓解“瓶颈设备”的压力。

DRAM:动态随机访问存储器

SRAM:静态随机存储器,现在CPU的缓存都是它。

4.4.4分支预测

4.5使用远跳转指令清空流水线

段描述符缓冲寄存器在 CPU 的实模式和保护模式中都同时使用,在不重新引用一个段时,段描述符
缓冲寄存器中的内容是不会更新的,无论是在实模式,还是保护模式’下, CPU 都以段描述符缓冲寄存器
中的内容为主。实模式进入保护模式时,由于段描述符缓冲寄存器中的内容仅仅是实模式下的 20 位的段
基址,很多属性位都是错误的值,这对保护模式来说必然会造成错误,所以需要马上更新段描述符缓冲寄
存器,也就是要想办法往相应段寄存器中加载选择子。

既要改变代码段描述符缓冲寄存器的值,又要清空流水线 。

代码段寄存器 cs,只有用远过程调用指令 call、远转移指令 jmp、远返回指令 retf 等指令间接改变,
没有直接改变 cs 的方法,如直接 mov cs, xx 是不行的。 故,用无条件远跳转指令 jmp 来解决上述两个问题将是一举两得的做法

4.6保护模式之内存段的保护

主要体现在段描述符的属性字段中。每个字段都不是多余的。这些属性只是用来描述一块内存的性质,是用来给 CPU 做参考的,当有实际动作在这片内存上发生时, CPU 用这些属性来检查动作的合法性,从而起到了保护的作用。

4.6.1向段寄存器加载选择子时的保护

首先根据选择子的值验证段描述符是否越界:描述符表基地址+选择子中的索引值*8+7<=描述符表基地址+描述符表界限值。

  1. 不超过描述符表的界限(描述符表基地址 + 描述符表界限值) 确定了描述符表的结束位置。条件确保选择的段描述符的地址不会超出这个范围。
  2. 指向正确的段描述符(描述符表基地址 + 段选择子索引 * 8) 是段选择子所指示的描述符在描述符表中的位置。由于每个描述符占用 8 字节(在 x86 中),因此乘以 8 就是为了找到正确的描述符。
  3. 最后一个字节的偏移不超过描述符表界限7 表示了描述符的长度(通常是 8 字节)减去 1,因为索引是从 0 开始的。所以要确保选择的描述符不超过这个界限,需要加上这个偏移量。

**+7:**他是从描述符表的基地址开始的,我们思考当我们现在只有0段时,他是从基地址开始的为基地址加上7字节就为一个0段,大小为8,他的界限为7,所以这也是这个加7的来源,后面剩余的段就大小是8,界限也是下一个开始不包含当前这个地址内容,为地址加8.

在选择子检查过后,就要检查段的类型了。 这里主要是检查段寄存器的用途和段类型是否匹配。

  • 只有具备可执行属性的段(代码段〉才能加载到 cs 段寄存器中。
  • 只具备执行属性的段(代码段〉不允许加载到除 cs 外的段寄存器中。
  • 只有具备可写属性的段(数据段)才能加载到 SS 枝段寄存器中。
  • 至少具备可读属性的段才能加载到 DS 、 ES 、陀、 GS 段寄存器中。

在这里插入图片描述

检查完 type 后,还会再检查段是否存在

4.6.2代码段和数据段的保护

对于代码段和数据段来说, CPU 每访问一个地址,都要确认该地址不能超过其所在内存段的范围 。

实际段界限值为:(描述符中段界限+1)*(段界限的粒度大小:4k或者1)-1 。

对于代码段来说,段基址存放在CS寄存器中,段内偏移地址,即有效地址,存放在EIP寄存器中。他要满足以下条件:EIP中的偏移地址+指令长度-1<=实际段界限大小

对于数据段也要遵循以下原则:偏移地址+数据长度-1<=实际段界限大小

4.6.3栈段的保护

段描述符 type 中的 e 位用来表示段的扩展方向,但它和别的描述符属性一样,仅仅是用来描述段的性质,即使 e 等于1向下扩展,依然可以引用不断向上递增的内存地址,即使e等于0向上扩展,也依然可以引用不断向下递减的内存地址。

检查是否超越段界限:

  • 对于向上扩展的段,实际的段界限是段内可以访问的最后一字节。
  • 对于向下扩展的段,实际的段界限是段内不可以访问的第一个字节。

内存分布

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

leetCode.89. 格雷编码

leetCode.89. 格雷编码 题目思路 代码 class Solution { public:vector<int> grayCode(int n) {vector<int> res(1,0); // n 0时&#xff0c;之后一位0while (n--) {// 想要实现对象超下来&#xff0c;就从末尾开始&#xff0c;让vector里面 加 元素for (int i …

js切割数组的两种方法slice(),splice()

slice() 返回一个索引和另一个索引之间的数据(不改变原数组),slice(start,end)有两个参数(start必需,end选填),都是索引,返回值不包括end 用法和截取字符串一样 splice() 用来添加或者删除数组的数据,只返回被删除的数据,类型为数组(改变原数组) var heroes["李白&q…

乡村振兴与文化传承:挖掘乡村历史文化资源,传承乡村优秀传统,打造具有地方特色的美丽乡村文化品牌

目录 一、引言 二、乡村历史文化资源的挖掘与保护 &#xff08;一&#xff09;乡村历史文化资源的内涵 &#xff08;二&#xff09;乡村历史文化资源的挖掘 &#xff08;三&#xff09;乡村历史文化资源的保护 三、乡村优秀传统的传承与创新 &#xff08;一&#xff09;…

十_信号14 - system()

意思是 应在在调用 system() 函数前 阻塞 SIGCHLD 信号&#xff0c;否则&#xff0c;子进程结束的时候&#xff0c;系统会向该进程(父)发送 SIGCHLD信号&#xff0c;则该进程认为是自己的一个子进程结束了&#xff0c;于是调用 wait函数获取子进程的终止状态。这本来是正常的操…

驱动开发之platform总线

1.前言 在前面的实验以及提出的各种问题中&#xff0c;为了提高移植性&#xff0c;降低模块耦合度&#xff0c;提让模块高内聚&#xff0c;分离device与driver是一个必然的趋势了。为了解决这个问题&#xff0c;心心念念的platform总线出来。 linux从2.6起就加入了一套新的驱…

区块链--Ubuntu上搭建以太坊私有链

1、搭建私链所需环境 操作系统&#xff1a;ubuntu16.04&#xff0c;开虚拟机的话要至少4G&#xff0c;否则会影响测试挖矿时的速度 软件&#xff1a; geth客户端 Mist和Ethereum Wallet&#xff1a;Releases ethereum/mist GitHub 2、安装geth客户端 sudo apt-get update …

ssm音乐网站-计算机毕业设计源码87184

摘 要 随着社会的发展&#xff0c;社会的方方面面都在利用信息化时代的优势。互联网的优势和普及使得各种系统的开发成为必需。 本文以实际运用为开发背景&#xff0c;运用软件工程原理和开发方法&#xff0c;它主要是采SSM技术和mysql数据库来完成对系统的设计。整个开发过程首…

linux系统下,mysql增加用户

首先&#xff0c;在linux进入mysql mysql -u root -p 然后查看当前用户&#xff1a; select user,host from user; 增加用户语句&#xff1a; CREATE USER 用户名host范围 IDENTIFIED BY 密码;

渗透测试报告生成工具

目录 1.前言 1.1 渗透测试报告是什么? 1.2 渗透测试报告的编写需要考虑以下几点&#xff1a; 1.3 一份优秀的渗透测试报告应该具备以下特点&#xff1a; 1.4 在编写渗透测试报告之前&#xff0c;需要进行一些准备工作&#xff1a; 1.5 渗透测试报告一般包括以下部分&…

【避坑全攻略】如何让私人的LLM学GPT4o一样说话——ChatTTS

OpenAI 发布 GPT4o 之后&#xff0c;使得越来越多的人都开始幻想属于自己的AI“伴侣”&#xff0c;这最让人惊艳的就是他们出色的TTS技术。而在此之前&#xff0c;主流的开源TTS有 XTTS 2 和 Bark。而近日&#xff0c;一个名为 ChatTTS 文本转语音项目爆火出圈&#xff0c;引来…

Vue3集成Phaser-飞机大战游戏(设计与源码)

文章目录 引言项目初始化游戏设计和结构游戏程序实现Vue页面嵌入PhaserPreloader 场景加载游戏场景功能实现功能类定义Boom爆炸类Bullet子弹类Enemy敌军类Player玩家类End游戏结束类 总结 更多相关内容可查看 引言 飞机大战&#xff08;也被称为射击游戏或空战游戏&#xff09…

PID算法入门

文章目录 122.12.22.3 344.14.24.3 1 e(t) 是偏差 实 和 目u(t) 是运算结果 2 层层叠加 得出完整的离散公式 2.1 kp 越大 系统偏差 减小的越快kp大的时候 会出现过冲现象&#xff1f; 0.5 那个会快他解释过冲 &#xff1a; 0.2的 5分钟正好到了 那0.5的五分钟 升的就比20多 就…

springboot 自带的定时任务

启用springboot 定时任务 在springboot 启动类上增加EnableScheduling 注解 如下 SpringBootApplication EnableScheduling public class SpringApplication {public static void main(String[] args) {SpringApplication.run(SpringApplication.class, args);} }编写定时逻辑…

WHAT - 容器化系列(三)- Kubernetes - k8s

目录 一、前言二、Kubernetes 架构图三、KubernetesKubernetes和Docker的关系最小调度单元Pod 四、基本概念容器生态和标准化资源Workload资源&#xff1a;控制器对象服务担保Service&Ingress1. 两者的介绍以及与 OSI 七层模型关系2. 常见的 Service 类型3. Ingress 和 Ing…

自然资源-农村土地流转知识全解

自然资源-农村土地流转知识全解 随着农村经济的发展和城市化进程的加快&#xff0c;农村土地面临着多方面的压力&#xff0c;如人口增长、城市扩张、环境恶化等。这些压力导致了农村土地利用率低、经济效益差、农民收入水平低、农村社会经济不发达等问题。因此&#xff0c;改变…

Rust 第三方库创建和导入(cargo --lib)

前言 日常开发过程中&#xff0c;难免会有一些工具方法&#xff0c;多个项目之间可能会重复使用。 所以将这些方法集成到一个第三方包中方便后期维护和管理&#xff0c; 比如工具函数如果需要修改&#xff0c;多个项目可能每个都需要改代码&#xff0c; 抽离到单独的包中只需要…

AI办公自动化:用kimi批量提取音频中的标题并重命名

很多音频文件&#xff0c;文件名很乱&#xff0c;需要根据音频信息中的标题聪明吗 在kimi中输入提示词&#xff1a; 你是一个Python编程专家&#xff0c;一步步的思考&#xff0c;完成以下脚本的撰写&#xff1a; 打开文件夹&#xff1a;E:\有声\a16z播客 读取里面所有的mp3格…

关于vlookup的第一个参数的个人理解

VLOOKUP&#xff08;查阅值&#xff0c;包含查阅值和返回值的查找区域&#xff0c;查找区域中返回值的列号&#xff0c;精确查找或近似查找&#xff09; 我个人理解&#xff0c;第一个参数应该叫线索值&#xff0c;因为我们要通过它去找与其对应的&#xff08;也就是与其同行的…

Soulmask灵魂面甲服务器一键开服联机教程

1、购买后登录服务器&#xff08;百度莱卡云&#xff09; 进入控制面板后会出现正在安装的界面&#xff0c;安装大约5分钟&#xff08;如长时间处于安装中请联系我们的客服人员&#xff09; 2、创建端口 点击网络➡创建新的网络设置 需要创建两个端口&#xff0c;一个 查询端口…

SAP_SD模块-销售交货并开票后发现物料没维护价格的完整处理方法(含POD功能)

销售流程完结后&#xff0c;发现物料价格没维护时&#xff0c;如何处理 一、业务背景&#xff1a; 1、问题发现时间&#xff1a;2024年6月2日&#xff1b; 2、问题描述&#xff1a; 2024年5月份的单据业务存在交货成本和开票成本为0的单据&#x…