码迷,mamicode.com
首页 > 其他好文 > 详细

一个操作系统的实现(3)-保护模式进阶

时间:2016-05-18 18:34:37      阅读:263      评论:0      收藏:0      [点我收藏+]

标签:

上节内容是从实模式进入到保护模式,只是进入保护模式打印了一个字母P。但是没有体现出保护模式的优势,也没有从保护模式中返回。这节就是要体验保护模式下读写大地址内存的能力和从保护模式返回到实模式。

这节要做的内容如下:首先在屏幕的第11行输出In Protect Mode now. ^-^。然后在屏幕第12行输出内存中起始地址为5MB的连续的8个字节。然后向这个以5MB开始的内存中写入ABCDEFGH。再次在第13行输出这8个字节。结果演示如下:

技术分享

源代码300多行,很长,分段讲述,主要讲新增的部分

源代码解释

首先是GDT段

GDT段

[SECTION .gdt]
; GDT
;                            段基址,        段界限 , 属性
LABEL_GDT:         Descriptor    0,              0, 0         ; 空描述符
LABEL_DESC_NORMAL: Descriptor    0,         0ffffh, DA_DRW    ; Normal 描述符
LABEL_DESC_CODE32: Descriptor    0, SegCode32Len-1, DA_C+DA_32; 非一致代码段, 32
LABEL_DESC_CODE16: Descriptor    0,         0ffffh, DA_C      ; 非一致代码段, 16
LABEL_DESC_DATA:   Descriptor    0,      DataLen-1, DA_DRW    ; Data
LABEL_DESC_STACK:  Descriptor    0,     TopOfStack, DA_DRWA+DA_32; Stack, 32 位
LABEL_DESC_TEST:   Descriptor 0500000h,     0ffffh, DA_DRW
LABEL_DESC_VIDEO:  Descriptor  0B8000h,     0ffffh, DA_DRW    ; 显存首地址
; GDT 结束

GdtLen        equ    $ - LABEL_GDT    ; GDT长度
GdtPtr        dw    GdtLen - 1    ; GDT界限
        dd    0        ; GDT基地址

; GDT 选择子
SelectorNormal        equ    LABEL_DESC_NORMAL    - LABEL_GDT
SelectorCode32        equ    LABEL_DESC_CODE32    - LABEL_GDT
SelectorCode16        equ    LABEL_DESC_CODE16    - LABEL_GDT
SelectorData        equ    LABEL_DESC_DATA        - LABEL_GDT
SelectorStack        equ    LABEL_DESC_STACK    - LABEL_GDT
SelectorTest        equ    LABEL_DESC_TEST        - LABEL_GDT
SelectorVideo        equ    LABEL_DESC_VIDEO    - LABEL_GDT
; END of [SECTION .gdt]

相比于上一节,这里新增了下面几个描述符表项:
LABEL_DESC_NORMAL:这个描述符用在从保护模式返回实模式的过程,为了让对应的段描述符告诉缓冲寄存器中含有合适的界限和属性。这儿有点不明白

LABEL_DESC_CODE16:在保护模式返回到实模式的过程中用到。用来将SeletorNormal选择子赋给ds、es、fs、gs、ss,跟上面LABEL_DESC_NORMAL合作实现返回实模式的准备工作。在书上43页

LABEL_DESC_DATA:数据段描述符

LABEL_DESC_STACK:栈段描述符

LABEL_DESC_TEST:用来测试的大地址内存(5MB起始的内存)

自然地,需要增加了相应的选择子。

接下来新增了数据段。

数据段

[SECTION .data1]     ; 数据段
ALIGN    32
[BITS    32]
LABEL_DATA:
SPValueInRealMode    dw    0
; 字符串
PMMessage:        db    "In Protect Mode now. ^-^", 0    ; 在保护模式中显示
OffsetPMMessage        equ    PMMessage - $$
StrTest:        db    "ABCDEFGHIJKLMNOPQRSTUVWXYZ", 0
OffsetStrTest        equ    StrTest - $$
DataLen            equ    $ - LABEL_DATA
; END of [SECTION .data1]

对于上面的代码

SPValueInRealMode:实模式中栈顶指针的值会保存在这里。干什么用?

ALIGN 32:这是个伪指令,告诉编译器本伪指令下面的内存变量必须从下一个能被32整除的地址开始分配。

接下来你会发现定义的两个数据块PMMessageStrTest下面都定义了另外一个符号Offset_PMMessageOffsetStrTest,用来表示对应的上一个字符串相对于本节开始处(也就是LABEL_DATA处)的偏移。定义这两个符号是因为在保护模式下需要用到这个偏移来定位字符串的位置。而不再需要实模式下的地址。

你看到GDT中关于数据段的段基址并没有初始化,所以需要对数据段描述符进行初始化,在[SECTION .s16]中,初始化代码如下:

        ; 初始化数据段描述符
        xor     eax, eax 
        mov     ax, ds
        shl     eax, 4
        add     eax, LABEL_DATA
        mov     word [LABEL_DESC_DATA + 2], ax
        shr     eax, 16
        mov     byte [LABEL_DESC_DATA + 4], al
        mov     byte [LABEL_DESC_DATA + 7], ah

回顾一下,保护模式下段值仍然是用16位的寄存器来存储。段值×16+偏移地址在此时它仅仅变成了一个索引,这个索引指向的就是GDT的一个表项,这个表项里面详细定义了段的起始地址、界限、属性等内容。在书的31页有详细介绍。

接下来定义了全局堆栈段,因为保护模式下用到了堆栈,这些都是需要我们自己定义的。因此需要在实模式下新建堆栈段。

堆栈段

定义堆栈段的源代码如下:

; 全局堆栈段
[SECTION .gs]
ALIGN    32
[BITS    32]
LABEL_STACK:
    times 512 db 0

TopOfStack    equ    $ - LABEL_STACK - 1

; END of [SECTION .gs]

这里定义的栈的大小是512字节,栈顶是TopOfStack

但计算栈顶的时候减一是为什么呢?这儿我也没搞清楚。栈为空时,ss:esp指向的栈的最底部单元下面的单元,这个单元的偏移地址应该为栈最底部的双字(因为是32位的堆栈段)单元的偏移地址+4。但是这里我无论怎么计算都不是在所说的位置,好像栈空间小于512字节了,难道是为了避免访问越界,减1更有保障?

[SECTION .s16]有对应的堆栈段描述符如下:

        ; 初始化堆栈段描述符
        xor     eax, eax 
        mov     ax, ds
        shl     eax, 4
        add     eax, LABEL_STACK
        mov     word [LABEL_DESC_STACK + 2], ax
        shr     eax, 16
        mov     byte [LABEL_DESC_STACK + 4], al
        mov     byte [LABEL_DESC_STACK + 7], ah

再看看GDT中的关于堆栈的表项,属性是DA_DRWA+DA_32。DA_DRWA表示存在的已访问可读写数据段类型值。DA_32表示他是一个32位的堆栈段。

[SECTION .s32]有对应的堆栈初始化代码如下:

        mov     ax, SelectorStack
        mov     ss, ax                  ; 堆栈段选择子

        mov     esp, TopOfStack

关于数据段、栈段定义位置,描述符定义的位置,相应的寄存器初始化位置

从上面可以看到:

数据段,栈段,代码段的定义是独立的;

数据段,栈段,32位代码段描述符的初始化是在16位代码段中定义的;

数据段,栈段相应寄存器的初始化要看它门在哪个代码段中使用,比如说这里的栈段是在32位代码段中使用的,所以ss,esp寄存器的初始化过程实在32位代码段中进行的。

16位代码段(运行在实模式)

这段用来对GDT进行初始化并进入保护模式,详细的介绍参考上一节文章。

[SECTION .s16]
[BITS    16]
LABEL_BEGIN:
    mov    ax, cs
    mov    ds, ax
    mov    es, ax
    mov    ss, ax
    mov    sp, 0100h

    mov    [LABEL_GO_BACK_TO_REAL+3], ax
    mov    [SPValueInRealMode], sp

    ; 初始化 16 位代码段描述符
    mov    ax, cs
    movzx    eax, ax
    shl    eax, 4
    add    eax, LABEL_SEG_CODE16
    mov    word [LABEL_DESC_CODE16 + 2], ax
    shr    eax, 16
    mov    byte [LABEL_DESC_CODE16 + 4], al
    mov    byte [LABEL_DESC_CODE16 + 7], ah

    ; 初始化 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

    ; 初始化数据段描述符
    xor    eax, eax
    mov    ax, ds
    shl    eax, 4
    add    eax, LABEL_DATA
    mov    word [LABEL_DESC_DATA + 2], ax
    shr    eax, 16
    mov    byte [LABEL_DESC_DATA + 4], al
    mov    byte [LABEL_DESC_DATA + 7], ah

    ; 初始化堆栈段描述符
    xor    eax, eax
    mov    ax, ds
    shl    eax, 4
    add    eax, LABEL_STACK
    mov    word [LABEL_DESC_STACK + 2], ax
    shr    eax, 16
    mov    byte [LABEL_DESC_STACK + 4], al
    mov    byte [LABEL_DESC_STACK + 7], ah

    ; 为加载 GDTR 作准备
    xor    eax, eax
    mov    ax, ds
    shl    eax, 4
    add    eax, LABEL_GDT        ; eax <- gdt 基地址
    mov    dword [GdtPtr + 2], eax    ; [GdtPtr + 2] <- gdt 基地址

    ; 加载 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    
    ; 执行这一句会把 SelectorCode32 装入 cs, 并跳转到 Code32Selector:0  处

mov [LABEL_GO_BACK_TO_REAL+3], axmov [SPValueInRealMode], sp:这条指令是干什么的呢?先告诉你它是为了后面从保护模式返回到实模式用,具体为什么是这样在下面有详细的介绍

程序刚加载就会执行这个代码段,之后跳到32位代码段中运行,32位代码段如下。

32位代码段(运行在保护模式)

相应的代码如下。虽然很长,但很简单,

首先,

是初始化相应段的寄存器。包括数据段测试段视频段(屏幕)、堆栈段

然后,

显示字符串In Protect Mode now. ^-^

然后,

显示从内存地址5MB起始的8个字节,然后向内存5MB起始的地址中写入数据段中的字符串,然后再显示从内存5MB起始的8个字节。每次显示完成后都执行换行操作。

最后通过跳转指令jmp SelectorCode16:0返回到实模式,到这里32位代码段就结束了。又回到了实模式。下面的一小节介绍跳转到的目的地做了什么工作。

先附上32位代码段,里面有详细的注释供参考

[SECTION .s32]; 32 位代码段. 由实模式跳入.
[BITS    32]

LABEL_SEG_CODE32:
    mov    ax, SelectorData
    mov    ds, ax                    ; 数据段选择子
    mov    ax, SelectorTest
    mov    es, ax                    ; 测试段选择子
    mov    ax, SelectorVideo
    mov    gs, ax                    ; 视频段选择子

    mov    ax, SelectorStack
    mov    ss, ax                    ; 堆栈段选择子

    mov    esp, TopOfStack


    ; 下面显示一个字符串
    mov    ah, 0Ch                    ; 0000: 黑底    1100: 红字
    xor    esi, esi
    xor    edi, edi
    mov    esi, OffsetPMMessage    ; 源数据偏移
    mov    edi, (80 * 10 + 0) * 2    ; 目的数据偏移。屏幕第 10 行, 第 0 列。
    cld                         ; 置标志位DF的置为零
.1:
    lodsb                       ; al=((ds)*16+(esi))、(esi)=(esi)+1
    test    al, al              ; al^al,结果不保存,只影响标志位
    jz    .2                      ; 如果ZF标志位为1,转移到.2
    mov    [gs:edi], ax            ; 否则,输出到屏幕
    add    edi, 2                  ; 屏幕偏移寄存器加2
    jmp    .1                      ; 跳转到.1
.2:                                ; 显示完毕

    call    DispReturn          ; 换行

    call    TestRead            ; 读出内存5MB起始的连续8字节内容
    call    TestWrite           ; 向5MB起始的内存中写入字符串
    call    TestRead            ; 再一次读出内存5MB起始的连续8字节内容
                                ; 如果正确,读出的内存与写入的内容相同

    ; 到此停止
    jmp    SelectorCode16:0        ; 跳转到准备工作(返回实模式的准备工作代码)

; ------------------------------------------------------------------------
TestRead:                       ;输出到屏幕函数
    xor    esi, esi                ;esi置0
    mov    ecx, 8                  ; 向屏幕读出8个字节
.loop:
    mov    al, [es:esi]            ; 将内存字节内容传送到al中
    call    DispAL              ; 以16进制的形式显示出来
    inc    esi                     ; 内存地址增加1
    loop    .loop               ; 继续循环读出到屏幕

    call    DispReturn          ; 调用换行函数

    ret                         ; 函数返回
; TestRead 结束-----------------------------------------------------------


; ------------------------------------------------------------------------
TestWrite:                      ; 写入内存函数
    push    esi                 ; 保存寄存器esi内容
    push    edi                 ; 保存寄存器edi内容
    xor    esi, esi                ; esi置0
    xor    edi, edi                ; edi置0
    mov    esi, OffsetStrTest        ; 源数据偏移
    cld                         ; DF标志位置0
.1:                             ; 因此内存-内存没有直接通路,所以下面需要al中转
    lodsb                       ; al=((ds)*16+(esi))、(esi)=(esi)+1
    test    al, al              ; al^al,结果不保存,只影响标志位
    jz    .2                      ; 如果ZF标志位为0,跳转到.2
    mov    [es:edi], al            ; 将字符写入内存
    inc    edi                     ; 内存偏移到下一位
    jmp    .1                      ; 循环到写入的字符串为0时结束
.2:

    pop    edi                     ; 还原子函数用到的寄存器edi、esi,注意顺序
    pop    esi

    ret
; TestWrite 结束----------------------------------------------------------


; ------------------------------------------------------------------------
; 以16进制显示 AL 中的数字
; 默认地:
;    数字已经存在 AL 中
;    edi 始终指向要显示的下一个字符的位置
; 被改变的寄存器:
;    ax, edi
; ------------------------------------------------------------------------
DispAL:
    push    ecx                 ; 保存子程序中用到的寄存器ecx、edx
    push    edx

    mov    ah, 0Ch                    ; 0000: 黑底    1100: 红字
    mov    dl, al                  ; al高4位,低4位分开处理
    shr    al, 4                   ; 先处理高四位
    mov    ecx, 2                  ; 循环两次,第一次高四位,第二次低四位
.begin:
    and    al, 01111b
    cmp    al, 9                   ; 如果大于9,要显示A~F的ASCii码
    ja    .1
    add    al, ‘0‘                 ; 如果小于或等于9,显示0~9的ASCii码
    jmp    .2
.1:
    sub    al, 0Ah
    add    al, ‘A‘
.2:
    mov    [gs:edi], ax            ; 输出到屏幕上
    add    edi, 2                  ; 屏幕偏移指针+2

    mov    al, dl                  ; 处理低4位
    loop    .begin
    add    edi, 2                  ; edi要时刻指向要显示下一个字符的位置

    pop    edx                     ; 还原子函数用到的寄存器edx、ecx,注意顺序
    pop    ecx

    ret                         ; 子程序返回
; DispAL 结束-------------------------------------------------------------


; ------------------------------------------------------------------------
DispReturn:                     ; 实现屏幕输出的换行
    push    eax                 ; 保存用到的eax、ebx
    push    ebx

    mov    eax, edi                ;
    mov    bl, 160                 ;
    div    bl                      ; 执行结束后,ax存放的是当前行的行号码
    and    eax, 0FFh               ; 执行结束后,eax存放的是当前行的行号码
    inc    eax                     ; 让eax存放下一行的行号码
    mov    bl, 160                 ;
    mul    bl                      ; 执行结束后,eax存放的是下一行的行首偏移值
    mov    edi, eax                ; 将下一行的行首偏移置传送到edi中

    pop    ebx                     ; 还原子函数用到的寄存器ebx、eax,注意顺序
    pop    eax

    ret                         ; 子程序返回
; DispReturn 结束---------------------------------------------------------

SegCode32Len    equ    $ - LABEL_SEG_CODE32
; END of [SECTION .s32]

从保护模式跳转到实模式的16位代码段

从保护模式返回到实模式有点复杂。因为在准备结束保护模式回到实模式之前,需要加载一个合适的描述符选择子到有关的寄存器,以使对应段描述符高速缓冲寄存器中含有合适的段界限和属性。而且,我们不能从32位的代码段返回实模式,只能从16位的代码段中返回。这是因为无法实现从32位代码段返回时cs告诉缓冲寄存器中的属性符合实模式的要求(实模式不能改变属性)。

所以,在这里,我们新增一个Normal描述符。在返回实模式之前把对应选择子SelectorNormal加载到ds、es和ss,就是上面所说的这个原因

下面就来看一下保护模式返回到实模式前用到的16位代码段:

; 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]

这段代码前面6条mov指令就是上面所说的目的:为了使对应段描述符告诉缓存寄存器中含有合适的段界限和属性,需要加载一个合适的描述符选择子到有关寄存器中。这边我现在也不太了解,先记住吧

接下来的3条指令实现将cr0的PE位(第0位)置零,代表运行(将要运行)在保护模式下。

还记得上面提到的mov [LABEL_GO_BACK_TO_REAL+3], ax指令吗?上面只是说它要用在从保护模式返回到实模式中,这里就详细说一下它为什么能够实现这样的功能:

你看这里的jmp指令的段地址是0,但是这是程序刚加载到内存中的时候,随着运行到mov [LABEL_GO_BACK_TO_REAL+3], ax会发生什么呢?

首先看一下jmp 0:LABEL_REAL_ENTRY的机器码:

   BYTE1      BYTE2     BYTE3      BYTE4     BYTE5
0EAhoffsetSegment

由上图可以看出,LABEL_GO_BACK_TO_REAL+3恰好就是Segment的地址,而执行mov [LABEL_GO_BACK_TO_REAL+3], ax之前ax的值已经是实模式下的cs(假设记为cs_real_mode)了,所以它这条mov指令将把cs保存到segment的位置,等到jmp指令执行时,它已经不再是:

jmp    0:LABEL_REAL_ENTRY

而是:

jmp    cs_real_mode:LABEL_REAL_ENTRY

这条指令将会跳转到标号LABEL_REAL_ENTRY处。现在已经跳回到实模式了,接下来就是要重新设置各个寄存器的值,并回复sp的值,然后关闭A20,打开中断,重新回到原来的样子。LABEL_REAL_ENTRY的代码如下:

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]

这里我们又看到了SPValueInRealMode,还记得上面没有详细说的指令mov [SPValueInRealMode], sp吗?从这而很容易可以看出,它保存实模式下sp的值,也是为了现在回到实模式回复sp的值。

关闭A20地址先,开中断之后,通过int 21h中断返回到DOS。

这样整个程序的运行过程就结束了哈哈。

编译运行

通过nasm编译成.com文件,这里面还有如何突破引导扇区512字节的限制。弄明白了再详细记录。

完整源代码

下面是主程序的完整源代码


; ==========================================
; pmtest2.asm
; 编译方法:nasm pmtest2.asm -o pmtest2.com
; ==========================================

%include    "pm.inc"    ; 常量, 宏, 以及一些说明

org    0100h
    jmp    LABEL_BEGIN

[SECTION .gdt]
; GDT
;                            段基址,        段界限 , 属性
LABEL_GDT:         Descriptor    0,              0, 0         ; 空描述符
LABEL_DESC_NORMAL: Descriptor    0,         0ffffh, DA_DRW    ; Normal 描述符
LABEL_DESC_CODE32: Descriptor    0, SegCode32Len-1, DA_C+DA_32; 非一致代码段, 32
LABEL_DESC_CODE16: Descriptor    0,         0ffffh, DA_C      ; 非一致代码段, 16
LABEL_DESC_DATA:   Descriptor    0,      DataLen-1, DA_DRW    ; Data
LABEL_DESC_STACK:  Descriptor    0,     TopOfStack, DA_DRWA+DA_32; Stack, 32 位
LABEL_DESC_TEST:   Descriptor 0500000h,     0ffffh, DA_DRW
LABEL_DESC_VIDEO:  Descriptor  0B8000h,     0ffffh, DA_DRW    ; 显存首地址
; GDT 结束

GdtLen        equ    $ - LABEL_GDT    ; GDT长度
GdtPtr        dw    GdtLen - 1    ; GDT界限
        dd    0        ; GDT基地址

; GDT 选择子
SelectorNormal        equ    LABEL_DESC_NORMAL    - LABEL_GDT
SelectorCode32        equ    LABEL_DESC_CODE32    - LABEL_GDT
SelectorCode16        equ    LABEL_DESC_CODE16    - LABEL_GDT
SelectorData        equ    LABEL_DESC_DATA        - LABEL_GDT
SelectorStack        equ    LABEL_DESC_STACK    - LABEL_GDT
SelectorTest        equ    LABEL_DESC_TEST        - LABEL_GDT
SelectorVideo        equ    LABEL_DESC_VIDEO    - LABEL_GDT
; END of [SECTION .gdt]

[SECTION .data1]     ; 数据段
ALIGN    32
[BITS    32]
LABEL_DATA:
SPValueInRealMode    dw    0
; 字符串
PMMessage:        db    "In Protect Mode now. ^-^", 0    ; 在保护模式中显示
OffsetPMMessage        equ    PMMessage - $$
StrTest:        db    "ABCDEFGHIJKLMNOPQRSTUVWXYZ", 0
OffsetStrTest        equ    StrTest - $$
DataLen            equ    $ - LABEL_DATA
; END of [SECTION .data1]


; 全局堆栈段
[SECTION .gs]
ALIGN    32
[BITS    32]
LABEL_STACK:
    times 512 db 0

TopOfStack    equ    $ - LABEL_STACK - 1

; END of [SECTION .gs]


[SECTION .s16]
[BITS    16]
LABEL_BEGIN:
    mov    ax, cs
    mov    ds, ax
    mov    es, ax
    mov    ss, ax
    mov    sp, 0100h

    mov    [LABEL_GO_BACK_TO_REAL+3], ax
    mov    [SPValueInRealMode], sp

    ; 初始化 16 位代码段描述符
    mov    ax, cs
    movzx    eax, ax
    shl    eax, 4
    add    eax, LABEL_SEG_CODE16
    mov    word [LABEL_DESC_CODE16 + 2], ax
    shr    eax, 16
    mov    byte [LABEL_DESC_CODE16 + 4], al
    mov    byte [LABEL_DESC_CODE16 + 7], ah

    ; 初始化 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

    ; 初始化数据段描述符
    xor    eax, eax
    mov    ax, ds
    shl    eax, 4
    add    eax, LABEL_DATA
    mov    word [LABEL_DESC_DATA + 2], ax
    shr    eax, 16
    mov    byte [LABEL_DESC_DATA + 4], al
    mov    byte [LABEL_DESC_DATA + 7], ah

    ; 初始化堆栈段描述符
    xor    eax, eax
    mov    ax, ds
    shl    eax, 4
    add    eax, LABEL_STACK
    mov    word [LABEL_DESC_STACK + 2], ax
    shr    eax, 16
    mov    byte [LABEL_DESC_STACK + 4], al
    mov    byte [LABEL_DESC_STACK + 7], ah

    ; 为加载 GDTR 作准备
    xor    eax, eax
    mov    ax, ds
    shl    eax, 4
    add    eax, LABEL_GDT        ; eax <- gdt 基地址
    mov    dword [GdtPtr + 2], eax    ; [GdtPtr + 2] <- gdt 基地址

    ; 加载 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    
    ; 执行这一句会把 SelectorCode32 装入 cs, 并跳转到 Code32Selector:0  处

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

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]


[SECTION .s32]; 32 位代码段. 由实模式跳入.
[BITS    32]

LABEL_SEG_CODE32:
    mov    ax, SelectorData
    mov    ds, ax            ; 数据段选择子
    mov    ax, SelectorTest
    mov    es, ax            ; 测试段选择子
    mov    ax, SelectorVideo
    mov    gs, ax            ; 视频段选择子

    mov    ax, SelectorStack
    mov    ss, ax            ; 堆栈段选择子

    mov    esp, TopOfStack


    ; 下面显示一个字符串
    mov    ah, 0Ch            ; 0000: 黑底    1100: 红字
    xor    esi, esi
    xor    edi, edi
    mov    esi, OffsetPMMessage    ; 源数据偏移
    mov    edi, (80 * 10 + 0) * 2    ; 目的数据偏移。屏幕第 10 行, 第 0 列。
    cld
.1:
    lodsb
    test    al, al
    jz    .2
    mov    [gs:edi], ax
    add    edi, 2
    jmp    .1
.2:    ; 显示完毕

    call    DispReturn

    call    TestRead
    call    TestWrite
    call    TestRead

    ; 到此停止
    jmp    SelectorCode16:0

; ------------------------------------------------------------------------
TestRead:
    xor    esi, esi
    mov    ecx, 8
.loop:
    mov    al, [es:esi]
    call    DispAL
    inc    esi
    loop    .loop

    call    DispReturn

    ret
; TestRead 结束-----------------------------------------------------------


; ------------------------------------------------------------------------
TestWrite:
    push    esi
    push    edi
    xor    esi, esi
    xor    edi, edi
    mov    esi, OffsetStrTest    ; 源数据偏移
    cld
.1:
    lodsb
    test    al, al
    jz    .2
    mov    [es:edi], al
    inc    edi
    jmp    .1
.2:

    pop    edi
    pop    esi

    ret
; TestWrite 结束----------------------------------------------------------


; ------------------------------------------------------------------------
; 显示 AL 中的数字
; 默认地:
;    数字已经存在 AL 中
;    edi 始终指向要显示的下一个字符的位置
; 被改变的寄存器:
;    ax, edi
; ------------------------------------------------------------------------
DispAL:
    push    ecx
    push    edx

    mov    ah, 0Ch            ; 0000: 黑底    1100: 红字
    mov    dl, al
    shr    al, 4
    mov    ecx, 2
.begin:
    and    al, 01111b
    cmp    al, 9
    ja    .1
    add    al, ‘0‘
    jmp    .2
.1:
    sub    al, 0Ah
    add    al, ‘A‘
.2:
    mov    [gs:edi], ax
    add    edi, 2

    mov    al, dl
    loop    .begin
    add    edi, 2

    pop    edx
    pop    ecx

    ret
; DispAL 结束-------------------------------------------------------------


; ------------------------------------------------------------------------
DispReturn:
    push    eax
    push    ebx
    mov    eax, edi
    mov    bl, 160
    div    bl
    and    eax, 0FFh
    inc    eax
    mov    bl, 160
    mul    bl
    mov    edi, eax
    pop    ebx
    pop    eax

    ret
; DispReturn 结束---------------------------------------------------------

SegCode32Len    equ    $ - LABEL_SEG_CODE32
; END of [SECTION .s32]


; 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]

一个操作系统的实现(3)-保护模式进阶

标签:

原文地址:http://blog.csdn.net/friendley/article/details/51429695

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!