带你写操作系统-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位

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信息查看
    在这里插入图片描述
Logo

技术共进,成长同行——讯飞AI开发者社区

更多推荐