GDTR
写GDTR有一个专用的指令:LGDT。
该指令在实模式和保护模式下都可以执行,用这条指令就可以成功定位GDT在内存中的位置。
到这里,我们访问32位地址的内存就没有障碍了,方法是把寄存器GDTR和表GDT的内容分别设置好,就能通过操控16位段寄存器(CS,DS,ES,SS,FS,GS)来实现目标。
整个流程控制都是由CPU实现的,这种控制模式就叫保护模式。
一旦进入保护模式,16位的段寄存器(CS,DS,ES,SS,FS,GS)的数据结构定义就将发生变化:
不再是实模式下的具体段地址了,而是一个带有“索引”的逻辑地址,通过这个索引值到GDT中寻找32位的内存起始地址。这时候,这些段寄存器就有了一个新的称呼:段选择子。
我们要访问任何内存数据,就必须首先定义好这个段选择子。
jmp dword 0x0008:inprotectmode是在实模式下编译的,它是切换到保护模式唯一方式。
dword字段的含义是指定跳转之后的偏移地址要用32位来定义,也即会将inprotectmode的偏移地址装入EIP。
jmp到0x0008的含义是:CS=0x0008,这里就务必要注意,这个时候因为已经打开了保护模式,那么CS就不再是段地址的含义了,而是保护模式下的含义,它代表的是要将段选择子数据设置成0x0008
堆栈区
保护模式下的堆栈区是否正常工作:这是一个非常重要的地方,关系到32位系统能否正常启动运行。
因为,如果你没有特别注意堆栈区的设置,很可能使用PUSH和POP指令的时候,会把数据写到代码区或者数据区了。在进入32保护模式后,我们要做的第一件事其实就应该是设置SS和ESP的值。
为何不能直接用C编译生成的代码运行?
C语言编译生成的目标代码是ELF格式的,不是机器代码序列,直接加载无法运行。
需要将所需机器代码从C语言编译后的目标文件(obj)里抽取出来。
这里就有一个很重要的问题:指令INC DWORD [EAX] 是直接对内存进行寻址操作,内存直接寻址默认的寄存器应该是DS,所以这个数据访问寻址实际上是:DS:-4+EBP(LEA指令的作用是取偏移地址,这里LEA EAX,DWORD [-4+EBP]实际上等效于mov eax,-4+EBP,只不过nasm语法要求必须要加[])。EBP最开始的时候取自于ESP,ESP取决于我们的系统设置,在我的系统中我设置的是0x00007c00。所以,变量i的绝对地址:-4+EBP=0x00007bfc。那么现在我们要用DS:-4+EBP访问0x00007bfc必须要确保DS=0才行!故为了C语言编译后的程序能在我们的系统中正常运行,就必须要在进入保护模式之后在GDT中把DS段描述符的线性基地址映射成0x00000000。否则,C语言程序就无法集成到你的系统中。
任务切换
任务切换最核心的地方就是要保证这些CPU在每次重新运行的时候,都必须要保持和上一次一模一样的状态。为此,这3个CPU就可以理解为一个临时的CPU状态暂存区,用它来存储每个任务退出的时候真正CPU的最新状态,而下次重新运行该任务时,只需要从这个逻辑CPU装载状态到真正的CPU即可。那么如何定义一个CPU的状态呢?其实很简单,它就是所有和程序运行有关的寄存器集合。包括:通用寄存器、专用寄存器、段寄存器、状态寄存器等。