《30天自制操作系统》是一本学习操作系统的好教材,它教我们怎么从建立引导区开始,从零实现一个操作系统。但是,实现书中例子的时候,我们需要不断将我们写好的操作系统代码写入软盘并且还要不断重启电脑来试验我们的代码,大家一定感到很头疼吧。
与其不停的重启,不如使用模拟器,向大家推荐一款模拟器qemu,它是由Fabrice Bellard编写,功能非常强大。那么下面我就来教大家使用qemu运行书中自制操作系统的方法吧。
一、安装qemu
我使用的是苹果笔记本,苹果有一个非常好用的程序管理工具叫brew,安装brew需要在终端中输入
sudo ruby -e "$(curl -fsSLhttps://raw.github.com/mxcl/homebrew/go)"
或者
curl -LsSf http://github.com/mxcl/homebrew/tarball/master | sudo tar xvz -C/usr/local --strip 1
然后使用brew安装qemu
sudo brew install qemu --env=std --use-gcc
如果成功,就会在/usr/local/bin/中创建出许多以qemu开头的文件,其中qemu-img和qemu-system-i386是对我们来讲最重要的两个文件。
二、自制操作系统
为了方便读者,我把书中前三天内容的精髓汇总在一个程序中,其中包含FAT软盘格式、从软盘中读扇区和在屏幕中输出文字。程序如下:
; 程序名称ipl.nas
; hello-os
; TAB=8
ORG 0x7C00 ; 程序加载到内存地址0x7C00后
; 以下的记述用于标准FAT12格式的软盘
start:
JMP entry
DB "HELLOIPL" ; 启动区名称(8字节)
DW 512 ; 扇区大小(512字节)
DB 1 ; 簇大小(1扇区)
DW 1 ; FAT起始位置
DB 2 ; FAT个数
DW 224 ; 根目录大小(224项)
DW 2880 ; 磁盘大小(2880扇区)
DB 0xf0 ; 磁盘种类
DW 9 ; FAT长度
DW 18 ; 每个磁道扇区数
DW 2 ; 磁头数
DD 0 ; 不使用分区
DD 2880 ; 重写一次磁盘大小
DB 0,0,0x29 ; 意义不明
DD 0xffffffff ; 可能是卷标号码
DB "HELLO-OS " ; 磁盘名称(11字节)
DB "FAT12 " ; 格式名称(8字节)
RESB 18 ; 空出18字节
entry:
MOV AX,0 ; 初始化寄存器
MOV SS,AX
MOV SP,0x7c00
MOV DS,AX
MOV ES,AX
; 读磁盘
CYLS EQU 10
MOV AX,0x0820
MOV ES,AX
MOV CH,0 ; 柱面0
MOV DH,0 ; 磁头0
MOV CL,2 ; 扇区2
readloop:
MOV SI,0 ; 记录失败次数
retry:
MOV AH,0x02 ; 读盘
MOV AL,1 ; 1个扇区
MOV BX,0
MOV DL,0x00 ; A驱动器
INT 0x13 ; 调用磁盘BIOS
JNC next ; 没出错跳转fin
ADD SI,1
CMP SI,5 ; 比较SI与5
JAE error ; SI >= 5时,跳转到error
MOV AH,0x00
MOV DL,0x00
INT 0x13 ; 重置驱动器
JMP retry
next:
MOV AX,ES
ADD AX,0x0020 ; 把内存地址后移0x200
MOV ES,AX ; 因为没有ADD ES,0x20
ADD CL,1
CMP CL,18
JBE readloop ; 如果CL <= 18,跳转至readloop
MOV CL,1
ADD DH,1 ; 读磁盘另一面
CMP DH,2
JB readloop
MOV DH,0
ADD CH,1
CMP CH,CYLS ; 读CYLS个柱面
JB readloop
; 输出helloworld
MOV SI,msg
putloop:
MOV AL,[SI]
ADD SI,1 ; 给SI加1
CMP AL,0
JE fin
MOV AH,0x0e ; 显示一个文字
MOV BX,15 ; 指定字符颜色
INT 0x10 ; 调用显卡BIOS
JMP putloop
fin:
HLT
JMP fin
error:
MOV SI,errmsg
errloop:
MOV AL,[SI]
ADD SI,1 ; 给SI加1
CMP AL,0
JE fin
MOV AH,0x0e ; 显示一个文字
MOV BX,15 ; 指定字符颜色
INT 0x10 ; 调用显卡BIOS
JMP errloop
msg:
DB 0x0a, 0x0a ; 换行2次
DB "hello, world"
DB 0x0a ; 换行
DB 0
errmsg:
DB 0x0a, 0x0a ; 换行2次
DB "disk error"
DB 0x0a ; 换行
DB 0
marker:
RESB 0x1fe-(marker-start)
DB 0x55, 0xaa
; 以下是磁盘其他内容
DB 0xf0, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00
RESB 4600
DB 0xf0, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00
RESB 1469432
看过前三章的读者理解上面的程序应该不难。
另外fat启动扇区格式要安照http://www.ntfs.com/fat-partition-sector.htm,所以假如你发现在汇编后的机器码中由于JMP entry的机器代码长度(需占前三个字节)有变导致与fat格式不符合(需从第四字节即地址为0x03开始),那么你要回到程序中在JMP
entry后使用DB手工添加占位字节。
三、汇编
我使用mac自带的nasm汇编器。查看能过汇编成哪些类型的机器代码可以输入
nasm -hf
我们需要把代码汇编成bin格式,这也是nasm的默认格式。因为在我们的代码中全部都是intel的16位汇编指令,而mac的cpu使用的也是intel芯片,所以汇编后的代码x86和x64都可以执行。
nasm -f bin ipl.nas -o ipl.bin -l ipl.lst
-f后面跟输出格式,-o后面是输出文件,-l后面是list文件,其内容是汇编语言和机器语言的对照表。
你可以用xxd查看二进制文件的内容:
xxd ipl.bin | less
以及:
file ipl.bin # 显示为:DOS floppy 1440k, x86 hard disk boot sector
qemu-img info ipl.bin # 其对应的qemu镜像类型为raw
四、虚拟机
在终端中输入:
qemu-system-i386 -fda ipl.bin -boot a
其中
-fda/-fdb 指定软盘
-hda/-hdb/-hdc/-hdd 指定硬盘
-cdrom 指定光盘
-boot 指定从哪个设备启动
a(软盘),c(硬盘),d(光盘),n(网络)
因为ipl.bin是软盘格式且为启动盘,我们使用-fda ipl.bin -boot a
运行结果如下:
五、制作u盘磁盘镜像
我们当然也能把ipl.bin写入u盘然后从u盘启动,此时u盘其实就相当于一个软盘。
当插入u盘后,我的/dev中多了一个disk2。
sudo diskutil unmountDisk /dev/disk2
sudo dd if=ipl.bin of=/dev/disk2 # 将软盘镜像写入u盘中
写好后重新插拔一次u盘
sudo diskutil unmountDisk /dev/disk2
sudo qemu-system-i386 -fda /dev/disk2 -boot a
运行结果同上。