实模式向保护模式跳转
不知道能不能把这个问题说清楚,我是被这个东西搞得很头晕,虽然原理听上去还不算难,但是,想把它真的弄得很透彻真的很不容易,下面
,我就试着把自己的思路顺一下:
一:我们知道机器从加电到引导系统这过程,首先是工作在实模式下的,也就是按实地址方式寻址。寻址空间也只能达到1M,(因为只有20位地址线,所以只可以寻址到2的20次方)而到了386时代,地址线已经达到了32位,所以不用分段分页机制下的独立寻址空间已经达到了4GB,(这个数字不难得到吧^_^),现代操作系统,比方说WINDOWS都是工作在这种方式下的,而要想得到这么强的寻址能力,显然,我们的8086汇编课本上没有教你怎么做,物理地址=段值*16+偏移,的时代已经一去不回来了,所以才有了保护模式,至于为什么叫保护模式,可能会有很多的理由,但是我想只要把特权级说出来,大家就可以很想当然的理解为出于安全的分级管理。所以,我们要做的工作就是完成,这一跳转!
二:那么在保护模式下又是怎么寻址的呢,很明显,还是会用到几个段寄存器,在这里它们并不是直接的表示为段的地址,而是换了个名字叫做选择子,故名思意,它的意思是,通过段寄存器,就可以找到相应段的入口,和以前一样嘛,呵呵,但是在哪里选呢,于是又加了这样一个数据结构GDT(全局描述符表)每个描述符项都是一个特定的数据结构,里面记录了该段的基址,界限,访问权限等信息。那么这个GDT又是放在哪里的呢,还能放在哪,寄存器无疑是最理想的空间,快,准,稳嘛!还有寄存器啊?对,就是寄存器多,于是又来了一个专门用于放置GDT的寄存器,gdtr,那每一个段都会在这里登记下来它所处的位置,即添加关于它的描述符项,然后再定义一个选择子,(其实就是相对于GDT表最顶部的相对位移),这样就可以由这个选择子找到这个段的入口,从而达到跳转的目的。
三:所以我们可以这样做:
1:定义GDT段,准备GDT表;
2:定义个数据,代码及堆栈段;
3:为相应的段定义段选择子;
4:用lgdt指令加载gdtr;
5:打开A20地址线(这是历史的原因,因为开机以后20位地址线是没有打开的,需要手动打开才可以用)
6:置cr0的PE位(这是一个控制寄存器,此位置1则可以启用保护模式)
7:跳转,进入保护模式;(怎么跳?jmp,很好用的)
四:但是在跳的过程中,可能会遇到很多问题,Privilege,还有16位地址和32位地址不能直接跳等问题也很难。
而且,我们不能只跳过去,还要想到怎么才能跳出来,也就是怎么样才能再从保护模式跳回到实模式。
下面,我一边默写代码,一边回忆这样一个过程,大家可以从下面的代码里很好的理解我上面的思想。
org 0100h
jmp LABEL_BEGIN
[section .gdt] ;全局描述符表段
;======================================================
;GDT
LABEL_GDT: Descriptor 0, 0, 0 ;空描述符
LABEL_DESC_CODE32: Descriptor 0, SegCode32Len - 1, DA_C + Da_32 ;代码段,32位
LABEL_DESC_VIDEO: Descriptor 0B8000h, 0FFFFh, DA_DRW ;存在的可读写的数据段
;GDT 结束
GdtLen equ $ - LABEL_GDT ;GDT长度
GdtPtr dw GdtLen - 1 ;GDT界限
dd 0 ;GDT基地址
;GDT选择子
SelectorCode32 equ LABEL_DESC_CODE32 - LABEL_GDT
SelectorVideo equ LABEL_DESC_VIDEO - LABEL_GDT
;END of [section .gdt]
;========================================================
[BITS 16]
LABEL_BEGIN:
mov ax, cs
mov ds, ax
mov es, ax
mov ss, ax
mov sp, 0100h ;指向本程序的开始
;初始化32位代码段描述符
xor eax, eax
mov ax, cs
shl eax, 4
add eax, LABEL_SEG_CODE32
mov word [LABEL_DESC_CODE32 + 2], ax
shr eax, 16
mov byte [LABEL_DESC_CODE32 + 4], al
mov byte [LABEL_DESC_CODE32 + 7], ah
;为加载gdtr做准备
xor eax, eax
mov ax, ds
shl eax, 4
add eax, LABEL_GDT
mov dword [GdtPtr +2], eax
;加载gdtr
lgdt [GdtPtr]
;关中断
cli
;打开地址线A20
in al, 92h
or al, 00000010b
out 92h, al
;准备切换到保护模式
mov eax, cr0
or eax, 1
mov cr0, eax
;真正进入保护方式
jmp dword SelectorCode32:0
;End of [section .s16]
;==================================================
[section .s32]
[BITS 32]
LABEL_SEG_CODE32:
mov ax, SelectorVideo
mov gs, ax
mov edi, (80 *10 +0) *2 ;屏幕第10行,第0列
mov ah, 02h
mov al, 'P'
mov [gs:edi], ax
jmp $
SegCode32Len equ $ - LABEL_SEG_CODE32
;End of [Section .S32]
;===================================================
下面是我们Intel Architecture中看到的有关于保护模式的文档,先来看这个图:
这个图很清楚的说明了我刚才提到的GDT是如何工作的,这里并没有LDT,但是有LinearAdress,和PhysicalAddress,关于三种地址的转换问题,我会在下一讲里谈到,其实上面也就是我们在操作系统课里强调最多段页式存储。在这里,你会看得更清楚,因为你懂得了它的原理!甚至我们都用代码实现了!爽吧!
好了,时间不早了,我要睡了,可能今天对这个问题还没有吃透,明天继续,看来,我用自己的语言把这个东西说一遍真的印象加深了很多,而且,本来以为懂的东西,结果还是很糊涂,本来看不下去的大段英文,现在想用自己的语言表达,力求精确的时候,我又不得不仔细的读它,看来,这样学习,还真是个好方法,您不妨试试,或许我们还可以多多交流!晚安!