一个boot程序
这是“从零开始写OS”系列(一个大坑)的第一篇。
环境
- 编译器: nasm
- 虚拟机: bochs 2.6.9
- 启动介质: 1.44M 软盘
BIOS 引导原理
BIOS自检结束后会根据启动选项的设置选择启动设备,我们这里设置的启动介质是软盘。BIOS会检查第0磁道第1扇区是否以0xaa55
(小端)结尾,如果是,BIOS就认为这是一个引导扇区,然后将这个扇区的数据复制到内存的0x7c00
(小端)处,随后从此处执行。
不要问为什么是0xaa55
和0x7c00
,这是设定!
要注意的是,1.44M软盘的一个扇区只有512B。
实现
第一个boot程序很简单,并不会加载loader,只是简单的打印一串文字。这个程序分为4个部分,初始化、清屏、设置焦点、显示, 其中并非所有步骤都是必要的。
初始化
设置起始地址为0x7c00
。由于BIOS的设定是到0x7c00
执行引导程序,我们也没办法,只能这么做。随后设置栈指针,顺便赋值需要打印的变量,代码如下:
org 0x7c00
base equ 0x7c00
msg db "too young too simple"
mov sp, base
清屏
通过BIOS中断服务int 10h
实现屏幕显示相关操作,和Linux系统调用类似,需要对a寄存器设置不同的值实现不同的操作,接着按照需要设置b, c, d寄存器的值相当于这个操作所需的参数,代码如下:
clear:
mov ax, 0x0600
mov bx, 0x0700
mov cx, 0
mov dx, 0xffff
int 0x10
jmp focus
ah = 0x6表示滚动窗口(即ax的高位), al 表示滚动的列数,0则实现清屏功能
设置焦点
同清屏
,不过ah = 0x2, bh表示页码,dh为光标列,dl为行,代码如下:
focus:
mov ax, 0x0200
mov bx, 0
mov dx, 0x0505
int 0x10
jmp elder
显示
这时需要设置 ah = 0x13, cx表示显示的字符串长度,字符串的地址存在bp寄存器,代码如下:
elder:
mov ax, 0x1301
mov bx, 0x000f
mov dx, 0x1010
mov bx, 0x0002
mov cx, 20
mov bp, msg
int 0x10
jmp start
最后,必要的工作
因为必须以 0xaa55
结尾才能被BIOS认为是一个引导扇区,又由于软盘是块设备,这时我们需要将空闲的空间填充起来。一个扇区的大小是512B,除去0xaa55
两个字节后还剩下510B,需要填充的大小就是510B减去我们程序已经使用的空间。这里可以利用nasm的$
和$$
计算,其中$
表示当前行编译后的地址,$$
表示分节编译后的地址。$ - $$
则是编译后占用的大小,因此,通过填充 510 - ($ - $$)
个0即可。
start:
jmp clear
call start
times 510 - ($ - $$) db 0
dw 0xaa55
编译运行
配置bochsrc
floppya: type=1_44, 1_44="boot.img", status=inserted, write_protected=1
boot: floppy
floppy_bootsig_check: disabled=0
编译和运行
nasm boot.asm -o boot.bin
dd if=boot.bin of=boot.img bs=512 count=1 conv=notrunc
bochs -f bochsrc
结果如图:
完整代码在这里