上节初步认识了保护模式,这节继续顺着这本书的思路,完成保护模式到实模式的转换。
与上节相比,在跳入保护模式之前,以下两行代码比较诡异:
73 mov [LABEL_GO_BACK_TO_REAL+3],ax
74 mov [SPValueInRealMode],sp
然后增加了以下保护模式跳入实模式的代码(这里只贴出部分):
144 LABEL_REAL_ENTRY: ; 从保护模式跳回到实模就到了这里
mov ax, cs
mov ds, ax
mov es, ax
mov ss, ax
mov sp, [SPValueInRealMode]
in al, 92h ;`.
and al, 11111101b ;关闭A20 地址线
out 92h, al ;
sti ; 开中断
mov ax, 4c00h ;`.
int 21h ; 回到DOS
; END of [SECTION .s16]
; 16 位代码段. 由32 位代码段跳入, 跳出后到实模式
308 [SECTION.s16code]
ALIGN 32
[BITS 16]
LABEL_SEG_CODE16:
; 跳回实模式:
mov ax, SelectorNormal
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
mov ss, ax
mov eax, cr0
and al, 11111110b
mov cr0, eax
LABEL_GO_BACK_TO_REAL:
jmp 0:LABEL_REAL_ENTRY ; 段地址会在程序开始处被设置成正确的值
Code16Len equ $ - LABEL_SEG_CODE16
; END of [SECTION .s16code]
由于在实模式下和保护模式下,都有自己的堆栈,因此在实模式->保护模式时,需要先保存SP(即代码74 mov [SPValueInRealMode], sp),与之对应,在保护模式->实模式时,需要恢复SP的值(即代码150 mov sp, [SPValueInRealMode])。
在保护模式->实模式前,需要加载一个合适的描述符选择子到有关段寄存器中,以使对应的段描述符高速缓冲寄存器(隐藏在段寄存器之后的寄存器)中含有合适的段界限和属性,因此,有了代码313行 mov ax, SelectorNormal)。需要注意的是,不能从32位代码段返回实模式,只能从16位代码段中返回。
好了,只剩下代码73行 (mov [LABEL_GO_BACK_TO_REAL+3], ax)和代码325行(jmp 0:LABEL_REAL_ENTRY)的问题了。看到这两行代码很莫名,认真看了作者的描述,我的理解如下:
在运行程序前,代码已经编译为比特串。运行时,在CPU看来,它仅仅是存在内存中的比特。LABEL_GO_BACK_TO_REAL+3地址中存储的是jmp操作数的段地址,即取代了jmp 0:LABEL_REAL_ENTRY中的“0”。因此,执行代码73行 (mov [LABEL_GO_BACK_TO_REAL+3], ax)后,其实是替代了jmp指令操作数的段地址。
总结如下:
实模式->保护模式:
(1) 暂存SP
(2) 初始化段描述符
(3) 加载GDTR,关中断
(4) 打开A20
(5) 置cr0位1,跳转入保护模式
保护模式->实模式:
(1) 跳入16位代码段
(2) 设置段寄存器值,使其符合实模式规范
(3) 置cr0为0,跳转入实模式