读取并执行软盘内容
根据前面的内容我们知道,上电之后,根据CPU硬件电路设计,会自动将软盘第1扇区加载并执行。如果我们实现的OS只需要使用512个字节的代码,那就不需要主动读取软盘,只依靠CPU的硬件设计自动读取第1扇区就可以了。但这显然不可能。
所以接下来,我们就依靠第1扇区这512个字节的代码量,读取更多的软盘内容。
读取软盘内容至内存
我们将自己写的操作系统的代码编译成二进制文件后,存储在软盘上。然后利用第1个扇区的代码(开机后被BIOS自动加载至0x7c00处并开始执行),将软盘上的内容拷贝到内存中。拷贝完成后,再跳转到我们拷贝的目的内存地址,开始执行。
软盘的结构
上图来自《30天自制操作系统》
从上图中,我们可以看到,软盘有一圈有18个扇区Sector
,从外向内分为80个柱面Cylinder
,正反两面分别对应不同的磁头Heads
来读写,这称之为CHS寻址模式,对应三部分的首字母。前面提到每个扇区可以存储512个字节,所以整个软盘共可以存储512*80*18*2=1440KB
读取1个扇区
通过调用BIOS int 13
中断,可以读取软盘数据,调用代码如下:
MOV AX, 0x0820 ; 内存目的地址 0x08200
MOV ES, AX
MOV CH, 0 ; 柱面0
MOV DH, 0 ; 磁头0
MOV CL, 2 ; 扇区2
MOV AH, 0x02 ; AH=0x02 : 读盘
MOV AL, 1 ; 1个扇区
MOV BX, 0
MOV DL, 0x00 ; A驱动器
INT 0x13 ; 调用BIOS int 13中断
读取10个柱面
如果上面的代码封装为一个函数,那读取整个软盘的逻辑用c语言描述大约如下:
for (int i = 1; i <= 10; i++) {
for (int j = 1; j <= 2; j++) {
for (int k = 1; k <= 18; k++) {
read(i, j, k);
}
}
}
但现在还不能用c语言,只能用汇编实现。写出来如下:
mov ax, 0x0820
mov es, ax
mov ch, 0 ;柱面0
mov dh, 0 ;磁头0
mov cl, 2 ;扇区2
readloop: ;循环
mov ah, 0x02
mov al, 1
mov bx, 0
mov dl, 0x00
int 0x13
jnc next ;跳转到next部分
next:
;读完18个扇区中剩余部分
mov ax, es
add ax, 0x0020
mov es, ax
add cl, 1 ;扇区++
cmp cl, 18 ;扇区<=18
jbe readloop
mov cl, 1 ;重置扇区为1
add dh, 1 ;磁头++
cmp dh, 2 ;磁头<2
jb readloop
mov dh, 0
add ch, 1 ;柱面++
cmp ch, 10 ;柱面<10
jb readloop
软盘文件格式
在真实的物理环境中,我们可以将编译好的二进制文件拷贝到软盘上,将软盘插入新机器中。启动机器后,将自动读取软盘内容并开始执行。
软盘存储文件,也是遵循一定的文件系统格式的,最常见的就是FAT12
格式。
实际上我们使用虚拟机进行开发,用到的也是一块软盘镜像文件。这块镜像文件,我们也要将它按照FAT12
进行格式化处理。如下内容是FAT12
文件系统头的一些规则,我们需要将它写入软盘镜像文件的头部。
db 0x90
db "tianwen1"; 8bytes
dw 512
db 1
dw 1
db 2
dw 224
dw 2880
db 0xf0
dw 9
dw 18
dw 2
dd 0
dd 2880
db 0,0,0x29
dd 0xffffffff
db "My First OS"
db "FAT12 "
执行OS代码
OS代码
我们新建一个sys.asm文件,在其中实现一个死循环,如下:
org 0xc400
fin:
jmp fin
这个死循环就是我们当前实现的操作系统功能。我们将它编译成一个独立的二进制文件sys.bin
。这个二进制文件只有2个字节,内容是0xEB 0xFE
。
将OS代码保存至软盘
在linux中,我们可以将软盘镜像mount
至某个目录下,然后将sys.bin
拷贝至该目录下,再umount
。这样就将我们的操作系统二进制文件保存至软盘中了。参考脚本如下:
sudo mkdir -p /mnt/floppy
sudo mount -o loop a.img /mnt/floppy -o fat=12
sleep 1
sudo cp sys.bin /mnt/floppy
sleep 1
sudo umount /mnt/floppy
mount之后,我们可以在linux上查看磁盘中的内容。
OS代码在软盘中的位置
根据FAT12
文件系统的规则,sys.bin
文件应该存储在镜像文件中0x4400
的位置。我们可以验证一下。
umount之后,我们得到了已经拷贝了sys.bin
的磁盘镜像文件。使用二进制文件查看工具直接打开这个磁盘镜像文件,可以看到在0x4400
处有一些非0的内容。我们将这些内容与sys.bin
对应的二进制内容比对,可以发现是一致的。因此可以确认,sys.bin
文件存储在磁盘镜像的0x4400
处。
跳转至sys.bin并执行
前面,我们已经将磁盘的18个扇区,2个磁头,10个柱面的内容拷贝至内存0x8000
处。又已知我们的sys.bin
文件位于磁盘开始的0x4400
处。因此内存拷贝结束之后,sys.bin
的文件内容应该位于内存的0xc400
处。
那我们使用跳转指令跳转至该处,即可开始执行sys.bin
。
jmp 0xc400