zoukankan      html  css  js  c++  java
  • 自制操作系统Antz(2)——进入保护模式 (上) jmp到保护模式

      Antz系统更新地址: https://www.cnblogs.com/LexMoon/category/1262287.htm

      Linux内核源码分析地址:https://www.cnblogs.com/LexMoon/category/1267413.html

    0. 如果你不知道什么是保护模式

      你可能不知道什么是保护模式,没有关系,在你知道之前让我们先来看一段代码,如果你没有接触过这些内容,可能会觉得一头雾水,不知所云,不要紧,我们可以一点一点来分析。

      os.asm :  

    %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        ; 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 * 10 + 0) * 2    ; 屏幕第 10 行, 第 0 列。
        mov    ah, 0Ch            ; 0000: 黑底    1100: 红字
        mov    al, 'P'
        mov    [gs:edi], ax
    
        ; 到此停止
        jmp    $
    
    SegCode32Len    equ    $ - LABEL_SEG_CODE32
    ; END of [SECTION .s32]

      

      pm.inc节选 :

    ; 描述符
    ; 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                (2 字节)
        dw    %1 & 0FFFFh                ; 段基址 1                (2 字节)
        db    (%1 >> 16) & 0FFh            ; 段基址 2                (1 字节)
        dw    ((%2 >> 8) & 0F00h) | (%3 & 0F0FFh)    ; 属性 1 + 段界限 2 + 属性 2        (2 字节)
        db    (%1 >> 24) & 0FFh            ; 段基址 3                (1 字节)
    %endmacro ; 共 8 字节

      读完之后你可能一头雾水,但是这段代码已经完成了实模式到保护模式的转换。

    nasm os.asm -o os.com

      先使用nasm编译os.asm生成os.com文件。

      然后使用DOS-BOX打开。

        

      屏幕中显示了一个黑底红字的字符 "P"

      接下来分析上面的代码:

       [SECTION.gdt]段中有三个Descriptor,是一个叫GDT的数组。接下来的GdtLen是GDT的长度。GdtPtr也是个小的数据结构,它有6个字节,前两个字节是GDT的长度GdtLen,后四个字节是GDT的基地址。

       另外定义了两个SelectorCode32,SelectorVideo的常量。暂时可不管它。

       [BITS 16]明确指明了它是一个16位的代码段,它修改了一些GDT的值,然后执行了一些不常见的指令,最后通过jmp指令进行了跳转。jmp Selectorcode32:0 ,执行这一句会真正进入保护模式,把 SelectorCode32 装入 cs, 并跳转到 SelectorCode32:0  处 。也就是第三个section,即[SECTION.s32]中,这个段是32位的,在结束处的 jmp $ 进入了无限循环。

      你可能会疑惑什么是GDT,那些看上去怪怪的指令到底做了什么。它们的内容如下:

        1)定义一个叫做GDT的数据结构。

        2)后面的16位代码进行了一些与GDT有关的操作。

        3)程序最后跳到了32位代码中做了一点操作显存的工作。

      那么GDT是什么?它是用来干什么的呢? 程序对GDT做了什么? jmp SelectorCode32:0和我们之前的jmp有什么不同呢?

      有了这些问题,我们现在就可以出发去了解保护模式了。

    1. GDT

      CPU有两种工作模式:实模式和保护模式。

      当我们开机时,开始的CPU是工作在实模式下的,经过某种机制之后,才进入保护模式。在保护模式下,CPU有着巨大的寻址模式,并为操作系统提供了更好的硬件模式。 那么从实模式到保护模式的转换其实就类似于政权的更替,开机时是在实模式下,就像皇帝A在执政,他有他的政策。后来通过了一种转换,类似于革命,皇帝B登基,新皇帝登基的那一刻就是一个历史性的 jmp , 然后开始了皇帝B的统治,他也有了他的一套全新的政策。当然新政策比老政策好得多,虽然他变复杂了,这套新政策就是保护模式。

      先来回顾一些旧政策,实模式。一个地址是由段地址和偏移地址两部分组成的,物理地址遵从这样的计算公式。

          物理地址 = 段地址 x 16 + 偏移地址 

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

      从386开始的32位时代,寻址空间可以达到4GB,所以16位寄存器已经不够用了。

      在实模式下,16位寄存器需要“段:偏移”才有 1MB的寻址能力,如今我们有了32位寄存器,一个寄存器就有了4GB的寻址哪里。那么是不是段值就可以被抛弃了呢?  

      其实不然,新政策下仍然使用 “SEG:OFFSET”的形式表示 , 只不保护模式下的段值概念发生了根本性的变化。实模式下,段值还可以看作是地址的一部分。而保护模式下,虽然段值仍然由原来的16位的CS,DS等寄存器表示,但此时他仅仅变成了一个索引,这个索引指向了一个数据结构的表项,其中详细定义了段的起始地址,界限,属性等内容。这个数据结构就是GDT(也可能是LDT)。GDT中的表项也有一个专门的名字,叫做描述符(Descriptor)。

        

      也就是说,GDT的作用是用来提供段式存储机制,这种机制是通过段寄存器和GDT中的描述符共同提供的。

      之前代码中的宏定义Descriptor这个宏用比较自动化的方法把段基址,段界限和段属性安排在描述符中合适的位置。

      再来看看之前代码中定义的一个Descriptor数组,LABEL_GDT ,LABEL_DESC_CODE32 , LABEL_DESC_VIDEO 。  

      LABEL_DESC_VIDEO的段基址0B8000h,这个描述符指向的正是显存。

      那么CS,DS等段寄存器如何与这些段对应起来呢?

      在[SECTION.32]中有两句代码是:

        mov ax,SelectorVideo

        mov gs,ax

      段寄存器gs的值变成了SelectorVideo, SelectorVideo的定义是:

        SelectorVideo  equ  LABEL_DESC_VIDEO - LABEL_GDT

      直观的看,它好像是DESC_VIDEO这个描述符相对GDT基址的偏移。实际上它有一个专门的名称叫做选择子,它也表示一个偏移,而是稍微复杂一点。

        mov  [gs:edi] , ax

      gs的值是SelectorVideo,它只是对应显存的描述符LABEL_DESC_VIDEO, 这条指令把ax的值写入显存中偏移位edi的位置。

      到了这里,可以想到,既然[SECTION.S32]是32位程序,并且在保护模式下执行,那么[SECTION.s16]的任务一定是从实模式向保护模式跳转了。

    2. 实模式到保护模式,不一般的 jmp

      在[SECTION.s16]段最后。

      jmp dword SelectorCode32:0   ;执行这一句会把 SelectorCode32 装入 cs, 并跳转到 SelectorCode32:0 处

      跳转的目标是描述符 DESC_CODE32对应的段的首地址,即标号LABEL_SEG_CODDE32处。

      此时,新皇帝登基,开始了保护模式。

      不过,这个jmp比看起来还要复杂一点,因为它不得不放在16位的段中,目标地址却是32位。从这一点来看,它是混合16位和32位代码。所以写为jmp SelectorCode32:0 是不严谨的,因为偏移地址是32位的,这样编译出来的只是16位的代码。假设目标地址的偏移不是0,而是一个32位的值,比如 jmp SelectorCode32:0x12345678,则编译之后偏移会被截断,只剩下0x5678。

      所以需要加上dword,但Nasm允许加在整个地址之前,就是我们之前写的那样,也就是我们为什么那样写了。

      那么进入保护模式的步骤就是:

        1)准备GDT

        2)用 lgdt 加载 gdtr

        3)打开 A20

        4)  跳转,进入保护模式

  • 相关阅读:
    Cordova各个插件使用介绍系列(四)—canvas2ImagePlugin保存二维码到手机本地
    如何实现一个简单的MVVM框架
    2015年总结
    基于setTimeout制作滚动广告板
    (Frontend Newbie)JavaScript基础之函数
    (Frontend Newbie)JavaScript基础之常见数据类型
    (Frontend Newbie)Web三要素(三)
    (Frontend Newbie)Web三要素(二)
    (Frontend Newbie) Web三要素(一)
    (Frontend Newbie)Web简史
  • 原文地址:https://www.cnblogs.com/LexMoon/p/antz02.html
Copyright © 2011-2022 走看看