操作系统——保护模式(五)

操作系统——保护模式(五)

2020-09-16 21:14:09 hawk


概述

  这篇博客主要涉及保护模式相关的基础知识,为后边程序的编写做知识铺垫。毕竟是基础知识的记录,会比较枯燥,对于有基础的或者对这些基础不是很感兴趣的,大家可以自行跳过,等需要的时候再回过头来翻看即可。因为我比较菜,所以先记录一下自己的学习过程。


 

保护模式

保护模式概述

  首先需要说明的是,保护模式实际上很多都是CPU硬件原生提供的——比如更为安全的管理内存,处理器在硬件上提供地址转换部件;操作系统提供转换过程中所需要的页表等;并且按照CPU的设计,也必须有这些东西,CPU才能正常运行。因此我们在学习保护模式的时候,不需要深入到硬件之中挖掘其工作原理,因为这些都是CPU提供给我们的功能,我们只需要将其当作黑箱,了解其功能并学会使用即可。

CPU——保护模式和实模式

  这里也需要说明一下,32位CPU具有保护模式和实模式这两种运行模式,可以兼容实模式下的程序。也就是说,当32位CPU以16位的实模式运行时,并不是变成纯粹的16位的CPU,其可以看作更为强大的16位的CPU,但其本质仍然是32位,因此仍然具备处理32位操作数的能力。书上有一个很形象的例子——就像中学生做小学生的题目一样,可以用小学生的知识方法来做,但这并不要求自己退化成小学生的知识水平;如果不强调方法,甚至可以使用中学知识来解决小学问题,无非大马拉小车罢了。

保护模式下寄存器

  保护模式下自然使用的是32位CPU,其对应的寄存器自然也是32位的。当然,众所周知,兼容性是计算机科学的优良传统,因此CPU自然兼容原始的16位下的CPU及其工作环境,32位CPU中寄存器主要如下所示

 

   可以看到,通用寄存器、指令指针寄存器和标志寄存器都由原来的16位拓展到了32位,对应的低16位适用于兼容实模式的,可以单独使用;而段寄存器和原来的保持一样,仍然是16位没有变化。当然,对于保护模式下的寄存器,还有一些知识没有提到——GDTR寄存器,这个等到后面需要的时候在进行讲解即可。

保护模式下寻址

  实际上,在保护模式下,内存寻址要比实模式下的内存寻址灵活的多。对于实模式下的内存寻址,其可以大概总结如下

 

 

   可以看到,实模式下对应的寄存器一般都有固定的使命,对于寻址来说,基本只能使用上面对应的寄存器。而在保护模式下,其可以用各种通用寄存器(除了esp不能当作变址寄存器),并且偏移量也变成了32位,如下所示

 

 

 

  可以看到,保护模式下的寻址方式明显比实模式下灵活了许多。注意一下,只能使用这里面出现过的寄存器(比如EAX包括BL,但是BL无论在实模式下还是在保护模式下都没有出现,则自然不能用来进行寻址)

保护模式下汇编

  前面已经分析过了,当CPU处于实模式下时,实际上并不是纯粹的16位CPU,其相当于加强版的16位CPU,仍然可以使用32位下的资源。那么同样一句汇编代码,编译器如何将其转换为对应的机器码呢?实际上编译器已经提供了相关的伪指令,如下所示

[bits 16]    ;表示下列的代码编译成16位的机器码
[bits 32]    ;表示下列的代码编译成32位的机器码

  

  而对应的范围是从当前的bits标签一直到下一个bits标签的范围,都编译成当前标签对应的机器位数。根据前面写汇编的经验——如果没有表明的话,所有代码默认按照16位的机器码进行编译。

 保护模式下指令拓展

  在16位的实模式下,CPU的操作数是16位的;然而到了32位的保护模式下,操作数拓展到了32位,自然指令很可能会产生不一样的效果,我们下面简单的列举、介绍一下后面会用到的。

  对于add指令和sub指令来说,还是比较简单的,如下所示

add    al, cl;    支持8位操作数
add    ax, cx;    支持16位操作数
add    eax, ecx;    支持32位操作数

sub    al, cl;    支持8位操作数
sub    ax, cx;    支持16位操作数
sub    eax, ecx;    支持32位操作数

 

  对于loop指令来说,在实模式下使用cx寄存器存储循环次数;而在保护模式下,使用ecx寄存器来存储循环次数;

  对于mul和imul乘法指令来说,如下所示

mul    寄存器/内存
;    如果寄存器/内存8位,则al是另一个乘数,16位结果存入ax
;    如果寄存器/内存16位,则ax是另一个乘数,32位结果存入eax
;    如果寄存器/内存32位,则eax是另一个乘数,64位结果的高32位存入edx,64位结果的低32存入eax

imul    寄存器/内存
;    如果寄存器/内存8位,则al是另一个乘数,16位结果存入ax
;    如果寄存器/内存16位,则ax是另一个乘数,32位结果存入eax
;    如果寄存器/内存32位,则eax是另一个乘数,64位结果的高32位存入edx,64位结果的低32存入eax

 

  对于div来说,实际上是类似的,如下所示

div    寄存器/内存
;    如果寄存器/内存8位,则ax是16位被除数,8位商在al,8位余数在ah
;    如果寄存器/内存16位,则dx、ax分别是32位被除数的高16位和低16位,16位商在ax,16位余数在dx
;    如果寄存器/内存32位,则edx、eax分别是64位被除数的高32位和低32位,32位商在eax,32位余数在edx

 

  而对于push、pop指令来说,会稍微复杂一点——因为16位实模式下仍然可以处理32位数据

  我们这里首先给出实模式下相关的情况,其测试代码如下所示

[bits 16]
push byte 0x7
push word 0x7
push dword 0x7
push al(通用8位寄存器)
push ax(通用16位寄存器)
push eax(通用32位寄存器)
push ss(段寄存器)

 

  其在qemu虚拟机中的情况如下所示

指令 push数据大小 push的数据
push byte 0x7 16位 0x0007
push word 0x7 16位 0x0007
push dword 0x7 32位 0x00000007
push al(无法压入8位寄存器) - -
push ax(通用16位寄存器) 16位 0x0007
push eax(通用32位寄存器) 32位 0x00000007
push ss(段寄存器) 16位 0x0007

 

  下面给出保护模式下的相关情况,测试代码如下所示

 

;    此时已经处于保护模式,然后进行测试

[bits 32]
push byte 0x7
push word 0x7
push dword 0x7
push al(通用8位寄存器)
push ax(通用16位寄存器)
push eax(通用32位寄存器)
push ss(段寄存器)

 

  其在qemu虚拟机中的运行情况如下所示

指令 push数据大小 push的数据
push byte 0x7 32位 0x00000007
push word 0x7 16位 0x0007
push dword 0x7 32位 0x00000007
push al(无法压入8位寄存器) - -
push ax(通用16位寄存器) 16位 0x0007
push eax(通用32位寄存器) 32位 0x00000007
push ss(段寄存器) 32位 0x00000007

 

  而对于pop指令来说,基本上和push一样的,我们同样给出不同模式下的测试情况,首先是实模式,其测试代码如下所示

[bits 16]
pop al(通用8位寄存器)
pop ax(通用16位寄存器)
pop eax(通用32位寄存器)
pop ss(段寄存器)

 

  结果如图所示

 

指令 pop数据大小 pop的数据
pop al - -
pop ax 16位 0x0007
pop eax 32位 0x00000007
pop ss 16位 0x0007

  其次是保护模式下,其测试代码如下所示

[bits 32]
pop al(通用8位寄存器)
pop ax(通用16位寄存器)
pop eax(通用32位寄存器)
pop ss(段寄存器)

 

  结果如图所示

 

 

指令 pop数据大小 pop的数据
pop al - -
pop ax 16位 0x0007
pop eax 32位 0x00000007
pop ss 16位 0x0007

  以上就是保护模式下相关的一些基础知识。

posted @ 2020-09-17 10:43  hawkJW  阅读(1538)  评论(0编辑  收藏  举报