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

一个操作系统的实现(2)-认识保护模式

时间:2016-05-18 19:23:04      阅读:197      评论:0      收藏:0      [点我收藏+]

标签:

今天开始学习intel处理器的保护模式。书的第二章

这节讲述的是如何从实模式进入保护模式。用的例子是在保护模式下向屏幕上输出字符P

如何进入保护模式呢?主要步骤如下:

0. 进入保护模式的步骤

  1. 准备GDT
  2. 用lgdt加载gdtr
  3. 打开A20
  4. 置r0的PE位位1
  5. 跳转,进入保护模式

下面是书的例子:

1. 进入保护模式实例

; ==========================================
; pmtest1.asm
; 编译方法:nasm pmtest1.asm -o pmtest1.bin
; ==========================================

%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; 非一致代码段
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          ; 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  处
; END of [SECTION .s16]


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

LABEL_SEG_CODE32:
        mov     ax, SelectorVideo
        mov     gs, ax                  ; 视频段选择子(目的)

        mov     edi, (80 * 11 + 79) * 2 ; 屏幕第 11 行, 第 79 列。
        mov     ah, 0Ch                 ; 0000: 黑底    1100: 红字
        mov     al, ‘P‘
        mov     [gs:edi], ax

        ; 到此停止
        jmp     $

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

用到的Descriptorpm.inc中定义,关于Descriptor定义的内容如下:

; 描述符
; usage: Descriptor Base, Limit, Attr
;        Base:  dd
;        Limit: dd (low 20 bits available)
;        Attr:  dw (lower 4 bits of higher byte are always 0)
%macro Descriptor 3
        dw      %2 & 0FFFFh                             ; 段界限1
        dw      %1 & 0FFFFh                             ; 段基址1
        db      (%1 >> 16) & 0FFh                       ; 段基址2
        dw      ((%2 >> 8) & 0F00h) | (%3 & 0F0FFh)     ; 属性1 + 段界限2 + 属性2
        db      (%1 >> 24) & 0FFh                       ; 段基址3
%endmacro ; 共 8 字节

刚开始看到上面的代码,我有点束手无策。因为也是最近才开始学习汇编,上面的程序我连指令都认不全。所以下面这一节对上面程序中语法的部分做一些讲解

2. 关于实例中汇编语法的讲解

%include "pm.inc":包含文件。类似c语言中的包含.h文件。

org 07c00horg是origin的缩写。告诉编译器下一条汇编语句的偏移地址是07c00h

[SECTION .gdt]:AT&T汇编语言格式,用于定义一个节。这里是定义一个结构体数组,数组名称是GDT,数组内部是三个Descriptor结构。

LABEL_GDT: Descriptor 0, 0, 0Descriptor是在pm.inc中定义的宏,8个字节。上面有列出来内部的定义。个人猜测定义中的%1%2%3是这里传进去的参数,按照位置分别是1、2、3。猜测跟shell中的位置参数类似。(现在先猜测一下,到影响继续学习的时候再深究)。上面定义的Descriptor这个宏能够用比较自动化的方法把段基址、段界限和段属性安排在一个描述符中合适的位置。这儿也不是很了解,不知道自动化是如何实现的

GdtLen equ $ - LABEL_GDTequ是伪指令。这句话的意思是用GdtLen来代替$ - LABEL_GDT。从这儿看类似于c语言中的define

GdtPtr          dw      GdtLen - 1      ; GDT界限
                dd      0               ; GDT基地址

这里定义一个结构体数组GdtPtr,共有6个字节。前2字节(处于低位)是GDT的界限;后4字节(处于高位)是GDT的基地址。

SelectorCode32 equ LABEL_DESC_CODE32 - LABEL_GDT:这句话定义GDT的选择子(sector)SelectorCode32。在下面讲解GDT里面会有详细介绍。

继续向下看

[BITS 16]:用来指明此节一个16位代码段。

lgdt [GdtPtr]lgdt指令用来将GdtPtr这个结构体装入寄存器GDTR

cli:关中断,对应的开中断指令是sti

这里面还出现了新的寄存器eax,下面的图说明了eaxaxahal的关系:

00000000 00000000 00000000 00000000
|===============EAX===============|--32个0,4个字节,2个字,1个双字
                  |======AX=======|--16个0,2个字节,1个字
                  |==AH===|        --8个0,1个字节
                          |===AL==|--8个0,1个字节

eax是32位的寄存器,但它实际上只是在原有的8086CPU的寄存器ax上增加了一倍的数据位数而已。所以eax和ax二者并不是独立的,而是整体与部分的关系。举例来说,对eax直接赋值,若更改了低16位自然会改变了ax值,同样ax又会影响eax整体。而ah,al寄存器和ax之间的关系也是如此。同样还有ebx,ecx,edx。(上面摘抄互联网并作了一些补充)

IA32还加了两个段寄存器fsgs。用来减缓es的压力。用法与es相同。

上面这些指令比较生疏。其他的指令在学习8086汇编的时候都是学过,比较熟悉。

那指令都认识了,但是对于上面代码的运行还是一头雾水,接下来对代码的含义进行分析。

3. 关于实例如何实现实模式到保护模式的切换

看上面的代码,

程序首先被加载到内存07c00h处,然后直接跳转到LABEL_BEGIN处。

在LABEL_BEGIN处,程序首先使dses寄存器指向与cs相同的段,上节里面说了,这是为了以后进行数据操作的时候能定位到正确的位置。然后初始化栈。

接下来初始化32位代码段描述符(32位代码段就是指程序最下面的[SECTION .s32])。这段初始化代码就是将下面那个32位代码段基址写到GDT中对应的描述符结构中。你看GDT结构体中LABEL_DESC_CODE32那一项的段界限与属性都定义好了,只有段基址没有定义,上面关于初始化32位代码描述符的作用就是初始化描述符中的基址。

要说上面的这步是干什么的,那么首先需要了解IA32的寻址过程了,下面有详细的介绍。不了解的需要先跳到下面关于GDT的讲解,再回来继续看。

到这里,GDT已经初始化好了,接下来的lgdt [GdtPtr]是把GDT的基地址和段界限加载到GDTR这个寄存器中。看看上面的GdtPtr结构体,它可不是随意定义的。它的结构与gdtr寄存器的结构是相同的,看看下面gdtr的结构,在对比上面介绍的GdtPtr,你就知道了。

32位基址16位界限
H-------------------------------------------------------------------------L

再下面是关中断,因为进入保护膜是之后中断处理机制与现在是不同的,所以在进入之前需要关中断。如果不关中断将会出现错误。

关中断之后的代码就是纯粹为了进入保护模式做准备的了。这里主要有两个步骤:

首先打开地址线A20。关于A20,书上是这样说的:

那么什么是A20呢?这又是一个历史问题。8086中,“段:偏移”这样的模式能表示的最大内存是FFFF:FFFF,即10FFEFh。可是8086只有20位的地址总线,只能寻址到1MB,那么如果试图访问超过1MB的地址时会怎样呢?实际上系统并不会发生异常,而是回卷(wrap)回去,重新从地址零开始寻址。可是,到了80286时,真的可以访问到1MB以上的内存了,如果遇到同样的情况,系统不会再回卷寻址,这就造成了向上不兼容,为了保证百分之百兼容,IBM想出一个办法,使用8042键盘控制器来控制第20个(从零开始数)地址位,这就是A20地址线,如果不被打开,第20个地址位将会总是零。显然,为了访问所有的内存,我们需要把A20打开,开机时它默认是关闭的。这里打开A20的方式是让92h这个端口的第1位(从低位0开始)的值置为1

接下来将cr0这个寄存器的第0位置为1。为什么要这么做呢?这是因为当该位为0时,CPU运行于实模式,为1时,运行于保护模式。所以当将cr0的第0位置1之后,我们就相当欲闭合了进入保护模式的开关。

也就是说,“mov cr0, eax”这一句之后,系统就运行于保护模式之下了。但是,此时cs的值仍然是实模式下的值,我们需要把代码段的选择子装入cs。所以,我们需要第71行的jmp指令:

jmp dword SelectorCode32:0
根据寻址机制我们知道,这个跳转的目标将是描述符DESC_CODE32对应的段的首地址,即标号LABEL_SEG_CODE32处。

到这里,执行jmp指令后,就真正进入了保护模式。

进入保护模式后,就开始运行[SECTION .s32]段的代码。这段代码比较简单:就是在屏幕的第12行80列输出一个红色的P,然后进入无线循环。

至此,整个程序运行完毕。

上面的介绍中只是粗略的讲了一下GDT,下面对IA32为什么引入GDT进行详细介绍。

4. GDT(Global Descriptor Table)

如果你熟悉Intel 8086汇编,那么你一定知道Intel 8086是16位的CPU,它有着16位的寄存器(Register)、16位的数据总线(Data Bus)以及20位的地址总线(Address Bus)和1MB的寻址能力。一个地址是由段和偏移两部分组成的,物理地址遵循这样的计算公式:

物理地址(Physical Address)=段值(Segment)×16+偏移(Offset)

其中,段值和偏移都是16位的。

从80386开始,Intel家族的CPU进入32位时代。80386有32位地址线,所以寻址空间可以达到4GB。所以,单从寻址这方面说,使用16位寄存器的方法已经不够用了。这时候,我们需要新的方法来提供更大的寻址能力。

在实模式下,16位的寄存器需要用“段:偏移”这种方法才能达到1MB的寻址能力,如今我们有了32位寄存器,一个寄存器就可以寻址4GB的空间,是不是从此段值就被抛弃了呢?实际上并没有,新政策下的地址仍然用“段:偏移”这样的形式来表示,只不过保护模式下“段”的概念发生了根本性的变化。实模式下,段值还是可以看做是地址的一部分的,段值为XXXXh表示以XXXX0h开始的一段内存。而保护模式下,虽然段值仍然由原来16位的cs、ds等寄存器表示,但此时它仅仅变成了一个索引,这个索引指向一个数据结构的一个表项,表项中详细定义了段的起始地址、界限、属性等内容。这个数据结构,就是GDT(还可能是LDT)。GDT中的表项也有一个专门的名字,叫做描述符(Descriptor)。

也就是说,GDT的作用是用来提供段式存储机制,这种机制是通过段寄存器和GDT中的描述符共同提供的。其中描述符有多种:代码段欲数据段描述符、系统段描述、门描述符。上面的程序用到了代码段的描述符,它的结构如下:

技术分享

上面除了BYTE5和BTYE6中的一堆属性看上去有点复杂以外,其他三个部分倒还容易理解,它们分别定义了一个段的基址和界限。不过,由于历史问题,它们都被拆开存放。至于那些属性,我们暂时先不管它。

好了,我们回头再来看看代码,Descriptor这个宏用比较自动化的方法把段基址、段界限和段属性安排在一个描述符中合适的位置,有兴趣的读者可以研究这个宏的具体内容。本例的GDT中共有3个描述符,为方便起见,在这里我们分别称它们为DESC_DUMMY、DESC_CODE32和DESC_VIDEO。其中DESC_VIDEO的段基址是0B8000h,顾名思义,这个描述符指向的正是显存。

现在我们已经知道,GDT中的每一个描述符定义一个段,那么cs、ds等段寄存器是如何和这些段对应起来的呢?你可能注意到了,在[SECTION.s32]这个段中有两句代码是这样的:

mov ax, SelectorVideo
mov gs, ax

看上去,段寄存器gs的值变成了SelectorVideo,我们在上文中可以看到,SelectorVideo是这样定义的:SelectorVideo equ LABEL_DESC_VIDEO-LABEL_GDT。直观地看,它好像是DESC_VIDEO这个描述符相对于GDT基址的偏移。实际上,它有一个专门的名称,叫做选择子(Selector),它也不是一个偏移,而是稍稍复杂一些,它的结构如图3.5所示。

1514131211109876543210
描述符索引TIRPL

不难理解,当TI和RPL都为零时,选择子就变成了对应描述符相对于GDT基址的偏移,就好像我们程序中那样。这点还是不太了解

看到这里,你肯定已经明白了mov [gs:edi], ax的意思,gs值为SelectorVideo,它指示对应显存的描述符DESC_VIDEO,这条指令将把ax的值写入显存中偏移位edi的位置。

总之,整个寻址方式如下图所示:
技术分享

上面关于GDT的内容引用自书本。

到这里,整个程序讲解完毕。

书上还有关于描述符属性的详细解释和突破软盘引导512字节的限制。

一个操作系统的实现(2)-认识保护模式

标签:

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

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