暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

编程开启保护模式

277



Hi~朋友,码字不易,点点关注


摘要




  1. 编写代码进入保护模式
  2. 程序实现
  3. 程序运行

如何进入保护模式




我们的计算机启动时,首先BIOS会进行自检操作,在自检通过以后就需要将控制权交给MBR程序,在MBR程序中我们跳转到我们的OBR(内核加载器)中。


程序实现





首先我们需要准备三个文件:

  • boot.inc:宏文件,定义了一些常用标识符
  • mbr.asm:MBR程序
  • loader.asm:内核加载程序

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 


本期编程开启保护模式就到这,扫码关注,更多内容我们下期再见!



往期推荐

如何开启保护模式

全局描述符表

CPU保护模式

实模式下CPU如何获取数据

CPU工作原理


文章转载自程序员修炼笔记,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论