第三步 设计我们的LOADER——跨入保护模式

文章目录

  • 前言
  • 一、我们需要什么样的LOADER?
  • 二、保护模式——启动!
    • 1、准备工作
    • 2、跨入保护模式
  • 三、实践检验!


查看系列文章点这里: 操作系统真象还原

前言

  我们在 第二步 完善MBR 中简要介绍了一下什么 LOADER,那么在这篇文章中简要介绍一下如何实现LOADER。


一、我们需要什么样的LOADER?

  LOADER 的作用是加载操作系统,而不同的操作系统差别可大了,也就是说 LOADER 的实现并不是唯一的,需要根据操作系统的操作系统的需求来设计。

  在这里,我们是实现我们自己的操作系统,所以我们不去谈论现代操作系统的 LOADER 是如何实现的,我们只讨论我们的 LOADER 需要实现什么。

  首先我们的操作系统是在32位保护模式下运行的,所以我们要实现从实模式到保护模式的跨越;其次,现代操作系统的内存管理大都采用分页机制,因此我们也紧跟潮流,也要为启动CPU的分页机制;最后,也是最重要的,那就是加载我们的操作系统,将计算机控制权交给操作系统。

  明确了我们对 LOADER 的需求后,我们接下来就先来实现从实模式到保护模式的跨越叭!

二、保护模式——启动!

1、准备工作

  我们在前面介绍段描述符各个字段的含义,也介绍了选择子,现在就到了使用它们的时候了。为了使用起来更加直观,也为了更符合规范,我们需要在 boot.inc 中定义一些常量。

  说明一下,段描述符8字节64位,为了避免出现过多0,定义时将其分别定义为高32位和低32位,在使用时在合并。现在我们来看看我们是如何定义这些常量的,如下所示:

  段描述符定义的格式:DESC_字段名_字段相关信息

;------------------------
; 全局描述符表(GDT)属性
;------------------------

; G 位(第 23 位)-> 段边界的单位:4KB
DESC_G_4K  equ 1000_0000_0000_0000_0000_0000b

; D 位(第 22 位)-> 32 位操作数
DESC_D_32  equ  100_0000_0000_0000_0000_0000b

; L 位(第 21 位)-> 32 位代码段
DESC_L_0   equ   00_0000_0000_0000_0000_0000b

; AVL 位(第 20 位)-> 硬件不管这一位,置0即可
DESC_AVL_0 equ    0_0000_0000_0000_0000_0000b

; 段界限的第二部分(第 16~19 位)-> 2^20 * 2^12 = 4GB
DESC_LIMIT_CODE2    equ 1111_0000_0000_0000_0000b
DESC_LIMIT_DATA2    equ 1111_0000_0000_0000_0000b
DESC_LIMIT_VIDEO2   equ 0000_0000_0000_0000_0000b

; P 位(第 15 位)-> 段在内存中
DESC_P_1    equ 1000_0000_0000_0000b

; DPL(13~14 位)-> 描述符特权级别的 4 个可能值
DESC_DPL_0  equ  000_0000_0000_0000b
DESC_DPL_1  equ  010_0000_0000_0000b
DESC_DPL_2  equ  100_0000_0000_0000b
DESC_DPL_3  equ  110_0000_0000_0000b

; S 位(第 12 位)-> 描述段是系统段还是非系统段
DESC_S_CODE equ    1_0000_0000_0000b
DESC_S_DATA equ    1_0000_0000_0000b
DESC_S_sys  equ    0_0000_0000_0000b

; TYPE 位(第 8~11 位)-> 代码(可执行)/ 数据(可读)段的类型
DESC_TYPE_CODE equ   1000_0000_0000b
DESC_TYPE_DATA equ   0010_0000_0000b

; 描述符的高 4 字节
DESC_CODE_HIGH4  equ (0x00<<24) + DESC_G_4K + DESC_D_32 + DESC_L_0 + \
	DESC_AVL_0 + DESC_LIMIT_CODE2  + DESC_P_1 + DESC_DPL_0 + DESC_S_CODE + \
	+ DESC_TYPE_CODE + 0x00

DESC_DATA_HIGH4  equ (0x00<<24) + DESC_G_4K + DESC_D_32 + DESC_L_0 + \
	DESC_AVL_0 + DESC_LIMIT_DATA2  + DESC_P_1 + DESC_DPL_0 + DESC_S_DATA + \
	+ DESC_TYPE_DATA + 0x00

DESC_VIDEO_HIGH4 equ (0x00<<24) + DESC_G_4K + DESC_D_32 + DESC_L_0 + \
	DESC_AVL_0 + DESC_LIMIT_VIDEO2 + DESC_P_1 + DESC_DPL_0 + DESC_S_DATA + \
	+ DESC_TYPE_DATA + 0x0b

;------------------------
; 选择子属性
;------------------------

; RPL 位(第 0~1 位)-> RPL 的 4 个可能值
RPL_0  equ  00b
RPL_1  equ  01b
RPL_2  equ  10b
RPL_3  equ  11b

; TI 位(第 2 位)-> 指示段描述符在 GDT 中还是在 LDT 中
TI_GDT equ 000b
TI_LDT equ 100b

  相关常量的定义就这些,当然这并不完整,不过我们就只用的了那么多,就不多定义了。大家可能发现了这里只定义了段描述符的高32位与选择子的低3位,没有定义的部分我们会在 LOADER 里定义。

  让我们进入下一步,编写 LOADER 叭!

2、跨入保护模式

  进入保护模式非常简单,只需要三步,分别是:

  • 打开A20
  • 加载GDT
  • 将cr0 的 PE 位置 1

  这三个步骤不一定要顺序也不一定要连续,只要完成就进入保护模式,我们挨个来讲讲每一步都要怎么做。

  首先,打开A20,就是启用 A20 这根地址线,在实模式下,寻址范围为 1MB,如不启用A20,就无法访问地址大于 1MB 的内容,超过的部分就直接舍弃了,相当于对 1MB 取模,而打开后,就可以畅通无阻的访问啦!

  下面是打开的方法,非常简单,将0x92端口第1位置1即可,如下:

	in al, 0x92
    or al, 0000_0010B
    out 0x92, al

  加载GDT应该不用多说,就是告诉 CPU GDT表在哪里,通过 lgdt 指令为 GDTR 寄存器赋值,告诉起始位置和大小即可,如下:

	lgdt [gdt_ptr]

  CR0寄存器是控制寄存器中的一个,我们之前并没有提到过,不过我们用到的不多,暂时先不专门介绍。CR0寄存器第0位是PE,若PE=1,则为保护模式。所以我们只需要将其置1即可,如下:

	mov eax, cr0
    or eax, 0x00000001
    mov cr0, eax

  是时候给出完整代码了,我们在最后打印了“ 3 LOADER IN PRORECT ”,相信大家都看得懂,宣告现在是保护模式的天下啦!

%include "boot.inc"
; 加载器加载到内存中的位置
SECTION LOADER vstart=LOADER_BASE_ADDR
; 栈是向下增长,故加载器的起始地址也是栈的起始地址
LOADER_STACK_TOP equ LOADER_BASE_ADDR

jmp loader_start

; ============================================================
; 构建GDT及其内部描述符
; ============================================================

;第0个不可段描述符(因为不可用,所以全部初始化为0)
GDT_BASE:
    dd 0x00000000
    dd 0x00000000

;代码段描述符 -> 段基址为0x0,段大小为4GB
CODE_DESC:
    dd 0x0000FFFF
    dd DESC_CODE_HIGH4

;数据段和栈段描述符 -> 段基址为0x0,段大小为4GB
DATA_STACK_DESC: 
    dd 0x0000FFFF
    dd DESC_DATA_HIGH4

;显存段描述符 -> 段基址为0xb8000,段大小为32KB
VIDEO_DESC:
    dd 0x80000007
    dd DESC_VIDEO_HIGH4

;通过地址差获得GDT的大小 
GDT_SIZE equ $-GDT_BASE
;减1得到段界限
GDT_LIMIT equ GDT_SIZE - 1

;预留一定空间方便扩充
times 60 dq 0

;构建代码段、数据段、显存段的选择子
SELECTOR_CODE   equ (0x0001 << 3) + TI_GDT + RPL_0
SELECTOR_DATA   equ (0x0002 << 3) + TI_GDT + RPL_0
SELECTOR_VIDEO  equ (0x0003 << 3) + TI_GDT + RPL_0

;构建DGT的指针,在lgdt加载GDT到gdtr寄存器时使用
gdt_ptr:
    dw GDT_LIMIT
    dd GDT_BASE

loadermsg db '2 loader in real.'

loader_start:

    ;mov sp, LOADER_BASE_ADDR
    ;mov bp, loadermsg
    ;mov cx, 17
    ;mov ax, 0x1301
    ;mov bx, 0x001f
    ;mov dx, 0x1800
    ;int 0x10

    mov byte [gs:0xa0], '2'
    mov byte [gs:0xa1], 0xA4
    mov byte [gs:0xa2], ' '
    mov byte [gs:0xa3], 0xA4
    mov byte [gs:0xa4], 'L'
    mov byte [gs:0xa5], 0xA4
    mov byte [gs:0xa6], 'O'
    mov byte [gs:0xa7], 0xA4
    mov byte [gs:0xa8], 'A'
    mov byte [gs:0xa9], 0xA4
    mov byte [gs:0xaa], 'D'
    mov byte [gs:0xab], 0xA4
    mov byte [gs:0xac], 'E'
    mov byte [gs:0xad], 0xA4
    mov byte [gs:0xae], 'R'
    mov byte [gs:0xaf], 0xA4

; ============================================================
; 准备进入保护模式
; ============================================================

    ;打开A20
    in al, 0x92
    or al, 0000_0010B
    out 0x92, al

    ;加载DGT
    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:0x140], '3'
    mov byte [gs:0x141], 0xA4
    mov byte [gs:0x142], ' '
    mov byte [gs:0x143], 0xA4
    mov byte [gs:0x144], 'L'
    mov byte [gs:0x145], 0xA4
    mov byte [gs:0x146], 'O'
    mov byte [gs:0x147], 0xA4
    mov byte [gs:0x148], 'A'
    mov byte [gs:0x149], 0xA4
    mov byte [gs:0x14a], 'D'
    mov byte [gs:0x14b], 0xA4
    mov byte [gs:0x14c], 'E'
    mov byte [gs:0x14d], 0xA4
    mov byte [gs:0x14e], 'R'
    mov byte [gs:0x14f], 0xA4
    mov byte [gs:0x150], ' '
    mov byte [gs:0x151], 0xA4
    mov byte [gs:0x152], 'I'
    mov byte [gs:0x153], 0xA4
    mov byte [gs:0x154], 'N'
    mov byte [gs:0x155], 0xA4
    mov byte [gs:0x156], ' '
    mov byte [gs:0x157], 0xA4
    mov byte [gs:0x158], 'P'
    mov byte [gs:0x159], 0xA4
    mov byte [gs:0x15a], 'R'
    mov byte [gs:0x15b], 0xA4
    mov byte [gs:0x15c], 'O'
    mov byte [gs:0x15d], 0xA4
    mov byte [gs:0x15e], 'T'
    mov byte [gs:0x15f], 0xA4
    mov byte [gs:0x160], 'E'
    mov byte [gs:0x161], 0xA4
    mov byte [gs:0x162], 'C'
    mov byte [gs:0x163], 0xA4
    mov byte [gs:0x164], 'T'
    mov byte [gs:0x165], 0xA4

    jmp $

三、实践检验!

  直接编译传输,没什么好说的,指令如下:

  	nasm -I ./include/ -o loader.bin loader.S
  	dd if=./loader.bin of=../bochs/hd60M.img bs=512 count=4 seek=2 conv=notrunc

在这里插入图片描述

  接下来我们开始验证,启动bochs。从下面这张图可以看到,启动前PE=0。

在这里插入图片描述

  现在按下c,直接执行到最后,打印信息和我们预想的一致,PE也被置为1了。

在这里插入图片描述
在这里插入图片描述

  再来看看我们加载的GDT表对不对,输入info gdt,查看GDT表,可以看到一共有4个,第一个是我们预留的,后三个是我们定义的,可见也是正确的。

在这里插入图片描述

  太好了,我们已经成功进入保护模式啦!


  持续更新中~~

  • 42
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值