前置知识
段描述符格式
- 段界限:表示段边界的扩展最值
- 此段界限是一个单位量,其单位要么是字节,要么是4KB,具体可由G位进行指定
- 最终段界限=此段界限值*单位
- 实际段界限计算公式:(此段界限值+1)*(G位)-1
- G:粒度位,用于解释段界限的单位
- 为0:表示段界限的单位表示为字节
- 为1:表示段界限的单位表示为4KB
- S:描述符类型
- 为0,表示系统段
- 为1,表示非系统段(代码段或者数据段)
- type:用于指示描述符的子类型
- DPL:Descriptor Privilege Level,即描述符特权级
- 特权级共有0、1、2、3,用于解释段界限的含义
- P:段存在位,用于指示描述符所对应的段是否存在
- P位是由处理器负责检查的。每当通过描述符访问内存中的段时,如果P位是“0”,处理器就会产生一个异常中断
- 该中断处理过程是由操作系统提供的,该处理过程的任务是负责将该段从硬盘换回内存,并将P位置1
- D/B:标志位,为了能够在32位处理器上兼容运行16位保护模式的程序。该标志位对不同的段有不同的效果。
- 对于代码段,此位称作“D”位,用于指示指令中默认的有效地址和操作数尺寸;
- D=0表示指令中的有效地址或者操作数是16位的;
- D=1,指示32位的有效地址或者操作数。
- 对于栈段和向下扩展的数据段来说,该位被叫作“B”位,用于指定在进行隐式的栈操作时,是使用寄存器SP还是寄存器ESP,隐式的栈操作指令包括push、pop和call等。
- 如果该位是“0”,在访问那个段时,使用寄存器SP;
- 否则就是使用寄存器ESP。
- AVL:表示软件是否可以使用(Available),通常由操作系统来用,处理器并不使用它。
- L:用于设置是否是64位代码段
- 为1,表示64位代码段
- 为0,表示32位代码段
选择子
- RPL:请求特权级
- TI(table indicator):表示在GDT(全局描述符表)还是在LDT(局部描述符表)中索引描述符
- 为0,在GDT中进行索引
- 为1,在LDT中进行索引
- 3~15位,具体描述符的索引值,2^13=8192,故最多可以索引8192个段
GDT寄存器GDTR
- 作用:用于指向GDT的内存地址
- 访问格式:lgdt 48位内存数据
由上述GDTR寄存器的格式可以看出,GDT的大小占16位,也就是65536个字节,而每个描述符占8个字节,因此GDT最多可以容纳的描述符数量是65536/8=8192个
控制寄存器CR0
控制寄存器是CPU的窗口,既可以用来展示CPU的内部状态,也可以用于控制CPU的运行机制
其中CR0的PE位是保护模式的开关
- 为0时,表示在实模式下运行
- 为1时表示在保护模式下运行
进入保护模式
boot.inc
;-----loader and kernel-----
LOADER_BASE_ADDR equ 0x900 ;loader在内存中位置
LOADER_START_SECTOR equ 0x2 ;loader在磁盘中的逻辑扇区地址,即LBA地址
;----- gdt描述符属性 ---------
DESC_G_4K equ 1_00000000000000000000000b ;设置段界限的单位为4KB
DESC_D_32 equ 1_0000000000000000000000b ;设置代码段/数据段的有效地址(段内偏移地址)及操作数大小为32位
DESC_L equ 0_000000000000000000000b ;64位代码段标记位,现在是32位操作系统,因此标记为0即可。
DESC_AVL equ 0_00000000000000000000b
;定义段界限位
;段界限的第2部分,即描述符的高32位中的第16~19位,最终的代码段段界限为0xFFFFF
DESC_LIMIT_CODE2 equ 1111_0000000000000000b ;定义代码段要用的段描述符高32位中16~19段界限为全1
DESC_LIMIT_DATA2 equ DESC_LIMIT_CODE2 ;定义数据段要用的段描述符高32位中16~19段界限为全1
DESC_LIMIT_VIDEO2 equ 0000_000000000000000b ;定义我们要操作显存时对应的段描述符的高32位中16~19段界限为全0
DESC_P equ 1_000000000000000b ;定义了段描述符中的P标志位,表示该段描述符指向的段是否在内存中
;定义描述符的特权级别位
DESC_DPL_0 equ 00_0000000000000b
DESC_DPL_1 equ 01_0000000000000b
DESC_DPL_2 equ 10_0000000000000b
DESC_DPL_3 equ 11_0000000000000b
;定义类型位
DESC_S_CODE equ 1_000000000000b ;代码段和数据段都是非系统段,故类型字段s设置为1
DESC_S_DATA equ DESC_S_CODE ;代码段和数据段都是非系统段,故类型字段s设置为1
DESC_S_sys equ 0_000000000000b ;系统段的类型字段设置为0
;定义子类型位
DESC_TYPE_CODE equ 1000_00000000b ;x=1,c=0,r=0,a=0 代码段是可执行的,非一致性,不可读,已访问位a清0
DESC_TYPE_DATA equ 0010_00000000b ;x=0,e=0,w=1,a=0,数据段不可执行,向上扩展,可写,已访问位a清0
;拼接代码段的描述符
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
;拼接显存段的描述符位
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
原书勘误,显存描述符应该为的最后应该是0x0b,而不是0x00
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
%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
;代码段描述符的低4字节部分,其中高两个字节表示段基址的0~15位,在这里定义为0x0000
;低两个字节表示段界限的0~15位,由于使用的是平坦模型,因此是0xFFFF
CODE_DESC:
dd 0x0000FFFF
dd DESC_CODE_HIGH4;段描述符的高4字节部分
DATA_STACK_DESC:
dd 0x0000FFFF
dd DESC_DATA_HIGH4
;定义显存段的描述符
;文本模式下的适配器地址为0xb8000~0xbffff,为了方便显存操作,显存段不使用平坦模型
;因此段基址为0xb8000,段大小为0xbffff-0xb8000=0x7fff,
;段粒度位4k,因此段界限的值为0x7fff/4k=7
VIDEO_DESC:
dd 0x80000007
dd DESC_VIDEO_HIGH4
GDT_SIZE equ $-GDT_BASE
GDT_LIMIT equ GDT_SIZE-1
times 60 dq 0 ;此处预留60个描述符的空位
;------------- 构建选择子 -------------
SELECTOR_CODE equ (0x0001<<3) + TI_GDT + RPL0
SELECTOR_DATA equ (0x0002<<3) + TI_GDT + RPL0
SELECTOR_VIDEO equ (0x0003<<3) + TI_GDT + RPL0
;------------- 定义gdtr(指向GDT的寄存器) -------------
gdt_ptr dw GDT_LIMIT
dd GDT_BASE
;------------- 加载器的显式信息 -------------
loadermsg db '2 loader in real .'
loader_start:
;------------------------------------------
;INT 0x10 功能号:0x13 功能描述:打印字符串
;------------------------------------------
;输入:
;AH:功能号
;AL:显示输出方式
; 0——字符串中只含显示字符,显示属性在BH中,显示后光标位置不变
; 1——字符串中只含显示字符,显示属性在BH中,显示后光标位置改变
; 2——字符串中含显示字符和显示属性,显示后光标位置不变
; 3——字符串中含显示字符和显示属性,显示后光标位置改变
;BH:页码
;BL:属性
;CX:字符串长度
;(DH、DL):坐标(行、列)
;ES:BP 字符串地址
;无返回值
mov sp,LOADER_BASE_ADDR
mov bp,loadermsg
mov cx,17
mov ax,0x1301
mov bx,0x001f;页号为0,蓝底粉红字
mov dx,0x1800
int 0x10
;------------- 准备进入保护模式 -------------
;1.打开A20
;2.加载gdt
;3.置cr0的PE位为1
;------------- 打开A20 -------------
in al,0x92
or al,0000_0010B
out 0x92,al
;------------- 加载gdt -------------
lgdt [gdt_ptr]
;------------- 置cr0的PE位为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 ;初始化站栈指针,将栈指针地址放入bsp寄存器
mov ax,SELECTOR_VIDEO ;初始化显存段寄存器,显存段的选择子放入gs寄存器
mov gs,ax
mov byte [gs:160],'p'
jmp $
编译
nasm -I ./include/ -o mbr.bin mbr.S
nasm -I ./include/ -o loader.bin loader.S
磁盘写入
注意loader.S写入磁盘时count参数为2
dd if=./osCode/mbr.bin of=./bochs/hd60M.img bs=512 count=1 conv=notrunc
dd if=./osCode/loader.bin of=./bochs/hd60M.img bs=512 count=2 seek=2 conv=notrunc
运行
输出p说明GDT成功建立,如下调试所示