好吧,一开始我觉得从保护模式应该就是设置一下cr0然后jmp一下就回到实模式了,这个没必要去实践。不过看到书中里面有些代码觉得有些冗余,然后里面有段话也是没看明白,动手实践才恍然大悟。
于渊在书中写这个例子的思路大概是这样的:
1. 从实模式中跳转进入保护模式。(参考前面几篇)
2. 在保护模式下,访问一个地址偏大点的(实模式寻址范围之外)内存块进行读写,验证一下保护模式寻址范围是不是比实模式寻址大。
3. 返回实模式,返回实模式是这段代码:
; 16 位代码段. 由 32 位代码段跳入, 跳出后到实模式
[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]
而跳回实模式之后执行的代码如下:
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]
问题
跳回实模式前,设置ds、es、fs、gs、ss有什么作用,跳回实模式之后不是又设置了ds、es等寄存器了么?
原文描述:“在准备结束保护模式回到实模式前,需要加载一个合适的描述符选择子到有关的段寄存器,以使对应段描述符高速缓冲寄存器中案有合适的段界限和属性。”
80286开始,保护模式下,为避免每次形成线性地址的时时,都要使用选择子相关的信息而去访问内存,cpu配备了相应的段描述符高速缓冲寄存器。
所以重新加载一下ds、es、ss相当于清缓存!为什么这里跳回实模式的代码段必须是在16位的代码段中?
原文如下:“不能从32位代码段返回实模式,只能从16位代码段中返回。这是因为无法实现从32为位代码段返回时CS高速缓冲寄存器中的属性符合实模式的要求(实模式不能改变段属性)。”
我的理解,原因还是跟问题1一样,如果在32位的代码段中要跳回16位的实模式,那么设置完cr0后,准备用jmp跳回实模式的地址去,但是此时的cs相关的段描述符高速缓冲里面的属性还是32位保护模式的段属性,必须跟上个问题一样,清一下缓存,但是jmp跳过去之后是16位的实模式了,没法清缓存了!
后来看了篇文章,与我的理解一致:关于从保护模式切换到实模式的相关说明 。- 从32位的代码段跳回实模式不行吗?
从问题2我们知道,保护模式跳回实模式关键的一点,就是要保证跳回实模式后,相关的段的段属性是正确的。之前有个想法就是,从32位的代码段跳回实模式,然后使跳回实模式后代码段属性符合16位实模式下不就行了么?因为跳回去实模式后已经无法修改相关的段属性,所以必须在跳回实模式之前设置好,那么只能在段描述符中设置好,但这有个问题,如果这个描述符标志的是32位的段,从这个32位的段跳回后就无法修改了,所以这个描述符就只能是标志为16位的段。 jmp 0:LABEL_REAL_ENTRY ; 段地址会在程序开始处被设置成正确的值
这怎么实现的?
好吧,这是个小技巧。思路是这样,当这块代码读入内存后,直接修改内存中这个指令。(指令与数据,只是人为的区分,在内存中,它们就都只是二进制数据)