操作系统编写 – 进入保护模式
进入保护模式,是操作系统必行的一关,今天抽出一点时间,将这个巨大的bug给大家揪出来。
话说我在进入保护模式的时候碰到了一堆bug,调试了几天几夜也没有成果,几乎被气坏了,我写操作系统是跟着一本书学的,结果我几乎不照着书上的代码写,自己写了一通,完全达不到效果。于是我把书上的代码复制了下来,一试居然书上的代码大错特错,完全没有达到效果,一闪一闪的,退回了BIOS界面。
为了解决这个问题,我翻了好多资料,终于找到方法了,现在跟大家讲一讲怎么进入保护模式。
具体代码
MBR
boot.asm
section MBR vstart=0x7c00: ;从BIOS进入MBR
mov ax, cs ;cs=0, ax=cs=0
mov ds, ax ;ds=es=fs=ss=ax=0
mov es, ax
mov fs, ax
mov ss, ax
mov sp, 0x7c00 ;sp=0x7c00,0x7c00是BIOS的起始地址
mov ax, 0xb800 ;ax=0xb800,0xb800是文字显存的起始地址
mov gs, ax ;gs=ax=0xb800
;清屏
mov ax, 0600h
mov bx, 0700h
mov cx, 0
mov dx, 184fh
int 10h ;调用BIOS中断
;输出hello
mov byte [gs:0x00], 'h'
mov byte [gs:0x02], 'e'
mov byte [gs:0x04], 'l'
mov byte [gs:0x06], 'l'
mov byte [gs:0x08], 'o'
LOADER_START_SECTOR equ 0x2
LOADER_BASE_ADDR equ 0x900
mov eax, LOADER_START_SECTOR ;加载器的起始扇区
mov bx, LOADER_BASE_ADDR ;加载器的起始地址
mov cx, 4 ;要读取的扇区
call read_loader ;读取加载器
jmp LOADER_BASE_ADDR ;跳转到加载器
read_loader: ;读取加载器
mov esi, eax ;备份eax
mov di, cx ;备份cx
mov dx, 0x1f2 ;存入要读的扇区数
mov al, cl ;将cl(要读的扇区数)的值存入al
out dx, al ;写入0x1f2
mov eax, esi ;恢复eax中的数据
mov dx, 0x1f3 ;操作LBA低8位的端口
out dx, al ;写入LBA低八位
mov cl, 8 ;将8存入cl
shr eax, cl ;将eax右移8位
mov dx, 0x1f4 ;操作LBA中8位的端口
out dx, al ;写入0x1f4
shr eax, cl ;将eax右移八位
mov dx, 0x1f5 ;操作LBA高8位的端口
out dx, al ;写入0x1f5
shr eax, cl ;将eax右移8位
and al, 0x0f ;与命令
or al, 0xe0 ;或命令
mov dx, 0x1f6 ;操作设备的端口
out dx, al ;写入0x1f6
mov dx, 0x1f7 ;命令端口
mov al, 0x20 ;写入读取指令(0x20)
out dx, al ;写入命令
.while_not_ready: ;当硬盘没有准备好的时候
nop ;什么都不做
in al, dx ;读取dx(0x1f7)
and al, 0x88 ;和运算
cmp al, 0x08 ;做减法
jnz .while_not_ready ;如果没有准备好就继续运行
mov ax, di
mov dx, 256
mul dx
mov cx, ax
mov dx, 0x1f0 ;0x1f0是读取数据的端口
.read_loader_data:
in ax, dx ;读取数据
mov [bx], ax ;将加载器加载到内存
add bx, 2 ;将bx加2
loop .read_loader_data ;重复运行
ret
times 510-($-$$) db 0 ;将文件填充到510字节,$是当前的地址,$$是当前扇区的起始地址
db 0x55, 0xaa ;填充最后两个字节,将文件填充到512字节
loader
boot-loader.asm
section loader vstart=0x900:
;输出hello
mov byte [gs:0x00], 'h'
mov byte [gs:0x02], 'e'
mov byte [gs:0x04], 'l'
mov byte [gs:0x06], 'l'
mov byte [gs:0x08], 'o'
LOADER_STACK_TOP equ 0x7c00
jmp loader_start ;跳转到进入保护模式的代码
gdt:
dd 0x00000000
dd 0x00000000
dd 0x0000ffff
dd 0x00cf9800
dd 0x0000ffff
dd 0x00cf9200
dd 0x80000007
dd 0x00c0920b
lgdt_value:
dw $-gdt-1
dd gdt
selector_code equ 0x0001<<3
selector_data equ 0x0002<<3
selector_video equ 0x0003<<3
[bits 16]
loader_start:
cli ;阻止CPU级别的中断
;加载GDT
lgdt [lgdt_value]
;打开A20
in al, 0x92
or al, 00000010b ;or是或运算,要让某一位是1就在哪一位上写1(or 1, 0=1, or 1, 1=1)
out 0x92, al
;cr1 PE
cli
mov eax, cr0
or eax, 1
mov cr0, eax ;or是或运算,要让某一位是1就在哪一位上写1(or 0, 1=1, or 1, 1=1)
;jmp $
jmp dword selector_code:protection_mode_start ;刷新流水线并跳转到protection_mode_start
[bits 32]
protection_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:0xa0], '#'
jmp $
如果以上代码运行出错,请在文件开头加上
LOADER_BASE_ADDR equ 0x900
LOADER_START_SECTOR equ 0x2
BIOS_BASE_ADDR equ 0x7c00
CUI_BASE_ADDR equ 0xb800
编译代码
先下载msys2和qemu,这在以前的文章中讲过
新建一个文件夹,在这个文件夹下建立一个文件夹bin
'''
C:/
_
|---| 操作系统
|---| bin
|- boot.asm
|- boot-loader.asm
|- OS.img
'''
创建OS.img的光盘文件,可以使用bochs的bximage来创建,bochs可以自己找到官网下载
创建光盘的过程如下所示
圈出的地方是要输入的,其它的地方直接回车就可以了
然后在msys2中输入编译命令
nasm boot.asm -o bin/boot.bin;nasm boot-loader.asm -o bin/boot-loader.bin;dd if=bin/boot.bin of=OS.img bs=512 count=1 conv=notrunc;dd if=bin/boot-loader.bin of=OS.img bs=512 seek=2 count=4 conv=notrunc
然后使用qemu启动系统