CPU工作模式- 保护模式

保护模式

概述

  • 随着软件的规模不断增加,需要更高的计算量、更大的内存容量
  • 内存一大,首先要解决的问题是寻址问题,因为16位的寄存器最好只能表示 2 16 2^{16} 216个地址,所以CPU的寄存器和运算单元都要扩展成32位
  • 虽然扩展CPU内部器件的位数解决了计算和寻址问题,但仍然没有解决前面那个实模式场景下的问题,导致前面的场景出现的问题的原因
    • CPU对任何指令不加区分地执行
    • CPU对访问内存的地址不加限制

保护模式寄存器

寄存器描述
EAX、EBX、ECX、EDX、EDI、ESI、EBP32位通用寄存器,里面可以存放数据、地址、参与运算
EIP32位程序指针寄存器,始终指向下一条指令的地址
ESP栈指针寄存器,始终指向当前栈顶
CS、DS、ES、SS、FS、GS16位段寄存器,里面存放一个内存段的描述符索引
EFLAGS32位CPU标志寄存器,里面存放CPU执行指令产生的状态位
CR0、CR1、CR2、CR332位CPU控制器,控制CPU的功能控制特性,如开启保护模式等
  • 保护模式相比于实模式,增加了一些控制寄存器和段寄存器,扩展通用寄存器的位宽,所有的通用寄存器都是32位
  • 还可以单独使用低16位,这个低16位又可以拆分成两个8位寄存器

保护模式特权级

  • 为了区分哪些指令(如in、out、cli)和哪些资源(如寄存器/I/O端口、内存地址)可以被访问,CPU实现了特权级

  • 在这里插入图片描述

    • 特权分为4级,R0 ~ R3,每个特权级执行指令的数量不同,R0可以执行所有指令,R1、R2、R3依次递减,它们只能执行上一级指令数量的子集
    • 而内存的访问则是靠后面所说的段描述符和特权级相互配合去实现
  • 上面的圆环图,从外到内,既能体现权利的大小,又能体现各特权级对资源控制访问的多少,还能体现各特权级之间的包含关系

  • R0拥有最大权利,可以访问低特权级的资源,反之则不行

保护模式段描述符

  • 目前为止,内存还是分段模型,要对内存进行保护,就可以转换成对段的保护

  • 由于CPU的扩展导致了32位的段基地址和段内偏移,所以 16 位的段寄存器肯定放不下

  • 放不下就要找内存借空间,然后把描述一个段的信息封装成特定格式的段描述符,放在内存中

    • 在这里插入图片描述

    • 一个段描述符有64位8字节数据,里面包含了段基地址、段长度、段权限、段类型(可以是系统段、代码段、数据段)、段是否读写、可执行

  • 在这里插入图片描述

    • 多个段描述符在内存形成全局段描述符表,该表的基地址和长度由CPU和GDTR寄存器指示
    • 一眼看出,段寄存器中不再存放段基地址,而是具体段描述符的索引,访问一个内存地址时
    • 段寄存器中的索引首先会结合GDTR寄存器找到内存中的段描述符,再根据其中的段信息判断能不能访问成功

保护模式段选择子

  • 在这里插入图片描述

    • CS、DS、ES、SS、FS、GS这些段寄存器是由影子寄存器、段描述符索引、描述符表索引、权限级别组成
    • 影子寄存器是靠硬件来操作,对系统程序员不可见,是硬件为了减少性能损耗而设计的一个段描述符的高速缓存,不然每次内存访问都要要去内存中查表,那性能损失是巨大的,影子寄存器也正好是64位,里面存放了8字节段描述符数据
    • 低三位之所以能放TI和RPL,是因为段描述符8字节对齐,每个索引低3位都位0
      • 这部分的意思是指,因为段描述符8字节对齐,所以它的索引都是8的整数倍,因此低3位都为0
      • 类似1000,10000,11000这样的。所以低3位可以用来做其他事,这里就用来放TI和RPL
  • 通常情况下,CS和SS中RPL就组成了CPL(当前权限级别),所以常常是RPL=CRL

    • 进而 CPL 就表示发起访问者要以什么权限去访问目标段,当 CPL 大于目标段 DPL 时,则 CPU 禁止访问
    • 只有 CPL 小于等于目标段 DPL 时才能访问

保护模式平坦模型

  • 但是 x86 CPU 并不能直接使用分页模型,而是要在分段模型的前提下,根据需要决定是否要开启分页
  • 因为这是硬件的规定,程序员是无法改变的。但是我们可以简化设计,来使分段成为一种“虚设”,这就是保护模式的平坦模型
  • 发现 CPU 32 位的寄存器最多只能产生 4GB 大小的地址,而一个段长度也只能是 4GB
    • 所以我们把所有段的基地址设为 0,段的长度设为 0xFFFFF
    • 段长度的粒度设为 4KB,这样所有的段都指向同一个(0~4GB-1)字节大小的地址空间
  • 例子
GDT_START:
knull_dsc: dq 0
;第一个段描述符CPU硬件规定必须为0
kcode_dsc: dq 0x00cf9e000000ffff
;段基地址=0,段长度=0xfffff
;G=1,D/B=1,L=0,AVL=0 
;P=1,DPL=0,S=1
;T=1,C=1,R=1,A=0
kdata_dsc: dq 0x00cf92000000ffff
;段基地址=0,段长度=0xfffff
;G=1,D/B=1,L=0,AVL=0 
;P=1,DPL=0,S=1
;T=0,C=0,R=1,A=0
GDT_END:

GDT_PTR:
GDTLEN  dw GDT_END-GDT_START-1
GDTBASE  dd GDT_START
  • 段长度需要和 G 位配合,若 G 位为 1 则段长度等于 0xfffff 个 4KB
  • 上面段描述符的 DPL=0,这说明需要最高权限即 CPL=0 才能访问

保护模式中断

概述

  • 因为实模式下CPU不需要做权限检查,所以它可以直接中断向量表中的值装载CS:IP寄存器

中断门结构

  • 在这里插入图片描述

  • 而保护模式下的中断权限检查,还有特权级的切换,就需要扩展中断向量表的信息

  • 即每个中断用一个中断门描述符来表示,也可以简称中断门

中断流程图例

  • 在这里插入图片描述

  • 保护模式要实现中断,必须在内存中有一个中断向量表,同样由IDTR寄存器指向

  • 只不过中断向量表中的条目变成了中断门描述符

中断流程

  • 产生中断后,CPU首先会检查中断号是否大于最后一个中断门描述符, x86 CPU 最大支持256个中断源(即中断号: 0 ~ 255)
  • 然后检查描述符类型(是否是中断门或者陷阱门)、是否为系统描述符,是否存在于内存中
  • 接着,检查中断门描述符中的段选择子指向的段描述符
  • 权限检查
    • 如果CPL小于等于中断门的DPL并且CPL大于等于中断门中的段选择子,就指向段描述符的DPL
    • 进一步的,CPL等于中断门中的段选择子指向段描述符的DPL,则为同级权限不进行栈切换,否则进行栈切换
    • 如果进行栈切换,还需要从TSS中加载具体权限的SS、ESP、当然也要对SS中段选择子指向的段描述符进行检查
  • 做完这一系列检查之后,CPU才会加载中断门描述符中目标代码段选择子到CS寄存器中,把目标代码段偏移加载到EIP寄存器中

切换到保护模式

概述

  • x86 CPU在第一次加电和每次reset后,都会自动进入实模式,想要进入保护模式,就需要程序员写代码实现从实模式切换到保护模式

切换步骤

第一步,准备全局段描述符
GDT_START:
knull_dsc: dq 0
kcode_dsc: dq 0x00cf9e000000ffff
kdata_dsc: dq 0x00cf92000000ffff
GDT_END:
GDT_PTR:
GDTLEN  dw GDT_END-GDT_START-1
GDTBASE  dd GDT_START
第二步,加载设置GDTR寄存器,使之指向全局段描述符表
lgdt [GDT_PTR]
第三步,设置CR0寄存器,开启保护模式
;开启 PE
mov eax, cr0
bts eax, 0                      ; CR0.PE =1
mov cr0, eax         
第四步,进行长跳转,加载 CS 段寄存器,即段选择子
jmp dword 0x8 :_32bits_mode ;_32bits_mode为32位代码标号即段偏移
  • 为什么要进行长跳转,这是因为我们无法直接或间接 mov 一个数据到 CS 寄存器中
  • 因为刚刚开启保护模式时,CS 的影子寄存器还是实模式下的值,所以需要告诉 CPU 加载新的段信息
最后
  • 接下来,CPU 发现了 CRO 寄存器第 0 位的值是 1
  • 就会按 GDTR 的指示找到全局描述符表,然后根据索引值 8,把新的段描述符信息加载到 CS 影子寄存器,当然这里的前提是进行一系列合法的检查
  • CPU 真正进入了保护模式,CPU 也有了 32 位的处理能力
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值