自制操作系统-Loader
带你写操作系统-Loader0. 当前代码结构1. Loader程序需要做什么Loader需要打开保护模式准备GDT打开保护模式Loader需要加载Kernel程序2. 什么是保护模式此前操作的内存仅仅在1M以内,我们当前的操作系统一般都是4G或更大内存,所以我们需要操作更多的内存此前的程序可以修改任意处的内容, 这样没有安全限制的操作如果运行用户程序是不安全的总结开启1M以上的内存空间对内存区域
·
带你写操作系统-Loader
0. 当前代码结构
1. Loader程序需要做什么
- Loader需要打开保护模式
- 准备GDT
- 打开保护模式
- Loader需要加载Kernel程序
2. 什么是保护模式
- 此前操作的内存仅仅在1M以内,我们当前的操作系统一般都是4G或更大内存,所以我们需要操作更多的内存
- 此前的程序可以修改任意处的内容, 这样没有安全限制的操作如果运行用户程序是不安全的
- 总结
- 开启1M以上的内存空间
- 对内存区域进行安全校验
3. 如何实现内存安全校验
- 目标: 将内存区域划分为不同等级,内核区域不允许用户程序操作
- 如何给某一块儿内存标记是内核代码还是用户代码?
- GDT(Global Describe Table)用于描述内存区域的详细信息, 可以简单理解为GDT是一个数组每一个元素项记录了某一块儿内存的权限及其他信息
- 谁来做信息校验?
- CPU已经实现了这些信息校验, 不过我们需要按照CPU要求的GDT格式进行全局内存的规划, 告诉CPU我们是如何划分内存权限的
- 总结
- 我们需要提供GDT描述表给CPU
- CPU利用GDT检测每一次操作是否拥有权限
4. GDT的结构
-
GDT结构如下图, 图片来源于《操作系统真象还原》
-
具体字段解释
- 低32位
- 0-15:为当前所指内存段的界限的前16位(也就是从段基址开始可以利用的内存大小)
- 16-31:段基址的前16位,表示当前段从内存哪里开始
- 高32位:
- 0-7:段基址的16-23位
- type:表示当前段属于什么类型,此字段需要和S字段配合, 如果S为1代表非系统段(此处非系统是站在CPU角度所以咱们写的操作系统也是非系统代码段)S为0代表系统段
- S:0代表系统段,1代表非系统段
- DPL:代表访问当前段需要的权限,2位所以有四种级别, 0 1 2 3, 数字越小权限越大
- p:代表当前描述符描述的内存段是否实际存在
- 16-19:段界限, 段界限一共20位, 难道段界限最大就是1M?
- AVL:课任意使用
- L:表示当前段是否位64位代码,当前是32位所以直接为0
- D/B:
- 若为代码段则此处为D,D代表有效地址的位数,0代表16位 1代表32位
- 若为栈段则此处为B,B为0代表16位的情况,1代表32位的情况
- G:代表段界限的单位,0表示字节, 1代表4K, 也就是最大的段界限是4G
- 24-31:也就是段基址一共32位
- 低32位
5. 准备GDT描述符表
- 目前准备以下三个描述符
- 代码段描述符
- 段基址:0x0
- 段限大小:4G
- 数据段描述符
- 段基址:0x0
- 段限大小:4G
- 显存段描述符
- 段基址:0xb8000
- 段限:0xbffff 减去 0xb8000 个字节
- 代码段描述符
5.1 具体代码
- 以下代码文件名称 common_info.conf, 通过下面的文件可以在最后构造处我们需要的三个段描述符的高32位
;------------- loader和kernel ----------
LOADER_BASE_ADDR equ 0x600
LOADER_START_SECTOR equ 0x2
;-------------- gdt描述符属性 从第0位开始 -------------
; 23位 G, 限制单位 0 => 1 byte 1 => 4K
DESC_G_4K equ 1_000_0000_0000_0000_0000_0000b
; 22位 D/B 1 代表32代码操作数
DESC_D_32 equ 1_00_0000_0000_0000_0000_0000b
; 21位 L 64位代码标记,此处标记为0便可。
DESC_L equ 0_0_0000_0000_0000_0000_0000b
; 20位 AVL cpu不用此位,暂置为0
DESC_AVL equ 0_0000_0000_0000_0000_0000b
; 16-19位,段限直接全设置1
DESC_LIMIT_CODE2 equ 1111_0000_0000_0000_0000b
; 16-19位,段限直接全设置1
DESC_LIMIT_DATA2 equ DESC_LIMIT_CODE2
DESC_LIMIT_VIDEO2 equ 000_0000_0000_0000_0000b
; 15位 P 设置为1代表村子啊
DESC_P equ 1000_0000_0000_0000b
; 四种不同的权限
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 系统段还是非系统段
DESC_S_CODE equ 1_0000_0000_0000b
DESC_S_DATA equ DESC_S_CODE
; 系统段占时不用
DESC_S_sys equ 0_0000_0000_0000b
;x=1,c=0,r=0,a=0 代码段是可执行的,非依从的,不可读的,已访问位a清0.
DESC_TYPE_CODE equ 1000_0000_0000b
;x=0,e=0,w=1,a=0 数据段是不可执行的,向上扩展的,可写的,已访问位a清0.
DESC_TYPE_DATA equ 0010_0000_0000b
; 构造不同内存段类型的高32位
; 代码段
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
; 数据段
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
; 显存段,注意最后的不是0, 而是0x0b
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 + 0x0b
;-------------- 选择子属性 ---------------
RPL0 equ 00b
RPL1 equ 01b
RPL2 equ 10b
RPL3 equ 11b
TI_GDT equ 000b
TI_LDT equ 100b
- 在Loader文件中正式定义三个段描述符
%include "common_info.conf"
section loader vstart=LOADER_BASE_ADDR
LOADER_STACK_TOP equ LOADER_BASE_ADDR
jmp loader_start
;构建gdt及其内部的描述符
GDT_BASE: dd 0x00000000
dd 0x00000000
CODE_DESC: dd 0x0000FFFF
dd DESC_CODE_HIGH4
DATA_STACK_DESC: dd 0x0000FFFF
dd DESC_DATA_HIGH4
;因为单位是4K, 所以limit=(0xbffff-0xb8000)/4k=0x7
VIDEO_DESC: dd 0x80000007
dd DESC_VIDEO_HIGH4
GDT_SIZE equ $ - GDT_BASE
GDT_LIMIT equ GDT_SIZE - 1
times 10 dq 0
; 相当于(CODE_DESC - GDT_BASE)/8 + TI_GDT + RPL0
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的指针,前2字节是gdt界限,后4字节是gdt起始地址
gdt_ptr dw GDT_LIMIT
dd GDT_BASE
loader_start:
mov sp, LOADER_BASE_ADDR
mov byte [gs:0xa0],'L'
mov byte [gs:0xa1],0xA4
mov byte [gs:0xa2],'O'
mov byte [gs:0xa3],0xA4
mov byte [gs:0xa4],'A'
mov byte [gs:0xa5],0xA4
mov byte [gs:0xa6],'D'
mov byte [gs:0xa7],0xA4
mov byte [gs:0xa8],'E'
mov byte [gs:0xa9],0xA4
mov byte [gs:0xaa],'R'
mov byte [gs:0xab],0xA4
;---------------------------------------- 准备进入保护模式 -----------------------------
;1 打开A20
;2 加载gdt
;3 将cr0的pe位置1
;----------------- 打开A20 ----------------
in al,0x92
or al,0000_0010B
out 0x92,al
;----------------- 加载GDT ----------------
lgdt [gdt_ptr]
;----------------- cr0第0位置1 ----------------
mov eax, cr0
or eax, 0x00000001
mov cr0, eax
jmp 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:320], 'P'
mov byte [gs:322], 'R'
mov byte [gs:324], 'O'
mov byte [gs:326], 'T'
mov byte [gs:328], 'E'
mov byte [gs:330], 'C'
mov byte [gs:332], 'T'
hlt
6. 代码编译脚本
- 在环境中的文件
- bochs配置文件中的内容
romimage: file=/usr/local/share/bochs/BIOS-bochs-latest
vgaromimage: file=/usr/local/share/bochs/VGABIOS-lgpl-latest
#floppya: 1_44=boot.img, status=inserted
ata0-master: type=disk, path="boot.img", mode=flat
boot: disk
- 具体编译脚本
# 编译之前的Boot脚本
nasm boot.asm -o boot.bin
# 编译loader
nasm -I include/ loader.asm -o loader.bin
# 将loader.bin写入第二个扇区
dd if=loader.bin of=boot.img bs=512 count=10 seek=2 conv=notrunc
# bochs 启动
bochs -f bochsConf
7. 最终效果
- 打印效果
- GDT信息查看
更多推荐
所有评论(0)