整个操作系统的启动引导工作里,第一座“大山”就是文件系统了吧。 :ava_headhurts:
虽然说是“大山”,但其实我们本章要介绍的FAT12文件系统特别简单,所以其实也只算一座“小山”罢了。 :diana_yeah:
文件系统
大家都知道我们的硬盘之所以能管理文件,是因为其中有文件系统在对文件进行管理。
随着计算机相关存储技术不断发展,常用的文件系统也越来越多。比如,从系统角度来说,现在Windows系统主要使用的NTFS、Linux系统常用的EXT4、macOS常用的APFS以及三个系统都能够使用的exFAT;从存储介质角度来说,上述文件系统常用在各种硬盘上。对于大容量U盘,(前几年)主要使用FAT32文件系统,小容量U盘也有使用FAT16文件系统的。再往前还在使用软盘的时代,使用的就是FAT12文件系统,也就是我们今天要主要介绍的系统了。实际上,随着近年来U盘技术的不断更新,甚至是移动硬盘的问世,在移动设备上也逐渐转向了exFAT和NTFS文件系统。
FAT文件系统
FAT全称为File Allocation Table,翻译过来就是文件分配表,FAT文件系统有多个版本,其中一部分上一节也提到了,分别为FAT12、FAT16、FAT32以及exFAT。这些文件系统都各有其特点,在boot阶段,我们先暂时采用最简单的FAT12,如果以后有需要再升级或更改文件系统。
在很久很久以前,Windows还没有推出NT Kernel的时候,Windows就是使用的FAT文件系统。不过因为一些缺点(FAT文件系统在长期使用以后会在硬盘中产生许多片段,在片段太多以后,就会导致读写速度变慢。这个问题的具体成因在我们后续开发文件系统驱动时就能体现出来了),导致后来被微软淘汰掉了。自从微软退出了NT Kernel以后,文件系统就换成NTFS了(使用NT Kernel的系统)。我猜测Windows里的“磁盘整理”工具(现在好像叫“优化驱动器”)就是为了解决这个问题的。(不过我没有详细研究过这个软件)
在这篇文章中,主要介绍FAT12文件系统,其中标题、名词等除非特殊指明,否则一律指FAT12。
我们把FAT12文件系统所管理的硬盘分成4个大区(引导扇区、FAT表区、根目录区、数据区),下面每一节介绍一个大区。
引导扇区
在操作系统开发(3)——操作系统的Hello world中,我们完成了一个用于实现字符串的简单“引导程序”。之所以引导程序加引号,就是因为其实那段代码根本算不上引导程序,毕竟它什么也没引导出来,只是显示了一个字符串。
其实引导扇区中除了功能性的代码,还应该有一些其他数据,下面来对这些数据简单进行介绍。
名称 | 偏移量 | 数据长度 | 详细描述 | 本系统数据示例 |
跳转指令 | 0x00 | 3 | 跳转到真正的启动代码,启动代码之前都是 FAT12引导扇区结构表,这一点有 在操作系统开发(3)——操作系统的Hello world中提到过 | jmp short start |
OEM名称 | 0x03 | 8 | OEM名称,这部分如果不足8个字节,需要用空格补齐 MS-DOS检查这个区域以确定使用启动记录中的 哪一部分数据(来自Wikipedia) | 'KNOSBOOT' |
每扇区字节数 | 0x0b | 2 | 每扇区字节数 | 512 |
每簇扇区数 | 0x0d | 1 | 每簇扇区数 | 1 |
保留扇区数 | 0x0e | 2 | 保留扇区数 | 1 |
文件分配表数 | 0x10 | 1 | FAT表的数量,一般是两份,其中一份用作备份 | 2 |
最大根目录条目数 | 0x11 | 2 | 根目录能保存的最大目录条数,这一点与FAT的目录表有关 详细内容会在介绍文件目录树结构时讲到 | 224 |
总扇区数 | 0x13 | 2 | 总扇区数,此处如果值为0,则使用0x20偏移处的值 不知道为什么要这么设计 | 2880 |
介质描述 | 0x15 | 1 | 介质描述,此处值Wikipedia是这样描述的: 0xF8 单面、每面80磁道、每磁道9扇区 0xF9 双面、每面80磁道、每磁道9扇区 0xFA 单面、每面80磁道、每磁道8扇区 0xFB 双面、每面80磁道、每磁道8扇区 0xFC 单面、每面40磁道、每磁道9扇区 0xFD 双面、每面40磁道、每磁道9扇区 0xFE 单面、每面40磁道、每磁道8扇区 0xFF 双面、每面40磁道、每磁道8扇区 同样的介质描述必须在重复复制到每份FAT的第一个字节。 有些操作系统(MSX-DOS 1.0版)全部忽略启动扇区参数, 而仅仅使用FAT的第一个字节的介质描述确定文件系统参数。 | 0xf0 |
每个文件分配表的扇区数 | 0x16 | 2 | 每个文件分配表占用的扇区数(也就是FAT表的长度) | 9 |
每个磁道的扇区数 | 0x18 | 2 | 每个磁道的扇区数 | 18 |
磁头数 | 0x1a | 2 | 磁头数 | 2 |
隐藏扇区数 | 0x1c | 4 | 隐藏扇区数 | 0 |
总扇区数 | 0x20 | 4 | 总扇区数,如果此处超过0xffff,则使用0x13偏移处的值 | 0 |
int 13h的驱动器号 | 0x24 | 1 | int 13h的驱动器号 | 0 |
保留 | 0x25 | 1 | 保留 | 0 |
扩展引导标记 | 0x26 | 1 | ? | 0x29 |
卷序列号 | 0x27 | 4 | 卷序列号 | 0 |
卷标 | 0x2b | 11 | 卷标,11字节,不足的需要使用空格补足 | 'KNOS ' |
文件系统类型 | 0x36 | 8 | 文件系统类型,8字节,不足的需要使用空格补足 | 'FAT12 ' |
引导代码、数据等其他程序 | 0x3e | 448 | 引导代码、数据等其他程序 | ... |
引导扇区识别标志 | 0x1fe | 2 | 引导扇区结束识别标志 | 0xaa55 |
FAT表(文件分配表【表】)
我知道这个翻译很奇怪,这个“表”的名字就叫“文件分配表‘表’”(File Allocation Table 表)。当然,直接叫FAT或者文件分配表也可以,这不是重点。
文件分配表是FAT文件系统中比较重要的一个概念(毕竟文件系统的名字就叫FAT)。这一部分内容的概念理解起来还是比较简单,不过要用代码实现出来还是有一定难度的。代码实现部分会在正篇中细说,这里就只介绍其概念和原理。
FAT12文件系统以簇为单位来分配存储空间,每个簇的长度为每扇区字节数×每簇扇区数(其意义见上方表格)。在数据区中(下方会详细介绍),簇号和FAT表的表项是一一对应的,所以文件在FAT文件系统中的存储单位是簇,而非字节或扇区,不过因为操作系统会做标准化处理,所以从应用软件层面上来说没什么不同的。不过虽然操作系统会进行标准化操作,但实际上的空间占用还是以簇为单位,所以硬盘占用一般情况下都会比实际文件大小要大,因为即使文件只有不到一个簇的大小(本系统中为512字节),甚至可能只有1个字节,但是都会为其分配1个簇的空间。
在FAT12文件系统中,FAT表项占12位(所以叫FAT12嘛,以此类推,FAT16则是一个表项占16位,FAT32则是一个表项占32位)。一个大型文件在长期的使用中,很难保证一定在一段连续的扇区内,其往往会被分成多个片段。FAT表便可以将这些片段按照簇号连接起来。
对于FAT的表项值,有一些特殊的意义。首先是第0项和第1项(FAT[0]、FAT[1]),这两项不参与运算,而且有特殊含义。首先是FAT[0],其低8位与介质描述(其意义见上方表格)相同,其余位全部为1;FAT[1]表示第一个簇已被占用,其值一般在镜像创建时就被写为0xfff。