Hi~朋友,码字不易,点点关注呗
摘要
如何进入保护模式
我们的计算机启动时,首先BIOS会进行自检操作,在自检通过以后就需要将控制权交给MBR程序,在MBR程序中我们跳转到我们的OBR(内核加载器)中。
程序实现
首先我们需要准备三个文件:
boot.inc
;配置信息文件(loader和kernel)
LOADER_BASE_ADDR equ 0x900
LOADER_START_SECTOR equ 0x2
;----------------全局描述符表属性----------------
; 定义段界限的单位为4k
DESC_G_4K equ 10000000_00000000_00000000b
; 定义指令中有效地址和操作数为32位
DESC_D_32 equ 1000000_00000000_00000000b
; 定义32位代码段
DESC_L equ 000000_00000000_00000000b
; 对于操作系统,CPU不会使用此位
DESC_AVL equ 00000_00000000_00000000b
; 代码段界限第2部分(16~19位)
DESC_LIMIT_CODE2 equ 1111_00000000_00000000b
; 数据段界限第2部分(16~19位)
DESC_LIMIT_DATA2 equ DESC_LIMIT_CODE2
; 显存段
DESC_LIMIT_VIDEO2 equ 0000_00000000_00000000b
; 段存在于内存中
DESC_P equ 10000000_00000000b
; 0特权级
DESC_DPL_0 equ 0000000_00000000b
; 1特权级
DESC_DPL_1 equ 0100000_00000000b
; 2特权级
DESC_DPL_2 equ 1000000_00000000b
; 3特权级
DESC_DPL_3 equ 1100000_00000000b
; 代码段
DESC_S_CODE equ 10000_00000000b
; 数据段
DESC_S_DATA equ DESC_S_CODE
; 系统段
DESC_S_SYS equ 00000_00000000b
; 代码段可执行,非一致性,不可读,已访问为清0
DESC_TYPE_CODE equ 1000_00000000b
; 数据段不可执行,向上扩展,可写,已访问为清0
DESC_TYPE_DATA equ 0010_00000000b
;定义代码段的高位4个字节,0x00 << 24代表24~31位的段基址,0x00代表0~7的段基址
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
;定义数据段的高位4个字节,0x00 << 24代表24~31位的段基址,0x00代表0~7的段基址
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 + 0x00
;----------段选择子------------
RPL0 equ 00b
RPL1 equ 01b
RPL2 equ 10b
RPL3 equ 11b
TI_GDT equ 000b
TI_LDT equ 100b
loader.asm
%include "boot.inc"
SECTION loader vstart=LOADER_BASE_ADDR
LOADER_STACK_TOP equ LOADER_BASE_ADDR
jmp loader_start
; 构建全局描述符表和内部段描述符
GDT_BASE: dd 0x00000000
dd 0x00000000
CODE_DESC: dd 0x0000FFFF
dd DESC_CODE_HIGH4
DATA_STACK_DESC: dd 0x0000FFFF
dd DESC_DATA_HIGH4
VIDEO_DESC: dd 0x80000007;
dd DESC_VIDEO_HIGH4
GDT_SIZE equ $ - GDT_BASE
; 获取GDT界限
GDT_LIMIT equ GDT_SIZE - 1
times 60 db 0
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_ptr dw GDT_LIMIT ;写入GDT界限
dd GDT_BASE ;写入GDT起始内存地址
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
;准备进入保护模式
;1. 打开A20
in al, 0x92
or al, 0000_0010b
out 0x92, al
;2. 加载GDT
lgdt [gdt_ptr]
;3. 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:160], 'P'
jmp $
mbr.asm
%include "boot.inc"
;主引导程序
SECTION MBR vstart=0x7c00
; 初始化寄存器
mov ax, cs
mov ds, ax
mov es, ax
mov ss, ax
mov fs, ax
mov sp, 0x7c00
;0xb800是实模式文本模式显示适配器起始地址
mov ax, 0xb800
mov gs, ax
; AH = 0x06,上卷全部行,AL = 上卷的行数,如果为0,表示全部
mov ax,0600h
; BH = 上卷行属性
mov bx,0700h
; (CL, CH) = 窗口左上角的位置(x,y)
mov cx,0
; (DL, DH) = 窗口右下角的位置(x,f)
mov dx,184fh
; 中断清屏
int 10h
; 输出字符串'1 MBR'
mov byte [gs:0x00], '1'
mov byte [gs:0x01], 0xA4; A表示绿色背景闪烁,4代表前景为红色
mov byte [gs:0x02], ' '
mov byte [gs:0x03], 0xA4
mov byte [gs:0x04], 'M'
mov byte [gs:0x05], 0xA4
mov byte [gs:0x06], 'B'
mov byte [gs:0x07], 0xA4
mov byte [gs:0x07], 'R'
mov byte [gs:0x08], 0xA4
; loder的扇区位置
mov eax, LOADER_START_SECTOR
; 设置loder被加载到内存以后的地址
mov bx, LOADER_BASE_ADDR
; 读取的扇区数,由于我们自己编写的loader不会超过512字节,因此设置为1
mov cx, 1
call rd_disk_m_16
jmp LOADER_BASE_ADDR
;读取硬盘第n个扇区
rd_disk_m_16:
mov esi, eax ;备份eax
mov di, cx ;备份cx
; 第一步
; 设置要读取的扇区数
mov dx, 0x1f2 ;0x1f2是端口号
mov al, cl
out dx, al
mov eax, esi ;恢复eax寄存器
; 第二步
; LBA地址0~7位写入端口0x1f3
mov dx, 0x1f3
out dx, al
; LBA地址8~15位写入0x1f4
mov cl, 8
shr eax, cl
mov dx, 0x1f4
out dx, al
; LBA地址16~23位写入0x1f5
shr eax, cl
mov dx, 0x1f5
out dx, al
; device端口
shr eax, cl
and al, 0x0f ; lba第24~27位
or al, 0xe0 ; 设置device高4位为1110,表示LBA模式
mov dx, 0x1f6
out dx, al
; 第三步,向0x1f7端口写入读命令0x20
mov dx, 0x1f7
mov al, 0x20
out dx, al
; 第4步 检测硬盘状态
.not_ready:
nop
in al, dx
and al, 0x88 ; 第4位为1表示硬盘控制器已准备好数据传输,第7位为1表示硬盘忙
cmp al, 0x08
jnz .not_ready ; 如果没有准备好,继续等
mov ax, di ;di为要读取的扇区数
mov dx, 256 ; 扇区有512字节,每次读入一个字(实模式下2个字节),要读取的扇区数为1,1*(512/2),所以是di*256
mul dx
mov cx, ax
mov dx, 0x1f0
; 循环读取数据
.go_on_read:
in ax, dx
mov [bx], ax
add bx, 2
loop .go_on_read
ret
times 510 - ($-$$) db 0
db 0x55, 0xaa
程序运行
cd boot
# 编译mbr和loader
nasm -I include -o loader.bin loader.asm
nasm -I include -o mbr.bin mbr.asm
# 创建虚拟镜像
qemu-img create -f raw vm1.raw 1G
# 写入Mbr
dd if=mbr.bin of=vm1.raw bs=512 count=1 conv=notrunc
# 写入loader
dd if=loader.bin of=vm1.raw bs=512 count=1 seek=2 conv=notrunc
# 启动程序
qemu-system-x86_64 vm1.raw