按照IA32编程手册的描述,从保护模式跳转到实模式需要两个段选择符:一个用于刷新DS/SS/ES等寄存器缓存,称作规范描述符,另一个用于刷新CS寄存器,称为16位代码描述符,分别定义如下:
1)NormalDesc:FFFF0000 00009200
2)Code16Desc:FFFF0000 00009800
上述两个描述符都是在miniker.asm中定义的,其索引(相对GDT表头的偏移)分别为0x30和0x38。这两个段描述符与保护模式CS、DS等段描述符,位于同一个GDT表中。定义好切换需要的段描述符后,看一下6KB处的汇编代码:
上述代码保存了保护模式下的几个寄存器的值,主要是中断描述符表寄存器IDTR,CR3寄存器等。在接下来的代码中,这些寄存器会被修改。这样在重新返回保护模式时,直接从保存的全局变量中恢复即可。接下来的代码是过渡代码,用于初始化实模式下的中断描述符表寄存器,清除DS/SS等寄存器值,为正式进入实模式做准备:
上述jmp指令正式跳转到了实模式下的代码处。接下来是实模式的实现代码:
上述实模式功能表实际上是一些代码标号的数组,每个代码标号标注了一段实现特定功能的代码。上述__BIOS_BEGIN处的代码,根据EAX寄存器(目前只有几个功能号,因此使用ax就足够了)中存放的功能号,计算出功能函数(标号)在功能表中的偏移,然后调用即可。下面是功能表的样子:
具体的功能函数的实现就不详细介绍了,读者可直接阅读realinit.asm中的代码,都比较简单。调用BIOS功能完成后,接着就需要返回保护模式。下面是从实模式重新返回保护模式的代码:(www.xing528.com)
上面的代码比较简单,指令后面的注释足够说明代码的作用了。有两个地方需要进一步说明一下。
1)在引用全局变量,或者使用绝对地址跳转的时候,都是在标号基础上再加上4096,比如在恢复CR3寄存器的时候,使用的指令是mov ebx,dword[__CR3+4096]。这里__CR3标号是一个全局变量,保存了保护模式下的CR3寄存器。之所以增加4096,是因为__CR3标号的偏移值是相对于realinit.bin模块的,而realinit.bin模块被加载到了物理内存的4K位置处。因此在引用绝对地址的时候,需要在标号基础上加上4K。
2)在切换到实模式时,由于中断控制器8259原先工作在保护模式下,其第一个中断向量号是32,这不符合实模式的需要。在实模式下,外部中断是从8开始的,因此必须对8259重新进行初始化。同样的道理,在切换回保护模式后,又需要重新对其进行设置,使其第一个中断向量号变为32。由于在前面没有讲解8259芯片的初始化方法,再次补充一下,下面是把8259从保护模式转换为实模式的代码:
代码比较繁琐,但是逻辑比较简单,就是通过向8259芯片的控制字寄存器和命令字寄存器中写入一些值,使之满足实模式的需要。各行代码的具体含义,在这里就不详细讲了,读者可到网络上搜索相关资料进行对比分析。关于8259中断控制器的编程,不论是在互联网上还是在计算机接口课程中,都可以找到非常多的资料。
好了,从保护模式切换到实模式的过程介绍完了。虽然这个过程对整个操作系统的核心机制来说不是很重要,但是比较复杂。作者在实现这个切换过程的时候,遇到了非常多的问题,足足用了一个星期,才初步搞定。之所以说是初步搞定,是因为上述代码在大多数情况下都是正常工作的,但是在一些个别情况下,仍然会引发异常。具体原因到现在也没有彻底搞清楚。好在从保护模式切换回实模式只是为了试验目的和解决临时问题而存在的,在正式的商用场合,不会采用这种方式,因此问题还不算非常严重。
另外需要说明的是,从实模式切换到保护模式的资料非常多,但是从保护模式切换到实模式的资料却非常少,且在已有的少量资料和实例中,大多是针对CPU本身工作模式的切换。须知在实际系统中,仅仅CPU切换回来是不够的,与之配套的系统辅助部件的工作模式也必须对应切换,比如8259中断控制器的切换、中断描述符表的切换等。作者在这里描述的切换过程,完全是经过独立分析之后得出的方法,没有任何实现作为参考,可以说是纯粹的原创。
免责声明:以上内容源自网络,版权归原作者所有,如有侵犯您的原创版权请告知,我们将尽快删除相关内容。