整个操作系统的启动引导工作里,第一座“大山”就是文件系统了吧。 :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。
上图是一个只有基本的FAT12格式的引导扇区部分,可以看到在0x1f0结尾时有0x55和0xaa两个字节,这表示该扇区为引导扇区,随后(0x200开始处)为0xf0 0xff 0xff 这一三个字节便是第0项和第1项(FAT[0]、FAT[1]),因为是12位为一个表,所以是“三个字母”,也就是一个半字节。红框为FAT[0],绿框为FAT[1]。绿框就不解释了,大家都能看懂,但是有的同学可能会疑惑:红框内容明明是0xf0f,为什么说是0xff0呢?实际上,这是因为FAT表并不是按照顺序来直接进行表示的,要得到正确的FAT表项值,需要经过一些计算。具体计算方式为:将三个字节分为一组,将每组中间的高8位移动至该组最高处,低8位移动至最低处,下图是个简单的例子:
所以 0xf0 0xf? 经过一系列操作就得到了表项0xff0,由此也可以得到其实FAT[1]的0xfff也不是直接拼接得到,而是经过了一系列计算的结果,从上面的描述和图示也能看出来,每次都需要两个FAT表项参与计算,并得到两个结果。 :ava_jellyfish:
每个表项其实是有一些特殊值的,其取值说明如下表(FAT[0]和FAT[1]比较特殊,上面说过了就不再说了):
| 表项值 | 描述 |
| 0x000 | 空闲簇 |
| 0x001 | 保留簇 |
| 0x002 - 0xFEF | 被占用的簇;指向下一个簇 |
| 0xFF0 - 0xFF6 | 保留值 |
| 0xFF7 | 坏簇 |
| 0xFF8 - 0xFFF | 文件最后一个簇 |
根目录(目录表)
根目录区是专门用来保存根目录的信息的(好像说了句废话),用过Linux的同学都知道,与Windows使用C、D之类的字母(盘符)来区分硬盘分区不同,Linux操作系统没有"盘符"这个概念,而是以一个树形存在,其最顶端便是根目录(/)。在FAT12文件系统中,可以理解为根目录区就是保存"/"下的目录信息的。当然,只有1个根目录区并不代表FAT12文件系统只能有一层目录,其实,目录树的子节点目录项都保存在数据区,其格式与根目录区格式一样。
目录表是一个特殊的文件类型,跟现在的文件夹的概念差不多,目录表的每个表项都占32位,一个表项中包含了文件(目录)名、扩展名、创建时间与日期、文件(目录)所在起始地址、文件(目录)大小等信息。目录项的格式和具体内容如下表:
| 偏移量 | 长度 | 描述 |
| 0x00 | 11 | 文件名及扩展名,其中文件名占8字节 扩展名占3字节。 在FAT12文件系统中文件名最长仅能为8字节。 如果不足8字节则用空格补足(扩展名同理)。 如果超过8字节,在FAT16和FAT32中会引入一个 长目录的概念,后续相关章节会进行详细介绍。 |
| 0x0b | 1 | 文件属性,有以下值: 0x01 只读 0x02 隐藏 0x04 系统 0x08 卷标 0x10 子目录 0x20 文件 0x40 设备(Wikipedia:内部使用,磁盘上看不到) 0x80 没有使用 0x0f 长文件名条目 |
| 0x0c | 10 | 保留项,NT系统中有使用到 |
| 0x16 | 2 | 最后更改时间 |
| 0x18 | 2 | 最后更改日期 |
| 0x1a | 2 | 起始位置(簇号) |
| 0x1c | 4 | 文件大小 |
数据区
顾名思义,这个区域是用来存文件数据的(当然,也可能包含目录表),这到没有什么好说的。
有了这些基础知识以后,我们就可以继续完善boot引导程序了。 :eileen_love: :eileen_love: :eileen_love:



