根据前面的内容我们知道,上电之后,根据CPU硬件电路设计,会自动将软盘第1扇区加载并执行。如果我们实现的OS只需要使用512个字节的代码,那就不需要主动读取软盘,只依靠CPU的硬件设计自动读取第1扇区就可以了。但这显然不可能。

所以接下来,我们就依靠第1扇区这512个字节的代码量,读取更多的软盘内容。

读取软盘内容至内存

我们将自己写的操作系统的代码编译成二进制文件后,存储在软盘上。然后利用第1个扇区的代码(开机后被BIOS自动加载至0x7c00处并开始执行),将软盘上的内容拷贝到内存中。拷贝完成后,再跳转到我们拷贝的目的内存地址,开始执行。

软盘的结构

floppy

上图来自《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上查看磁盘中的内容。

fat12

OS代码在软盘中的位置

根据FAT12文件系统的规则,sys.bin文件应该存储在镜像文件中0x4400的位置。我们可以验证一下。

umount之后,我们得到了已经拷贝了sys.bin的磁盘镜像文件。使用二进制文件查看工具直接打开这个磁盘镜像文件,可以看到在0x4400处有一些非0的内容。我们将这些内容与sys.bin对应的二进制内容比对,可以发现是一致的。因此可以确认,sys.bin文件存储在磁盘镜像的0x4400处。

sys.bin

跳转至sys.bin并执行

前面,我们已经将磁盘的18个扇区,2个磁头,10个柱面的内容拷贝至内存0x8000处。又已知我们的sys.bin文件位于磁盘开始的0x4400处。因此内存拷贝结束之后,sys.bin的文件内容应该位于内存的0xc400处。

那我们使用跳转指令跳转至该处,即可开始执行sys.bin

    jmp 0xc400