操作系统——保护模式(五)
操作系统——保护模式(五)
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 |
以上就是保护模式下相关的一些基础知识。