保护模式入门

本博客参考书籍 《操作系统 真象还原》 作者:郑钢
在以前就知道实模式,虚拟模式,保护模式。但就知道个名字。今天实模式入门还是挺开心的。这里面东西很多。咱们重点谈段描述符和全局描述符表


1、段描述符

在这里插入图片描述
段描述符是全局描述符表的组成部分。顾名思义,段描述符是描述段的相关信息。我们现在还没有看到具体的段描述符,但是我们可以简单的预测一些段描述符的相关信息。首先为了防止越界,我们要加入界限的相关信息。为了访问更大的内存,我们需要更多位来表示段基址。更多的位数,我们可以将他们放入内存…
具体的段描述符格式见下图。
每一位详细的介绍,见书上。
在这里插入图片描述

2、全局描述符表

由于段描述符已经很长了。如果还是使用段地址:偏移地址,则要花很长的寄存器来存储段描述符。
那怎么办?重点来了。
实模式下,段寄存器存储的不再是段地址,而是选择子。
段选择子,类似于数组的下表,通过这个下表便可以在全局描述符找到段的信息。
段描述符,本质上,是段的“身份证”。选择子的作用是确定段描述符。

全局描述符表GDT(由于我现在还不清楚局部描述符表LDT,所以暂时不考虑它。),存放在内存中。
CPU通过寄存器GDTR来存储全局描述符表的位置和限界。
通过lgdt 指令来赋值GDTR。

在这里插入图片描述

3、进入实模式

        ; 打开A20地址线
        in al, 0x92
        or al, 00000010B
        out 0x92, al

        ; 加载gdt
        lgdt [gdt_ptr]

        ; cr0第0位置1
        mov eax, cr0
        or eax, 0x00000001
        mov cr0, eax

4、代码

4.1 boot.inc

;---------------------------loader和kernel-------------
LOADER_BASE_ADDR equ 0x900 ;loader放入内存的位置
LOADER_START_SECTOR equ 0x2  ;loader从硬盘读取loader的位置。即loader在硬盘的存放位置


; gdt描述符属性
; 段描述符高23位,表示段界限的粒度为4KB.段界限*粒度=2^20*4k=4G
DESC_G_4K equ 100000000000000000000000b   

; D/B为,1表示运行在32位模式下.(地址和操作数是32位)
DESC_D_32 equ 10000000000000000000000b

; 高21位,如果为1表示为64位代码段,目前我们都是在32位模式下操作,故为零
DESC_L equ     0000000000000000000000b

; 没有明确的用途,取值随意
DESC_AVL equ   000000000000000000000b

; 第二部分段界限值,由于采用了32位平坦模型,所以段界限为(4GB / 4KB) - 1 = 0xFFFFF,故为全1
DESC_LIMIT_CODE2 equ 11110000000000000000b
DESC_LIMIT_DATA2 equ DESC_LIMIT_CODE2

; 书中取全零,怀疑是错误的,因为保护模式的基地址是0xb8000,所以最后8位应该是b,而不是0
;书中应该是写错了
DESC_LIMIT_VIDEO2 equ 00000000000000000000000000001011b

DESC_P equ 1000000000000000b ;在内存中,P置1
DESC_DPL_0 equ 000000000000000b ;下面几个是特权级,分别是0,1,2,3
DESC_DPL_1 equ 010000000000000b
DESC_DPL_2 equ 100000000000000b
DESC_DPL_3 equ 110000000000000b

DESC_S_CODE equ  1000000000000b ;S为1是非系统段,S为0是系统段
DESC_S_DATA equ  DESC_S_CODE
DESC_S_sys equ  0000000000000b

;x=1,c=0,r=0,a=0 代码段可执行,非一致性,不可读,已访问位清零
DESC_TYPE_CODE equ 100000000000b
;x=0,c=0,r=1,a=0 数据段不可执行,可读写数据段,已访问位清零
DESC_TYPE_DATA equ 001000000000b

; 代码段描述符的高32位表示,其中(0x00 << 24表示最高8位的段基址值,由于我们采用的是平坦模型,故基址为零),后面唯一可变的就是段界限值
DESC_CODE_HIGH4 equ (0x00 << 24) + DESC_G_4K + DESC_D_32 + \
    DESC_L + DESC_AVL + DESC_LIMIT_CODE2 + \
    DESC_P + DESC_DPL_0 + DESC_S_CODE + DESC_TYPE_CODE + 0x00

;数据段描述符高32位
DESC_DATA_HIGH4 equ (0x00 << 24) + DESC_G_4K + DESC_D_32 + \
    DESC_L + DESC_AVL + DESC_LIMIT_DATA2 + \
    DESC_P + DESC_DPL_0 + DESC_S_DATA + DESC_TYPE_DATA + 0x00

;显存段描述符高32位
DESC_VIDEO_HIGH4 equ (0x00 << 24) + DESC_G_4K + DESC_D_32 + \
    DESC_L + DESC_AVL + DESC_LIMIT_VIDEO2 + \
    DESC_P + DESC_DPL_0 + DESC_S_DATA + DESC_TYPE_DATA + 0x00

; 选择字属性
RPL0 equ 00b;低两位的请求特权
RPL1 equ 01b
RPL2 equ 10b
RPL3 equ 11b
TI_GDT equ 000b;0代表GDT,1代表LDT
TI_LDT equ 100b

4.2 loader.asm

%include "boot.inc"

section loader vstart=LOADER_BASE_ADDR
LOADER_STACK_TOP equ LOADER_BASE_ADDR

jmp loader_start

; 这里其实就是GDT的起始地址,第一个描述符为空
GDT_BASE: dd 0x00000000
          dd 0x00000000

; 代码段描述符,一个dd为4字节,段描述符为8字节,上面为低4字节
; 段界限全是f,所以能最大访问4G
CODE_DESC: dd 0x0000FFFF
           dd DESC_CODE_HIGH4

; 栈段描述符,和数据段共用
DATA_STACK_DESC: dd 0x0000FFFF
                 dd DESC_DATA_HIGH4

; 显卡段,非平坦
; 显存的地址范围是:0xb8000~0xbffff。b在高四个字节中哦,在头文件中已经设置过了。
; 低四个字节从b开始。按照粒度为4k划分,最大限度为7
VIDEO_DESC: dd 0x80000007
            dd DESC_VIDEO_HIGH4

GDT_SIZE equ $ - GDT_BASE
GDT_LIMIT equ GDT_SIZE - 1
times 120 dd 0
SELECTOR_CODE equ (0x0001 << 3) + TI_GDT + RPL0 ;分别是三个选择子
SELECTOR_DATA equ (0x0002 << 3) + TI_GDT + RPL0
SELECTOR_VIDEO equ (0x0003 << 3) + TI_GDT + RPL0

gdt_ptr dw GDT_LIMIT ;低两个字节表GDT界限
        dd GDT_BASE  ;高四个字节表GDT在内存的起始地址:0+start=0x900

loadermsg db '2 loader in real.'

loader_start: 

        ; 调用10h号中断显示字符串
        mov sp, LOADER_BASE_ADDR
        mov bp, loadermsg
        ; 字符串长度
        mov cx, 17
        ; 子功能号以及显示方式
        mov ax, 0x1301
        ; 页号:0, 蓝底粉红字
        mov bx, 0x001f
        mov dx, 0x1800
        int 0x10

        ; 打开A20地址线
        in al, 0x92
        or al, 00000010B
        out 0x92, al

        ; 加载gdt
        lgdt [gdt_ptr]

        ; cr0第0位置1
        mov eax, cr0
        or eax, 0x00000001
        mov cr0, eax

        ; 刷新流水线
        jmp dword SELECTOR_CODE:p_mode_start

[bits 32]
p_mode_start:
    mov ax, SELECTOR_DATA
    mov ds, ax

    mov es, ax
    mov ss, ax

    mov esp, LOADER_STACK_TOP
    mov ax, SELECTOR_VIDEO
    mov gs, ax

    mov byte [gs:160], 'P'
    ; 与书中不同,这里使用蓝底白字
    mov byte [gs:161], 0x1f

    jmp $

5、结果

在这里插入图片描述

6、tips

1、
在这里插入图片描述我们来分析下。
显存的地址范围是:0xb8000~0xbffff。
段描述符中,段基址分为三部分:31~24, 23~16,15~0。
所以:DESC_LIMIT_VIDEO2 equ 00000000000000000000000000001011b ;第二部分是左边这样滴。
前两部分在高四个字节。最后一部分,在低四个字节的最高一个字节中。
VIDEO_DESC: dd 0x80000007 ;limit=(0xbffff-0xb8000)/4k=7
dd DESC_VIDEO_HIGH4
参考:https://github.com/da1234cao/tiny-os

2、
gdt_ptr dw GDT_LIMIT ;低两个字节表GDT界限
dd GDT_BASE ;高四个字节表GDT在内存的起始地址:0+start=0x900

3、

info gdt ;查看GDT的相关信息
creg  ;查看控制寄存器的命令

4、
选择结构对流水线有很大影响。

5、
检查是否越界
在这里插入图片描述


注:这一章,最难理解的是代码,文章的截图来自书本。详细内容见书本。

发布了104 篇原创文章 · 获赞 134 · 访问量 17万+

猜你喜欢

转载自blog.csdn.net/sinat_38816924/article/details/96366402
今日推荐