文章目录
- 前言
- 一、我们需要什么样的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个,第一个是我们预留的,后三个是我们定义的,可见也是正确的。
太好了,我们已经成功进入保护模式啦!
持续更新中~~