当前位置: 首页 > news >正文

TF卡的存储数据结构—fat32格式

引言:为何要了解FAT32?

在嵌入式开发与存储设备领域,FAT32文件系统无处不在。无论是SD卡、TF卡还是U盘,FAT32因其极高的兼容性和简洁性成为事实上的标准。本文将带你深入FAT32的底层世界,解析其数据存储的奥秘,并探讨如何在单片机环境中进行实际操作。

一、FAT32物理存储结构:卷的整体布局

一张格式化后的FAT32存储卡,其空间被划分为几个连续且结构清晰的区域:

偏移量 (Sector)名称大小描述
0DBR (引导扇区)通常 1 扇区包含分区信息、引导代码和FAT32的关键参数,是所有计算的起点。
1 - (Reserved - 1)保留扇区DBR定义 (通常为32扇区)保留供系统使用。
ReservedFAT1 (文件分配表1)DBR定义第一份FAT表,是整个文件系统的核心地图,记录簇的分配状态和链式关系。
Reserved + FAT_SizeFAT2 (文件分配表2)同FAT1FAT1的完整备份,用于冗余,提高数据安全性。
Reserved + 2*FAT_Size数据区 (Data Region)剩余所有空间存放文件和目录实际内容的区域,空间在此被划分为簇(Cluster)

关键概念:簇 (Cluster)

  • 簇是FAT32分配空间的基本单位,由一个或多个连续的扇区组成(如64扇区=32KB)。

  • 所有文件和目录都占用一个或多个簇。

1. 引导扇区(DOS Boot Record, DBR)

  • 位置:分区的第一个扇区(通常是512字节)。

  • 作用

    • 存储关键参数:包含让操作系统挂载(识别)这个文件系统所必需的全部信息。这些参数在格式化时就确定了。

    • 包含引导代码(可选):如果该分区是活动分区,这里的代码可以用于启动操作系统。

  • 关键参数举例(这些都是写在DBR里的):

    • 每扇区字节数(Bytes Per Sector):通常是512。

    • 每簇扇区数(Sectors Per Cluster):这是关键!FAT32不再操作单个扇区,而是将连续的扇区组合成一个“簇(Cluster)”作为基本存储单位。这个值可以是1, 2, 4, 8, ... 64(即簇大小可以是512B, 1KB, 2KB, ... 32KB)。大容量磁盘会用更大的簇来减少FAT表的大小。

    • 保留扇区数(Reserved Sectors):通常为32。指从分区开始到FAT表之前的所有扇区,包括DBR本身。

    • FAT表份数(Number of FATs):通常是2。为了冗余备份,有两份一模一样的FAT表(FAT1和FAT2)。

    • 每个FAT表的大小(Sectors Per FAT):告诉系统FAT表占用了多少扇区。

    • 根目录起始簇(Root Directory Cluster)这是FAT32与FAT16的关键区别之一。FAT32的根目录不再固定位置和大小,它和普通文件一样,可以位于数据区的任何位置,并且大小可以动态增长。DBR里记录了根目录的第一个簇是哪个。


2. 文件分配表(File Allocation Table, FAT)

  • 位置:紧跟在保留扇区(DBR等)之后。

  • 作用这是FAT文件系统的心脏。它是一个数字数组,数组的每个下标对应数据区的一个簇号。这个数组元素的值告诉我们下一个簇在哪里。

  • 工作方式

    • 数据区的所有簇都在FAT表中有一个对应的“表项”。

    • 每个表项的长度是 32位(4字节),这也是“FAT32”名字中“32”的由来。

    • FAT表项的值定义了对应簇的状态:

      • 0: 表示该簇是空闲的,可以使用。

      • 2 - 0x0FFFFFEF: 表示该簇已被文件占用,并且这个值代表该文件的下一个簇的簇号。这就形成了一个簇链(Cluster Chain)。

      • 0x0FFFFFF7: 表示这是一个坏簇,不再使用。

      • 0x0FFFFFF8 - 0x0FFFFFFF: 表示这是文件使用的最后一个簇(End Of File)。

举个例子理解簇链:
假设一个文件被存储在簇号 589 这三个簇中。
那么FAT表的内容看起来是这样的:

簇号FAT表项的值 (下一个簇)
58
89
90x0FFFFFFF (EOF)

操作系统要读取这个文件,会先从目录项中找到它的起始簇是 5,然后:

  1. 读簇 5 的内容。

  2. 查FAT[5],得到下一个簇是 8

  3. 读簇 8 的内容。

  4. 查FAT[8],得到下一个簇是 9

  5. 读簇 9 的内容。

  6. 查FAT[9],发现是EOF,文件读取结束。


3. 数据和目录区(Data Region)

  • 位置:在FAT表(通常有两份)之后。

  • 作用:存放文件和目录的实际数据。

  • 目录(Directory)的实现

    • 目录在FAT32中本质上是一种特殊文件,它的内容不是文本或图片,而是一个由 32字节 的“目录项(Directory Entry)”组成的表格。

    • 每个文件或子目录在它所属的目录文件中都对应一个或多个目录项。

    • 目录项的结构(32字节):

      • 文件名(8字节)+ 扩展名(3字节):经典的“8.3”格式。例如README.TXT

      • 属性(1字节):标识这是普通文件、目录、隐藏文件还是系统文件等。

      • 创建/修改/访问时间戳

      • 起始簇号高16位/低16位(2+2字节)这是最关键的信息! 它指明了这个文件/目录的第一个簇的簇号。操作系统通过这个簇号,去FAT表中查找,就能得到整个文件的簇链。

      • 文件大小(4字节):这限制了FAT32单个文件最大不能超过4GB(2^32字节)。

  • 长文件名(Long File Name, LFN)的实现

    • 为了兼容DOS,“8.3”格式的目录项是必须存在的。

    • 长文件名(如This is a long file name.txt)是通过在主目录项之前放置多个额外的、属性标记为“长名”的目录项来实现的。

    • 每个长文件名目录项可以存储13个字符(UTF-16编码)。长文件名被倒序存放在这些额外的目录项中。

    • 操作系统读取时,会先看到一系列长文件名项,组装出长名,最后看到传统的“8.3”短名项,其中包含了起始簇号等关键信息。

1. DBR (DOS Boot Record) 详解

DBR位于第一个扇区(512字节),其结构如下表所示。所有值均为小端字节序 (Little-Endian)

偏移量 (字节)长度 (字节)名称示例值描述与计算公式
0x003跳转指令 (Jmp Boot)0xEB5890跳过引导代码的指令。
0x038OEM ID"MSWIN4.1"格式化此卷的操作系统标识。
0x0B2每扇区字节数 (Bytes Per Sector)0x0200512。几乎所有硬盘和SD卡都是512。
0x0D1每簇扇区数 (Sectors Per Cluster)0x2032。这意味着簇大小 = 32 * 512 = 16 KiB
0x0E2保留扇区数 (Reserved Sectors)0x002032。从分区开始到FAT表之前的扇区总数。
0x101FAT表份数 (Number of FATs)0x022。通常为2(FAT1和FAT2)。
0x112根目录项数 (Root Entries)0x0000FAT32中必须为0。根目录现在是簇链的一部分。
0x132总扇区数 (16位)0x0000如果为0,则使用在0x20处的32位值。
0x151介质描述符 (Media Descriptor)0xF80xF8表示固定磁盘(硬盘、SD卡)。
0x162每个FAT的大小 (16位) (Sectors Per FAT16)0x0000FAT32中必须为0。使用在0x24处的32位值。
...............
0x204总扇区数 (32位) (Total Sectors 32)0x01D29600**** 分区的总扇区数 
0x244每个FAT的大小 (32位) (Sectors Per FAT32)0x00001CE3**** 每个FAT表占用的扇区数 
...............
0x2C4根目录起始簇号 (Root Cluster)0x00000002根目录的簇号! 通常是2。这是FAT32与FAT16的关键区别。
...............
0x1FE2结束标志 (Boot Signature)0xAA55标识这是一个有效的引导扇区。

关键计算(基于DBR中的参数):

  • FAT1的起始扇区 = 保留扇区数 = 32

  • FAT2的起始扇区 = 保留扇区数 + 每个FAT的大小 = 32 + 0x1CE3

  • 数据区的起始扇区 = 保留扇区数 + (FAT表份数 * 每个FAT的大小) = 32 + (2 * 0x1CE3)

  • 簇N对应的第一个扇区号 = 数据区起始扇区 + ((N - 2) * 每簇扇区数)

    • *为什么是N-2?因为数据区的簇号从2开始编号。*


2. 文件分配表 (FAT) 详解

FAT表是一个大数组,数组的每个索引号对应数据区的一个簇号。每个FAT项占4字节(32位)

FAT项索引 (簇号)FAT项值 (32位)描述
00xFFFFFFF8介质类型。通常与DBR中的介质描述符相同。
10xFFFFFFFF脏卷标志。如果文件系统未正常卸载,此值可能被设置。
20x0000000F根目录的簇链。值0x0F表示根目录的下一个簇是15。
.........
100x00000011一个文件的一部分。表示簇10的下一个簇是簇17。
110x00000012表示簇11的下一个簇是簇18。
120x00000013表示簇12的下一个簇是簇19。
130x0FFFFFFF簇13是文件结束 (EOF)。这是文件占用的最后一个簇。
140x00000000簇14是空闲的 (Free),可以被分配。
150x0FFFFFF7簇15是坏簇 (Bad),不会被使用。
.........

簇链示例: 如果一个文件的簇链是 10 -> 11 -> 12 -> 13 (EOF),那么它在FAT表中的记录就如上表所示。


3. 目录项 (Directory Entry) 详解

无论是根目录还是子目录,其内容都是由32字节的目录项组成的数组。

标准 8.3 格式目录项 (32字节)
偏移长度字段名示例值 (Hex)说明
0x08文件名 (Name)```'F', 'I', 'L', 'E', ' ', ' ', ' ', ' '````主文件名,不足用空格(0x20)填充。首字节为0xE5表示已删除,0x00表示未使用。
0x83扩展名 (Ext)'T', 'X', 'T'文件扩展名,不足用空格填充。
0xB1属性 (Attr)0x20位掩码: 0x01(只读), 0x02(隐藏), 0x04(系统), 0x08(卷标), 0x10(目录)0x20(存档)
0xC1保留0x18
0xD1创建时间(10ms)0x7B以10毫秒为单位的创建时间(0-199)。
0xE2创建时间0x6C66格式: HH(5)MM(6)SS/2(5) -> HH:MM:SS
0x102创建日期0x4A4D格式: (Y-1980)(7)M(4)D(5) -> YYYY-MM-DD
0x122最后访问日期0x4A4D格式同创建日期。
0x142起始簇号高16位0x0000文件/目录起始簇号的高位字。
0x162最后修改时间0x6C66格式同创建时间。
0x182最后修改日期0x4A4D格式同创建日期。
0x1A2起始簇号低16位0x000A文件/目录起始簇号的低位字。
0x1C4文件大小0x00004000文件的实际字节数(字节)。对于目录,此项为0。
  • 重要: 起始簇号 = (起始簇号高16位 << 16) + 起始簇号低16位 = 0x0000000A (簇10)。

长文件名 (LFN) 目录项 (32字节)

当文件名不符合“8.3”格式时(如Long File Name.txt),系统会创建附加目录项放在标准项之前。

偏移长度字段名示例值 (Hex)说明
0x01序列号 (Ordinal)0x43第4项,且是最后一项(0x40)。
0x110名字部分1 (Name1)0x004C...UTF-16编码的Unicode字符(5个)。
0xB1属性 (Attr)0x0F总是0x0F,表示这是一个LFN项。
0xC1类型0x00总是0。
0xD1校验和0x4A根据短文件名计算出的校验和。
0xE12名字部分2 (Name2)0x0066...UTF-16编码的Unicode字符(6个)。
0x1A2总是零0x0000
0x1C4名字部分3 (Name3)0x0065...UTF-16编码的Unicode字符(2个)。

LFN工作方式: LFN项是倒序存放的。操作系统会收集所有序列号最高位为0的LFN项,按顺序拼接出完整的长文件名,最后找到对应的短名标准项来获取起始簇号和大小。


4. 数据区:簇的内容

情况A:簇存储文件数据 (例如簇10, 11, 12, 13)
簇10的偏移数据 (Hex)说明 (对应文件内容)
0x000048 65 6C 6C 6F 2C 20 57 6F 72 6C 64 21 0D 0A"Hello, World!\r\n" (文本开头)
...... (更多数据) ...文件的后续内容
0x1FFF...簇10的最后一个字节
  • 注意: 文件结束由目录项中的文件大小决定,而不是簇内的特殊标记。簇13可能只有部分数据有效,后面是垃圾数据。

情况B:簇存储目录内容 (例如簇2,根目录)
目录项偏移数据内容 (简化表示)类型解析
0x0000.           ...  clust=2...自引用项当前目录是簇2。
0x0020..          ...  clust=0...父目录项父目录是簇0(根目录的父目录是它自己)。
0x0040LONGFI~1TXT ...  clust=10 size=16384标准短名项文件LONGFI~1.TXT,起始簇10,大小16KB。
0x0060[LFN Entry for Part 2]LFN项长文件名的一部分。
0x0080[LFN Entry for Part 1]LFN项长文件名Long File Name.txt的第一部分。
0x00A0SUBDIR      ...  clust=15 size=0标准目录项一个名为SUBDIR的子目录,起始簇15。
...... (剩余可能是空白或更多项) ...

二.如何判断一个簇里面存取的是数据还是目录

你无法单独通过查看一个簇本身或它在FAT表中的条目来确认它是“项目簇”(目录簇)还是“数据簇”(文件数据簇)。

它们的物理存储格式完全相同,都是512字节 * 每簇扇区数的数据块。区分一个簇类型的唯一方法,是查看指向它的那个“目录项(Directory Entry)”中的“属性(Attribute)”字段。

一个目录里面包含的所有东西(无论是子目录还是文件),都统一用一种方式记录:即32字节的“目录项(Directory Entry)”。

这些目录项就像是一个表格里的一行行记录,紧密地排列在这个目录所占据的数据簇里。区分一条记录是“子目录”还是“文件”,完全取决于该目录项中那个至关重要的“属性(Attribute)”字节。

FAT表的表项和簇中存储的数据在物理上是完全分开的,它们不是紧挨着存放的。

你可以把FAT表想象成一本目录,而把数据区想象成图书馆的藏书库。目录(FAT表)里只写着“这本书的下本书的编号是XXX”,它本身并不包含书的内容。

所以,一个簇的“后面”并不是直接跟着FAT表的32位项。正确的逻辑关系是:

  1. FAT表:存储在磁盘上一个固定的、集中的区域(在保留扇区之后)。它只是一个巨大的“簇号-下一簇号”的映射表。

  2. :存储在磁盘上另一个巨大的区域,叫做数据区(在FAT表之后)。这里存放的才是文件和目录的实际内容

当一个簇被分配用于存储时,它后面跟着的数据取决于这个簇的用途


情况一:簇用于存储文件数据

这是最常见的情况。簇的内容就是文件本身的原始二进制数据,没有任何额外的头或尾。

  • 示例:假设一个文本文件hello.txt的内容是:"Hello, World!"(13字节),并且它被存储在簇号200中,簇大小为4KB(4096字节)。

  • 那么簇200的内容将是

    偏移 (在簇内)数据 (十六进制)数据 (ASCII/说明)
    0x000048 65 6C 6C 6F 2C 20 57 6F 72 6C 64 21H e l l o , W o r l d !
    0x000D00 00 00 ... (直到4096字节)未使用的空间,内容是随机的、无效的旧数据
  • 关键点

    • 文件系统不会在数据簇的开头或结尾添加文件结束符(EOF)或文件名等信息。

    • 文件的有效长度不是由簇内的任何标记决定的,而是由该文件目录项中那个4字节的“文件大小”字段精确定义的。系统读取时,读够这个字节数就会停止,不管簇里还剩多少空间。


情况二:簇用于存储目录(文件夹)

当一个簇被分配给一个目录时,它的内容就有了一个非常严格的结构。它不再是无结构的二进制流,而是一个由32字节的“目录项”(Directory Entry) 组成的数组。

  • 示例:假设簇号300被分配给了MyFolder这个目录。

  • 那么簇300的内容将是

    偏移 (在簇内)目录项内容 (简化表示)类型与说明
    0x0000.  (Attr=目录, 起始簇=300)特殊项: 指向当前目录自身。
    0x0020.. (Attr=目录, 起始簇=100)特殊项: 指向父目录(这里是簇100)。这是实现嵌套的关键。
    0x0040FILE1  TXT (Attr=存档, 起始簇=401, 大小=1500)标准项: 描述一个名为FILE1.TXT的文件。
    0x0060SUBDIR       (Attr=目录, 起始簇=500, 大小=0)标准项: 描述一个名为SUBDIR的子目录。
    0x0080LFN Entry (Attr=长文件名)长名项: A Long File Name.txt的一部分。
    0x00A0ALONG~1TXT (Attr=存档, 起始簇=402, 大小=2000)标准项: 对应上面长名的短名项。
    ......更多文件或子目录项...
    0x1FE000 00 00 ...未使用的目录项(首字节为0x00)。

fat32格式查找数据流程图

下面是读取写入tf卡通用的开源模块

https://elm-chan.org/fsw/ff/

http://www.dtcms.com/a/391569.html

相关文章:

  • led的带宽在模拟太阳光中设备中的影响
  • go资深之路笔记(三) sync.WaitGroup, sync.errgroup和 sync.go-multierror
  • Docker 与数据库环境
  • Node.js 模块系统详解
  • proxy代理应用记录
  • 基于python大数据的汽车数据分析系统设计与实现
  • WebSocket实现原理
  • 从保存到加载Docker镜像文件操作全图解
  • IDEA文件修改后改变文件名和文件夹颜色
  • 【MySQL 】MySQL 入门之旅 · 第十篇:子查询与嵌套查询
  • TM52F1376 SSOP24电子元器件 HITENX海速芯 8位微控制器MCU 芯片 深度解析
  • 基于Matlab图像处理的工件表面缺陷检测系统
  • 业务上云实践MYSQL架构改造
  • 深入解析TCP/IP协议分层与通信原理
  • 【人工智能通识专栏】第二十讲:科创项目选题
  • 数据治理系列(三):SQL2API 平台格局与发展趋势
  • 软考-系统架构设计师 软件项目管理详细讲解
  • three.js添加CSS2DRenderer对象
  • 磁共振成像原理(理论)9:射频回波 (RF Echoes)-三脉冲回波(2)
  • 栈的主要知识
  • question:使用同一请求数据且渲染顺序不确定时复用
  • Redis群集三种模式介绍和创建
  • 【LeetCode 每日一题】1935. 可以输入的最大单词数
  • eeprom和flash的区别
  • [vibe code追踪] 分支图可视化 | SVG画布 | D3.js
  • [硬件电路-264]:数字电路的电源系统的主要特性包括哪些
  • 算法题(212):01背包(空间优化)
  • TP4054和TP4056对比
  • AD5165(超低功耗逻辑电平数字电位器)芯片的详细用法
  • 38、多模态模型基础实现:视觉与语言的智能融合