实模式向保护模式跳转

不知道能不能把这个问题说清楚,我是被这个东西搞得很头晕,虽然原理听上去还不算难,但是,想把它真的弄得很透彻真的很不容易,下面

,我就试着把自己的思路顺一下:
一:我们知道机器从加电到引导系统这过程,首先是工作在实模式下的,也就是按实地址方式寻址。寻址空间也只能达到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位地址不能直接跳等问题也很难。
而且,我们不能只跳过去,还要想到怎么才能跳出来,也就是怎么样才能再从保护模式跳回到实模式。
下面,我一边默写代码,一边回忆这样一个过程,大家可以从下面的代码里很好的理解我上面的思想。

#include        "pm.inc"        ;常量,宏,以及一些说明
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]
;
========================================================

[section    .s16]
[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,关于三种地址的转换问题,我会在下一讲里谈到,其实上面也就是我们在操作系统课里强调最多段页式存储。在这里,你会看得更清楚,因为你懂得了它的原理!甚至我们都用代码实现了!爽吧!
好了,时间不早了,我要睡了,可能今天对这个问题还没有吃透,明天继续,看来,我用自己的语言把这个东西说一遍真的印象加深了很多,而且,本来以为懂的东西,结果还是很糊涂,本来看不下去的大段英文,现在想用自己的语言表达,力求精确的时候,我又不得不仔细的读它,看来,这样学习,还真是个好方法,您不妨试试,或许我们还可以多多交流!晚安!

posted on 2006-03-06 00:39  Stone_石头  阅读(2505)  评论(0编辑  收藏  举报