首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >CPU保护模式

CPU保护模式

作者头像
shysh95
发布2021-07-16 10:16:19
发布2021-07-16 10:16:19
1.1K00
举报
文章被收录于专栏:shysh95shysh95
运行总次数:0

Hi~朋友,点点关注不迷路

摘要

  1. 为什么需要保护模式
  2. 寄存器扩展
  3. 寻址扩展
  4. 运行模式反转
  5. 指令扩展

1. 保护模式

保护模式是在CPU发展过程中相对于实模式的一种模式,实模式在安全和内存访问方面具有以下缺点:

  • 实模式下操作系统和用户程序处于同一特权级
  • 用户程序所引用的地址是真实的物理地址
  • 用户程序可以自由修改段基址,可以访问其他的程序的内存

以上三点主要是针对我们的程序安全问题。

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

以上三点主要是内存访问时具有弊端。

为了保护我们的操作系统不受损坏和解决内存管理问题,CPU厂商开发出保护模式。物理地址不再被应用程序访问,程序内部的地址(虚拟地址)需要被转化为物理地址以后再去访问内存。

这种地址转换是由CPU和操作系统共同完成,CPU提供转换部件,操作系统提供转换过程中的页表。

2. 寄存器扩展

CPU在发展为32位地址总线以后,寻址空间可以达到4GB。16位CPU时的寄存器只有16位的宽度,为了可以访问到20位地址总线的寻址空间(1MB),我们需要借助段基址:段内偏移地址的方式来突破16位寄存器的访问限制,这种方式在当时只是一种妥协。

当保护模式出现以后,CPU开发商不再偷懒,直接重做CPU,为了让寄存器可以访问到4GB的空间,需要将寄存器宽度提升至32位。

因此除了段寄存器外,通用寄存器、指令指针寄存器、标志寄存器都由原来的16位扩展到32位。

在原有的寄存器基础上,还有两个寄存器(在后续的全局描述符时再讲):

  • 全局描述符寄存器
  • 段描述符缓冲寄存器

3. 寻址方式扩展

CPU实模式下的内存寻址中的基址寻址寄存器只能是BX和BP,变址寄存器只能是SI和DI。

当CPU进入保护模式运行以后,限制将会烟消云算,基址寄存器可以使用所有的32位通用寄存器,变址寄存器可以使用除ESP以外的32位通寄存器,偏移量由16位扩展为32位,另外变址寄存器还可以乘以一个因子,不过该因子只能是1、2、4、8。

内存寻址方式如上图所示,左侧为实模式下寻址方式,右侧为保护模式寻址方式。

代码语言:javascript
代码运行次数:0
运行
复制
mov eax, [eax+edx*8+0x12345678]
mov eax, [eax+edx*2+0x8]
mov eax, [ecx*4+0x1234]

4. 运行模式反转

CPU可以同时支持16位指令和32位指令,假如在我们的程序中前半段运行在实模式,此时运行的指令都是16位,后半段运行在保护模式下,此时的运行的指令都是32位指令。

编译器提供了伪指令bits告诉编译器我的指令要编译成多少位,格式为[bits 16]或[bits 32]。

代码语言:javascript
代码运行次数:0
运行
复制
[bits 16]
mov ax, 0x1234
mov dx, 0x1234

[bits 32]
mov eax, 0x1234
mov edx, 0x1234

通过nasm -o test.bin test.asm -l test.lst编译生成的机器码如下:

通过上图可以看出,不同模式下虽然具有同样的操作码,寻址方式和操作数类型,但是意义却不相同,这里16位下代表的是ax、dx寄存器,而在32位下代表的是eax、edx寄存器。

CPU运行在实模式下可以使用保护模式的资源,同样的在保护模式下也可以使用实模式的资源,但是同样的操作码代表的是不一样的意义,为了能让CPU准确知道执行的是哪种模式下的命令,在机器码的最前面就应该存放一些标识来区分,因此在指令的最前面有个前缀字段,用来告诉CPU应用此指令的模式。

运行模式反转需要需要借助两个反转前缀:

  • 操作数反转前缀:0x66
  • 寻址方式反转前缀:0x67

4.1 操作数反转0x66

在指令中添加了0x66反转前缀以后:

  • 假设当前运行模式是16位实模式,操作数将变为32位
  • 假设当前运行模式是32位保护模式,操作数将变为16位
代码语言:javascript
代码运行次数:0
运行
复制
[bits 16]
mov ax, 0x1234
mov eax, 0x1234

[bits 32]
mov ax, 0x1234
mov eax, 0x1234

上述汇编代码生成的机器码如下:

通过编译后的代码我们可以看出,由于我们第3行使用到了32位的寄存器eax,然而该代码的运行模式又处于16位,因此需要使用0x66前缀将16位转换为32位;同样的在第6行我们在32位模式下使用到了16位的寄存器ax,因此也需要使用0x66来将32位模式转为16位模式。

4.3 寻址方式反转前缀0x67

代码语言:javascript
代码运行次数:0
运行
复制
[bits 16]
mov word [bx], 0x1234
mov word [eax], 0x1234
mov dword [eax], 0x1234

[bits 32]
mov dword [eax], 0x1234
mov word [eax], 0x1234
mov dword [bx], 0x1234

上述汇编代码生成的机器码如下:

上述我们的目的操作数都是采用的内存寻址中的基址寻址,16位模式下我们使用的是BX寄存器,32位模式下我们使用的是eax寄存器。

word代表两个字节,dword代表四个字节。

第2行代码的含义是将0x1234写入bx寄存器指向的内存地址处(写入的数据宽度为2字节),符合是16位模式,因此不包含任何反转前缀。

第3行代码的含义是将0x1234写入eax寄存器指向的内存地址(写入的数据宽度为2字节),由于32位模式下的eax运行在16位模式下,因此需要将通过0x67进行寻址方式的反转。

第四行除了和第三行一样都使用32位的基址寻址以外(因此需要加0x67前缀),还通过dword表示需要向eax寄存器指定的内存处连续写入4个字节(表明操作数为36位),因此还需要通过操作数反转前缀(0x66)使得操作数转化为36位模式。

第7行代码dword表明操作数为36位,eax又是32位模式下的寄存器,又因为该代码运行在32位模式下,因此不需要添加任何反转前缀。

第8行代码word表示写入两个字节(16位),因此需要添加操作数反转前缀0x66。

第9行代码由于在32位模式下使用了16位模式下的基址寄存器,因此需要添加寻址方式反转前缀0x67,但由于写入的操作数为dword(32位),因此不需要添加操作数反转前缀0x66。

5. 指令扩展

寄存器,操作数、寻址方式都在32位出来以后得到扩展,指令也不例外。比如我们常用的push指令:

5.1 push压入立即数

当运行在实模式下时:

  • 如果立即数是8位,会将其扩展为16位,然后再将sp-2
  • 如果立即数是16位,则将sp-2
  • 如果立即数是32位,则将sp-4

当运行在保护模式下时:

  • 如果立即数是8位,会将其扩展为32位,然后再将sp-4
  • 如果立即数是16位,则将sp-2
  • 如果立即数是32位,则将sp-4

5.2 push压入段寄存器

当运行在实模式下时,CPU压入2字节,然后再将sp-2。

当运行在保护模式下时,CPU压入4字节,然后再将sp-4。

5.4 push压入通用寄存器或内存

代码语言:javascript
代码运行次数:0
运行
复制
push ax
push eax
push word [0x1234]
push dword [0x1234]

无论实模式或者保护模式:

  • 如果操作数是16位,sp-2
  • 如果操作数是32位,sp-4

本期CPU保护模式的扩展就到这,更多保护模式的特性我们下期再见!

感谢阅读,欢迎“转发”+“留言”

赏个在看吧,你的点赞是对我最大的鼓励

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-07-02,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 程序员修炼笔记 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档