实模式和保护模式是32位CPU的两种工作模式。32位CPU工作在实模式下的时候,并没有变成16位CPU,就像中学生做小学题目的时候并没有变成小学生一样。现代CPU是64位的,工作模式叫做长模式。
从实模式进入保护模式
流程如下:
- 创建好GDT。
- 创建好GdtPtr,保存GDT的物理地址和GDT中的字节限制(初始值是0,最大值是GDT的长度减1)。
- 把GdtPtr放入专用寄存器
gdtr,lgdt [GdtPtr]。 - 关闭中断,
cli。 - 打开
A20。 - 打开
cr0的PE位。 - 跳入保护模式,
jmp dword 32位段代码段选择子:偏移量。
执行jmp dword 32位段代码段选择子:偏移量时,已经是在保护模式下,但代码是在16位模式下编译的,使用dword使得操作数大于16位时不会被截取为16位。
dword会使jmp dword 32位段代码段选择子:偏移量这条语句在16位代码下加上反转前缀0x66。有了这个前缀,操作数偏移量会被扩展为32位。这是作者的猜测。未经证实。
从实模式进入保护模式的流程,是一个比较固定的模板,除了那句跳转语句,作者也没有其他疑问。没有什么好纠结的。
从保护模式切换到实模式
流程如下:
- 先从保护模式下的32位代码进入保护模式下的16位代码。
- 在保护模式下的16位代码段设置ds、es、fs、gs、ss的值是normalSelector选择子(一个符合实模式要求的选择子)。
- 关闭cr0的pe位。
- 跳回实模式下的16位代码段。
两个问题
为什么不能在保护模式下的32位代码段返回实模式下的16位代码段?
为什么在保护模式下的32位代码段返回实模式下的16位代码段必须通过一个中间代码段(保护模式下的16位代码段)?
上面两个问题实际是一个问题。
存储段描述符的寄存器,例如ds、cs、es、fs、ss,存在对应的高速缓冲寄存器,称这批高速缓冲寄存器为R。实模式下,R有特定值。实模式和保护模式下,R的值不相同。从保护模式切换到实模式,必须先修改R中的值,把R的值从保护模式下的值修改成实模式下的值。
从保护模式切换到实模式的临界操作是设置cr0的PE位的值为0,称呼这个操作是O。由于实模式对R的要求,需要在O之前修改R的值,让R的值符合实模式的要求。R中除cs对应的高速缓冲寄存器(称这个寄存器是R-CS)外,都能用mov修改。修改R-CS只能用jmp、call、iretf。当前,最可能使用的是jmp。称这个修改操作是C。C的目的是,把R-CS修改为实模式的要求:16位。
分两种情况。
第一种,C在O之前,即C在保护模式下。执行C后,进入保护模式下的16位代码。此时,执行O,是在保护模式下的16位代码,即,在16位代码下切换到实模式。
第二种,C在O之后,即C在实模式下。会报错。因为,R-CS的值仍是保护模式下32位代码的。
对上面两种情况的分析,当然是正确的,可是,这根本不是在回答开头的问题啊。
再论证,为什么在保护模式的32位代码段切换到实模式下的16位代码段必须经过一个中间的保护模式下的16位代码段。
只有两种情况,经过中间代码段,不经过中间代码段。
分析不经过中间代码段这种情况。
在保护模式下的32位代码,执行O操作。此时,R-CS的值仍然是保护模式下的值,不符合实模式的要求,进入实模式失败。
那么,在执行O操作前,先执行C修改R-CS的值,那么,进入了保护模式下的16位代码段。此时,实质仍然是,经过了中间代码段。
证明了”不经过中间代码段“行不通,那么,自然只剩下”经过中间代码段“这种情况了。
开头的两个问题,已经被回答了。