《操作系统真相还原》第四章 保护模式入门

保护模式概述

保护模式强调的就是“保护”,它是在Inter80286CPU中首次出现。保护模式的保护究竟体现在哪,我们下面慢慢来看。

为什么有保护模式

我们首先看看以前的实模式的缺点。

  • 实模式下操作系统和用户程序属于同一特权级。
  • 用户程序所引用的地址都是指向真实的物理地址,也就是说逻辑地址等于物理地址。
  • 用户程序可以自由修改段基址,可以任意的访问内存。

这三个原因都是安全问题,安全问题肯定是最严重的问题,这从根本上就决定了用户程序甚至操作系统的数据可能被任意的修改。

  • 访问超过64KB的内存区域时要切换段基址。
  • 一次只能运行一个程序,无法充分利用计算机资源。
  • 共20条地址线,最大可用内存为1MB。

前两条是使用方面的问题,但是最后一条访问的内存实在是太小了。
为了解决这种困境,开发出了保护模式,这样物理内存地址不能直接被程序访问,程序内部的地址(虚拟地址)需要被转化为物理地址后再去访问。地址转换由处理器和操作系统共同协作完成的,处理器在硬件上提供地址转换部件,操作系统提供转换过程所需要的页表。

32位CPU具有保护模式和实模式两种模式,记住是两种模式,这两种模式都是CPU的,我想怎么用就怎么用,是可以切换的,不是说我在保护模式就无法使用实模式的寄存器了,仍然可以继续使用。
其实也就是一句话,我们所说的实模式指的是32位的CPU运行在16位模式下的状态,不是CPU变成16位的了。

初见保护模式

保护模式之寄存器扩展

计算机无论怎么发展,都要向下兼容。我认为CPU最大的特点之一就是它足够的兼容。
CPU发展到32位后,地址总线和数据总线也发展到了32位,其寻址空间更达到了2的32次方,4GB。但是寻址方式还是16位时的老方法,段基址:段内偏移地址,寄存器可以用来指定段内偏移地址。
为了让一个寄存器就能访问4GB的空间,需要将寄存器宽度提升到32位。
除了段寄存器外,其他的寄存器也都由原来的16位扩展到了32位。段寄存器16位就够用了。
既然CPU都要向下兼容,那么寄存器也同样需要,我们不能吧一切推倒重来,我们就必须在原来的基础上进行扩展,各寄存器在原来16位的基础上再次向高位拓展16位,变成32位的寄存器。扩展的英文extend,我们就在寄存器的前面加上e。请添加图片描述
图中的低16位是为了兼容实模式,可以单独使用。高16位没办法单独使用。
偏移地址跟实模式是一样的,但是段基址就不一样了。因为我们的保护模式强调的就是保护,那肯定首先的就是安全,为了安全我们要为段基址添加点约束条件,这些约束条件就是对内存段的描述信息了。我们专门找了个数据结构——全局描述符表,其中有一个表项为段描述符,大小为64字节,用来描述各个内存段的起始地址,大小,权限等信息。这个描述符表很大,所以放在内存中,由GDTR寄存器指向。
这是段寄存器保存的就不再是段基址了,而是一个”选择子“,这个我们下面再说,这里不多说。
但是访问内存中的段描述符表是很耗费时间的,CPU的效率就会降低,所以我们的保护模式引入了一个叫段描述符缓冲寄存器。CPU把获得的内存段信息,整理后放入段描述符缓冲寄存器,以后每次访问相同的段时,直接拿来用就好了。
在实模式下,段基址左移4位后的结果被放入段描述符缓冲寄存器,以后每次应用一个段时,就直接走段描述符缓冲寄存器,知道该段寄存器被重新赋值。
虽然80286中引入了保护模式,但是80286的问题也很明显,单独的一个寄存器无法访问到全部的内存空间,也就是若用寄存器存储段内偏移地址,只能访问到64KB大小的段,所以后来Inter直接将地址总线改为32位。

保护模式之寻址扩展

在实模式下寄存器都有固定的使命。但是在保护模式下,优势就体现出来了,基址寄存器是所有的32位通用寄存器,变址寄存器时除了esp之外的所有32位通用寄存器,偏移量由实模式的16位变成了32位,还增加了一个比例因子。请添加图片描述

保护模式之运行模式反转

在实模式下,指令和操作数都是16位的,但是它也可以使用32位的资源。同样的在保护模式下,指令和操作数都是32位的,它也可以使用16位的资源。兼容带来了好处也带来了坏处,好处就是CPU很强大,可以同时支持16位指令和32位指令,但是坏处就是虽然两种模式可以转换,但是机器怎么知道你输入的指令是需要编译16位的还是32位的呢?
所以我们需要让机器知道我们要让机器帮我们编译成多少位的机器码。
我们有一个bits的指令,它的格式就是[bits16]或[bits32]
范围就是当前的bits标签到下一个bits标签。
接下来我们看一看bits的具体用法
在这里插入图片描述

源码                    地址       内容              反汇编
[bits 16]
mov ax, 0x1234			00007C00  B83412            mov ax,0x1234
mov dx, 0x1234			00007C03  BA3412            mov dx,0x1234

[bits 32]
mov eax, 0x1234			00007C06  B83412            mov ax,0x1234
						00007C09  0000              add [bx+si],al
mov edx, 0x1234			00007C0B  BA3412            mov dx,0x1234
						00007C0E  0000              add [bx+si],al

我们主要看内容列,在16位的模式下mov ax,0x1234的机器码是B83412,32位的机器码是B834120000,操作码都是B8,所以在不同模式下的相同指令的操作码是相同的。但是意义是不相同的,一个是ax寄存器,一个是eax寄存器。
我们可以看见操作码都是相同的,那么计算机怎么识别出来呢?所以在指令的最前面要加一个前缀,用来告诉CPU应用此指令的模式。
我们说完了前缀,下面我们主要说说反转前缀,什么是反转前缀?如果要用另一种模式下的操作数大小,需要在指令前添加指令前缀0x66,将当前模式临时的变为另一种模式。这就是反转。有操作数反转前缀0x66和寻址方式反转前缀0x67.
在这里插入图片描述

源码                   		 地址       内容              反汇编
1 [bits 16]
2 mov ax, 0x1234			00007C00  B83412           mov ax,0x1234
3 mov eax, 0x1234			00007C03  66B834120000     mov eax,0x1234

4 [bits 32]
5 mov ax, 0x1234			00007C09  66B83412     		mov eax,0x34b81234
6 mov eax, 0x1234			00007C0F  B8341200         adc al,[bx+si]
							00007C11  00                db 0x00

这是一个实际的反转前缀的例子,第二行本来就是16位的指令,所以机器码是不变的,但是第三行中32位的寄存器,属于32位操作数,但是当前模式是16位的,所以用0x66来反转操作数前缀,第五行是同样的,在32位的模式下,16位的寄存器也同样需要进行反转。上面介绍的是反转操作数大小前缀0x66,接下来我们说反转前缀0x67.
不同模式之间不仅可以使用对方模式下的操作数,还可以使用对方模式下的寻址方式。接下来我们实践一下

源码                   			 地址       内容              反汇编
[bits 16]
1 mov word [bx], 0x1234			00007C00  C7073412          mov word [bx],0x1234
2 mov word [eax], 0x1234		00007C04  67C7003412        mov word [eax],0x1234
3 mov dword [eax], 0x1234		00007C09  6667C70034120000  mov dword [eax],0x1234
[bits 32]
4 mov dword [eax], 0x1234		00007C11  C7003412          mov word [bx+si],0x1234
								00007C15  0000              add [bx+si],al
5 mov word [eax], 0x1234		00007C17  66C7003412    	mov dword [bx+si],0xc7671234
6 mov dword [bx], 0x1234		00007C1E  67C707            pop es
								00007C1F  3412              xor al,0x12
								00007C21  0000              add [bx+si],al

跟上面的0x66反转前缀差不多,本身符合16位模式的,不需要添加,如第一行。但是第二行和第三行是eax寄存器作为基址寻址,本身不属于实模式,所以要添加0x67,我们可以看到第三行中不但有0x67还有0x66,dword是连续写入4字节大小的数据,操作数大小也由2字节变成了4字节,添加了0x66前缀。第四行不需要解释。第五行word伪指令,表示2字节,连续写入2字节。这改变了操作数的大小,所以有0x66。第六行跟前面的第二行是一样的都改变了基址寻址的寄存器。

保护模式之指令扩展

在16位的实模式下,CPU的操作数是16位,在32位的保护模式下,操作数扩展到了32位,于是涉及到操作数变化的指令也要跟着扩展,既要兼容16的操作数,也要支持32位的操作数。
比如add加法指令,sub减法指令,在实模式下,它的操作数是8位,16位,但是在如今的保护模式下,它不仅要支持8位,16位,还要支持32位。
同样的指令,不管是在实模式下还是在保护模式下都可以同时处理16位和32位的数据,我们具体看看push这个指令。
push的操作数类型有三个,分别是立即数,寄存器,内存。
我们先看第一种情况,对立即数。
在实模式下,压入8位立即数,由于实模式默认的操作数是16位的,CPU会将其扩展到16位后再压栈。
压入16位立即数,CPU直接入栈,sp-2.压入32位立即数,同样的直接入栈,sp-4。
对寄存器和内存,无论是在16位模式下,还是32位模式下,
压入的是16位数据,CPU直接压入2字节,栈指针esp-2,
压入的是32位数据,CPU压入4字节,栈指针esp-4。

全局描述符表

什么是全局描述符表?

保护模式下内存段不再是段寄存器加载一下段基址就好了,段变得复杂起来,段的信息也开始增加,我们需要提前把段定义好再开始使用。
全局描述符表就是保护模式下内存段的登记表。

段描述符

在保护模式中我们依然使用的是段基址:段内偏移地址的形式,保护模式突出就是一个安全,我们要在保证安全性的情况下,依然是这种形式。
我们清晰的明白我们是在保护模式下,为了突出保护两个字,我们需要在访问段的时候加一些适当的约束。所以有了这些约束之后,我们一个段寄存器是肯定放不下的,因此我们设计了一个数据结构–全局描述符表,在这个表里有一个表项为段描述符,大小为64字节,这个描述符就是用来描述自己所应对的那个内存段的起始地址,大小,权限等信息。全局描述符表表示了内存的所有信息,很大,因此就出现了一个GDTR的寄存器专门指向表地址。
在这里插入图片描述
上面的图片是地址的寻找过程。
有了这个下数据结构后,我们段基址寄存器保存的就不是段基址了,他保存的就是所寻址段在全局描述符表下的某一个段描述符的索引,我们可以将全局描述符看成一个数组,然后每个段描述符是其中的元素,我们只要寻找段描述符需要给出的下标进行索引就好了,段寄存器就保存了这个下标,它的另一个名字叫做选择子。
请添加图片描述
全局描述符表,首先我们了解到它是类似于段描述符为元素的数组,所以我们先给出段描述符的结构(占8字节)
下面我们对这张图进行解释
**保护模式的地址总线宽度是32位的,段基址需要32位来表示。
段界限表示段扩展边界的最值,即最大扩展了多少或最小扩展了多少,段界限用20位来表示,所以最大界限为2^20,这个段界限值为一个单位量,它的单位不是字节就是4KB,这是由描述符中的G位来决定的,因此段的大小不是2的20次方个字节就是2的20次方****4KB=4GB.
G位如果为1,则表示段界限粒度为1字节,若为0则表示段界限粒度为4KB
S字段,一个段描述符,在CPU中分为两大类,一个是描述系统段,一个是描述数据段。在CPU眼里,凡是硬件运行所需要的东西都可以称之为系统,凡是软件运行所需要的东西都可以称之为数据,无论是代码,还是数据,包括栈都是作为硬件的输入,都只是给硬件提供数据而已,所以代码段在描述符中也属于数据段。S为0表示系统段,S为1表示数据段。
8~11位为TYPE字段,用来指定本描述符的类型。这个TYPE字段需要由上述S位来决定具体意义,**具体对应的结构看下面这个表
请添加图片描述

段描述符寄存器GDTR

内存中存在着一个全局描述符表GDT,里面存放着一个个段描述符,所以我们需要一个寄存器来指向这个描述符表,这个寄存器就是GDTR,这个寄存器有48位。
请添加图片描述
这个寄存器的低16位表示GDT以字节为单位的界限值,相当于GDT字节大小-1,后32位表示GDT的起始地址,而每个段描述符占用八字节,则最大的段描述表可存放2^16/8=8192=2 ^13个段或门,不过对于此寄存器的访问无法用mov gdtr,xxxx这种指令对其进行初始化,存在专门的指令实现这个功能,这就是lgdt指令。虽然我们是为了进入保护模式才使用这个指令,看似此指令只在实模式下执行,其实在保护模式下也可以执行。
lgdt指令格式为:lgdt 48位内存数据
我们知道了全局描述符表,段描述符,选择子的概念后,下一步肯定就是要对其进行合理的运用了。
由于咱们的段基址寄存器CS,DS,ES,FS,GS,SS为16位,所以他们存储的选择子也是16位的,其中低2位用来存储RPL,即请求特权级,可以表示4中特权级,在选择子的第2位是TI位,用来指示选择子是在GDT还是LDT中。TI为0在GDT,为1在LDT。剩余的13位表示索引值。请添加图片描述

打开A20地址线

我们首先要知道什么是地址回绕,出入实模式下时,只有20位地址线,即A0~A19,20位地址线能表示2^20字节,即1MB的大小,0x0 ~0xFFFFF,若内存超过1MB,则需要21条地址线支持。因此若地址进位到1MB以上,如0x100000,由于没有21位地址线,则会丢弃多余位数,变成0x00000,这就是回绕。
请添加图片描述
当CPU发展到80286后,虽然地址线从20位发展到24位,可以访问16MB,但任何时候兼容都是第一位的。80286是第一款具有保护模式的CPU,它在实模式下也应该和之前的8086一模一样。也就是仍然只使用20条地址线,但是80286有24条地址线,也就是A20地址线是存在的,若访问0x100000~0x10FFEF之间的内存,系统将直接访问这块物理内存,而不会像之前内样回绕到0.

开启保护模式

说了这么多,终于要开始保护模式了
进入保护模式的最后一步,我们先介绍一下控制寄存器CR0
请添加图片描述
请添加图片描述
我们只需要关注PE段,此为用于开启保护模式,是保护模式的开关,只有当打开此位后,CPU才会真正的进入保护模式。

进入保护模式实现

我们已经把保护模式的相关的概念解释了很多了,下面我们要实现进入保护模式。
定义了模块化段描述符字段宏和模块化选择子字段宏的boot.inc

;---------- loader 和 kernel ------------
LOADER_BASE_ADDR equ 0x900     ;内存首址
LOADER_START_SECTOR equ 0x2     ;硬盘扇区
;---------- gdt描述符属性 ---------------
DESC_G_4K equ 1_000000000000000000000000b     ;G位,表示粒度
DESC_D_32 equ  1_00000000000000000000000b     ;D位,表示为32位
DESC_L    equ   0_0000000000000000000000b     ;64位代码标记,此处为0即可
DESC_AVL  equ    0_000000000000000000000b     ;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_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_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 + 0x00

;----------- 选择子属性 --------------
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 ;loader在实模式下的栈指针地址
jmp loader_start

;构建gdt及其内部描述符
GDT_BASE: dd 0x00000000             ;4字节
          dd 0x00000000             ;4字节,无效描述符,防止选择子未初始化
CODE_DESC: dd 0x0000FFFF            ;dd是伪指令,表示define double-word,也就是定义双字变量,这里的0xFFFF表示段界限
            dd DESC_CODE_HIGH4      ;代码段描述符
DATA_STACK_DESC: dd 0x0000FFFF
                  dd DESC_DATA_HIGH4    ;栈段描述符,也就是数据段描述符,这俩共用一个段是因为方便,至于为什么这里栈的P位为什么不是1,也就是向下扩展,这是因为段描述符是由CPU检查的,而CPU并不知道这个段的作用,程序员若要实现栈向下扩展只需要使得其esp在push时减小即可
VIDEO_DESC: dd 0x80000007;limit=(0xbffff-0xb8000)/4k=0x7,这里的0xb8000~0xbffff是实模式下文本模式显存适配器的内存地址,因此段界限即为上述方程
            dd DESC_VIDEO_HIGH4 ;此时dpl为0,此乃显存段描述符
;-------- 以上共填充了3个段描述符 + 1个首段无效描述符-------------
GDT_SIZE equ $ - GDT_BASE           ;计算当前GDT已经填充的大小
GDT_LIMIT equ GDT_SIZE - 1          ;GDT界限值,表示GDT大小
times 60 dq 0           ;此处预留60个描述符空位,这里跟上面一致,表示define quad-word ,也就是定义60个以0填充的段描述符,这里的times是循环次数

;------ 这里定义选择子------------
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    ;define word
        dd GDT_BASE
loadermsg db 'I am loader in real.'

loader_start:
;----------------------------------------------
;INT 0x10   功能号:0x13    功能描述:打印字符串
;----------------------------------------------
;输入:
;AH子功能号=13H
;BH = 页号
;BL = 属性
;CX = 字符串长度
;(DH,DL) = 字符串地址
;AL = 显示输出方式
; 0 -- 字符串只含显示字符,显示属性在BL中,显示后光标位置不变
; 1 -- 字符串只含显示字符,显示属性在BL中,显示后光标位置改变
; 2 -- 字符串只含显示字符和显示属性,显示后光标位置不变
; 3 -- 字符串只含显示字符和显示属性,显示后光标位置改变
; 无返回值
  mov sp ,LOADER_BASE_ADDR
  mov bp ,loadermsg     ;ES:BP = 字符串地址
  mov cx ,20            ;CX = 字符串长度                ;注意这里使用的是BIOS中断,一旦进入保护模式就没有BIOS中断了
  mov ax ,0x1301        ;AH = 13H,AL = 01H
  mov bx ,0x001f        ;页号为0,蓝底粉红字
  mov dx ,0x1800
  int 0x10              ;10H号中断

;---------------- 准备进入保护模式 ------------------
;1 打开A20
;2 加载gdt
;3.将CR0的PE位置0,

  ;------------=- 打开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'    ;默认文本显示模式是80×25,也就是每行是80个字符,没个字符两个字节,因此偏移地址为80×2 = 160

  jmp $

该代码实现的主要功能就是首先在实模式下输出 I am loader in real.,然后在保护模式下输出‘p’。
在我们没进入到保护模式时,我们首先来查看CR0寄存器的位值,这里小写说明为0,大写说明为1
在这里插入图片描述
然后我们进入保护模式之前,我偶们可以使用info gdt命令来查看我们自己构建的gdt表,如下图,这里也看到确实有咱们构造的三个段描述符,
在这里插入图片描述
在我们进入保护模式之后,我们再次使用命令creg来查看CR0寄存器的值,这里可以看到PE大写了
在这里插入图片描述
然后我们确实可以看到我们在虚拟机中实模式下输出 I am loader in real.,然后在保护模式下输出‘p’。
在这里插入图片描述

处理器微架构

什么是流水线

我们看张图就能理解
在这里插入图片描述
这个图片展示的很清晰了。

乱序执行

乱序执行就是在保持不发生数据冲突的前提下,我们可以将后面的代码移到前面来做,这是根据编译器来自主实现的。

分支预测

分支预测是指当处理器遇到一个分支指令时,是该把分支左边的指令放到流水线上还是右边的流水线上。可以理解为处理器根据局部性原理,通过计算那边的代码可能出现的概率大,然后就将其加入流水线。当然可能会出现预测错误的情况,这种情况没事,我们只需要清空流水线就好了。

清空流水线

我们看到上面的代码中有这条命令

  jmp dword SELECTOR_CODE:p_mode_start ;刷新流水线

这个刷新流水线是个什么东西,没有这行可以吗?
这句话是什么意思?
第一,段描述符缓冲寄存器还没有更新,还是实模式下的值,在进入保护模式之后要填写正确的信息。这里的寄存器低20位为段基址,其余默认为0.
第二,流水线中指令译码错误,在默认的情况下,若未使用bits伪指令来设置运行环境,编译器将一直以16位实模式来作为指令编译格式,我们都知道CPU为了提高流水线效率而采用流水线,这样指令就是重叠执行的,我们在执行上面这条指令时被编译为16位,因此咱们添加了dword指令,因此其机器码会添加0x66模式反转前缀,这里我们就不对其进行反汇编编译了,可以看前面。而在这条指令之后由于添加了bits32伪指令,所以全是32位指令。

流水线怎么工作

在我们即将把cr0的pe位置1时,它之后的部分指令已经被送上了流水线,但是段描述符缓冲寄存器在实模式下已经使用了,其中低20位为段基址,其他默认为0,因此描述符中的D位为0,表示16位,这里就有问题了,也就是说现在流水线上都是按照16位操作数来译码的,所以我们现在所做的工作就是既要改变代码段描述符缓冲寄存器的值,又要清空流水线。
有人会问了,这个代码能同时完成这两个功能吗?
肯定是可以的,代码段寄存器cs只有用远过程调用指令call,远转移指令jmp,远返回指令retf等指令改变,没有直接改变cs的值的方法。但当CPU遇到jmp指令时,会将已经送上流水线的指令清空,所以jmp有着清空流水线的功能。

内存段保护

当我们引用一个内存段时,实际上就是往寄存器中加载选择子,这里的保护有索引越界,也就是保持你选择子的索引字段不超过段描述表的下标值
索引越界异常检查后还需要检查段寄存器的用途与段类型是否匹配,也就是检查段描述符中的TYPE类型
请添加图片描述
然后就是检查段是否存在,那就是检查段描述符中的P位是否为1,若为1则可以将选择子载入段寄存器,同时段描述符缓冲寄存器也会更新为选择子对应的描述符内容。

总结

这章从为什么有保护模式到保护模式下的寄存器,寻址,运行模式,指令的扩展,到保护模式特有的全局描述符表。后面的全局描述符表部分还是十分的复杂而且有难度,还是需要反复的思考和揣摩。
参考添加链接描述
添加链接描述

  • 16
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值