经过一系列的文章,我们终于完成了从实地址模式跳转到保护模式,并且实现了分段、分页以及保护模式下的中断与异常机制。 保护模式究竟“保护”了什么
可是我们除了最初的时候,在磁盘第一个扇区写入一段代码来实现最初“hello world”,此后,我们就一直用 DOS 来拉起我们的“操作系统”,那么,你是不是有种不够尽兴的感觉?明明说好要写自己的操作系统,却要用别人的操作系统来引导,我们能不能自己写一个引导区呢? 答案当然是可以的,编写一个引导区看起来是如此的容易,只需要创建一个新的文件,里面指定被载入内存的初始地址,然后在引导扇区的代码中先将这个新的文件内容载入内存,再用一条跳转指令,跳转过去即可。 而这个“新的文件”就被称为操作系统的 loader,他负责在操作系统启动前进行一系列的准备工作,然后从实地址模式跳转到保护模式,再加载内核。
既然要让引导扇区找到磁盘上的文件,那我们首先要来看看磁盘空间是如何构成的。 通常,磁盘拥有远大于内存的容量,要想合理利用这些容量,就需要将他们进行划分,让数据能够在各自的区域内,从而方便迅速定位数据。
如图所示,我们可以将硬盘看做是多个光盘的组合,而每个“光盘”都被称为一个“盘片”,由于盘片位于硬盘盒内,不在暴露在空气中,因此,盘片得以更加高速的旋转,且数据密度也达到光盘所无法企及的程度,但其原理其实与光盘仍然非常类似。 而正如上面的图上所示,硬盘中并不只有一个盘片,也不只有一个磁头,所有的同心旋转的盘片经由磁头扫过的磁道共同构成了一个圆柱面,这就是“柱面”。
如上图所示,借由盘片的旋转,让磁头能够读取到的一圈圈轨道,就是“磁道”,数据就位于磁道上。
每连续的 512 字节数据组成了一个扇区,若干个扇区又组成了一个簇,簇是磁盘空间分配的最小单位,也就是说,你即使创建一个 1 字节的文件,实际上这个文件至少也要占用磁盘 1 簇的空间。 而每次内存与磁盘的交互中,操作的最小单位就是一页,1 页由若干簇构成,通常是 4096B。
FAT12 是一种“古老的”文件系统,到现在,软盘所使用的文件系统也通常是 FAT12,但其他地方已经基本上没有使用之处了。 不过现在的 FAT32 与 FAT12 从结构上来说是非常类似的,所以我们本文以 FAT12 来做讲解,未来会有专门的文章来介绍各个文件系统之间的区别,敬请期待。 本文,我们以一个 1.44M 的软盘为例进行介绍:
如上图所示,一个 1.44M 的软盘,可以划分为 2879 个扇区,共分为图上所示的五个区域。
第一个扇区就是引导扇区,他的具体取值是固定的:
名称 | 偏移 | 长度 | 内容 | 软盘参考值 |
---|---|---|---|---|
BS_jmpBoot | 3 | jmp LABEL_START | nop | |
BS_OEMName | 3 | 8 | 厂商名 | ‘ForrestY’ |
BPB_BytsPerSec | 11 | 2 | 每扇区字节数 | 0x200(512) |
BPB_SecPerClus | 13 | 1 | 每簇扇区数 | 0x01 |
BPB_RsvdSecCnt | 14 | 2 | Boot记录占用多少扇区 | 0x01 |
BPB_NumFATs | 16 | 1 | 共有多少FAT表 | 0x02 |
BPB_RootEntCnt | 17 | 2 | 根目录文件数最大值 | 0xE0 (224) |
BPB_TotSec16 | 19 | 2 | 扇区总数 | 0xB40(2880) |
BPB_Media | 21 | 1 | 介质描述符 | 0xF0 |
BPB_FATSz16 | 22 | 2 | 每FAT扇区数 | 0x09 |
BPB_SecPerTrk | 24 | 2 | 每磁道扇区数 | 0x12 |
BPB_NumHeads | 26 | 2 | 磁头数 | 0x02 |
BPB_HiddSec | 28 | 4 | 隐藏扇区数 | |
BPB_TotSec32 | 32 | 4 | 如果BPB_TotSec16是0,由这个值记录扇区数 | 0xB40(2880) |
BS_DrvNum | 36 | 1 | 中断13的驱动器号 | |
BS_Reserved1 | 37 | 1 | 未使用 | |
BS_BootSig | 38 | 1 | 扩展引导标记 | 0x29 |
BS_VolD | 39 | 4 | 卷序列号 | |
BS_VolLab | 43 | 11 | 卷标 | ‘OrangeS0.02’ |
BS_FileSysType | 54 | 8 | 文件系统类型 | ‘FAT12’ |
引导代码 | 62 | 448 | 引导代码、数据及其他填充字符等 | |
结束标志 | 510 | 2 | | 0xAA55 |
FAT 表又叫“文件分配表”,从图上可以看到,FAT12 具有两个 9 扇区大小的 FAT 表。 FAT2 通常是 FAT1 的备份,两者可以认为是一样的。 在 FAT 表中,每 12 位被称为一个 FAT 项(FAT Entry),第 0 个和第 1 个 FAT 项始终不使用,从第 2 个 FAT 项开始,每个 FAT 项对应数据区的一个簇,数据区首个簇号为 2,FAT Entry N 正好对应数据区簇号为 N 的簇。 每个 FAT 项中存储的是当前文件的当前簇的下一个簇的簇号,如果值大于等于 0xFF8,那么就表示这已经是文件的最后一个簇,0xFF7 则表示这对应了一个坏簇。
根目录区存储了若干条目录条目,每个目录条目长 32 字节,最多存储 BPB_RootEntCnt 个条目。 因此可以得到公式: 根目录区扇区数 = (BPB_RootEntCnt * 32)/BPB_BytsPerSec。 BPB_RootEntCnt 和 BPB_BytsPerSec 就是上文中起始扇区中定义的相应字段。 目录条目的存储内容为:
名称 | 偏移 | 长度 | 描述 |
---|---|---|---|
DIR_Name | 0xB | 文件名8字节,扩展名3字节 | |
DIR_Attr | 0xB | 1 | 文件属性 |
保留 | 0xC | 10 | |
DIR_WrtTime | 0x16 | 2 | 最后修改时间 |
DIR_WrtDate | 0x18 | 2 | 最后修改日期 |
DIR_FstClus | 0x1A | 2 | 此条目对应的开始簇号 |
DIR_FileSize | 0x1C | 4 | 文件大小 |
毋庸多言,数据区存储的就是文件的实际内容。 如果这个文件实际是一个目录,那么这个簇实际存储的就是这个目录下文件构成的条目列表,具体信息与根目录区中的条目格式相同。
经过上述 FAT12 分区的介绍,我们就已经可以清楚的知道如何在一个 FAT12 类型的磁盘上寻找一个文件了:
既然我们已经非常清楚了软盘的文件系统结构,你是否想要实践一下看看呢?是否一个软盘的文件系统真的如我们上面所描述的就是这样存储的呢?
通过下面的命令可以实现一个虚拟软盘镜像文件的创建:
dd if=boot.bin of=boot.img bs=512 count=1 conv=notrunc
通过下面的命令,可以将刚刚创建的软盘格式化为对应的文件系统格式:
mkfs.vfat floppy.img /建格式化为vfat文件系统/
mkdir /mnt/floppy
mount -o loop boot.img /mnt/floppy
你也可以增加 -t vfat
参数指定文件系统格式。
对于已经挂载好的 loop 设备,你可以随意去操作:
cp hello.txt /mnt/floppy cp world.txt /mnt/floppy rm /mnt/floppy/hello.txt
umount /mnt/floppy
虚拟软盘镜像内容是一个二进制文件,我们可以通过 vim 来读取:
vim -b boot.img
可是这样打开后,你仍然会发现看到了一团乱码,别急,通过下面的 vim 命令就可以切换到十六进制模式展示了:
:%!xxd -g 1