第4章 保护模式入门

保护模式概述

为什么要有保护模式?

  1. 实模式下操作系统和用户程序属于同一特权级,平起平坐,没有区别对待;
  2. 用户程序所引用的地址都是指向真实的物理地址,也就是说逻辑地址等于物理地址,实实在在的指哪打哪;
  3. 用户程序可以自由修改段基址,可以不亦乐乎地访问所有内存,没人拦得住;

以上是一些安全方面的考量,还有以下一些使用上的缺陷:

  1. 访问超过64KB的内存区域是要切换段基址,转来转去容易晕呼;
  2. 一次只能运行一个程序,无法充分利用计算机资源;
  3. 共20条地址线,最大可用内存为1MB,这即使在20年前也不够用。

注:实模式的CPU运行环境是16位,保护模式的运行环境是32位。

初见保护模式

保护模式之寄存器扩展

image.png
进入保护模式后,段寄存器中保存的再也不是段基址了,里面保存的内容叫选择子 selector,该选择子其实就是个数,用这个数来索引全局描述符表中的段描述符,把全局描述符表当成数组,选择子就像数组下标一样。

在 80286的保护模式中,为了提高获取段信息的效率,对段寄存器率先应用了缓存技术,将段信息用一个寄存器来缓存,这就是段描述符缓冲寄存器( Descriptor Cache Registers )。对程序员而言它是不可见的。 CPU 每次将千辛万苦获取到的内存段信息,整理成“完整的、通顺、不鳖脚”的形式后,存入段描述符缓冲寄存器,以后每次访问相同的段时,就直接读取该段寄存器对应的段描述符缓冲寄存器。
image.png

保护模式之寻址扩展

image.png

保护模式之运行模式反转

bits 的指令格式是 [bits 16]或 [bits 32]。
[bits 16]是告诉编译器,下面的代码帮我编译成 1 6 位的机器码 。
[bits 32]是告诉编译器,下面的代码帮我编译成 32 位的机器码。

注:
进入保护模式需要三个步骤。
(1) 打开 A20 。(2) 加载 gdt 。(3) 将 cr0 的 pe 位置 1 。

全局描述符表

全局描述符表( Global Descriptor Table, GDT )是保护模式下内存段的登记表,这是不同于实模式的显著特征之一 。

段描述符

image.png
image.png
8~ 11 位是 type 字段 , 共 4 位,用来指定本描述符的类型。这里要提前说下段描述符的 S 宇段了 。 是这样的, 一个段描述符,在 CPU 眼里分为两大类,要么描述的是系统段,要么描述的是数据段,这是由段描述符中的 S 位决定的,用它指示是否是系统段。在 CPU 眼里,凡是硬件运行需要用到的东西都可称之为系统,凡是软件(操作系统也属于软件, CPU 眼中,它与用户程序无区别)需要的东西都称为数据,无论是代码,还是数据,甚至包括栈,它们都作为硬件的输入,都是给硬件的数据而己,所以代码段在段描述符中也属于数据段(非系统段)。 S 为 0 时表示系统段, S 为 1 时表示数据段。 type 字段是要和 S 字段配合在一起才能确定段描述符的确切类型,只有 S 宇段的值确定后, type 宇段的值才有具体意义。

什么是系统段?

各种称为“门”的结构便是系统段,也就是硬件系统需要的结构,非软件使用的,如调用门、任务门。简而言之,门的意思就是入口,它通往一段程序。关于系统段这里咱们不再多说,目前主要是关注 S 为 1 时,非系统段的。type 子类型。
image.png

  • 表中的 A 位表示 Accessed 位,这是由 CPU 来设置的,每当该段被 CPU 访问过后, CPU 就将此位置 1.所以,创建一个新段描述符时,应该将此位置 0。我们在调试时,根据此位便能判断该描述符是否可用啦。

  • C 表示一致性代码段,也称为依从代码段, Conforming 。 一致性代码段是指如果自己是转移的目标段,并且自己是一致性代码段,自己的特权级一定要高于当前特权级,转移后的特权级不与自己的 DPL 为主,而是与转移前的低特权级一致,也就是听从、依从转移前的低特权级。 C 为 1 时则表示该段是一致性代码段, C 为 0 时则表示该段为非一致性代码段。

  • R 表示可读, R 为 1 表示可读, R 为 0 表示不可读。这个属性一般用来限制代码段的访问。如果指令执行过程中, CPU 发现某些指令对 R 为 0 的段进行访问,如使用段超越前缀 cs 来访问代码段, CPU 将抛出异常。啰嗦一小下,内存中的数据对 CPU 来说是要处理的数据,仅仅是 CPU 的输入而己, CPU 的铁骑可以踏遍任意角落。所以,不可读的代码段只是来限制代码指令的,并不是连 CPU 也不能看。

  • X 表示该段是否可执行, EXecutable 。我们所说的指令和数据,在 CPU 眼中是没有任何区别的,都是010101 这样类似的二进制。所以要用 type 中的 X 位来标识出是否是可执行的代码。代码段是可执行的,即 X 为 1 。 而数据段是不可执行的,即 X 为 0

  • E 是用来标识段的扩展方向, Extend 。 E 为 0 表示向上扩展,即地址越来越高,通常用于代码段和数据段。 E 为 1 表示向下扩展,地址越来越低,通常用于梳段。

  • W 是指段是否可写, Writable 。 W 为 1 表示可写,通常用于数据段。 W 为 0表示不可写入,通常用于代码段。对于 W 为 0 的段有写入行为,同样会引发 CPU 抛出异常。

  • 段描述符的第 15 位是 P 字段, Present,即段是否存在。如果段存在于内存中, P 为 1 ,否则 P 为 0 。

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

一个段描述符只用来定义(描述)一个内存段。代码段要占用一个段描述符、数据段和战段等,多个内存段也要各自占用一个段描述符,这些描述符放在哪里呢?答案是放在全局描述符表,就是本节开头所说的 GDT (Global Descriptor Table)。全局描述符表 GDT 相当于是描述符的数组,数组中的每个元素都是8 宇节的描述符。可以用选择子(马上会讲到)中提供的下标在 GDT 中索引描述符。
image.png
段寄存器 CS 、 DS、 ES 、 FS 、 GS 、 SS,在实模式下时,段中存储的是段基地址,即内存段的起始地址。而在保护模式下时,由于段基址已经存入了段描述符中,所以段寄存器中再存放段基址是没有意义的,在段寄存器中存入的是一个叫作选择子的东西一selector。选择子“基本上”是个索引值,这里说的是基本上,其中还有其他属性咱们一会再说。用此索引值在段描述符表中索引相应的段描述符,这样,便在段描述符中得到了内存段的起始地址和段界限值等相关信息。
image.png
由于段寄存器是 16 位,所以选择子也是 16 位,在其低 2 位即第 0~ 1 位,用来存储 RPL,即请求特权级,可以表示 0、 1 、 2、 3 四种特权级。关于 RPL 我们会在专门讲特权级的章节中详尽说明,此处可以理解为请求者的当前特权级(不理解也没关系,因为在本章中它不重要〉。在选择子的第 2 位是 TI 位,即 Table Indicator,用来指示选择子是在 GDT 中,还是 LDT 中索引描述符。 TI为 0 表示在 GDT 中索引描述符, TI 为 1 表示在 LDT 中索引描述符。选择子的高 13 位,即第 3~ 15 位是描述符的索引值,用此值在 GDT 中索引描述符。前面说过 GDT 相当于一个描述符数组,所以此选择子中的索引值就是 GDT 中的下标。
选择子的作用主要是确定段描述符,确定描述符的目的,一是为了特权级、界限等安全考虑,最主要的还是要确定段的基地址。
虽然到了保护模式,但 IA32 架构始终脱离不了内存分段,即访问内存必须要用“段基址:段内偏移地址”的形式。保护模式下的段寄存器中已经是选择子,不再是直接的段基址。段基址在段描述符中,用给出的选择子索引到描述符后, CPU 自动从段描述符中取出段基址,这样再加上段内偏移地址,便凑成了“段基址:段内偏移地址”的形式。
例如选择子是 0x8,将其加载到 ds 寄存器后,访问 ds: 0x9 这样的内存,其过程是: 0x8 的低 2 位是RPL,其值为 00。第 2 是 TI ,其值 0,表示是在 GDT 中索引段描述符。用 0x8 的高 13 位 0x1 在 GDT 中索引,也就是 GDT 中的第 1 个段描述符( GDT 中第 0 个段描述符不可用)。假设第 1 个段描述符中的 3个段基址部分,其值为 0x1234。CPU 将 0x1234 作为段基址,与段内偏移地址 0x9 相加, 0x1234+0x9=0x123d。用所得的和 0x123d 作为访存地址。

打开A20地址线

如今我们是在保护模式下,我们需要突破第 20 条地址线( A20 )去访问更大的内存空间 。 而这一切,只有关闭了地址回绕才能实现 。而关闭地址回绕,就是上面所说的打开 A20Gate 。

保护模式的开关, CRO 寄存器的 PE 位

image.png
image.png

;-------------	 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便可。
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

   %include "boot.inc"
   section loader vstart=LOADER_BASE_ADDR
   LOADER_STACK_TOP equ LOADER_BASE_ADDR
   jmp 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	       ;limit=(0xbffff-0xb8000)/4k=0x7
	       dd    DESC_VIDEO_HIGH4  ; 此时dpl已改为0

   GDT_SIZE   equ   $ - GDT_BASE
   GDT_LIMIT   equ   GDT_SIZE -	1 
   times 60 dq 0					 ; 此处预留60个描述符的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


   ;-----------------  打开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	     ; 刷新流水线,避免分支预测的影响,这种cpu优化策略,最怕jmp跳转,
   jmp  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'

   jmp $

保存模式之内存段的保护

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

image.png
首先根据选择子的值验证段描述符是否超越界限。

检查过程如下:处理器先检查 TI 的值,如果 TI是0 ,则从全局描述符表寄存器gdtr中拿到 GDT 基地址和 GDT 界限值。如果TI是1,则从局部描述符表寄存器 ldtr 中拿到 LDT 基地址和 LDT 界限值。有了描述符表基地址和描述符表界限值后,把
选择子的高 13 位代入上面的表达式,若不成立,处理器则抛出异常。

在选择子检查过后,就要检查段的类型了。
这里主要是检查段寄存器的用途和段类型是否匹配。大的原则如下:
• 只有具备可执行属性的段(代码段)才能加载到 cs 段寄存器中。
• 只具备执行属性的段(代码段)不允许加载到除 cs 外的段寄存器中。
• 只有具备可写属性的段(数据段)才能加载到 SS 栈段寄存器中。
• 至少具备可读属性的段才能加载到 DS 、 ES 、FS、 GS 段寄存器中。
image.png
检查完 type 后,还会再检查段是否存在。 CPU 通过段描述符中的 P 位来确认内存段是否存在,如果P 位为 1 ,则表示存在,这时候就可以将选择子载入段寄存器了,同时段描述符缓冲寄存器也会更新为选择子对应的段描述符的内容,随后处理器将段描述符中的 A 位置为 1 ,表示己经访问过了。如果 P 位为 0,则表示该内存段不存在,不存在的原因可能是由于内存不足,操作系统将该段移出内存转储到硬盘上了。这时候处理器会抛出异常,自动转去执行相应的异常处理程序,异常处理程序将段从硬盘加载到内存后井将 P 位置为 1 ,随后返回。 CPU 继续执行刚才的操作,判断 P 位。
注意啦,以上所涉及到的 P 位,其值由软件(通常是操作系统)来设置,由 CPU 来检查。 A 位由 CPU来设置。

代码段和数据段的保护

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

栈段的保护

image.png

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值