第四章 保护模式入门
本文是对《操作系统真象还原》第四章学习的笔记,欢迎大家一起交流。
知识部分
为什么要有保护模式?
实模式下安全问题:
- 实模式下操作系统和用户程序属于同一特权级,平起平坐,没有区别对待;
- 用户程序所引用的地址都是指向真实的物理地址,也就是说逻辑地址等于物理地址,实实在在的指哪打哪;
- 用户程序可以自由修改段基址,可以不亦乐乎地访问所有内存,没人拦得住;
实模式下使用上的缺陷:
- 访问超过 64KB 的内存区域是要切换段基址,转来转去容易晕呼;
- 一次只能运行一个程序,无法充分利用计算机资源;
- 共 20 条地址线,最大可用内存为 1MB,这即使在 20 年前也不够用。
而保护模式提供了一种保护机制让程序不能随意访问所有内存空间,同时 CPU 的寻址范围也达到了 4GB。
保护模式的特点
保护模式之寄存器扩展
一张图即可,e 代表 extend
注意段寄存器并没有扩展,进入保护模式后,段寄存器中保存的再也不是段基址了,里面保存的内容叫选择子 selector,该选择子其实就是个数,用这个数来索引全局描述符表中的段描述符,把全局描述符表当成数组,选择子就像数组下标一样在,后面讲 GDT 的时候会详细说。
保护模式之寻址扩展
保护模式之运行模式反转
操作数反转前缀 0x66,寻址方式反转前缀 0x67。
bits 的指令格式是 [bits 16]或 [bits 32]。
[bits 16]是告诉编译器,下面的代码帮我编译成 1 6 位的机器码 。
[bits 32]是告诉编译器,下面的代码帮我编译成 32 位的机器码。
注:
进入保护模式需要三个步骤。
(1) 打开 A20 。(2) 加载 gdt 。(3) 将 cr0 的 pe 位置 1 。
后面代码部分有详细步骤。
全局描述符表(GDT)
实模式与保护模式寻址的不同
实模式下用 段基址:偏移
来寻址,即 段基址*16+偏移
,段基址是存在段寄存器中的。
在保护模式下,要访问 4gb 的内存空间,再采取实模式下的方法是远远不够的,所以段寄存器中存储的数据变成了选择子,其格式如下图所示,其中索引值即 gdt 中索引值,TI 用于指示是去 GDT 还是 LDT 中寻找段基址,RPL 是特权级
段描述符
GDT 是一个表格,其中每一个表项就对应一个全局描述符,操作系统在寻址市首先根据 gdtr 寄存器得到 GDT 内存起始地址,然后加上选择子的偏移(8 字节为单位),就找到了对应的段描述符,然后在段描述符里可以获取对应段的起始地址和界限,需要注意的是,GDT 表第 0 个段描述符不可用。因为选择子忘记设置的话,就会是 0(就像我们的 MBR 代码一上来就将段寄存全部初始化为 0),就会访问这个段描述符,而如果这个段描述符有内容的话,就会将段基址定位到其他我们并不想要的地方去,所以干脆直接让 GDT 表第 0 个段描述符不可用,未设置的选择子访问这个段描述符 CPU 就会产生异常并阻止。其中 gdtr 寄存器格式如下图所示:
段描述符格式如下图所示,可以看到段基址和段界限被区分成了不同部分,十分奇怪,这其实是为了兼容不同的 cpu 所导致的。
段描述符的格式有点复杂,下面对重点字段进行说明,详见书 P151:
G 字段:1 代表单位是 4k,0 代表 1 字节,可以看到段界限共 20 位,为 0 时正好是 1MB,为 1 时正好是 4GB
D/B 字段:1 代表有效地址和数据是 32 位,0 代表 16 位
L 字段:1 代表 64 位代码,0 代表 32 位
AVL 字段:看操作系统需求用,暂时不关注
P 字段:存在位
DPL 字段:0-3,4 个特权级
S 字段:是否是系统段(看是否是硬件需要的)
TYPE 字段:需要和 s 字段结合起来看,如下图所示:
再说一下段基址和段界限,我们现在的 cpu 都是处于平坦模型下,即是从 0x00000000-0xffffffff
所以段基址是 0x0,段界限是 0xfffff
,在对应位填上即可
但是也有的段比较特殊,比如显存段,我们前面说过,显存段的范围是 0xB8000-0xBFFFF
,所以段基址是 0xB8000
,段界限是 (0xBFFFF-0xB8000+1)/4k-1=7
代码部分
首先 mbr 文件和 loader 编译命令要变一下,因为我们 loader 变大了,所以一个扇区不太够,我们直接改成 4 个,一劳永逸。
下面是 boot.inc 代码,我们新增了很多 GDT 的描述
;------------- loader 和 kernel ----------
LOADER_BASE_ADDR equ 0x900
LOADER_START_SECTOR equ 0x2
;-------------- gdt描述符属性 -------------
DESC_G_4K equ 1_00000000000000000000000b ; 4k粒度
DESC_D_32 equ 1_0000000000000000000000b ; 有效地址和数据是32位
DESC_L equ 0_000000000000000000000b ; 64位代码标记,此处标记为0便可。
DESC_AVL equ 0_00000000000000000000b ; cpu不用此位,暂置为0
DESC_LIMIT_CODE2 equ 1111_0000000000000000b ; 代码段段界限高四位 全1
DESC_LIMIT_DATA2 equ DESC_LIMIT_CODE2 ; 数据段段界限 同 代码段
DESC_LIMIT_VIDEO2 equ 0000_000000000000000b ; 显存段段界限大小为BFFFF-B8000 = 7FFF,在20位段界限下高4位全0
DESC_P equ 1_000000000000000b ; 存在位
DESC_DPL_0 equ 00_0000000000000b ; r0
DESC_DPL_1 equ 01_0000000000000b ; r1
DESC_DPL_2 equ 10_0000000000000b ; r2
DESC_DPL_3 equ 11_0000000000000b ; r3
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.
; 拼凑出三个段的高32位
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 ;最后加的是段基址BFFFF的23-16位
;-------------- 选择子属性 ---------------
;RPL代表特权级
RPL0 equ 00b ;定义选择字的RPL为0
RPL1 equ 01b ;定义选择子的RPL为1
RPL2 equ 10b ;定义选择字的RPL为2
RPL3 equ 11b ;定义选择子的RPL为3
TI_GDT equ 000b ;定义段选择子请求的段描述符是在GDT中
TI_LDT equ 100b ;定义段选择子请求的段描述符是在LDT中
注释很清楚了,这里不再多说。
下面是 loader.s 的内容
%include "boot.inc"
section loader vstart=LOADER_BASE_ADDR
LOADER_STACK_TOP equ LOADER_BASE_ADDR
jmp near loader_start
;构建gdt及其内部的描述符
GDT_BASE: dd 0x00000000
dd 0x00000000
CODE_DESC: dd 0x0000ffff
dd DESC_CODE_HIGH4
DATA_STACK_DESC: dd 0x0000ffff
dd DESC_DATA_HIGH4
VIDEO_DESC: dd 0x80000007 ;段基址:0xB8000到0xBFFFF为文字模式显示内存,此处取后四位
;limit:0x0007 (bFFFF-b8000+1)/4k = 0x8 由于从0开始,所以再减一
dd DESC_VIDEO_HIGH4
GDT_SIZE equ $ - GDT_BASE ;得到gdt大小
GDT_LIMIT equ GDT_SIZE - 1 ;大小减1即为gdt界限
times 50 dq 0 ;此处预留50个描述符的slot
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_LIMIT
dd 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——字符串中只含显示字符,其显示属性在BL中。显示后,光标位置改变
; 2——字符串中含显示字符和显示属性。显示后,光标位置不变
; 3——字符串中含显示字符和显示属性。显示后,光标位置改变
;无返回值
mov sp, LOADER_BASE_ADDR
mov bp, loadermsg ; ES:BP = 字符串地址
mov cx, 17 ; CX = 字符串长度
mov ax, 0x1301 ; AH = 13, AL = 01h
mov bx, 0x001f ; 页号为0(BH = 0) 蓝底粉红字(BL = 1fh)
mov dx, 0x1800 ;
int 0x10 ; 10h 号中断
;---------------------------------------- 准备进入保护模式 ------------------------------------------
;1 打开A20
;2 加载gdt
;3 将cr0的pe位置1
;1 打开A20
in al, 0x92
or al, 0000_0010B
out 0x92, al
;2 加载gdt
lgdt [gdt_ptr]
;3 将cr0的pe位置1
mov eax, cr0
or eax, 0x00000001
mov cr0, eax
jmp dword SELECTOR_CODE:p_mode_start ; 刷新流水线,避免分支预测的影响,这种cpu优化策略,最怕jmp跳转,
; 这将导致之前做的预测失效,从而起到了刷新的作用。
[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' ; 显示在第二行
; 默认的文本显示模式是80*25,即每行是 80 个字符(0~79),每个字符占 2 字节,故传入偏移地址是 80*2=160。
jmp $
上面代码的主要作用还是打印字符,一个是利用 BIOS 字段一个是直接操纵显存打印不同字符,注释很详细了,下面对关键点进行说明
6-15 行:构建 GDT 表,包含代码段描述符,数据/栈段描述符,显存段描述符
17-18 行:得到 GDT 表起始地址和大小,后面填到 gdtr 寄存器
19 行:填充了 50 个空白的段描述符,以便后面使用
20-22 行:得到三个段的选择子
50-56 行:利用 BIOS 字段打印字符
63-74 行:进入保护模式
76 行:无条件跳转,用于刷新流水线,因为在执行下面 32 位代码之前,已经先把代码送上了流水线,进行取指、译码等操作,在这两步 32 位和 16 位又有很大的不同,段寄存器的使用不同,16 位编译 32 位指令会加上 0x66/0x67 等反转符,所以我们要清空流水线,以便顺利进入 32 位
80-90 行:进入到 32 位模式,打印字符 P
最终效果如下:
标签:保护模式,GDT,入门,32,equ,描述符,第四章,DESC From: https://www.cnblogs.com/fdxsec/p/18655799