Gos —— 开启保护模式
文章目录
- 实模式
- 保护模式
- 实模式的五宗罪
- 寄存器扩展
- 内存访问
- 段描述符
- 全局描述符表
- 段选择子
- 进入保护模式
- 打开A20地址线并加载全局描述符表GDT
- 打开保护模式开关cr0
- GO GO GO
- 参考文献
写在前面:自制操作系统Gos 第二章第五篇:主要内容是如何进入保护模式
实模式
我们既然这篇主要的内容是保护模式,那么其实就绕不开它的对立面:实模式了。
实模式,我们顾名思义其实就大概也能猜出来一点:**实模式下程序用到都是真实的物理地址!**像这个系列的前四章的程序其实都是在实模式下面运行的。
而在实模式下访问内存用到的就是我们之前用到的cs:ip
寄存器组合。这里再强调一遍:cs中存储的是段基地址,ip中存储的是这个段中的偏移。
好了,实模式就聊到这里吧,他也不是今天的主角。
保护模式
保护模式看到这四个名字,不知道读者有没有跟我一样的疑惑:它在执剑保护着谁呢?
实模式的五宗罪
抱歉,少了两种…
凡是都是要比较的,我们先来看看实模式有什么不足吧:
- 实模式下程序和操作系统是同权平级的,同等的权力滋长了用户程序的欲望
- 实模式下用的都是真实地址,能够肆意遨游内存空间。听起来很好?但是这就给了黑客大哥们可乘之机
- 访问超过64KB的内存区域需要切换段基地址。因为寄存器是16位,最大访问就是216=64KB
- 一次只能运行一个程序。哦豁,不能边听歌边打游戏
- 总共20条地址线,最大访问空间只有小小的1M
为了克服这些缺陷,也是保护模式的使命。硬件厂商设计出了保护模式。其最大的功能是:物理内存地址不能直接被程序访问,程序的虚拟地址需要被转换成物理地址后再去访问。而地址转换是由操作系统和处理器共同协作完成的,处理器在硬件上提供地址转换不见,操作系统提供转换过程中所需要的页表。
而程序是怎么转换的,可以看这篇博客:程序地址转换
寄存器扩展
进入保护模式之后,最大的变化其实就是寄存器从16位扩展为了32位,这样寄存器就能访问到4GB的空间了。
注:
这里的低16位是为了兼容。
内存访问
刚刚我们提出了保护模式在保护谁这么一个问题。其实保护的有很多,不过最主要的就是保护内存了。
在保护模式下,段偏移地址还是和实模式下一样,但是段基址的变化可大了去了。
在保护模式下,专门新增了一个数据结构:全局描述符表 来存放内存段的描述信息。每个表项被称之为段描述符,其大小为64位,用来描述各个内存段的起始地址、大小权限等信息。
注:
64位可不是小数字,何况还是一个表项的大小。所以全局描述符表被放在了内存之中,通过寄存器GDTR
指向它。
但是全局描述符表毕竟在内存中,对于CPU来说还是太慢了。所以为了提高获取段信息的效率,使用段描述符缓冲寄存器。CPU将获取到的内存段信息存入其中,之后再访问就直接从寄存器取数据就可以了
这样,段寄存器中保存的就不是实模式下的段基址了,而是全局描述符表项的下标,被称之为选择子。
段描述符
刚刚我们列举了实模式的五宗罪。而段描述符就是为了应对其而生的,所以其必然有一些属性去保护内存的安全。
- 实模式下用户程序可以破坏存储代码的内存区域,所以要添加个内存段类型属性来阻止这种行为
- 实模式下用户程序和操作系统是同一级别,所以要添加特权级属性来区分用户程序和操作系统
- 内存段是一片内存区域,访问内存就要提供段基址,所以要有段基址属性
- 为了限制程序访问的内存范围,需要段界限属性
注:
段界限表示段边界的扩展最值。扩展方向主要有上下两种。对于数据段和代码段,段的扩展方式是向上;对于栈段,短的扩展方向是向下。
- 段界限:段界限用20位来表示,其是一个单位量。单位一般是1字节或者4K,所以他最多能访问4GB的空间
- G:如果G位为0,表示段界限的粒度为1字节;如果G位为1,段界限粒度为4KB
- D/B:对于代码段来说,此位是D位。若D为0,表示指令中的有效地址和操作数是16位;若D为1,表示其为32位;对于栈段来说,此位是B位,同理。
- L:用来设置是否是64位代码段
- AVL:暂时无用
- P:表示此段是否在内存中,因为根据置换算法,其可能在磁盘中
- DPL:表示当前段的特权级
- S:表示是否是系统段
- TYPE:总共四位,表示此描述符的类型详情如下表
内存段类型 | X | C | R | A | 说明 |
---|---|---|---|---|---|
代码段 | 1 | 0 | 0 | - | 只执行代码段 |
1 | 0 | 1 | - | 可执行可读代码段 | |
1 | 1 | 0 | - | 可执行一致性代码段 | |
1 | 1 | 1 | - | 可执行、可读、一致性代码段 | |
内存段类型 | X | E | W | A | 说明 |
数据段 | 0 | 0 | 0 | - | 只读数据段 |
0 | 0 | 1 | - | 可读写数据段 | |
0 | 1 | 0 | - | 只读、向下扩展数据段 | |
0 | 1 | 1 | - | 可读写,向下扩展数据段 |
全局描述符表
一个段描述符只能用来定义一个内存段,而全局描述符GDT用来存储多个段描述符。其相当于描述符的数组,数组的每个元素都是8字节的描述符,可以用选择子中提供的下标在GDT中索引段描述符。
全局描述符表存储在内存中,专门的寄存器GDTR指向它之后,CPU才知道它在哪里,才能去取段描述符数据。
注:
要访问这个寄存器不能用mov
,只能用lgdt
指令
段选择子
现在段描述符有了,全局描述符表也有了,还差一个段选择子了。
段寄存器CS、DS、ES、FS、GS、SS
在保护模式中存储的就是段选择子selector。选择子基本上来说是一个索引值,但是也只是基本上了。
- TI:用来指示段选择子是全局表GDT还是局部表LDT
- RPL:用来表示当前的特权级
进入保护模式
进入保护模式总共有三步:
- 打开A20地址线
- 加载全局描述符表GDT
- 将 cr0 寄存器的 PE 位置置为1
打开A20地址线并加载全局描述符表GDT
在实模式上总共有20跟地址线,编号0~19。而在保护模式下,打开A20地址线意味着进入保护模式。
而打开A20Gate的方式是及其简单的,只需要将端口0x92的第一位置为1就可以了:
in al,0x92
or al,0000_0010B
out 0x92,al
而加载全局描述符则更简单了:
# gdt_ptr 为我们创建的全局描述符表的位置
lgdt [gdt_ptr]
打开保护模式开关cr0
CRx
系列都是控制寄存器,其是CPU的窗口,既可以用来展示CPU的内部状态,也可以用来控制CPU的运行机制。
而我们今天要用到的就是第一位PE位。其是保护模式的开关,而其他除了无效位也有各自的用途:
标志位 | 描述 |
---|---|
PE | 保护模式开关 |
MP | 监控协处理器 |
EM | 仿真? TODO |
TS | 任务选择 |
ET | 扩展类型 |
NE | 数字错误 |
WP | 写保护 |
AM | 对齐掩码 |
NW | 不写?TODO |
CD | 禁用缓存 |
PG | 开启分页 |
而打开这个开关的代码也很简单:
mov eax,cr0
or eax,0x00000001
mov cr0,eax
GO GO GO
下面就上源码了,首先是记录全局描述符表以及段描述符信息的头文件boot.inc
:
;*************************
LOADER_BASE_ADDR equ 0x900
LOADER_START_SECTOR equ 0x2 ;第二扇区;*************************
;新增段描述符属性以及段选择子
DESC_G_4k equ 1_00000000000000000000000b ;表示描述符粒度为4k
DESC_D_32 equ 1_0000000000000000000000b
DESC_L equ 0_000000000000000000000b ;64位代码标记,此处标记为0
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;定义代码段的高4字节
;(0x00 << 24)表示段基址24~31的部分
;DESC_G_4k: 4k的粒度
;DESC_D_32: 表示描述符中的D/B位
;DESC_L: 段描述符L位
;DESC_AVL: 无意义
;DESC_LIMIT_CODE2: 段界限第二部分,和第一部分组成0xfffff
;DESC_P: 表示段是否存在
;DESC_DPL_0: 表示特权级是0
;DESC_S_CODE: 表示代码段的S位,其为1表示其为普通内存段,非代码段
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 + 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;*************************
;选择子属性
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;构建全局段描述符GDT及其内部描述符
GDT_BASE: dd 0x00000000dd 0x00000000
CODE_DESC: dd 0x0000FFFFdd DESC_CODE_HIGH4
DATA_STACK_DESC: dd 0x0000FFFFdd DESC_DATA_HIGH4
VIDEO_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 db 0 ;预留60个描述符的空位SELECTOR_CODE equ (0x0001<<3) + TI_GDT + RPL0 ;相当于(CODE_DESC - GDT_BASE)/8 + TI_GDT + RPI0
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 'Gos loading...';*************************
loader_start:
;*************************
; 打印字符串mov sp,LOADER_BASE_ADDR ;初始化栈指针mov bp,loadermsg ;字符串压栈mov cx,14 ;字符串长度mov ax,0x1301 ;ah=13,al=01h 调用子功能号13 01表示输出方式mov bx,0x000F ;页号为0,黑底白字0Fmov dx,0x1800 ;字符串打印在0x18的那一行int 0x10 ;10中断;*************************
;进入保护模式
;1.打开A20 地址线
;2.加载全局段描述符表
;3.设置保护模式标志位 cr0的pe位为1;*************************
;打开A20地址线
in al,0x92
or al,0000_0010B
out 0x92,al;*************************
;加载gdt
lgdt [gdt_ptr];*************************
;开启保护模式
mov eax,cr0
or eax,0x00000001
mov cr0,eaxjmp 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],'G'jmp $
之后编译源代码:
nasm -I ../include/ -o loader.bin loader.S
将源代码写入hd60.img
硬盘之中:
sudo dd if=./loader.bin of=/bochs/bo_tmp/bin/hd60M.img bs=512 count=4 seek=2 conv=notrunc
参考文献
[1] 操作系统真相还原
Gos —— 开启保护模式相关推荐
- Bochs源码分析 - 20: 开启保护模式
前言 在邓志老师的<x86/x64体系探索及编程>中讲述了开启保护模式的顺序,我尝试着翻阅intel手册,但很遗憾似乎自己没有找到对此的描述(应该是有的,但是自己没找到,日后找到了会补充上 ...
- [书]操作系统真象还原 -- 第5章 开启保护模式、开启分页机制
mbr:加载loader,跳转 loader: 1)调用BIOS中断获取内存大小; 2)构建GDT.开启保护模式; 3)构建页目录表和页表.开启分页机制; FILE:loader.asm ; FIL ...
- redis踩坑:redis哨兵开启了保护模式导致主从切换不同步
故障表现 哨兵只存在两个的时候,当哨兵模式的redis主节点挂掉以后,业务组件不能切换到新主节点 故障原因 redis哨兵依旧认为旧主为主节点,没有触发failover 故障原因定位 哨兵集群部署方式 ...
- x86CPU 实模式 保护模式 傻傻分不清楚? 基于Xv6-OS 分析CR0 寄存器
基于Xv6-OS 分析CR0 寄存器 之前一直认为晕乎乎的...啥?什么时候切换real model,怎么切换,为什么要切换? ------------------------------------ ...
- 跳转到保护模式并显示一个LOGO
注:本程序为原创,若发现bug,万望指出,若有问题,欢迎交流,转载请指明出处.若能有助于一二访客,幸甚. 以下为结果截图,显示的LOGO为小篆字体的欢迎 baby os 加载完成...几个字. 保护模 ...
- 操作系统引导--从实模式到保护模式
从开始到保护--系统开机引导 ------没有一个文档能写的通俗易懂,我希望写出来. 开机引导和实模式: 两个星期加上假期吐血整理,所述为计算机的开机引导,其中包括一系列计算机内存设置等等,由于没有老 ...
- 32位x86处理器编程导入——《x86汇编语言:从实模式到保护模式》读书笔记08
在说正题之前,我们先看2个概念. 1.指令集架构(ISA) ISA 的全称是 instruction set architecture,中文就是指令集架构,是指对程序员实际"可见" ...
- 3.操作系统——CPU的实模式、保护模式和长模式
有实模式.保护模式.长模式 实模式16(实地址模式) 真实分为两个方面: 运行真实指令.不区分指令动作,只是直接执行指令的真实功能 发往内存的地址是真实.不加限制的. 总结来说就是,这个模式下直接往物 ...
- i386 Linux内核进入保护模式引导流程
在系统引导过程中,Bootloader将内核镜像加载到内存后,并将控制权转交给内核 ,通过长转移指令跳转到入口startup_32. 实际上进入startup_32入口前,CPU已经处于了 ...
- [书]x86汇编语言:从实模式到保护模式 -- 第17章 中断、任务切换、分页机制、平坦模型
# 任务切换 内核任务.用户任务1.用户任务2,之前的轮询切换 利用RTC芯片的硬件中断来实现任务切换 计算机主板上有实时时钟芯片RTC,可以设置RTC芯片,使得它每次更新CMOS中的时间信息后,发出 ...
最新文章
- Go 学习笔记(28)— nil(nil 不能比较、不是关键字或保留字、nil 没有默认类型、不同类型的 nil 指针是一样的、不同类型的 nil 是不能比较的、相同类型的 nil 可能也无法比较)
- 【算法与数据结构】在n个数中取第k大的数(基础篇)
- 面试官问你B树和B 树,就把这篇文章丢给他
- 复现经典:《统计学习方法》第22章 无监督学习方法总结
- c语言填空题删除字符串k右边,计算机二级C语言上机模拟试题及解题思路
- springboot的IOC依赖注入与控制反转-举例(转载+自己整理)
- python123外汇兑换计算器_Python 3.x--使用re模块,实现计算器运算实例
- 4-STM32物联网开发WIFI(ESP8266)+GPRS(Air202)系统方案升级篇(远程升级WIFI内部程序)
- 学计算机的普通学生那里就业,学计算机我后悔了 现在好就业吗
- hdu 6203 ping ping ping(贪心+树状数组+dfs序)
- 游戏 mysql优化工具_MySQL 性能优化神器 Explain 使用分析
- 树莓派android p,Android P最新测试版带来更多的UI和图标方面的改进
- bzoj 1800 [Ahoi2009]fly 飞行棋——模拟
- 操作系统课程设计之磁盘调度系统的设计与实现c语言
- Matlab底层算法实现图像转置--宽高互换
- 如何实现网易公开课的倍速播放?
- 秀米html编辑器,ueditor集成秀米编辑器
- 元宇宙专题001 | 他们居然将元宇宙和心理学写到一起了
- 微软总裁:杀手机器人的崛起「势不可挡」【智能快讯】
- 解决windows下无法ctrl+A全选数据快捷键的问题
热门文章
- 永恒之蓝方程式利用工具使用教程
- knx智能照明控制系统电路图_智能照明KNX灯控软件
- ML之SHAP:机器学习可解释性之SHAP值之理解单样本单特征预测
- 速算扣除法php,关于速算扣除数法的计税方法 这两点必须知道
- java 利用Future做超时任务处理
- domcontentloaded事件和laod事件区别
- MATLAB算法实战应用案例精讲-【图像处理】缺陷检测(补充篇)(附matlab实现代码)
- python lncrna_一文解决TCGA任意肿瘤的差异lncRNA,miRNA,mRNA
- Windows11 校园网连ftp登录上传作业失败
- Pr 入门教程:如何使用项目面板?