1.简介
在早期计算机刚发展的时候,那时候硬盘大小、flash设备容量都比较小,随着技术的不断迭代更新,硬盘容量越来越大。在早期,面对小容量的硬盘/flash,往往采用对应地址存放对应数据的方案,由于数据量不大,操作起来尚还可以。但是发展到今天,随着硬盘/flash容量不断增大,存储的数据也越来越多,早期单一的对应地址存放对应数据的方案已经无法满足我们的需求,操作硬盘/flash会变得异常的困难复杂。
因此针对上述问题,一群大佬们便开始设计文件系统这样一个东西,用来管理硬盘/flash上的数据信息,像我们电脑上打开文件夹,访问里面的文件,这其实就是基于文件系统访问电脑硬盘上数据的一个操作。
发展至今,文件系统已有众多版本,本文主要分享 关于FAT文件系统的详细设计, FAT文件系统适用于嵌入式设备,如SD卡、SD nand、spi nor flash等众多存储设备,同时基于此文件系统的文件亦能被电脑正常读取。
2.基础概念
在研究文件系统之前,我们需要首先弄清楚关于内存这块的几个基本概念:
区分 ==扇区、块、簇== 的概念
扇区(sector):flash可操作的最小单元,通常指我们擦除的最小单元大小,以sd nand举例,通常最小为512Byte
块(block) 以及 簇(cluster):其实这是两个相同的概念,只是由于历史原因,在不同系统上的不同称呼,在windows中称簇,而在linux中称块。一个簇/块由多个扇区组成,由于一个扇区的空间较小,因此文件系统通过会将多个扇区组合在一起形成一个簇,并以簇为单位进行读写操作! 一个簇通常可以由 2、4、8、… 、2的n次方个扇区组成。
FAT文件系统总共由FAT12、FAT16以及FAT32三个版本,这是由于随着存储技术不断发展,FAT文件系统迭代导致,数字越大,版本越新,新版本对老版本完全兼容!
3.FAT文件系统组成介绍
FAT文件系统在flash上的布局如下图所示,总共由四个区域组成:
保留区
FAT区
根目录区 (FAT32类型不包含此区域)
数据区
接下来,我们对一张格式化为FAT格式的SD卡进行分析,理解FAT文件系统的实现细节:
4.FAT文件系统分析
4.1 采用FAT格式格式化SD nand/sd卡
使用win10格式化一张118.5M的SD nand / sd卡,我这里用的是手上的一颗 创世CS 家的sd nand加一块转接板,和SD卡完全没有区别,且SD nand在稳定性上比SD卡具有优势。
==此处由于SD nand(SD卡)大小原因,默认采用FAT16进行了格式化!因此在下文中我们先以FAT16进行分析,之后再重新格式化为FAT32进行分析,就很容易懂了!==
4.2 引导扇区分析
使用 winhex 工具打开对应磁盘,注意需使用管理员权限运行
==此处由于SD nand(SD卡)大小原因,默认采用FAT16进行了格式化!因此在下文中我们先以FAT16进行分析,之后再重新格式化为FAT32进行分析,就很容易懂了!==
4.2 引导扇区分析
使用 winhex 工具打开对应磁盘,注意需使用管理员权限运行
a) 首先是FAT12/16/32公共部分,(偏移值 0 - 35):
EB 3C 90:BS_JmpBoot,跳转指令
4D 53 44 4F 53 35 2E 30:BS_OEMName,MSDOS 5.0,一个名字,指示创建此卷的操作系统,无其他作用
00 02:BPB_BytsPerSec,扇区大小 512 字节
04:BPB_SecPerClus,每次操作的最小扇区数,簇 Cluster,4 (与格式化时选择的大小匹配 2048 = 512 * 4)
06 00:BPB_RsvdSecCnt,保留区的扇区数,6 (通过此可计算,FAT区起始地址为 6 * 512 = 0xC00)
02:BPB_NumFATs,FATs的个数,2(一般此值为2,多一个用来做冗余备份,解决系统异常导致第一个损坏时,增大恢复的可能性,表示FAT区有两个FATs备份)
00 02:BPB_RootEntCnt,512,在FAT12/16系统中,此字段表示根目录中32字节目录条目数量,设置此值时需注意对齐,为了最大的兼容性,FAT16系统上此值应设置为512,FAT32系统上此值应设置为0
00 00:BPB_TotSec16,16位大小区域描述FAT卷扇区总数,0。当FAT12/16系统扇区数 ≥0x10000(65536)时,此字段应设置为0,真实值存放在 BPB_TotSec32 字段;对于FAT32系统,此值必须为0。(此处由于我们的总扇区数=118.510241024/512 = 242688 > 65536,所以此字段为0)
F8:BPB_Media 媒体类型
ED 00:BPB_FATSz16,237,一个FAT占用的扇区数,此字段仅在FAT12/16系统使用;FAT32系统,此字段必须为0,使用BPB_FATSz32字段替代。FAT区总大小等于 BPB_FATSz?? BPB_NumFATs 扇区(2372*512=242688=0x3B400,由此可推算根目录区起始地址:0x3B400+0xC00=0x3C000)。
3F 00:BPB_SecPerTrk,每个磁道的扇区数,此字段仅与具有几何形状且仅用于 IBM PC 的磁盘 BIOS 的介质相关,不用管。
FF 00:BPB_NumHeads,头数量,此字段仅与具有几何形状且仅用于 IBM PC 的磁盘 BIOS 的介质相关,不用管。
00 00 00 00:BPB_HiddSec,0,FAT 卷之前的隐藏物理扇区数(当磁盘被分区之后,当前分区并不一定是从扇区头开始的)
00 B4 03 00:BPB_TotSec32,242688,32位大小区域描述FAT卷扇区总数(整个卷空间大小)。 FAT12/16系统,扇区总数小于0x10000时,此字段必须为0,真实值存放在BPB_FATSz16;FAT32系统,此字段一直有效。(118.5M = 512 * 242688)
b) 接下来是FAT12/16特有字段(偏移值36)
80:BS_DrvNum,IBM PC 的磁盘 BIOS 使用的驱动器号,00h代表软盘,80h代表固定磁盘
00:BS_Reserved,保留字段,0
29:BS_BootSig,扩展引导签名,表示以下存在三个字段
83 3E 07 E4:BS_VolID,与 BS_VolLab 一起构成卷序列号,一般在格式化的时候结合时间生成
4E 4F 20 4E 41 4D 45 20 20 20 20:(解析为:”NO NAME “),BS_VolLab,11byte卷标,当卷标不存在时,此值应设置为”NO NAME”
46 41 54 31 36 20 20 20:(解析为:”FAT16 “),BS_FilSysType文件系统类型,支持字段有:”FAT12 “, “FAT16 “ or “FAT “,注意很多人认为是通过此字段区分FAT12/16/32系统类型,实际是错误的,文件系统类型实际上是根据磁盘大小确定的,官方文档 “Determination of FAT sub-type” 章节或本博文后文有描述,不过为了最大的兼容性考虑,此字段应设置为对应文件系统的名字。
33 C9 ~ CB D8:BS_BootCode,引导启动程序,与平台有关,不使用时填充为0
55 AA:BS_BootSign,0xAA55,引导签名,指示这是一个有效的引导扇区
当扇区大小大于512字节时,剩余的字段应全部使用0x0填充。
c) 如果是FAT32,则采用FAT32特有字段解析(偏移值和FAT12/16特有字段一致为36)
虽然此处我们的是FAT16格式,不过此处也将FAT的字段进行描述,方便理解。
BPB_FATSz32:一个FAT占用的扇区数,此字段仅在FAT32系统有效。FAT区总大小等于 BPB_FATSz?? * BPB_NumFATs 扇区。
BPB_ExtFlags:扩展标识字段,bit7=0,表示所有FAT都是镜像的和活跃的;bit7=1,表示只有bit3-0表示的FAT是有效的。
BPB_FSVer:FAT32版本,高字节是主版本号,低字节是次版本号。
BPB_RootClus:根目录的第一个簇号,此值通常为2,因为前两个簇一般用于保留。
BPB_FSInfo:FSInfo结构扇区与FAT32卷顶部的偏移扇区值。此值通常为1,因为其通常位于引导扇区旁边。
BPB_BkBootSec:备份引导扇区与FAT32卷顶部的偏移扇区值。此值通常为6,考虑最大的兼容性,此值不建议为其他值。
BPB_Reserved:保留
BS_DrvNum:含义与FAT12/16字段一样
BS_Reserved:含义与FAT12/16字段一样
BS_BootSig:含义与FAT12/16字段一样
BS_VolID:含义与FAT12/16字段一样
BS_VolLab:含义与FAT12/16字段一样
BS_FilSysType:始终为”FAT32 “,对FAT类型的确定没有任何影响。
BS_BootCode32:引导启动程序,与平台有关,不使用时填充为0
BS_BootSign:0xAA55,引导签名,指示这是一个有效的引导扇区
当扇区大小大于512字节时,剩余的字段应全部使用0x0填充。
以上就是引导扇区内容的详细分析了,通过引导扇区的内容,我们即可知道FAT文件系统依赖的硬件存储空间大小、簇大小、扇区大小以及以及FAT系统版本等重要信息。
同时通过引导扇区的内容,我们便可计算出对应的FAT的四个区域的大小及起始偏移位置等重要信息,接下来计算FAT四个分区的起始位置及大小。
4.3 分区偏移及大小计算
FAT卷总共分为以下四个区域:
保留区
第一个扇区为引导扇区,存放BPB(BIOS Parameter Block)数据,存放的是FAT卷的配置参数。
上述参数中以 BPB 命名的字段都是 BPB 的一部分,而以 BS 标题命名的字段都不是 BPB 的一部分,而只是引导扇区的一部分
FAT区(分区表装载区)
根目录区
数据区
各分区偏移地址及大小如下:
此外,关于FAT区,通常存在一个以上的FAT,如此处所格式化的sd卡便存在两个FAT,对应的偏移地址和大小如下:
4.4 FAT子类型确认
关于FAT的类型是FAT12/16/32确认:FAT类型由数据区内簇的数量决定,除此之外无其他办法!
当一个卷,簇的数量 ≤4085 时,为FAT12
当一个卷,簇的数量 ≥4086 且 ≤65525 时,为FAT16
当一个卷,簇的数量 ≥65526 时,为FAT32
簇的数量计算公式:CountofClusters = DataSectors / BPB_SecPerClus;
如我们这里:CountofClusters = ==242176== / 4 = 60544,==所以为 FAT16!==
当簇的大小从 512 ~ 32768字节的各种条件下,不同类型FAT对应卷的大小范围如下:
4.4 访问FAT条目
FAT区由一条条FAT条目构成,关于 FAT[N] 对应的条目具体位置计算如下:
格外需要注意的是,不同格式,对应的FAT条目的长度和格式不一样:
此外对于FAT32格式,高4位是保留位,只有低28位有效!
具体如下图所示:
4.5 文件与簇之间的关系
那么文件和簇之间的相互关系又是怎样的呢?我们又是如何准确的找到存放在flash上的文件的呢?接下来让我们看下文件与簇之间的关系映射。
在FAT卷上文件通过目录管理,==目录是一个32字节数组组成的目录条目结构==,此目录结构包含:文件名、文件大小、时间戳以及文件所在的第一个簇号。
簇号为0和1的簇被保留,由参数BPB_RootClus可知,有效簇从第2号簇开始。==FAT2对应数据区的第一个簇==。
因此第N个簇的位置计算公式如下:
FirstSectorofCluster = DataStartSector + (N - 2) * BPB_SecPerClus
==每个条目所在的位置,对应一个簇。当文件长度大于一个簇长度时,条目内的值为下一个条目的索引,直到文件所在的最后一个簇,由此构成簇链!文件所在的最有一个簇所对应的FAT条目内的值由一个特殊的值(EOC)组成,它永远不会匹配任何有效的簇号==,如下:
FAT12: 0xFF8 - 0xFFF (typically 0xFFF)
FAT16: 0xFFF8 - 0xFFFF (typically 0xFFFF)
FAT32: 0x0FFFFFF8 - 0x0FFFFFFF (typically 0x0FFFFFFF)
存在一些特殊的值被用来做损坏簇的标记,如下:
FAT12: 0xFF7
FAT16:0xFFF7
FAT32:0xFFFFFFF7
不过此处需要注意,在FAT12/16系统上,上述特殊值绝不会和任何有效簇匹配,但是在FAT32上有可能,因此为了防止混淆,你在创建FAT32系统的时候应该避免这种情况发生!因此FAT32系统上簇的上限为268435445(大于256M个簇)
FAT条目初始化的时候,FAT[2] 及以后的数据应被初始化为0,指示未被使用处于空闲状态,如果值不为0,则意味着簇被损坏或被使用状态。在FAT12/16系统上,空闲簇的数量未被记录,而在FAT32系统上,FAT32支持FSInfo结构体,里面记录了空闲簇的数量。
==关于FAT[0]和FAT[1]:==
此两个保留的条目,没有与任何簇有联系;不过具有其他意义,如下:
FAT12: FAT[0] = 0xF??; FAT[1] = 0xFFF;
FAT16: FAT[0] = 0xFF??; FAT[1] = 0xFFFF;
FAT32: FAT[0] = 0xFFFFFF??; FAT[1] = 0xFFFFFFFF;
FAT[0]中的?? 与 BPB_Media 相同;
FAT[1] 记录了错误历史记录:卷脏标志(FAT16:bit15、FAT32:bit31),系统在启动的时候清除此位,正常关闭的时候恢复。
如果此位已经清除,表明上次未被正常关闭,可能存在逻辑卷错误;硬件错误标志(FAT16:bit14、FAT32:bit30)当出现无法恢复的读写错误时清除,表明需要进行全面检查。
==关于FAT区域,有两个重点注意事项:==
第一个是FAT的最后一个扇区可能没有被完全使用。在大多数情况下,FAT在扇区的中间结束。FAT驱动程序不应该对未使用的区域做出任何假设。在格式化卷时,应该用零填充它,并且在此之后不应更改它。
另一个是BPB_FATSz16/32可以指示比卷需要的值大的值。换句话说,未使用的扇区可以跟随每个FAT。这可能是数据区域对齐或其他原因导致的。同时,在格式化时这些扇区也会被用零填充。
下表展示了不同FAT类型中FAT值所对应的含义解释:
4.6 FSInfo扇区结构及备份引导扇区
此部分内容只在FAT32系统上存在,对于FAT12系统FAT区域大小最大6KB,对于FAT16系统FAT区域最大128KB,但是在FAT32系统上FAT区域通常上达数MB,这是因为FAT32系统支持FSInfo数据结构。
在FAT32系统上新增FSInfo数据结构的原因是:在FAT12/16系统上,想要知道flash上剩余的簇数需要扫描整个FAT区才能计算出来,但随着flash容量的不断扩大,扫描花费的时长越来越长,为了避免扫描浪费过多的时间,因此在FAT32系统上增加了FSInfo结构,用于记录flash上剩余的簇数。
FSInfo数据结构如下:
| 字段名 | 偏移 | 大小 | 描述 |
| — | — | — | — |
| FSI_LeadSig | 0 | 4 | 固定值为0x41615252,头部签名 |
| FSI_Reserved1 | 4 | 480 | 保留区域,采用0x00覆盖 |
| FSI_StrucSig | 484 | 4 | 固定值为0x61417272,也是一个签名 |
| FSI_Free_Count | 488 | 4 | 记录了空闲的簇数,如果这个值为0xFFFF FFFF,则表示不知道具体的空闲簇数 |
| FSI_Nxt_Free | 492 | 4 | 提示驱动程序应该从此参数提示的簇开始寻找空闲的簇,通过此参数便可以不用从FAT区头开始寻找下一个空闲簇了,节省了大量时间;如果此参数为0xFFFF FFFF,则驱动程序应该从头部(2号簇)开始寻找空闲簇|
| FSI_Reserved2 |496 | 12 | 保留区域,采用0x00覆盖 |
| FSI_TrailSig | 508 | 4 | 固定值0xAA550000,尾部签名 |
注意:当扇区大小大于512字节时, 剩余空间采用0x00填充
4.7 FAT目录
FAT目录分为长文件名目录(LFN)以及短文件名目录(SFN),长文件目录是在短文件名目录上的一个扩展,具体采用长文件名还是短文件名由读取FAT文件系统的操作系统决定,如windows;设置长文件名时短文件名也被设置,具有兼容性。
此外,有一个很重要的概念:在FAT文件系统上目录也是一个文件,只是此文件的属性不一样而已。
在所有目录中,有一个比较特殊的是根目录,且根目录作为顶层目录必须存在。
在FAT12/16系统中,根目录不是一个文件,且放在根目录区,根目录的最大条目数由 BPB_RootEntCnt 参数指示;
在FAT32系统中,根目录与子目录没有什么区别,且根目录的起始簇由 BPB_RootClus 参数指示。
根目录与子目录的另外一个区别是,根目录不包含 . .. 此两个点目录,且它可以包含卷标(具有ATTR_VOLUME_ID属性的条目)
关于目录结构的第一个字段 DIR_Name 的第一个元素 DIR_Name[0] 在目录表中有着特殊作用,如下:
当此值为 0xE5 时,代表此目录条目未被使用(或已废弃)
当此值为 0x00 时,也代表此目录条目未被使用;此外还提示后续目录条目也未被使用,因为后续的目录条目 DIR_Name[0] 都会是 0x00
如果文件名的第一个字符为 0xE5 这个特殊值,则使用 0x05 替代。
这么设计的意义是什么呢?将 DIR_Name[0] 用作特殊字符,其目录在于方便文件删除!当我们删除一个文件的时候,文件系统并不会将此文件所对应的数据全部删除,因为那样太费时间了,也没有必要,而是直接将对应文件的目录项中的 DIR_Name[0] 修改为 0xE5 即可!
关于文件名字段 DIR_Name,在FAT文件系统中还有如下规定:
DIR_Name 字段的11字节的文件名分为两个部分:8 字节的主文件名 + 3字节的扩展名;
文件名中主文件名与扩展名中间的 . 被省略,不在此记录
如果主文件名长度不够,小于8字节,则使用 0x20 空格填充
用于设置文件名的字符也有限制,支持的字符有 ==0~9 A~Z ! # $ % & ‘ ( ) - @ ^ _ ` { } ~==
主文件名和扩展名中的(a~z)ASCII字符都会被转化成大写字符保存
以下为文件名存储示例:
4.7.2 LFN长文件名
长文件名是文件名的另外一种存储方式,由于SFN短文件名具有长度、字符等限制,在一些场景下不能很好的满足需求,因此就需要使用到长文件名,关于长文件名的具体内容如下:
长文件名是一个具有特殊属性的目录条目。长文件名目录属性 DIR_Attr 字段的值 ATTR_LONG_NAME = (ATTR_READ_ONLY | ATTR_HIDDEN | ATTR_SYSTEM | ATTR_VOLUME_ID) = 0x0F;
关于长文件名的目录属性如下:
| 字段名 | 偏移 | 大小 | 描述 |
| — | — | — | — |
| LDIR_Ord | 0 | 1 | 序号(1-20),用来表示此条目属于长文件名序列条目中的第几条。且长文件名序列首条条目的值应& 0x40以进行标识! |
| LDIR_Name1 | 1 | 10 | 长文件名 第1 ~ 第5 字符(注意此处一个字符占两个字节) |
| LDIR_Attr | 11 | 1 | 长文件名属性,此值永远为 ATTR_LONG_NAME 0x0F
| LDIR_Type | 12 | 1 | 类型,必须为0 |
| LDIR_Chksum | 13 | 1 | 和校验 |
| LDIR_Name2 | 14 | 12 | 长文件名 第6 ~ 第11 字符(注意此处一个字符占两个字节) |
| LDIR_FstClusLO | 26 | 2 | 必须为0 |
| LDIR_Name3 | 28 | 4 | 长文件名 第12 ~ 第13 字符(注意此处一个字符占两个字节) |
关于长文件名,有以下几点重要概念:
一个文件一定有短文名SFN,但不一定有长文件名LFN
长文件名LFN字段中==仅包含文件名信息==,不包含其他内容,其他内容需要通过短文件名SFN查看
如果一个文件既有长文件名也有短文件名,则长文件名是其主要名字,而短文件名则为附加名字
==长文件名LFN条目在对应的短文件名SFN条目前面==
一个文件的长文件名最长255字符,对应最多20个长文件名LFN条目
长文件名简单理解起始就是存储一个字符串,因此没有类似SFN的限制,允许有空格、支持大小写、允许多个.符号等
LFN条目文件名长度不够,仍然采用0x20填充
下图是官方关于一个名为 “MultiMediaCard System Summary.pdf” 的长文件名在flash上的长文件名条目,如下所示,一眼没看明白也没关系,后文有实例说明,对长文件名有概念了就行!
关于长文件名的checksum字段和计算,算法如下:
uint8_t create_sum (const DIR* entry)
{
int i;
uint8_t sum;
for (i = sum = 0; i < 11; i++) { /* Calculate sum of DIR_Name[] field */
sum = (sum >> 1) + (sum << 7) + entry->DIR_Name[i];
}
return sum;
}
4.7.3 LFN系统对于SFN的兼容
在使用LFN长文件名的系统中,会自动生成SFN短文件名已确保此文件在短文件名的文件系统中可使用。同时为了防止生成的短文件名冲突,SFN的生成采用 名称+数字后缀+扩展 的格式,同时采用以下规则生成SFN:
小写自动转大写
如果存在空格,则删去空格,设置有损转换标识
已.开头的文件删除头部的.,并设置有损转换标识
存在多个.的文件名,仅保留最后一个作为文件名与扩展的分隔,并设置有损转换标识
其他不支持的字符,采用_代替,并设置有损转换标识
文件名如果是Unicode编码,则转化为ANSI/OEM编码;不能转换的字符采用_代替,并设置有损转换标识
长度超过8字节的部分,截断,并设置有损转换标识
扩展名字段超过3字节的,截断,并设置有损转换标识
有损转转换标识为:~,ASCII值为0x7E,十进制126
示例如下:
至此,FAT文件系统的理论部分已经描述完了,接下来我们继续使用winhex对数据进行分析。
-
驱动器
+关注
关注
52文章
8151浏览量
145962 -
SD卡
+关注
关注
2文章
559浏览量
63791 -
Flash存储
+关注
关注
0文章
38浏览量
8277 -
FatFS文件系统
+关注
关注
0文章
12浏览量
7518
发布评论请先 登录
相关推荐
评论