查看“FAT”的源代码
←
FAT
跳到导航
跳到搜索
因为以下原因,您没有权限编辑本页:
您请求的操作仅限属于该用户组的用户执行:
用户
您可以查看和复制此页面的源代码。
{{Filesystems}} DOS V1.0(可能是CP/M)引入了'''File Allocation Table''' ('''FAT''')文件系统。 FAT应该是由比尔·盖茨编写的,是一个非常简单的文件系统-- 只不过是一个巨大表格中的一个单链接的簇列表。 FAT文件系统使用的内存非常少(除非操作系统将整个分配表缓存在内存中),它即使不是现在使用的最基本的文件系统,也肯定是其中之一。 ==概述== FAT文件系统有几种不同的版本。 每个版本都是为不同大小的存储介质设计的。 === FAT 12 === FAT 12是为软盘设计的,可以管理最大16兆字节的大小,因为它使用12位来寻址磁盘簇。 === FAT 16 === FAT 16是为早期硬盘设计的,最多可以处理64K个clusters*clusters大小。 硬盘越大,簇的大小就越大,这会导致磁盘上有大量的“空闲空间”。 === FAT 32 === FAT 32是由Windows 95-B和Windows 98推的。 FAT32解决了FAT的一些问题。 不再有64K最大簇限制! 虽然FAT32每个FAT条目使用32位,但实际上只有底部的28位用于寻址磁盘上的簇(顶部的4位保留)。 每个FAT条目有28位,文件系统在一个分区中最多可以处理大约2.7亿个簇。 这使得非常大的硬盘仍然可以保持较小的簇大小,从而减少文件之间的空闲空间。 === ExFAT === {{Main|ExFAT}} ExFAT是微软创建的SDXC卡上使用的文件系统。 它仍是FAT32,但是每个FAT条目实际有32位,还能够指示在磁盘上完全连续的文件(允许你跳过读取FAT), 还有一些更先进的功能和完全重新设计的文件条目系统。 由于它与FAT32非常相似,所以请将exFAT文章中的任何信息合并到这篇文章中。 微软已经在 https://docs.microsoft.com/en-us/windows/win32/fileio/exfat-specification 公开官方定义文件 === VFAT === VFAT是FAT文件系统的扩展,可以使用长文件名(最多255个字符)。 首先由Windows 95引入,它使用了一个“kludge”,长文件名用“卷标”属性标记,文件名随后存储在顺序目录项中的11字节块中。 (这有点过于简单,但已经足够使用了)。 ==实现细节== FAT文件系统将存储介质视为簇的平面阵列。 如果在非常旧的硬盘和软盘上,物理介质没有将其数据作为扇区的平面列表进行寻址,则需要在将簇号发送到磁盘之前对其进行翻译。 存储介质分为三个基本区域。 * 引导记录 * 文件分配表(FAT) * 目录和数据区 ===引导记录(Boot Record)=== 引导记录占用一个扇区,并且总是放在“分区”的逻辑编号0扇区中。 如果介质未划分为分区,则这是介质的开头。 这是计算机加载时在分区上最容易找到的扇区。 如果存储介质已分区(如硬盘),则实际介质的开头包含[[MBR (x86)]]或其他形式的分区信息。 在这种情况下,每个分区的第一个扇区都有一个[[Volume Boot Record|卷引导记录]]。 ==== BPB (BIOS Parameter Block) ==== 引导记录包含混合在一起的代码和数据。 非代码的数据称为BPB。 {| {{wikitable}} |- ! 偏移量(十进制) ! 偏移量 (十六进制) ! 大小(字节) ! Meaning |- | 0 | 0x00 | 3 | 前三个字节EB 3C 90反汇编为JMP SHORT 3C NOP。(3C值可能不同。)这样做的原因是为了跳过磁盘格式信息(BPB和EBPB)。 由于磁盘的第一个扇区加载到位置0x0000:0x7c00处的ram中并执行,如果没有此跳转,处理器将尝试执行非代码的数据。 即使对于不可引导的卷,Windows和OS X也需要提供与此模式匹配的代码(或使用E9跳转操作码)。 为了满足这个要求,这里可以放置一个无限循环,其中包含字节EB FE 90。 |- | 3 | 0x03 | 8 | OEM标识符。 前8个字节(3-10)是正在使用的DOS版本。 接下来的八个字节29 3A 63 7E 2D 49 48和43读取版本名称。 微软的官方FAT规范表示,这个字段实际上毫无意义,MS FAT驱动程序忽略了它,但它确实建议使用“MSWIN4.1”值,因为一些第三方驱动程序可能会检查它,并期望它具有该值。 旧版本的dos也会报告MSDOS5.1、linux格式的软盘可能会在这里带有“MKDOSF”,而FreeDOS格式的磁盘在这里被观察到带有“FRDOS5.1”。 如果字符串小于8字节,则用空格填充。 |- | 11 | 0x0B | 2 | 每个扇区的字节数(记住,所有数字都是小端格式)。 |- | 13 | 0x0D | 1 | 每个簇的扇区数。 |- | 14 | 0x0E | 2 | 保留扇区的数量。引导记录扇区包含在此值中。 |- | 16 | 0x10 | 1 | 存储介质上文件分配表(FAT)的数量。该值通常为2。 |- | 17 | 0x11 | 2 | 根目录条目数(必须设置为根目录占用整个扇区)。 |- | 19 | 0x13 | 2 | 逻辑卷中的总扇区数。 如果该值为0,则表示卷中有超过65535个扇区,实际计数存储在0x20的大扇区计数条目中。 |- | 21 | 0x15 | 1 | 这个字节表示[https://en.wikipedia.org/wiki/Design_of_the_FAT_file_system#BPB20_OFS_0Ah 媒体描述符类型(media descriptor type)]。 |- | 22 | 0x16 | 2 | 每个FAT的扇区数。仅限FAT12/FAT16。 |- | 24 | 0x18 | 2 | 每个磁道的扇区数。 |- | 26 | 0x1A | 2 | 存储介质上的磁头或盘面数。 |- | 28 | 0x1C | 4 | 隐藏扇区的数量。(即分区开头的LBA。) |- | 32 | 0x20 | 4 | 大扇区计数。 如果卷中的扇区数超过65535个,导致0x13处的“扇区数”条目中的值不合适,则设置此字段。 |} 注: 最初格式化介质的程序不一定正确地知道媒体的 “几何形状” (每个轨道的扇区,磁头以及扇区中的字节数)。 此外,如果介质(从格式化介质的计算机)移动到另一台具有不同BIOS的计算机-- 则新BIOS可以为相同介质指定不同的几何结构。 因此,相信 “SPT” 或 “heads” 数字通常是一个非常糟糕的主意。 如果可能的话,从BIOS获取它们。 注2: BPB中的许多值没有正确 “对齐”。 也就是说,Word字大小的值不存储在字(“偶数”地址)边界上。 在某些体系结构上,访问未对齐的Word可能会导致代码崩溃。 通过制作BPB的副本 (在内存中的其他位置并向上移动一个字节) 可以解决问题。 ==== 扩展引导记录(Extended Boot Record) ==== 扩展引导记录信息紧跟在BPB之后。 开始时的数据称为EBPB。 它包含不同的信息,这取决于该分区是FAT 12、FAT 16还是FAT 32文件系统。 紧跟在EBPB之后的是实际引导代码,然后是标准0xAA55引导签名,以填充512字节的引导扇区。 偏移显示是从标准引导记录的开始。 ===== FAT 12 和 FAT 16 ===== {| {{wikitable}} |- ! 偏移量(小数) !偏移量 (十六进制) ! 长度(字节) ! 含义 |- | 36 | 0x024 | 1 | 驱动器号。 这里的值应该与BIOS中断0x13返回的值相同,或者在DL寄存器中传递; 例如软盘为0x00,硬盘为0x80。 此编号没有用,因为介质可能会移动到另一台计算机,然后插入具有不同驱动器号的驱动器中。 |- | 37 | 0x025 | 1 | Windows NT中的标志。另有保留。 |- | 38 | 0x026 | 1 |签名(必须为0x28或0x29)。 |- | 39 | 0x027 | 4 | VolumeID “串(Serial)” 号。用于跟踪计算机之间的卷。如果你愿意,你可以忽略这一点。 |- | 43 | 0x02B | 11 |卷标字符串。这片地上填满了空地。 |- | 54 | 0x036 | 8 |系统标识符串。 此字段是FAT文件系统类型的字符串表示形式。 它用空格填充。 规范说,永远不要相信这个字符串的内容有任何用途。 |- | 62 | 0x03E | 448 |Boot code. |- | 510 | 0x1FE | 2 | 可引导分区签名0xAA55。 |} ===== FAT 32 ===== {| {{wikitable}} |- ! 偏移量(十进制) ! 偏移量(十六进制) ! 长度 (以字节为单位) ! 含义 |- | 36 | 0x024 | 4 | 每个FAT的扇区数。以扇区为单位的FAT大小。 |- | 40 | 0x028 | 2 | Flags. |- | 42 | 0x02A | 2 | FAT版本号。高字节是主要版本,低字节是次要版本。FAT驱动应该尊重这个字段。 |- | 44 | 0x02C | 4 |根目录的簇号。此字段通常设置为2。 |- | 48 | 0x030 | 2 |FSInfo结构的扇区号。 |- | 50 | 0x032 | 2 | 备份引导扇区的扇区号。 |- | 52 | 0x034 | 12 |保留。格式化卷时,这些字节应为零。 |- | 64 | 0x040 | 1 |驱动器号。此处的值与BIOS中断0x13返回的值相同。软盘为0x00,硬盘为0x80。 |- | 65 | 0x041 | 1 | Windows NT中的标志。否则保留。 |- | 66 | 0x042 | 1 |签名(必须是0x28或0x29)。 |- | 67 | 0x043 | 4 |卷ID‘序列号’。用于跟踪计算机之间的卷。如果你愿意,你可以忽略这一点。 |- | 71 | 0x047 | 11 | 卷标签字符串。这个字段用空格填充。 |- | 82 | 0x052 | 8 |系统标识符字符串。始终是“FAT32”。规范中说永远不要相信这个字符串的内容有任何用途。 |- | 90 | 0x05A | 420 |Boot code. |- | 510 | 0x1FE | 2 |可引导分区签名0xAA55。 |} === FSInfo Structure (仅FAT32) === {| {{wikitable}} |- !偏移量 (十进制) ! 偏移量(十六进制) ! 长度(字节) ! 含义 |- | 0 | 0x0 | 4 | Lead签名(必须为0x4161525,以指示有效的FSInfo结构) |- | 4 | 0x4 | 480 |保留,不应使用这些字节 |- | 484 | 0x1E4 | 4 | 另一个签名 (必须为0x61417272) |- | 488 | 0x1E8 | 4 |包含卷上最后一次已知的可用簇计数。 如果值为0xFFFFFFFF,则空闲计数未知,必须计算。 但是,此值可能不正确,至少应进行范围检查 (<= 卷群集计数) |- | 492 | 0x1EC | 4 |指示文件系统驱动程序开始查找可用簇的簇号。 如果值为0xFFFFFFFF,则没有提示,驱动程序应从2开始搜索。 通常,此值设置为最后分配的簇编号。 与上一个字段一样,应检查该值的范围。 |- | 496 | 0x1F0 | 12 | 保留 |- | 508 | 0x1FC | 4 |Trail签名(0xAA550000) |} ==== exFat boot record ==== 对于exFAT,整个引导记录是从头开始重新创建的,而不是进一步扩展现有的FAT12/16/32引导记录。 你可以通过注意到在FAT12/16/32引导记录中,“bytes per sector” 为零来识别exFAT。 {| {{wikitable}} |- ! 偏移量(十进制) ! 偏移量(祸不单行) ! 大小 (以字节为单位) ! 含义 |- | 0 | 0x00 | 3 | 前三个字节EB3C90反汇编成JMP短小的3CNOP。 (3C值可能有所不同。)这样做的原因是跳过磁盘格式信息 (BPB和EBPB)。 由于磁盘的第一个扇区被加载到位置0x0000:0x7c00的ram中并执行,如果没有这个跳转,处理器将尝试执行非代码的数据。 即使对于不可引导的卷,Windows和OSX也要求存在与此模式匹配的代码(或使用E9 JUMP操作码)。 为了满足此要求,可以在此处放置一个具有字节EB FE 90的无限循环。 |- | 3 | 0x03 | 8 | OEM标识符。它包含字符串“EXFAT”。不用于确定文件系统,但这是一个很好的提示。 |- | 11 | 0x0B | 53 | 设置为零。这样可以确保任何FAT驱动程序都无法加载它。 |- | 64 | 0x40 | 8 | 分区偏移量。不知道为什么分区本身会有这个,但它在这里。可能是错的。也许最好还是忽略它。 |- | 72 | 0x48 | 8 | 卷长度。 |- | 80 | 0x50 | 4 | 从分区开始的FAT偏移 (在扇区中)。 |- | 84 | 0x54 | 4 | FAT长度(以扇区为单位)。 |- | 88 | 0x58 | 4 | 簇堆偏移量(以扇区为单位)。 |- | 92 | 0x5C | 4 | 簇计数 |- | 96 | 0x60 | 4 | 根目录簇。通常为4(但只需读取此值)。 |- | 100 | 0x64 | 4 | 分区的序列号。 |- | 104 | 0x68 | 2 | 文件系统修订 |- | 106 | 0x6A | 2 | Flags |- | 108 | 0x6C | 1 | 扇区移位(Sector shift) |- | 109 | 0x6D | 1 | 簇移位(Cluster shift) |- | 110 | 0x6E | 1 | FAT的数字 |- | 111 | 0x6F | 1 | 驱动器选择 |- | 112 | 0x70 | 1 | 使用百分比 |- | 113 | 0x71 | 7 | 保留(设置为0)。 |} 要读取文件系统,请找出“扇区”和“簇”有多大。 一个扇区是 (1 << sectorshift) 字节,一个簇是 (1 << (sectorshift + clustershift)) 字节。 然后,找到FAT的开始和簇堆的开始(请注意,第一个簇是 *仍然* 簇2)。 <source lang="C"> // This allows you to zero-index clusters: uint64_t clusterArray = clusterheapoffset * sectorsize - 2 * clustersize; uint64_t fatOffset = fatoffset * sectorsize; uint64_t usablespace = clustercount * clustersize; </source> 请注意,BPB中的所有值现在都是自然对齐的,并且此代码比FAT32的BPB读取简单得多。 === File Allocation Table === 文件分配表 (FAT) 是存储在存储介质上的表,它指示磁盘上所有数据群集的状态和位置。 它可以被视为磁盘的“目录”。 簇可能可供使用,可能由操作系统保留,可能由于磁盘上的坏扇区而不可用,或者可能正被文件使用。 文件的簇不必在磁盘上彼此相邻。 事实上,它们很可能分散在整个磁盘中。 FAT允许操作系统追查文件中簇“链”。 ==== FAT 12 ==== FAT 12使用12位对磁盘上的簇进行寻址。 FAT中的每个12位条目都指向磁盘上的下一个文件簇。 给定有效的簇号,下面是如何提取簇链中下一个群集值的方法: <source lang="C"> unsigned char FAT_table[sector_size]; unsigned int fat_offset = active_cluster + (active_cluster / 2);// 乘以1.5 unsigned int fat_sector = first_fat_sector + (fat_offset / section_size); unsigned int ent_offset = fat_offset % section_size; //at this point you need to read from sector "fat_sector" on the disk into "FAT_table". unsigned short table_value = *(unsigned short*)&FAT_table[ent_offset]; if(active_cluster & 0x0001) table_value = table_value >> 4; else table_value = table_value & 0x0FFF; //变量“table_value”现在包含了链中下一个簇所需的信息。 </source> 如果“table_value”大于或等于(>=)0xFF8,则链中没有更多的簇。 这意味着整个文件已被读取。 如果“table_value”等于(=)0xFF7,则该簇已被标记为“坏”。“坏”簇容易出错,应该避免。 如果“table_value”不是上述情况之一,则它是文件中下一个簇的簇号。 索引0和1下的条目是保留的。 第0个条目是保留的,因为索引0被用作表示给定簇空闲的其他条目的值。 第零个条目必须从低8位开始保持bpb_media字段的值,并且其余位必须被设置为零。 例如,如果BPB_Media为0xF8,则第零项应保存值0xFF8。 第一个条目保留供将来使用,并且必须保持值0xFFF。 由于FAT12使用的条目大小不能均匀地被8位整除,因此弄清楚如何解释FAT可能会有些混乱。 考虑两个连续的条目,值0x123和0x46. 第一个条目的第一个字节是底部的两个半字节(0x23),最高的半字节进入第二个字节的底部半字节(0x?1)。 由于下一个条目现在以mid-byte开始,因此只有最低的字节可以放入该字节 (0x6?),并且两个最高的字节进入下一个字节 (0x45)。 因此,这两个条目背靠背如下:0x23 0x61 0x45。 这个位置可能会让人困惑,但是如果我们考虑一台小端机器,它将开始变得更有意义。 如果在偏移量零处加载WORD值,则结果值将为0x6123。 现在半字节的顺序正确了,所以要获得两个条目中第一个条目的值,只需使用0xfff。 对于第二个条目,你必须首先在偏移量1处加载Word,从而产生值0x4561,然后将其向下移位4位(有效地移除底部半字节)。 ==== FAT 16 ==== FAT 16使用16位对磁盘上的簇进行寻址。因此,从16位文件分配表中提取值要容易得多。以下是如何完成的: <source lang="C"> unsigned char FAT_table[sector_size]; unsigned int fat_offset = active_cluster * 2; unsigned int fat_sector = first_fat_sector + (fat_offset / sector_size); unsigned int ent_offset = fat_offset % sector_size; //此时,你需要将磁盘上的扇区“fat_sector”读取到“fat_table”中。 unsigned short table_value = *(unsigned short*)&FAT_table[ent_offset]; //变量“TABLE_VALUE”现在有了链中下一个簇所需的信息。 </source> 如果 “table_value” 大于或等于 (>=) 0xFFF8,则链中不再有簇。 这意味着整个文件已被读取。 如果“table_value”等于(==)0xFFF7,则此簇已被标记为“坏”。“坏”簇容易出错,应该避免。 如果 “table_value” 不是上述情况之一,则它是文件中下一个簇的簇编号。 索引0和1下的条目是保留的。 第0个条目被保留,因为索引0被用作表示给定簇是空闲的其他条目的值。 第零项必须将BPB_Media字段的值保持在低8位中,其余的位必须设置为零。 例如,如果BPB_Media为0xF8,则第0个条目应包含值0xFFF8。 第一个条目保留供将来使用,必须保持值0xFFFF。 ==== FAT 32 and exFAT ==== FAT 32使用28位来寻址磁盘上的簇。 保留最高的4位。 这意味着它们在读取时应被忽略,而在写入时应保持不变。exFAT使用完整的32位对扇区号进行编码。 类似于对16位FAT的相同操作: <source lang="C"> unsigned char FAT_table[sector_size]; unsigned int fat_offset = active_cluster * 4; unsigned int fat_sector = first_fat_sector + (fat_offset / sector_size); unsigned int ent_offset = fat_offset % sector_size; //此时,你需要将磁盘上的扇区“fat_sector”读取到“fat_table”中。 //记住忽略高位4位。 unsigned int table_value = *(unsigned int*)&FAT_table[ent_offset]; if (fat32) table_value &= 0x0FFFFFFF; // 变量 “table_value” 现在具有你需要的有关链中下一个群集的信息。 </source> 如果“table_value”大于或等于(>=)0x0FFFFFF8(或0xFFFFFFF8表示exFAT),则链中不再有簇。 这意味着已经读取了整个文件。 如果 “table_value” 等于 (= =) 0x0FFFFFF7 (或exFAT为0xFFFFFFF7),则此群集已标记为 “坏”。 “坏”簇容易出错,应该避免。 如果“table_value”不是上述情况之一,则它是文件中下一个簇的簇号。 索引0和1下的条目是保留的。 第0个条目是保留的,因为索引0被用作表示给定簇空闲的其他条目的值。 第零个条目必须从低8位开始保持bpb_media字段的值,并且其余位必须被设置为零。 例如,如果BPB_Media为0xF8,则第零项应保存值0xfffff8。 第一个条目是为将来保留的,必须保存0xFFFFFF值。 请注意,在exFAT上,一些文件不会写出到FAT中。 如果文件是完全连续的,则exFAT允许操作系统对该信息进行编码,而不更新该文件的FAT。 因此,与FAT32不同,FAT表不用于簇的分配状态;而是有一个分配位图来处理这个问题。 有关这一点,请参阅下面的目录条目。 === FAT12/16/32上的目录 === 目录条目只存储了知道文件数据或文件夹子项在磁盘上存储位置所需的信息。 它还保存条目的名称、大小和创建时间等信息。 FAT文件系统中有两种类型的目录。 标准的8.3目录项(出现在所有FAT文件系统上)和长文件名目录项(可选地出现以允许更长的文件名)。 ====标准8.3格式==== {| {{Wikitable}} |- ! 偏移量 (以字节为单位) ! 长度(字节) ! 含义 |- | 0 | 11 | 8.3文件名。前8个字符是名称,后3个是扩展名。 |- | 11 | 1 | 文件的属性。 可能的属性包括: <pre>READ_ONLY=0x01 HIDDEN=0x02 SYSTEM=0x04 VOLUME_ID=0x08 DIRECTORY=0x10 ARCHIVE=0x20 LFN=READ_ONLY|HIDDEN|SYSTEM|VOLUME_ID </pre> (LFN表示此条目是 [[#Long_File_Names|长文件名条目]]) |- | 12 | 1 |保留供Windows NT使用。 |- | 13 | 1 |创建时间,单位为十分之一秒。 范围0-199 (含)。 基于简单的测试,Ubuntu16.10个存储0或100,而Windows 7在此字段中存储0-199。 |- | 14 | 2 |文件创建时间。将秒数乘以2。 {| {{Wikitable}} |- | 小时 | 5 bits |- | 分钟 | 6 bits |- | 秒 | 5 bits |} |- | 16 | 2 | 创建文件的日期。 {| {{Wikitable}} |- |年 | 7 bits |- | 月 | 4 bits |- | 日 | 5 bits |} |- | 18 | 2 |上次访问日期。格式与创建日期相同。 |- | 20 | 2 |该条目第一个簇号的高16位。对于FAT12和FAT16,该值始终为零。 |- | 22 | 2 | 上次修改时间。与创建时间格式相同。 |- | 24 | 2 |最后修改日期。格式与创建日期相同。 |- | 26 | 2 |该条目第一个簇号的低16位。使用此编号查找此条目的第一个群集。 |- | 28 | 4 | 以字节为单位的文件大小。 |} ==== 长文件名 ==== 长文件名条目“始终”具有它们所属的常规8.3条目。 长文件名条目始终放在其8.3条目之前。 这里是长文件名条目的格式。 {| {{Wikitable}} |- ! 偏移量(字节) ! 长度 (以字节为单位) ! 含义 |- | 0 | 1 |此条目在长文件名条目序列中的顺序。 该值可帮助你知道应将此条目中的字符放置在文件名中的什么位置。 |- | 1 | 10 | 本条目首5、2字节字符。 |- | 11 | 1 |属性。始终等于0x0F。(长文件名属性) |- | 12 | 1 |长条目类型。名称条目为零。 |- | 13 | 1 | 创建文件时短文件名的校验和。 如果分区安装在不支持长文件名的系统上,则短文件名可以在不更改长文件名的情况下更改。 |- | 14 | 12 |该条目接下来的6、2字节字符。 |- | 26 | 2 | 永远为零。 |- | 28 | 4 |此条目的最后2、2字节字符。 |} 下面是一个示例,展示了十六进制编辑器中前面带有一个长文件名条目的常规8.3条目可能是什么样子: <pre> 41 62 00 69 00 6E 00 00 00 FF FF 0F 00 7F FF FF FF FF FF FF FF FF FF FF FF FF 00 00 FF FF FF FF 42 49 4E 20 20 20 20 20 20 20 20 10 00 00 F7 01 D5 38 D5 38 00 00 F7 01 D5 38 03 00 00 00 00 00 </pre> 在文本编辑器中: <pre> Ab.i.n.......................... BIN ......8.8.....8...... </pre> 第一行是长文件名条目(第二行是常规的8.3条目)。 第一个字节(41)告诉我们两条重要的信息。 首先,出现 (01) 告诉我们这是常规8.3条目的第一个长文件名条目。 其次,四十(40)部分告诉我们,这也是常规8.3条目的“最后”长文件名条目。 接下来的10个字节拼写出长文件名的第一部分。在这种情况下,他们读到: <pre> b 00 i 00 n 00 00 00 FF FF </pre> 请注意,每个字符的长度为两个字节,并且名称为null终止。 末尾的两个FF是长文件名末尾的填充。 这也是长文件名条目中的其他FF。 关于长文件名条目,需要注意的最后一件重要的事情是它在偏移量11处的属性字节。 0x0F属性允许我们验证这确实是一个长文件名条目。 ===exFAT上的目录=== exFAT从头开始重新设计了这些目录条目。 {| {{Wikitable}} |- ! 偏移量(字节) ! 长度(字节) ! 含义 |- | 0 | 1 |条目类型 |- | 1 | 31 | 条目剩余部分。 |} 每个条目的基础是它们仍然是32个字节,并且它们都从第一个字节中的类型开始。 我遇到过与从磁盘读取文件相关的类型: ====文件条目==== {| {{Wikitable}} |- ! 偏移量 (以字节为单位) ! 长度(字节) ! 含义 |- | 0 | 1 | 条目类型 = 0x85 |- | 1 | 1 | 二级条目的计数。 |- | 2 | 2 | 条目集合校验和 |- | 4 | 2 | 文件属性 |- | 6 | 2 | 保留 |- | 8 | 4 创建日期和时间 |- | 12 | 4 | 修改日期和时间 |- | 16 | 4 |访问日期和时间 |- | 20 | 1 | 创建毫秒(0-199),将添加到FAT样式日期/时间中,以提高精确度。日期/时间格式见FAT12条目。 |- | 21 | 1 | 修改毫秒 (0-199)。 |- | 22 | 1 |创建时间的UTC偏移量 |- | 23 | 1 | 修改时间的UTC偏移量 |- | 24 | 1 | 访问时间的UTC偏移量 |- | 25 | 7 | 保留。 |} ==== 流“扩展”条目==== 它被称为扩展(extension),但100% 需要直接在 “文件” 条目之后存在。 {| {{Wikitable}} |- ! 偏移量(字节) ! 长度(字节) ! 含义 |- | 0 | 1 |条目类型=0xC0 |- | 1 | 1 |二级标志 |- | 2 | 1 | 保留 |- | 3 | 1 | 名字长度 |- | 4 | 2 |名称哈希 |- | 6 | 2 | 保留 |- | 8 | 8 | 有效数据长度。 写入大文件时,exFAT首先分配整个文件,然后在写入数据时以更新方式修改。 如果它没有有效数据长度告知用户,这种情况确实还不太好处理。 |- | 16 | 4 | 保留 |- | 20 | 4 | 第一个簇。 |- | 24 | 8 | 数据长度。 |} ====文件名条目==== {| {{Wikitable}} |- ! Offset (in bytes) ! Length (in bytes) ! Meaning |- | 0 | 1 | Entry type = 0xC1 |- | 1 | 1 | flags |- | 2 | 30 | File name characters (15 UTF16 code units). |} 要实际使用这些信息,它们通常按如下顺序出现: - 文件条目/File entry - 流扩展条目/Stream extension entry - 文件名条目/File name entry - (附加文件名条目/Additional file name entries) 文件条目包含文件元数据信息,流扩展名告诉你它是如何存储的,文件名条目告诉你它叫什么。 再也没有8.3的名字要求了。 读取文件时,流扩展辅助标志中的第二位指示它是否存储为范围,或者你是否需要使用FAT表。 如果设置了,则文件是连续的,FAT不是最新的;如果设置明确,则FAT是准确的,需要使用(但仍然可以说它是连续的)。 ==== 长文件名 ==== 长文件名条目 “总是” 具有它们所属的常规8.3条目。 长文件名条目总是放在其8.3条目之前。 以下是长文件名条目的格式。 {| {{Wikitable}} |- ! 偏移量 (以字节为单位) ! 长度(字节) ! 含义 |- | 0 | 1 | 本条目在长文件名条目的顺序。 此值帮助你知道此条目中的字符应放置在文件名的何处。 |- | 1 | 10 |该项的前5,2字节字符。 |- | 11 | 1 | 属性。总是等于0x0F。(长文件名属性) |- | 12 | 1 |长条目型。名称条目为零。 |- | 13 | 1 |文件创建时短文件名生成的校验和。 在分区安装在不支持长文件名的系统上的情况下,短文件名可以更改而无需更改长文件名。 |- | 14 | 12 |此条目的接下来6个2字节字符。 |- | 26 | 2 |始终为零。 |- | 28 | 4 | 本条目最后的2,2字节字符。 |} ==编程指南== 本节旨在为你提供有关在FAT文件系统上执行的常见功能的信息。 === 读取引导扇区 === 引导扇区始终位于逻辑扇区编号0处。 你可以将引导扇区读入数组并以这种方式访问其成员,也可以将其读入结构并通过结构进行访问。 无论哪种方式,引导扇区中的值都需要随时可用,以便在FAT文件系统中执行许多操作。 下面是C语言中一些引导扇区结构的示例。 <source lang="C"> typedef struct fat_extBS_32 { //extended fat32 stuff unsigned int table_size_32; unsigned short extended_flags; unsigned short fat_version; unsigned int root_cluster; unsigned short fat_info; unsigned short backup_BS_sector; unsigned char reserved_0[12]; unsigned char drive_number; unsigned char reserved_1; unsigned char boot_signature; unsigned int volume_id; unsigned char volume_label[11]; unsigned char fat_type_label[8]; }__attribute__((packed)) fat_extBS_32_t; typedef struct fat_extBS_16 { //extended fat12 and fat16 stuff unsigned char bios_drive_num; unsigned char reserved1; unsigned char boot_signature; unsigned int volume_id; unsigned char volume_label[11]; unsigned char fat_type_label[8]; }__attribute__((packed)) fat_extBS_16_t; typedef struct fat_BS { unsigned char bootjmp[3]; unsigned char oem_name[8]; unsigned short bytes_per_sector; unsigned char sectors_per_cluster; unsigned short reserved_sector_count; unsigned char table_count; unsigned short root_entry_count; unsigned short total_sectors_16; unsigned char media_type; unsigned short table_size_16; unsigned short sectors_per_track; unsigned short head_side_count; unsigned int hidden_sector_count; unsigned int total_sectors_32; //一旦驱动器真正知道这是什么类型的FAT,就会将其强制转换为特定类型。 unsigned char extended_section[54]; }__attribute__((packed)) fat_BS_t; </source> 可以从引导扇区中提取的重要信息包括: '''总扇区数量(包括VBR):''' <source lang="C"> total_sectors = (fat_boot->total_sectors_16 == 0)? fat_boot->total_sectors_32 : fat_boot->total_sectors_16; </source> '''扇区中的FAT大小:''' <source lang="C"> fat_size = (fat_boot->table_size_16 == 0)? fat_boot_ext_32->table_size_16 : fat_boot->table_size_16; </source> '''根目录的大小 (除非你有FAT32,在这种情况下,大小将为0):''' <source lang="C"> root_dir_sectors = ((fat_boot->root_entry_count * 32) + (fat_boot->bytes_per_sector - 1)) / fat_boot->bytes_per_sector; </source> 这一计算结果将得到汇总。32是FAT目录的大小(字节)。 '''第一个数据扇区(即可以存储目录和文件的第一个扇区):''' <source lang="C"> first_data_sector = fat_boot->reserved_sector_count + (fat_boot->table_count * fat_size) + root_dir_sectors; </source> '''The first sector in the File Allocation Table:''' <source lang="C"> first_fat_sector = fat_boot->reserved_sector_count; </source> '''数据扇区总数: ''' <source lang="C"> data_sectors = fat_boot->total_sectors - (fat_boot->reserved_sector_count + (fat_boot->table_count * fat_size) + root_dir_sectors); </source> '''簇总数:''' <source lang="C"> total_clusters = data_sectors / fat_boot->sectors_per_cluster; </source> 这进行了取整。 ''' 此文件系统的FAT类型: ''' <source lang="C"> if (sectorsize == 0) { fat_type = ExFAT; } else if(total_clusters < 4085) { fat_type = FAT12; } else if(total_clusters < 65525) { fat_type = FAT16; } else { fat_type = FAT32; } </source> ===读取目录=== 读取目录的第一步是查找和读取根目录。 在FAT 12或FAT 16卷上,根目录位于文件分配表之后的固定位置: <source lang="C"> first_root_dir_sector = first_data_sector - root_dir_sectors; </source> 在FAT32和exFAT中,根目录出现在给定簇的数据区域中,可以是簇链。 在exFAT中,它不能编码为扩展(extent),并且将始终存在于FAT中。 <source lang="C"> root_cluster_32 = extBS_32->root_cluster; </source> 对于每个给定的簇号,我们可以计算它的第一个扇区 (相对于分区的偏移量): <source lang="C"> first_sector_of_cluster = ((cluster - 2) * fat_boot->sectors_per_cluster) + first_data_sector; </source> 将正确的簇加载到内存后,下一步是读取并解析其中的所有条目。 每个条目为32字节长。 对于每个32字节条目,这是执行的流程: # 如果条目的第一个字节等于0,则此目录中不再有文件/目录。 FirstByte==0, 完成。 FirstByte!=0, 转到2。 # 如果条目的第一个字节等于0xE5,则该条目未使用。 FirstByte==0xE5, 转到8, FirstByte!=0xE5, 转到3。 # 这个条目是长文件名条目吗? 如果条目的第11个字节等于0x0F,则它是一个长文件名条目。 否则,不是。 11thByte==0x0F, 转到4。 11thByte!=0x0F, 转到5。 # 将长文件名的部分读入临时缓冲区。 Goto 8. # 使用本页上方的表格解析此条目的数据。 保存数据以供以后使用将是一个好主意。 可能在虚拟文件系统结构中。 转到6 # 临时缓冲区中是否有长文件名? 是,转到7。 否,转到8 # 将长文件名应用于你刚刚读取的条目,并清除临时缓冲区。转到8 转到8 # 递增指针和/或计数器,并检查下一个条目。(转到1) 应重复此过程,直到从簇中读取所有条目。 然后,你应该检查簇链中在此簇之后是否还有另一个簇,或者这是否是链中的最后一个簇。 有关更多信息,请参见 [[#跟进簇链|下面的跟进簇链部分]] 和 [[#File_Allocation_Table|FAT]] 部分。 你应该对链中的每个簇执行上述过程,然后执行该过程,直到链中不再剩下簇。 然后,你可以检查你刚才读取的条目中是否有任何是目录。 如果是,则应从存储在条目中的第一个簇号开始,以相同的方式读取它们。 ===跟进簇链=== 跟进簇链有两个基本步骤。 第一步是找出在链中的当前链接之后是否还有另一个 “链接” (簇)。 第二步是实际使用从FAT读取的值来读取下一个扇区。 以下是基本思想: # 从FAT中提取 _current _ 簇的值。(有关如何准确提取值的详细信息,请使用文件分配表上的上一节。) 转到数字2 # 此群集是否标记为链中的最后一个群集?(同样,更多细节请参见上述章节)是,转到4。否,转到3号 # 读取提取的值代表的簇,返回进行更多的目录解析。 # 已找到簇链的末端。我们这里的工作完成了。:) ===读取扩展extents=== 在exFAT上,文件可以在其标志中设置一个位,以指示其存储为extent-based存储。 这意味着整个文件是连续的,并且文件大小加上第一个簇指示 (整个) 文件在哪里。 FAT条目将包含垃圾,不可信。 要阅读此内容,请执行与上面相同的计算,只是你可以在每个步骤中假设下一个簇是数值上的下一个簇,并且已经为文件大小分配了足够的扇区。 == 创建一个新的FAT文件系统 == 通常在开发过程中,你希望使用FAT文件系统创建磁盘映像。 这有两种常见的方法,一种是使用直接在映像上工作的实用程序,另一种是使用[[Loopback Device]]并使用操作系统自己的驱动程序来处理映像。 不太常见的替代方法是在驱动器中安装实际磁盘。 最好用的buildscript工具是[[MTools]]- 它可以使用-i参数直接在磁盘映像上执行所有操作,并以这种方式提供与文件相关的每个DOS命令,仅以<tt>m</tt>为前缀。 它还可以使用配置文件以DOS方式访问驱动器,例如,你可以将A: 和C: 用作实际驱动器。 该工具可以在Windows环境下直接构建,并包含在许多linux软件包管理器中。 仅限Linux的开发人员通常可以使用一些sudo和玩一些权限魔术,将[[Loopback device]]与<TT>mkdosfs</TT>或<TT>mkfs.vfa</TT>以及分区编辑结合起来实现自动化。 此方法的可移植性较差,因为命令通常无法在Linux之外重用。 一些开发人员还犯了错误,将-F传递给mkdosf,试图选择一个FAT大小,这通常会导致创建一个损坏的文件系统,因为结果不符合[#读取引导扇区|FAT大小的官方规则]]。 Windows用户可以将[[Virtual Floppy Drive|VFD]]用于loopback设备。 它带有GUI,但代价是无法在脚本中适当自动化。 ==另见== ===论坛主题=== * 从原始位到目录列表 (发布的[[Topic:11247| 论坛]]代码) * [[Topic:13993|论坛]]中的公共域FAT32代码 * [[Topic:21155|论坛]]中的FAT12/FAT16引导扇区代码 === 外部链接 === * [http://www.osdever.net/downloads/docs/fatgen103.zip FAT32 File System Specification] - 来自Cottontail OS开发库 * [http://download.microsoft.com/download/1/6/1/161ba512-40e2-4cc9-843a-923143f3456c/fatgen103.doc FAT32 File System Specification] - 来自Microsoft(文档,但采用教程样式,包含许多代码示例) * [http://board.flatassembler.net/topic.php?t=12680 About an error in the above specification] * http://scottie.20m.com/fat.htm * http://www.maverick-os.dk/FileSystemFormats/FAT12_FileSystem.html * http://www.pjrc.com/tech/8051/ide/fat32.html * [http://web.archive.org/web/20170112194555/http://www.viralpatel.net/taj/tutorial/fat.php Intro into Sectors and Addressing] * http://elm-chan.org/fsw/ff/00index_e.html - 简单 (V)FAT12/16/32读/写库与良好的文档 * http://gitorious.org/unix-stuff/fat-util - 用于读取、删除和解压缩FAT12、16和32上的文件的实用程序 * http://www.larwe.com/zws/products/dosfs/index.html - 与FAT12/16/32兼容的文件系统驱动程序 * http://www.isdaman.com/alsos/protocols/fats/nowhere/FAT.HTM [[Category:Filesystems]] [[de:FAT]]{{Filesystems}} DOS V1.0(可能是CP/M)引入了'''File Allocation Table''' ('''FAT''')文件系统。 FAT应该是由比尔·盖茨编写的,是一个非常简单的文件系统-- 只不过是一个巨大表格中的一个单链接的簇列表。 FAT文件系统使用的内存非常少(除非操作系统将整个分配表缓存在内存中),它即使不是现在使用的最基本的文件系统,也肯定是其中之一。 ==概述== FAT文件系统有几种不同的版本。 每个版本都是为不同大小的存储介质设计的。 === FAT 12 === FAT 12是为软盘设计的,可以管理最大16兆字节的大小,因为它使用12位来寻址磁盘簇。 === FAT 16 === FAT 16是为早期硬盘设计的,最多可以处理64K个clusters*clusters大小。 硬盘越大,簇的大小就越大,这会导致磁盘上有大量的“空闲空间”。 === FAT 32 === FAT 32是由Windows 95-B和Windows 98推的。 FAT32解决了FAT的一些问题。 不再有64K最大簇限制! 虽然FAT32每个FAT条目使用32位,但实际上只有底部的28位用于寻址磁盘上的簇(顶部的4位保留)。 每个FAT条目有28位,文件系统在一个分区中最多可以处理大约2.7亿个簇。 这使得非常大的硬盘仍然可以保持较小的簇大小,从而减少文件之间的空闲空间。 === ExFAT === {{Main|ExFAT}} ExFAT是微软创建的SDXC卡上使用的文件系统。 它仍是FAT32,但是每个FAT条目实际有32位,还能够指示在磁盘上完全连续的文件(允许你跳过读取FAT), 还有一些更先进的功能和完全重新设计的文件条目系统。 由于它与FAT32非常相似,所以请将exFAT文章中的任何信息合并到这篇文章中。 微软已经在 https://docs.microsoft.com/en-us/windows/win32/fileio/exfat-specification 公开官方定义文件 === VFAT === VFAT是FAT文件系统的扩展,可以使用长文件名(最多255个字符)。 首先由Windows 95引入,它使用了一个“kludge”,长文件名用“卷标”属性标记,文件名随后存储在顺序目录项中的11字节块中。 (这有点过于简单,但已经足够使用了)。 ==实现细节== FAT文件系统将存储介质视为簇的平面阵列。 如果在非常旧的硬盘和软盘上,物理介质没有将其数据作为扇区的平面列表进行寻址,则需要在将簇号发送到磁盘之前对其进行翻译。 存储介质分为三个基本区域。 * 引导记录 * 文件分配表(FAT) * 目录和数据区 ===引导记录(Boot Record)=== 引导记录占用一个扇区,并且总是放在“分区”的逻辑编号0扇区中。 如果介质未划分为分区,则这是介质的开头。 这是计算机加载时在分区上最容易找到的扇区。 如果存储介质已分区(如硬盘),则实际介质的开头包含[[MBR (x86)]]或其他形式的分区信息。 在这种情况下,每个分区的第一个扇区都有一个[[Volume Boot Record|卷引导记录]]。 ==== BPB (BIOS Parameter Block) ==== 引导记录包含混合在一起的代码和数据。 非代码的数据称为BPB。 {| {{wikitable}} |- ! 偏移量(十进制) ! 偏移量 (十六进制) ! 大小(字节) ! Meaning |- | 0 | 0x00 | 3 | 前三个字节EB 3C 90反汇编为JMP SHORT 3C NOP。(3C值可能不同。)这样做的原因是为了跳过磁盘格式信息(BPB和EBPB)。 由于磁盘的第一个扇区加载到位置0x0000:0x7c00处的ram中并执行,如果没有此跳转,处理器将尝试执行非代码的数据。 即使对于不可引导的卷,Windows和OS X也需要提供与此模式匹配的代码(或使用E9跳转操作码)。 为了满足这个要求,这里可以放置一个无限循环,其中包含字节EB FE 90。 |- | 3 | 0x03 | 8 | OEM标识符。 前8个字节(3-10)是正在使用的DOS版本。 接下来的八个字节29 3A 63 7E 2D 49 48和43读取版本名称。 微软的官方FAT规范表示,这个字段实际上毫无意义,MS FAT驱动程序忽略了它,但它确实建议使用“MSWIN4.1”值,因为一些第三方驱动程序可能会检查它,并期望它具有该值。 旧版本的dos也会报告MSDOS5.1、linux格式的软盘可能会在这里带有“MKDOSF”,而FreeDOS格式的磁盘在这里被观察到带有“FRDOS5.1”。 如果字符串小于8字节,则用空格填充。 |- | 11 | 0x0B | 2 | 每个扇区的字节数(记住,所有数字都是小端格式)。 |- | 13 | 0x0D | 1 | 每个簇的扇区数。 |- | 14 | 0x0E | 2 | 保留扇区的数量。引导记录扇区包含在此值中。 |- | 16 | 0x10 | 1 | 存储介质上文件分配表(FAT)的数量。该值通常为2。 |- | 17 | 0x11 | 2 | 根目录条目数(必须设置为根目录占用整个扇区)。 |- | 19 | 0x13 | 2 | 逻辑卷中的总扇区数。 如果该值为0,则表示卷中有超过65535个扇区,实际计数存储在0x20的大扇区计数条目中。 |- | 21 | 0x15 | 1 | 这个字节表示[https://en.wikipedia.org/wiki/Design_of_the_FAT_file_system#BPB20_OFS_0Ah 媒体描述符类型(media descriptor type)]。 |- | 22 | 0x16 | 2 | 每个FAT的扇区数。仅限FAT12/FAT16。 |- | 24 | 0x18 | 2 | 每个磁道的扇区数。 |- | 26 | 0x1A | 2 | 存储介质上的磁头或盘面数。 |- | 28 | 0x1C | 4 | 隐藏扇区的数量。(即分区开头的LBA。) |- | 32 | 0x20 | 4 | 大扇区计数。 如果卷中的扇区数超过65535个,导致0x13处的“扇区数”条目中的值不合适,则设置此字段。 |} 注: 最初格式化介质的程序不一定正确地知道媒体的 “几何形状” (每个轨道的扇区,磁头以及扇区中的字节数)。 此外,如果介质(从格式化介质的计算机)移动到另一台具有不同BIOS的计算机-- 则新BIOS可以为相同介质指定不同的几何结构。 因此,相信 “SPT” 或 “heads” 数字通常是一个非常糟糕的主意。 如果可能的话,从BIOS获取它们。 注2: BPB中的许多值没有正确 “对齐”。 也就是说,Word字大小的值不存储在字(“偶数”地址)边界上。 在某些体系结构上,访问未对齐的Word可能会导致代码崩溃。 通过制作BPB的副本 (在内存中的其他位置并向上移动一个字节) 可以解决问题。 ==== 扩展引导记录(Extended Boot Record) ==== 扩展引导记录信息紧跟在BPB之后。 开始时的数据称为EBPB。 它包含不同的信息,这取决于该分区是FAT 12、FAT 16还是FAT 32文件系统。 紧跟在EBPB之后的是实际引导代码,然后是标准0xAA55引导签名,以填充512字节的引导扇区。 偏移显示是从标准引导记录的开始。 ===== FAT 12 和 FAT 16 ===== {| {{wikitable}} |- ! 偏移量(小数) !偏移量 (十六进制) ! 长度(字节) ! 含义 |- | 36 | 0x024 | 1 | 驱动器号。 这里的值应该与BIOS中断0x13返回的值相同,或者在DL寄存器中传递; 例如软盘为0x00,硬盘为0x80。 此编号没有用,因为介质可能会移动到另一台计算机,然后插入具有不同驱动器号的驱动器中。 |- | 37 | 0x025 | 1 | Windows NT中的标志。另有保留。 |- | 38 | 0x026 | 1 |签名(必须为0x28或0x29)。 |- | 39 | 0x027 | 4 | VolumeID “串(Serial)” 号。用于跟踪计算机之间的卷。如果你愿意,你可以忽略这一点。 |- | 43 | 0x02B | 11 |卷标字符串。这片地上填满了空地。 |- | 54 | 0x036 | 8 |系统标识符串。 此字段是FAT文件系统类型的字符串表示形式。 它用空格填充。 规范说,永远不要相信这个字符串的内容有任何用途。 |- | 62 | 0x03E | 448 |Boot code. |- | 510 | 0x1FE | 2 | 可引导分区签名0xAA55。 |} ===== FAT 32 ===== {| {{wikitable}} |- ! 偏移量(十进制) ! 偏移量(十六进制) ! 长度 (以字节为单位) ! 含义 |- | 36 | 0x024 | 4 | 每个FAT的扇区数。以扇区为单位的FAT大小。 |- | 40 | 0x028 | 2 | Flags. |- | 42 | 0x02A | 2 | FAT版本号。高字节是主要版本,低字节是次要版本。FAT驱动应该尊重这个字段。 |- | 44 | 0x02C | 4 |根目录的簇号。此字段通常设置为2。 |- | 48 | 0x030 | 2 |FSInfo结构的扇区号。 |- | 50 | 0x032 | 2 | 备份引导扇区的扇区号。 |- | 52 | 0x034 | 12 |保留。格式化卷时,这些字节应为零。 |- | 64 | 0x040 | 1 |驱动器号。此处的值与BIOS中断0x13返回的值相同。软盘为0x00,硬盘为0x80。 |- | 65 | 0x041 | 1 | Windows NT中的标志。否则保留。 |- | 66 | 0x042 | 1 |签名(必须是0x28或0x29)。 |- | 67 | 0x043 | 4 |卷ID‘序列号’。用于跟踪计算机之间的卷。如果你愿意,你可以忽略这一点。 |- | 71 | 0x047 | 11 | 卷标签字符串。这个字段用空格填充。 |- | 82 | 0x052 | 8 |系统标识符字符串。始终是“FAT32”。规范中说永远不要相信这个字符串的内容有任何用途。 |- | 90 | 0x05A | 420 |Boot code. |- | 510 | 0x1FE | 2 |可引导分区签名0xAA55。 |} === FSInfo Structure (仅FAT32) === {| {{wikitable}} |- !偏移量 (十进制) ! 偏移量(十六进制) ! 长度(字节) ! 含义 |- | 0 | 0x0 | 4 | Lead签名(必须为0x4161525,以指示有效的FSInfo结构) |- | 4 | 0x4 | 480 |保留,不应使用这些字节 |- | 484 | 0x1E4 | 4 | 另一个签名 (必须为0x61417272) |- | 488 | 0x1E8 | 4 |包含卷上最后一次已知的可用簇计数。 如果值为0xFFFFFFFF,则空闲计数未知,必须计算。 但是,此值可能不正确,至少应进行范围检查 (<= 卷群集计数) |- | 492 | 0x1EC | 4 |指示文件系统驱动程序开始查找可用簇的簇号。 如果值为0xFFFFFFFF,则没有提示,驱动程序应从2开始搜索。 通常,此值设置为最后分配的簇编号。 与上一个字段一样,应检查该值的范围。 |- | 496 | 0x1F0 | 12 | 保留 |- | 508 | 0x1FC | 4 |Trail签名(0xAA550000) |} ==== exFat boot record ==== 对于exFAT,整个引导记录是从头开始重新创建的,而不是进一步扩展现有的FAT12/16/32引导记录。 你可以通过注意到在FAT12/16/32引导记录中,“bytes per sector” 为零来识别exFAT。 {| {{wikitable}} |- ! 偏移量(十进制) ! 偏移量(祸不单行) ! 大小 (以字节为单位) ! 含义 |- | 0 | 0x00 | 3 | 前三个字节EB3C90反汇编成JMP短小的3CNOP。 (3C值可能有所不同。)这样做的原因是跳过磁盘格式信息 (BPB和EBPB)。 由于磁盘的第一个扇区被加载到位置0x0000:0x7c00的ram中并执行,如果没有这个跳转,处理器将尝试执行非代码的数据。 即使对于不可引导的卷,Windows和OSX也要求存在与此模式匹配的代码(或使用E9 JUMP操作码)。 为了满足此要求,可以在此处放置一个具有字节EB FE 90的无限循环。 |- | 3 | 0x03 | 8 | OEM标识符。它包含字符串“EXFAT”。不用于确定文件系统,但这是一个很好的提示。 |- | 11 | 0x0B | 53 | 设置为零。这样可以确保任何FAT驱动程序都无法加载它。 |- | 64 | 0x40 | 8 | 分区偏移量。不知道为什么分区本身会有这个,但它在这里。可能是错的。也许最好还是忽略它。 |- | 72 | 0x48 | 8 | 卷长度。 |- | 80 | 0x50 | 4 | 从分区开始的FAT偏移 (在扇区中)。 |- | 84 | 0x54 | 4 | FAT长度(以扇区为单位)。 |- | 88 | 0x58 | 4 | 簇堆偏移量(以扇区为单位)。 |- | 92 | 0x5C | 4 | 簇计数 |- | 96 | 0x60 | 4 | 根目录簇。通常为4(但只需读取此值)。 |- | 100 | 0x64 | 4 | 分区的序列号。 |- | 104 | 0x68 | 2 | 文件系统修订 |- | 106 | 0x6A | 2 | Flags |- | 108 | 0x6C | 1 | 扇区移位(Sector shift) |- | 109 | 0x6D | 1 | 簇移位(Cluster shift) |- | 110 | 0x6E | 1 | FAT的数字 |- | 111 | 0x6F | 1 | 驱动器选择 |- | 112 | 0x70 | 1 | 使用百分比 |- | 113 | 0x71 | 7 | 保留(设置为0)。 |} 要读取文件系统,请找出“扇区”和“簇”有多大。 一个扇区是 (1 << sectorshift) 字节,一个簇是 (1 << (sectorshift + clustershift)) 字节。 然后,找到FAT的开始和簇堆的开始(请注意,第一个簇是 *仍然* 簇2)。 <source lang="C"> // This allows you to zero-index clusters: uint64_t clusterArray = clusterheapoffset * sectorsize - 2 * clustersize; uint64_t fatOffset = fatoffset * sectorsize; uint64_t usablespace = clustercount * clustersize; </source> 请注意,BPB中的所有值现在都是自然对齐的,并且此代码比FAT32的BPB读取简单得多。 === File Allocation Table === 文件分配表 (FAT) 是存储在存储介质上的表,它指示磁盘上所有数据群集的状态和位置。 它可以被视为磁盘的“目录”。 簇可能可供使用,可能由操作系统保留,可能由于磁盘上的坏扇区而不可用,或者可能正被文件使用。 文件的簇不必在磁盘上彼此相邻。 事实上,它们很可能分散在整个磁盘中。 FAT允许操作系统追查文件中簇“链”。 ==== FAT 12 ==== FAT 12使用12位对磁盘上的簇进行寻址。 FAT中的每个12位条目都指向磁盘上的下一个文件簇。 给定有效的簇号,下面是如何提取簇链中下一个群集值的方法: <source lang="C"> unsigned char FAT_table[sector_size]; unsigned int fat_offset = active_cluster + (active_cluster / 2);// 乘以1.5 unsigned int fat_sector = first_fat_sector + (fat_offset / section_size); unsigned int ent_offset = fat_offset % section_size; //at this point you need to read from sector "fat_sector" on the disk into "FAT_table". unsigned short table_value = *(unsigned short*)&FAT_table[ent_offset]; if(active_cluster & 0x0001) table_value = table_value >> 4; else table_value = table_value & 0x0FFF; //变量“table_value”现在包含了链中下一个簇所需的信息。 </source> 如果“table_value”大于或等于(>=)0xFF8,则链中没有更多的簇。 这意味着整个文件已被读取。 如果“table_value”等于(=)0xFF7,则该簇已被标记为“坏”。“坏”簇容易出错,应该避免。 如果“table_value”不是上述情况之一,则它是文件中下一个簇的簇号。 索引0和1下的条目是保留的。 第0个条目是保留的,因为索引0被用作表示给定簇空闲的其他条目的值。 第零个条目必须从低8位开始保持bpb_media字段的值,并且其余位必须被设置为零。 例如,如果BPB_Media为0xF8,则第零项应保存值0xFF8。 第一个条目保留供将来使用,并且必须保持值0xFFF。 由于FAT12使用的条目大小不能均匀地被8位整除,因此弄清楚如何解释FAT可能会有些混乱。 考虑两个连续的条目,值0x123和0x46. 第一个条目的第一个字节是底部的两个半字节(0x23),最高的半字节进入第二个字节的底部半字节(0x?1)。 由于下一个条目现在以mid-byte开始,因此只有最低的字节可以放入该字节 (0x6?),并且两个最高的字节进入下一个字节 (0x45)。 因此,这两个条目背靠背如下:0x23 0x61 0x45。 这个位置可能会让人困惑,但是如果我们考虑一台小端机器,它将开始变得更有意义。 如果在偏移量零处加载WORD值,则结果值将为0x6123。 现在半字节的顺序正确了,所以要获得两个条目中第一个条目的值,只需使用0xfff。 对于第二个条目,你必须首先在偏移量1处加载Word,从而产生值0x4561,然后将其向下移位4位(有效地移除底部半字节)。 ==== FAT 16 ==== FAT 16使用16位对磁盘上的簇进行寻址。因此,从16位文件分配表中提取值要容易得多。以下是如何完成的: <source lang="C"> unsigned char FAT_table[sector_size]; unsigned int fat_offset = active_cluster * 2; unsigned int fat_sector = first_fat_sector + (fat_offset / sector_size); unsigned int ent_offset = fat_offset % sector_size; //此时,你需要将磁盘上的扇区“fat_sector”读取到“fat_table”中。 unsigned short table_value = *(unsigned short*)&FAT_table[ent_offset]; //变量“TABLE_VALUE”现在有了链中下一个簇所需的信息。 </source> 如果 “table_value” 大于或等于 (>=) 0xFFF8,则链中不再有簇。 这意味着整个文件已被读取。 如果“table_value”等于(==)0xFFF7,则此簇已被标记为“坏”。“坏”簇容易出错,应该避免。 如果 “table_value” 不是上述情况之一,则它是文件中下一个簇的簇编号。 索引0和1下的条目是保留的。 第0个条目被保留,因为索引0被用作表示给定簇是空闲的其他条目的值。 第零项必须将BPB_Media字段的值保持在低8位中,其余的位必须设置为零。 例如,如果BPB_Media为0xF8,则第0个条目应包含值0xFFF8。 第一个条目保留供将来使用,必须保持值0xFFFF。 ==== FAT 32 and exFAT ==== FAT 32使用28位来寻址磁盘上的簇。 保留最高的4位。 这意味着它们在读取时应被忽略,而在写入时应保持不变。exFAT使用完整的32位对扇区号进行编码。 类似于对16位FAT的相同操作: <source lang="C"> unsigned char FAT_table[sector_size]; unsigned int fat_offset = active_cluster * 4; unsigned int fat_sector = first_fat_sector + (fat_offset / sector_size); unsigned int ent_offset = fat_offset % sector_size; //此时,你需要将磁盘上的扇区“fat_sector”读取到“fat_table”中。 //记住忽略高位4位。 unsigned int table_value = *(unsigned int*)&FAT_table[ent_offset]; if (fat32) table_value &= 0x0FFFFFFF; // 变量 “table_value” 现在具有你需要的有关链中下一个群集的信息。 </source> 如果“table_value”大于或等于(>=)0x0FFFFFF8(或0xFFFFFFF8表示exFAT),则链中不再有簇。 这意味着已经读取了整个文件。 如果 “table_value” 等于 (= =) 0x0FFFFFF7 (或exFAT为0xFFFFFFF7),则此群集已标记为 “坏”。 “坏”簇容易出错,应该避免。 如果“table_value”不是上述情况之一,则它是文件中下一个簇的簇号。 索引0和1下的条目是保留的。 第0个条目是保留的,因为索引0被用作表示给定簇空闲的其他条目的值。 第零个条目必须从低8位开始保持bpb_media字段的值,并且其余位必须被设置为零。 例如,如果BPB_Media为0xF8,则第零项应保存值0xfffff8。 第一个条目是为将来保留的,必须保存0xFFFFFF值。 请注意,在exFAT上,一些文件不会写出到FAT中。 如果文件是完全连续的,则exFAT允许操作系统对该信息进行编码,而不更新该文件的FAT。 因此,与FAT32不同,FAT表不用于簇的分配状态;而是有一个分配位图来处理这个问题。 有关这一点,请参阅下面的目录条目。 === FAT12/16/32上的目录 === 目录条目只存储了知道文件数据或文件夹子项在磁盘上存储位置所需的信息。 它还保存条目的名称、大小和创建时间等信息。 FAT文件系统中有两种类型的目录。 标准的8.3目录项(出现在所有FAT文件系统上)和长文件名目录项(可选地出现以允许更长的文件名)。 ====标准8.3格式==== {| {{Wikitable}} |- ! 偏移量 (以字节为单位) ! 长度(字节) ! 含义 |- | 0 | 11 | 8.3文件名。前8个字符是名称,后3个是扩展名。 |- | 11 | 1 | 文件的属性。 可能的属性包括: <pre>READ_ONLY=0x01 HIDDEN=0x02 SYSTEM=0x04 VOLUME_ID=0x08 DIRECTORY=0x10 ARCHIVE=0x20 LFN=READ_ONLY|HIDDEN|SYSTEM|VOLUME_ID </pre> (LFN表示此条目是 [[#Long_File_Names|长文件名条目]]) |- | 12 | 1 |保留供Windows NT使用。 |- | 13 | 1 |创建时间,单位为十分之一秒。 范围0-199 (含)。 基于简单的测试,Ubuntu16.10个存储0或100,而Windows 7在此字段中存储0-199。 |- | 14 | 2 |文件创建时间。将秒数乘以2。 {| {{Wikitable}} |- | 小时 | 5 bits |- | 分钟 | 6 bits |- | 秒 | 5 bits |} |- | 16 | 2 | 创建文件的日期。 {| {{Wikitable}} |- |年 | 7 bits |- | 月 | 4 bits |- | 日 | 5 bits |} |- | 18 | 2 |上次访问日期。格式与创建日期相同。 |- | 20 | 2 |该条目第一个簇号的高16位。对于FAT12和FAT16,该值始终为零。 |- | 22 | 2 | 上次修改时间。与创建时间格式相同。 |- | 24 | 2 |最后修改日期。格式与创建日期相同。 |- | 26 | 2 |该条目第一个簇号的低16位。使用此编号查找此条目的第一个群集。 |- | 28 | 4 | 以字节为单位的文件大小。 |} ==== 长文件名 ==== 长文件名条目“始终”具有它们所属的常规8.3条目。 长文件名条目始终放在其8.3条目之前。 这里是长文件名条目的格式。 {| {{Wikitable}} |- ! 偏移量(字节) ! 长度 (以字节为单位) ! 含义 |- | 0 | 1 |此条目在长文件名条目序列中的顺序。 该值可帮助你知道应将此条目中的字符放置在文件名中的什么位置。 |- | 1 | 10 | 本条目首5、2字节字符。 |- | 11 | 1 |属性。始终等于0x0F。(长文件名属性) |- | 12 | 1 |长条目类型。名称条目为零。 |- | 13 | 1 | 创建文件时短文件名的校验和。 如果分区安装在不支持长文件名的系统上,则短文件名可以在不更改长文件名的情况下更改。 |- | 14 | 12 |该条目接下来的6、2字节字符。 |- | 26 | 2 | 永远为零。 |- | 28 | 4 |此条目的最后2、2字节字符。 |} 下面是一个示例,展示了十六进制编辑器中前面带有一个长文件名条目的常规8.3条目可能是什么样子: <pre> 41 62 00 69 00 6E 00 00 00 FF FF 0F 00 7F FF FF FF FF FF FF FF FF FF FF FF FF 00 00 FF FF FF FF 42 49 4E 20 20 20 20 20 20 20 20 10 00 00 F7 01 D5 38 D5 38 00 00 F7 01 D5 38 03 00 00 00 00 00 </pre> 在文本编辑器中: <pre> Ab.i.n.......................... BIN ......8.8.....8...... </pre> 第一行是长文件名条目(第二行是常规的8.3条目)。 第一个字节(41)告诉我们两条重要的信息。 首先,出现 (01) 告诉我们这是常规8.3条目的第一个长文件名条目。 其次,四十(40)部分告诉我们,这也是常规8.3条目的“最后”长文件名条目。 接下来的10个字节拼写出长文件名的第一部分。在这种情况下,他们读到: <pre> b 00 i 00 n 00 00 00 FF FF </pre> 请注意,每个字符的长度为两个字节,并且名称为null终止。 末尾的两个FF是长文件名末尾的填充。 这也是长文件名条目中的其他FF。 关于长文件名条目,需要注意的最后一件重要的事情是它在偏移量11处的属性字节。 0x0F属性允许我们验证这确实是一个长文件名条目。 ===exFAT上的目录=== exFAT从头开始重新设计了这些目录条目。 {| {{Wikitable}} |- ! 偏移量(字节) ! 长度(字节) ! 含义 |- | 0 | 1 |条目类型 |- | 1 | 31 | 条目剩余部分。 |} 每个条目的基础是它们仍然是32个字节,并且它们都从第一个字节中的类型开始。 我遇到过与从磁盘读取文件相关的类型: ====文件条目==== {| {{Wikitable}} |- ! 偏移量 (以字节为单位) ! 长度(字节) ! 含义 |- | 0 | 1 | 条目类型 = 0x85 |- | 1 | 1 | 二级条目的计数。 |- | 2 | 2 | 条目集合校验和 |- | 4 | 2 | 文件属性 |- | 6 | 2 | 保留 |- | 8 | 4 创建日期和时间 |- | 12 | 4 | 修改日期和时间 |- | 16 | 4 |访问日期和时间 |- | 20 | 1 | 创建毫秒(0-199),将添加到FAT样式日期/时间中,以提高精确度。日期/时间格式见FAT12条目。 |- | 21 | 1 | 修改毫秒 (0-199)。 |- | 22 | 1 |创建时间的UTC偏移量 |- | 23 | 1 | 修改时间的UTC偏移量 |- | 24 | 1 | 访问时间的UTC偏移量 |- | 25 | 7 | 保留。 |} ==== 流“扩展”条目==== 它被称为扩展(extension),但100% 需要直接在 “文件” 条目之后存在。 {| {{Wikitable}} |- ! 偏移量(字节) ! 长度(字节) ! 含义 |- | 0 | 1 |条目类型=0xC0 |- | 1 | 1 |二级标志 |- | 2 | 1 | 保留 |- | 3 | 1 | 名字长度 |- | 4 | 2 |名称哈希 |- | 6 | 2 | 保留 |- | 8 | 8 | 有效数据长度。 写入大文件时,exFAT首先分配整个文件,然后在写入数据时以更新方式修改。 如果它没有有效数据长度告知用户,这种情况确实还不太好处理。 |- | 16 | 4 | 保留 |- | 20 | 4 | 第一个簇。 |- | 24 | 8 | 数据长度。 |} ====文件名条目==== {| {{Wikitable}} |- ! Offset (in bytes) ! Length (in bytes) ! Meaning |- | 0 | 1 | Entry type = 0xC1 |- | 1 | 1 | flags |- | 2 | 30 | File name characters (15 UTF16 code units). |} 要实际使用这些信息,它们通常按如下顺序出现: - 文件条目/File entry - 流扩展条目/Stream extension entry - 文件名条目/File name entry - (附加文件名条目/Additional file name entries) 文件条目包含文件元数据信息,流扩展名告诉你它是如何存储的,文件名条目告诉你它叫什么。 再也没有8.3的名字要求了。 读取文件时,流扩展辅助标志中的第二位指示它是否存储为范围,或者你是否需要使用FAT表。 如果设置了,则文件是连续的,FAT不是最新的;如果设置明确,则FAT是准确的,需要使用(但仍然可以说它是连续的)。 ==== 长文件名 ==== 长文件名条目 “总是” 具有它们所属的常规8.3条目。 长文件名条目总是放在其8.3条目之前。 以下是长文件名条目的格式。 {| {{Wikitable}} |- ! 偏移量 (以字节为单位) ! 长度(字节) ! 含义 |- | 0 | 1 | 本条目在长文件名条目的顺序。 此值帮助你知道此条目中的字符应放置在文件名的何处。 |- | 1 | 10 |该项的前5,2字节字符。 |- | 11 | 1 | 属性。总是等于0x0F。(长文件名属性) |- | 12 | 1 |长条目型。名称条目为零。 |- | 13 | 1 |文件创建时短文件名生成的校验和。 在分区安装在不支持长文件名的系统上的情况下,短文件名可以更改而无需更改长文件名。 |- | 14 | 12 |此条目的接下来6个2字节字符。 |- | 26 | 2 |始终为零。 |- | 28 | 4 | 本条目最后的2,2字节字符。 |} ==编程指南== 本节旨在为你提供有关在FAT文件系统上执行的常见功能的信息。 === 读取引导扇区 === 引导扇区始终位于逻辑扇区编号0处。 你可以将引导扇区读入数组并以这种方式访问其成员,也可以将其读入结构并通过结构进行访问。 无论哪种方式,引导扇区中的值都需要随时可用,以便在FAT文件系统中执行许多操作。 下面是C语言中一些引导扇区结构的示例。 <source lang="C"> typedef struct fat_extBS_32 { //extended fat32 stuff unsigned int table_size_32; unsigned short extended_flags; unsigned short fat_version; unsigned int root_cluster; unsigned short fat_info; unsigned short backup_BS_sector; unsigned char reserved_0[12]; unsigned char drive_number; unsigned char reserved_1; unsigned char boot_signature; unsigned int volume_id; unsigned char volume_label[11]; unsigned char fat_type_label[8]; }__attribute__((packed)) fat_extBS_32_t; typedef struct fat_extBS_16 { //extended fat12 and fat16 stuff unsigned char bios_drive_num; unsigned char reserved1; unsigned char boot_signature; unsigned int volume_id; unsigned char volume_label[11]; unsigned char fat_type_label[8]; }__attribute__((packed)) fat_extBS_16_t; typedef struct fat_BS { unsigned char bootjmp[3]; unsigned char oem_name[8]; unsigned short bytes_per_sector; unsigned char sectors_per_cluster; unsigned short reserved_sector_count; unsigned char table_count; unsigned short root_entry_count; unsigned short total_sectors_16; unsigned char media_type; unsigned short table_size_16; unsigned short sectors_per_track; unsigned short head_side_count; unsigned int hidden_sector_count; unsigned int total_sectors_32; //一旦驱动器真正知道这是什么类型的FAT,就会将其强制转换为特定类型。 unsigned char extended_section[54]; }__attribute__((packed)) fat_BS_t; </source> 可以从引导扇区中提取的重要信息包括: '''总扇区数量(包括VBR):''' <source lang="C"> total_sectors = (fat_boot->total_sectors_16 == 0)? fat_boot->total_sectors_32 : fat_boot->total_sectors_16; </source> '''扇区中的FAT大小:''' <source lang="C"> fat_size = (fat_boot->table_size_16 == 0)? fat_boot_ext_32->table_size_16 : fat_boot->table_size_16; </source> '''根目录的大小 (除非你有FAT32,在这种情况下,大小将为0):''' <source lang="C"> root_dir_sectors = ((fat_boot->root_entry_count * 32) + (fat_boot->bytes_per_sector - 1)) / fat_boot->bytes_per_sector; </source> 这一计算结果将得到汇总。32是FAT目录的大小(字节)。 '''第一个数据扇区(即可以存储目录和文件的第一个扇区):''' <source lang="C"> first_data_sector = fat_boot->reserved_sector_count + (fat_boot->table_count * fat_size) + root_dir_sectors; </source> '''The first sector in the File Allocation Table:''' <source lang="C"> first_fat_sector = fat_boot->reserved_sector_count; </source> '''数据扇区总数: ''' <source lang="C"> data_sectors = fat_boot->total_sectors - (fat_boot->reserved_sector_count + (fat_boot->table_count * fat_size) + root_dir_sectors); </source> '''簇总数:''' <source lang="C"> total_clusters = data_sectors / fat_boot->sectors_per_cluster; </source> 这进行了取整。 ''' 此文件系统的FAT类型: ''' <source lang="C"> if (sectorsize == 0) { fat_type = ExFAT; } else if(total_clusters < 4085) { fat_type = FAT12; } else if(total_clusters < 65525) { fat_type = FAT16; } else { fat_type = FAT32; } </source> ===读取目录=== 读取目录的第一步是查找和读取根目录。 在FAT 12或FAT 16卷上,根目录位于文件分配表之后的固定位置: <source lang="C"> first_root_dir_sector = first_data_sector - root_dir_sectors; </source> 在FAT32和exFAT中,根目录出现在给定簇的数据区域中,可以是簇链。 在exFAT中,它不能编码为扩展(extent),并且将始终存在于FAT中。 <source lang="C"> root_cluster_32 = extBS_32->root_cluster; </source> 对于每个给定的簇号,我们可以计算它的第一个扇区 (相对于分区的偏移量): <source lang="C"> first_sector_of_cluster = ((cluster - 2) * fat_boot->sectors_per_cluster) + first_data_sector; </source> 将正确的簇加载到内存后,下一步是读取并解析其中的所有条目。 每个条目为32字节长。 对于每个32字节条目,这是执行的流程: # 如果条目的第一个字节等于0,则此目录中不再有文件/目录。 FirstByte==0, 完成。 FirstByte!=0, 转到2。 # 如果条目的第一个字节等于0xE5,则该条目未使用。 FirstByte==0xE5, 转到8, FirstByte!=0xE5, 转到3。 # 这个条目是长文件名条目吗? 如果条目的第11个字节等于0x0F,则它是一个长文件名条目。 否则,不是。 11thByte==0x0F, 转到4。 11thByte!=0x0F, 转到5。 # 将长文件名的部分读入临时缓冲区。 Goto 8. # 使用本页上方的表格解析此条目的数据。 保存数据以供以后使用将是一个好主意。 可能在虚拟文件系统结构中。 转到6 # 临时缓冲区中是否有长文件名? 是,转到7。 否,转到8 # 将长文件名应用于你刚刚读取的条目,并清除临时缓冲区。转到8 转到8 # 递增指针和/或计数器,并检查下一个条目。(转到1) 应重复此过程,直到从簇中读取所有条目。 然后,你应该检查簇链中在此簇之后是否还有另一个簇,或者这是否是链中的最后一个簇。 有关更多信息,请参见 [[#跟进簇链|下面的跟进簇链部分]] 和 [[#File_Allocation_Table|FAT]] 部分。 你应该对链中的每个簇执行上述过程,然后执行该过程,直到链中不再剩下簇。 然后,你可以检查你刚才读取的条目中是否有任何是目录。 如果是,则应从存储在条目中的第一个簇号开始,以相同的方式读取它们。 ===跟进簇链=== 跟进簇链有两个基本步骤。 第一步是找出在链中的当前链接之后是否还有另一个 “链接” (簇)。 第二步是实际使用从FAT读取的值来读取下一个扇区。 以下是基本思想: # 从FAT中提取 _current _ 簇的值。(有关如何准确提取值的详细信息,请使用文件分配表上的上一节。) 转到数字2 # 此群集是否标记为链中的最后一个群集?(同样,更多细节请参见上述章节)是,转到4。否,转到3号 # 读取提取的值代表的簇,返回进行更多的目录解析。 # 已找到簇链的末端。我们这里的工作完成了。:) ===读取扩展extents=== 在exFAT上,文件可以在其标志中设置一个位,以指示其存储为extent-based存储。 这意味着整个文件是连续的,并且文件大小加上第一个簇指示 (整个) 文件在哪里。 FAT条目将包含垃圾,不可信。 要阅读此内容,请执行与上面相同的计算,只是你可以在每个步骤中假设下一个簇是数值上的下一个簇,并且已经为文件大小分配了足够的扇区。 == 创建一个新的FAT文件系统 == 通常在开发过程中,你希望使用FAT文件系统创建磁盘映像。 这有两种常见的方法,一种是使用直接在映像上工作的实用程序,另一种是使用[[Loopback Device]]并使用操作系统自己的驱动程序来处理映像。 不太常见的替代方法是在驱动器中安装实际磁盘。 最好用的buildscript工具是[[MTools]]- 它可以使用-i参数直接在磁盘映像上执行所有操作,并以这种方式提供与文件相关的每个DOS命令,仅以<tt>m</tt>为前缀。 它还可以使用配置文件以DOS方式访问驱动器,例如,你可以将A: 和C: 用作实际驱动器。 该工具可以在Windows环境下直接构建,并包含在许多linux软件包管理器中。 仅限Linux的开发人员通常可以使用一些sudo和玩一些权限魔术,将[[Loopback device]]与<TT>mkdosfs</TT>或<TT>mkfs.vfa</TT>以及分区编辑结合起来实现自动化。 此方法的可移植性较差,因为命令通常无法在Linux之外重用。 一些开发人员还犯了错误,将-F传递给mkdosf,试图选择一个FAT大小,这通常会导致创建一个损坏的文件系统,因为结果不符合[#读取引导扇区|FAT大小的官方规则]]。 Windows用户可以将[[Virtual Floppy Drive|VFD]]用于loopback设备。 它带有GUI,但代价是无法在脚本中适当自动化。 ==另见== ===论坛主题=== * 从原始位到目录列表 (发布的[[Topic:11247| 论坛]]代码) * [[Topic:13993|论坛]]中的公共域FAT32代码 * [[Topic:21155|论坛]]中的FAT12/FAT16引导扇区代码 === 外部链接 === * [http://www.osdever.net/downloads/docs/fatgen103.zip FAT32 File System Specification] - 来自Cottontail OS开发库 * [http://download.microsoft.com/download/1/6/1/161ba512-40e2-4cc9-843a-923143f3456c/fatgen103.doc FAT32 File System Specification] - 来自Microsoft(文档,但采用教程样式,包含许多代码示例) * [http://board.flatassembler.net/topic.php?t=12680 About an error in the above specification] * http://scottie.20m.com/fat.htm * http://www.maverick-os.dk/FileSystemFormats/FAT12_FileSystem.html * http://www.pjrc.com/tech/8051/ide/fat32.html * [http://web.archive.org/web/20170112194555/http://www.viralpatel.net/taj/tutorial/fat.php Intro into Sectors and Addressing] * http://elm-chan.org/fsw/ff/00index_e.html - 简单 (V)FAT12/16/32读/写库与良好的文档 * http://gitorious.org/unix-stuff/fat-util - 用于读取、删除和解压缩FAT12、16和32上的文件的实用程序 * http://www.larwe.com/zws/products/dosfs/index.html - 与FAT12/16/32兼容的文件系统驱动程序 * http://www.isdaman.com/alsos/protocols/fats/nowhere/FAT.HTM [[Category:Filesystems]] [[de:FAT]]{{Filesystems}} DOS V1.0(可能是CP/M)引入了'''File Allocation Table''' ('''FAT''')文件系统。 FAT应该是由比尔·盖茨编写的,是一个非常简单的文件系统-- 只不过是一个巨大表格中的一个单链接的簇列表。 FAT文件系统使用的内存非常少(除非操作系统将整个分配表缓存在内存中),它即使不是现在使用的最基本的文件系统,也肯定是其中之一。 ==概述== FAT文件系统有几种不同的版本。 每个版本都是为不同大小的存储介质设计的。 === FAT 12 === FAT 12是为软盘设计的,可以管理最大16兆字节的大小,因为它使用12位来寻址磁盘簇。 === FAT 16 === FAT 16是为早期硬盘设计的,最多可以处理64K个clusters*clusters大小。 硬盘越大,簇的大小就越大,这会导致磁盘上有大量的“空闲空间”。 === FAT 32 === FAT 32是由Windows 95-B和Windows 98推的。 FAT32解决了FAT的一些问题。 不再有64K最大簇限制! 虽然FAT32每个FAT条目使用32位,但实际上只有底部的28位用于寻址磁盘上的簇(顶部的4位保留)。 每个FAT条目有28位,文件系统在一个分区中最多可以处理大约2.7亿个簇。 这使得非常大的硬盘仍然可以保持较小的簇大小,从而减少文件之间的空闲空间。 === ExFAT === {{Main|ExFAT}} ExFAT是微软创建的SDXC卡上使用的文件系统。 它仍是FAT32,但是每个FAT条目实际有32位,还能够指示在磁盘上完全连续的文件(允许你跳过读取FAT), 还有一些更先进的功能和完全重新设计的文件条目系统。 由于它与FAT32非常相似,所以请将exFAT文章中的任何信息合并到这篇文章中。 微软已经在 https://docs.microsoft.com/en-us/windows/win32/fileio/exfat-specification 公开官方定义文件 === VFAT === VFAT是FAT文件系统的扩展,可以使用长文件名(最多255个字符)。 首先由Windows 95引入,它使用了一个“kludge”,长文件名用“卷标”属性标记,文件名随后存储在顺序目录项中的11字节块中。 (这有点过于简单,但已经足够使用了)。 ==实现细节== FAT文件系统将存储介质视为簇的平面阵列。 如果在非常旧的硬盘和软盘上,物理介质没有将其数据作为扇区的平面列表进行寻址,则需要在将簇号发送到磁盘之前对其进行翻译。 存储介质分为三个基本区域。 * 引导记录 * 文件分配表(FAT) * 目录和数据区 ===引导记录(Boot Record)=== 引导记录占用一个扇区,并且总是放在“分区”的逻辑编号0扇区中。 如果介质未划分为分区,则这是介质的开头。 这是计算机加载时在分区上最容易找到的扇区。 如果存储介质已分区(如硬盘),则实际介质的开头包含[[MBR (x86)]]或其他形式的分区信息。 在这种情况下,每个分区的第一个扇区都有一个[[Volume Boot Record|卷引导记录]]。 ==== BPB (BIOS Parameter Block) ==== 引导记录包含混合在一起的代码和数据。 非代码的数据称为BPB。 {| {{wikitable}} |- ! 偏移量(十进制) ! 偏移量 (十六进制) ! 大小(字节) ! Meaning |- | 0 | 0x00 | 3 | 前三个字节EB 3C 90反汇编为JMP SHORT 3C NOP。(3C值可能不同。)这样做的原因是为了跳过磁盘格式信息(BPB和EBPB)。 由于磁盘的第一个扇区加载到位置0x0000:0x7c00处的ram中并执行,如果没有此跳转,处理器将尝试执行非代码的数据。 即使对于不可引导的卷,Windows和OS X也需要提供与此模式匹配的代码(或使用E9跳转操作码)。 为了满足这个要求,这里可以放置一个无限循环,其中包含字节EB FE 90。 |- | 3 | 0x03 | 8 | OEM标识符。 前8个字节(3-10)是正在使用的DOS版本。 接下来的八个字节29 3A 63 7E 2D 49 48和43读取版本名称。 微软的官方FAT规范表示,这个字段实际上毫无意义,MS FAT驱动程序忽略了它,但它确实建议使用“MSWIN4.1”值,因为一些第三方驱动程序可能会检查它,并期望它具有该值。 旧版本的dos也会报告MSDOS5.1、linux格式的软盘可能会在这里带有“MKDOSF”,而FreeDOS格式的磁盘在这里被观察到带有“FRDOS5.1”。 如果字符串小于8字节,则用空格填充。 |- | 11 | 0x0B | 2 | 每个扇区的字节数(记住,所有数字都是小端格式)。 |- | 13 | 0x0D | 1 | 每个簇的扇区数。 |- | 14 | 0x0E | 2 | 保留扇区的数量。引导记录扇区包含在此值中。 |- | 16 | 0x10 | 1 | 存储介质上文件分配表(FAT)的数量。该值通常为2。 |- | 17 | 0x11 | 2 | 根目录条目数(必须设置为根目录占用整个扇区)。 |- | 19 | 0x13 | 2 | 逻辑卷中的总扇区数。 如果该值为0,则表示卷中有超过65535个扇区,实际计数存储在0x20的大扇区计数条目中。 |- | 21 | 0x15 | 1 | 这个字节表示[https://en.wikipedia.org/wiki/Design_of_the_FAT_file_system#BPB20_OFS_0Ah 媒体描述符类型(media descriptor type)]。 |- | 22 | 0x16 | 2 | 每个FAT的扇区数。仅限FAT12/FAT16。 |- | 24 | 0x18 | 2 | 每个磁道的扇区数。 |- | 26 | 0x1A | 2 | 存储介质上的磁头或盘面数。 |- | 28 | 0x1C | 4 | 隐藏扇区的数量。(即分区开头的LBA。) |- | 32 | 0x20 | 4 | 大扇区计数。 如果卷中的扇区数超过65535个,导致0x13处的“扇区数”条目中的值不合适,则设置此字段。 |} 注: 最初格式化介质的程序不一定正确地知道媒体的 “几何形状” (每个轨道的扇区,磁头以及扇区中的字节数)。 此外,如果介质(从格式化介质的计算机)移动到另一台具有不同BIOS的计算机-- 则新BIOS可以为相同介质指定不同的几何结构。 因此,相信 “SPT” 或 “heads” 数字通常是一个非常糟糕的主意。 如果可能的话,从BIOS获取它们。 注2: BPB中的许多值没有正确 “对齐”。 也就是说,Word字大小的值不存储在字(“偶数”地址)边界上。 在某些体系结构上,访问未对齐的Word可能会导致代码崩溃。 通过制作BPB的副本 (在内存中的其他位置并向上移动一个字节) 可以解决问题。 ==== 扩展引导记录(Extended Boot Record) ==== 扩展引导记录信息紧跟在BPB之后。 开始时的数据称为EBPB。 它包含不同的信息,这取决于该分区是FAT 12、FAT 16还是FAT 32文件系统。 紧跟在EBPB之后的是实际引导代码,然后是标准0xAA55引导签名,以填充512字节的引导扇区。 偏移显示是从标准引导记录的开始。 ===== FAT 12 和 FAT 16 ===== {| {{wikitable}} |- ! 偏移量(小数) !偏移量 (十六进制) ! 长度(字节) ! 含义 |- | 36 | 0x024 | 1 | 驱动器号。 这里的值应该与BIOS中断0x13返回的值相同,或者在DL寄存器中传递; 例如软盘为0x00,硬盘为0x80。 此编号没有用,因为介质可能会移动到另一台计算机,然后插入具有不同驱动器号的驱动器中。 |- | 37 | 0x025 | 1 | Windows NT中的标志。另有保留。 |- | 38 | 0x026 | 1 |签名(必须为0x28或0x29)。 |- | 39 | 0x027 | 4 | VolumeID “串(Serial)” 号。用于跟踪计算机之间的卷。如果你愿意,你可以忽略这一点。 |- | 43 | 0x02B | 11 |卷标字符串。这片地上填满了空地。 |- | 54 | 0x036 | 8 |系统标识符串。 此字段是FAT文件系统类型的字符串表示形式。 它用空格填充。 规范说,永远不要相信这个字符串的内容有任何用途。 |- | 62 | 0x03E | 448 |Boot code. |- | 510 | 0x1FE | 2 | 可引导分区签名0xAA55。 |} ===== FAT 32 ===== {| {{wikitable}} |- ! 偏移量(十进制) ! 偏移量(十六进制) ! 长度 (以字节为单位) ! 含义 |- | 36 | 0x024 | 4 | 每个FAT的扇区数。以扇区为单位的FAT大小。 |- | 40 | 0x028 | 2 | Flags. |- | 42 | 0x02A | 2 | FAT版本号。高字节是主要版本,低字节是次要版本。FAT驱动应该尊重这个字段。 |- | 44 | 0x02C | 4 |根目录的簇号。此字段通常设置为2。 |- | 48 | 0x030 | 2 |FSInfo结构的扇区号。 |- | 50 | 0x032 | 2 | 备份引导扇区的扇区号。 |- | 52 | 0x034 | 12 |保留。格式化卷时,这些字节应为零。 |- | 64 | 0x040 | 1 |驱动器号。此处的值与BIOS中断0x13返回的值相同。软盘为0x00,硬盘为0x80。 |- | 65 | 0x041 | 1 | Windows NT中的标志。否则保留。 |- | 66 | 0x042 | 1 |签名(必须是0x28或0x29)。 |- | 67 | 0x043 | 4 |卷ID‘序列号’。用于跟踪计算机之间的卷。如果你愿意,你可以忽略这一点。 |- | 71 | 0x047 | 11 | 卷标签字符串。这个字段用空格填充。 |- | 82 | 0x052 | 8 |系统标识符字符串。始终是“FAT32”。规范中说永远不要相信这个字符串的内容有任何用途。 |- | 90 | 0x05A | 420 |Boot code. |- | 510 | 0x1FE | 2 |可引导分区签名0xAA55。 |} === FSInfo Structure (仅FAT32) === {| {{wikitable}} |- !偏移量 (十进制) ! 偏移量(十六进制) ! 长度(字节) ! 含义 |- | 0 | 0x0 | 4 | Lead签名(必须为0x4161525,以指示有效的FSInfo结构) |- | 4 | 0x4 | 480 |保留,不应使用这些字节 |- | 484 | 0x1E4 | 4 | 另一个签名 (必须为0x61417272) |- | 488 | 0x1E8 | 4 |包含卷上最后一次已知的可用簇计数。 如果值为0xFFFFFFFF,则空闲计数未知,必须计算。 但是,此值可能不正确,至少应进行范围检查 (<= 卷群集计数) |- | 492 | 0x1EC | 4 |指示文件系统驱动程序开始查找可用簇的簇号。 如果值为0xFFFFFFFF,则没有提示,驱动程序应从2开始搜索。 通常,此值设置为最后分配的簇编号。 与上一个字段一样,应检查该值的范围。 |- | 496 | 0x1F0 | 12 | 保留 |- | 508 | 0x1FC | 4 |Trail签名(0xAA550000) |} ==== exFat boot record ==== 对于exFAT,整个引导记录是从头开始重新创建的,而不是进一步扩展现有的FAT12/16/32引导记录。 你可以通过注意到在FAT12/16/32引导记录中,“bytes per sector” 为零来识别exFAT。 {| {{wikitable}} |- ! 偏移量(十进制) ! 偏移量(祸不单行) ! 大小 (以字节为单位) ! 含义 |- | 0 | 0x00 | 3 | 前三个字节EB3C90反汇编成JMP短小的3CNOP。 (3C值可能有所不同。)这样做的原因是跳过磁盘格式信息 (BPB和EBPB)。 由于磁盘的第一个扇区被加载到位置0x0000:0x7c00的ram中并执行,如果没有这个跳转,处理器将尝试执行非代码的数据。 即使对于不可引导的卷,Windows和OSX也要求存在与此模式匹配的代码(或使用E9 JUMP操作码)。 为了满足此要求,可以在此处放置一个具有字节EB FE 90的无限循环。 |- | 3 | 0x03 | 8 | OEM标识符。它包含字符串“EXFAT”。不用于确定文件系统,但这是一个很好的提示。 |- | 11 | 0x0B | 53 | 设置为零。这样可以确保任何FAT驱动程序都无法加载它。 |- | 64 | 0x40 | 8 | 分区偏移量。不知道为什么分区本身会有这个,但它在这里。可能是错的。也许最好还是忽略它。 |- | 72 | 0x48 | 8 | 卷长度。 |- | 80 | 0x50 | 4 | 从分区开始的FAT偏移 (在扇区中)。 |- | 84 | 0x54 | 4 | FAT长度(以扇区为单位)。 |- | 88 | 0x58 | 4 | 簇堆偏移量(以扇区为单位)。 |- | 92 | 0x5C | 4 | 簇计数 |- | 96 | 0x60 | 4 | 根目录簇。通常为4(但只需读取此值)。 |- | 100 | 0x64 | 4 | 分区的序列号。 |- | 104 | 0x68 | 2 | 文件系统修订 |- | 106 | 0x6A | 2 | Flags |- | 108 | 0x6C | 1 | 扇区移位(Sector shift) |- | 109 | 0x6D | 1 | 簇移位(Cluster shift) |- | 110 | 0x6E | 1 | FAT的数字 |- | 111 | 0x6F | 1 | 驱动器选择 |- | 112 | 0x70 | 1 | 使用百分比 |- | 113 | 0x71 | 7 | 保留(设置为0)。 |} 要读取文件系统,请找出“扇区”和“簇”有多大。 一个扇区是 (1 << sectorshift) 字节,一个簇是 (1 << (sectorshift + clustershift)) 字节。 然后,找到FAT的开始和簇堆的开始(请注意,第一个簇是 *仍然* 簇2)。 <source lang="C"> // This allows you to zero-index clusters: uint64_t clusterArray = clusterheapoffset * sectorsize - 2 * clustersize; uint64_t fatOffset = fatoffset * sectorsize; uint64_t usablespace = clustercount * clustersize; </source> 请注意,BPB中的所有值现在都是自然对齐的,并且此代码比FAT32的BPB读取简单得多。 === File Allocation Table === 文件分配表 (FAT) 是存储在存储介质上的表,它指示磁盘上所有数据群集的状态和位置。 它可以被视为磁盘的“目录”。 簇可能可供使用,可能由操作系统保留,可能由于磁盘上的坏扇区而不可用,或者可能正被文件使用。 文件的簇不必在磁盘上彼此相邻。 事实上,它们很可能分散在整个磁盘中。 FAT允许操作系统追查文件中簇“链”。 ==== FAT 12 ==== FAT 12使用12位对磁盘上的簇进行寻址。 FAT中的每个12位条目都指向磁盘上的下一个文件簇。 给定有效的簇号,下面是如何提取簇链中下一个群集值的方法: <source lang="C"> unsigned char FAT_table[sector_size]; unsigned int fat_offset = active_cluster + (active_cluster / 2);// 乘以1.5 unsigned int fat_sector = first_fat_sector + (fat_offset / section_size); unsigned int ent_offset = fat_offset % section_size; //at this point you need to read from sector "fat_sector" on the disk into "FAT_table". unsigned short table_value = *(unsigned short*)&FAT_table[ent_offset]; if(active_cluster & 0x0001) table_value = table_value >> 4; else table_value = table_value & 0x0FFF; //变量“table_value”现在包含了链中下一个簇所需的信息。 </source> 如果“table_value”大于或等于(>=)0xFF8,则链中没有更多的簇。 这意味着整个文件已被读取。 如果“table_value”等于(=)0xFF7,则该簇已被标记为“坏”。“坏”簇容易出错,应该避免。 如果“table_value”不是上述情况之一,则它是文件中下一个簇的簇号。 索引0和1下的条目是保留的。 第0个条目是保留的,因为索引0被用作表示给定簇空闲的其他条目的值。 第零个条目必须从低8位开始保持bpb_media字段的值,并且其余位必须被设置为零。 例如,如果BPB_Media为0xF8,则第零项应保存值0xFF8。 第一个条目保留供将来使用,并且必须保持值0xFFF。 由于FAT12使用的条目大小不能均匀地被8位整除,因此弄清楚如何解释FAT可能会有些混乱。 考虑两个连续的条目,值0x123和0x46. 第一个条目的第一个字节是底部的两个半字节(0x23),最高的半字节进入第二个字节的底部半字节(0x?1)。 由于下一个条目现在以mid-byte开始,因此只有最低的字节可以放入该字节 (0x6?),并且两个最高的字节进入下一个字节 (0x45)。 因此,这两个条目背靠背如下:0x23 0x61 0x45。 这个位置可能会让人困惑,但是如果我们考虑一台小端机器,它将开始变得更有意义。 如果在偏移量零处加载WORD值,则结果值将为0x6123。 现在半字节的顺序正确了,所以要获得两个条目中第一个条目的值,只需使用0xfff。 对于第二个条目,你必须首先在偏移量1处加载Word,从而产生值0x4561,然后将其向下移位4位(有效地移除底部半字节)。 ==== FAT 16 ==== FAT 16使用16位对磁盘上的簇进行寻址。因此,从16位文件分配表中提取值要容易得多。以下是如何完成的: <source lang="C"> unsigned char FAT_table[sector_size]; unsigned int fat_offset = active_cluster * 2; unsigned int fat_sector = first_fat_sector + (fat_offset / sector_size); unsigned int ent_offset = fat_offset % sector_size; //此时,你需要将磁盘上的扇区“fat_sector”读取到“fat_table”中。 unsigned short table_value = *(unsigned short*)&FAT_table[ent_offset]; //变量“TABLE_VALUE”现在有了链中下一个簇所需的信息。 </source> 如果 “table_value” 大于或等于 (>=) 0xFFF8,则链中不再有簇。 这意味着整个文件已被读取。 如果“table_value”等于(==)0xFFF7,则此簇已被标记为“坏”。“坏”簇容易出错,应该避免。 如果 “table_value” 不是上述情况之一,则它是文件中下一个簇的簇编号。 索引0和1下的条目是保留的。 第0个条目被保留,因为索引0被用作表示给定簇是空闲的其他条目的值。 第零项必须将BPB_Media字段的值保持在低8位中,其余的位必须设置为零。 例如,如果BPB_Media为0xF8,则第0个条目应包含值0xFFF8。 第一个条目保留供将来使用,必须保持值0xFFFF。 ==== FAT 32 and exFAT ==== FAT 32使用28位来寻址磁盘上的簇。 保留最高的4位。 这意味着它们在读取时应被忽略,而在写入时应保持不变。exFAT使用完整的32位对扇区号进行编码。 类似于对16位FAT的相同操作: <source lang="C"> unsigned char FAT_table[sector_size]; unsigned int fat_offset = active_cluster * 4; unsigned int fat_sector = first_fat_sector + (fat_offset / sector_size); unsigned int ent_offset = fat_offset % sector_size; //此时,你需要将磁盘上的扇区“fat_sector”读取到“fat_table”中。 //记住忽略高位4位。 unsigned int table_value = *(unsigned int*)&FAT_table[ent_offset]; if (fat32) table_value &= 0x0FFFFFFF; // 变量 “table_value” 现在具有你需要的有关链中下一个群集的信息。 </source> 如果“table_value”大于或等于(>=)0x0FFFFFF8(或0xFFFFFFF8表示exFAT),则链中不再有簇。 这意味着已经读取了整个文件。 如果 “table_value” 等于 (= =) 0x0FFFFFF7 (或exFAT为0xFFFFFFF7),则此群集已标记为 “坏”。 “坏”簇容易出错,应该避免。 如果“table_value”不是上述情况之一,则它是文件中下一个簇的簇号。 索引0和1下的条目是保留的。 第0个条目是保留的,因为索引0被用作表示给定簇空闲的其他条目的值。 第零个条目必须从低8位开始保持bpb_media字段的值,并且其余位必须被设置为零。 例如,如果BPB_Media为0xF8,则第零项应保存值0xfffff8。 第一个条目是为将来保留的,必须保存0xFFFFFF值。 请注意,在exFAT上,一些文件不会写出到FAT中。 如果文件是完全连续的,则exFAT允许操作系统对该信息进行编码,而不更新该文件的FAT。 因此,与FAT32不同,FAT表不用于簇的分配状态;而是有一个分配位图来处理这个问题。 有关这一点,请参阅下面的目录条目。 === FAT12/16/32上的目录 === 目录条目只存储了知道文件数据或文件夹子项在磁盘上存储位置所需的信息。 它还保存条目的名称、大小和创建时间等信息。 FAT文件系统中有两种类型的目录。 标准的8.3目录项(出现在所有FAT文件系统上)和长文件名目录项(可选地出现以允许更长的文件名)。 ====标准8.3格式==== {| {{Wikitable}} |- ! 偏移量 (以字节为单位) ! 长度(字节) ! 含义 |- | 0 | 11 | 8.3文件名。前8个字符是名称,后3个是扩展名。 |- | 11 | 1 | 文件的属性。 可能的属性包括: <pre>READ_ONLY=0x01 HIDDEN=0x02 SYSTEM=0x04 VOLUME_ID=0x08 DIRECTORY=0x10 ARCHIVE=0x20 LFN=READ_ONLY|HIDDEN|SYSTEM|VOLUME_ID </pre> (LFN表示此条目是 [[#Long_File_Names|长文件名条目]]) |- | 12 | 1 |保留供Windows NT使用。 |- | 13 | 1 |创建时间,单位为十分之一秒。 范围0-199 (含)。 基于简单的测试,Ubuntu16.10个存储0或100,而Windows 7在此字段中存储0-199。 |- | 14 | 2 |文件创建时间。将秒数乘以2。 {| {{Wikitable}} |- | 小时 | 5 bits |- | 分钟 | 6 bits |- | 秒 | 5 bits |} |- | 16 | 2 | 创建文件的日期。 {| {{Wikitable}} |- |年 | 7 bits |- | 月 | 4 bits |- | 日 | 5 bits |} |- | 18 | 2 |上次访问日期。格式与创建日期相同。 |- | 20 | 2 |该条目第一个簇号的高16位。对于FAT12和FAT16,该值始终为零。 |- | 22 | 2 | 上次修改时间。与创建时间格式相同。 |- | 24 | 2 |最后修改日期。格式与创建日期相同。 |- | 26 | 2 |该条目第一个簇号的低16位。使用此编号查找此条目的第一个群集。 |- | 28 | 4 | 以字节为单位的文件大小。 |} ==== 长文件名 ==== 长文件名条目“始终”具有它们所属的常规8.3条目。 长文件名条目始终放在其8.3条目之前。 这里是长文件名条目的格式。 {| {{Wikitable}} |- ! 偏移量(字节) ! 长度 (以字节为单位) ! 含义 |- | 0 | 1 |此条目在长文件名条目序列中的顺序。 该值可帮助你知道应将此条目中的字符放置在文件名中的什么位置。 |- | 1 | 10 | 本条目首5、2字节字符。 |- | 11 | 1 |属性。始终等于0x0F。(长文件名属性) |- | 12 | 1 |长条目类型。名称条目为零。 |- | 13 | 1 | 创建文件时短文件名的校验和。 如果分区安装在不支持长文件名的系统上,则短文件名可以在不更改长文件名的情况下更改。 |- | 14 | 12 |该条目接下来的6、2字节字符。 |- | 26 | 2 | 永远为零。 |- | 28 | 4 |此条目的最后2、2字节字符。 |} 下面是一个示例,展示了十六进制编辑器中前面带有一个长文件名条目的常规8.3条目可能是什么样子: <pre> 41 62 00 69 00 6E 00 00 00 FF FF 0F 00 7F FF FF FF FF FF FF FF FF FF FF FF FF 00 00 FF FF FF FF 42 49 4E 20 20 20 20 20 20 20 20 10 00 00 F7 01 D5 38 D5 38 00 00 F7 01 D5 38 03 00 00 00 00 00 </pre> 在文本编辑器中: <pre> Ab.i.n.......................... BIN ......8.8.....8...... </pre> 第一行是长文件名条目(第二行是常规的8.3条目)。 第一个字节(41)告诉我们两条重要的信息。 首先,出现 (01) 告诉我们这是常规8.3条目的第一个长文件名条目。 其次,四十(40)部分告诉我们,这也是常规8.3条目的“最后”长文件名条目。 接下来的10个字节拼写出长文件名的第一部分。在这种情况下,他们读到: <pre> b 00 i 00 n 00 00 00 FF FF </pre> 请注意,每个字符的长度为两个字节,并且名称为null终止。 末尾的两个FF是长文件名末尾的填充。 这也是长文件名条目中的其他FF。 关于长文件名条目,需要注意的最后一件重要的事情是它在偏移量11处的属性字节。 0x0F属性允许我们验证这确实是一个长文件名条目。 ===exFAT上的目录=== exFAT从头开始重新设计了这些目录条目。 {| {{Wikitable}} |- ! 偏移量(字节) ! 长度(字节) ! 含义 |- | 0 | 1 |条目类型 |- | 1 | 31 | 条目剩余部分。 |} 每个条目的基础是它们仍然是32个字节,并且它们都从第一个字节中的类型开始。 我遇到过与从磁盘读取文件相关的类型: ====文件条目==== {| {{Wikitable}} |- ! 偏移量 (以字节为单位) ! 长度(字节) ! 含义 |- | 0 | 1 | 条目类型 = 0x85 |- | 1 | 1 | 二级条目的计数。 |- | 2 | 2 | 条目集合校验和 |- | 4 | 2 | 文件属性 |- | 6 | 2 | 保留 |- | 8 | 4 创建日期和时间 |- | 12 | 4 | 修改日期和时间 |- | 16 | 4 |访问日期和时间 |- | 20 | 1 | 创建毫秒(0-199),将添加到FAT样式日期/时间中,以提高精确度。日期/时间格式见FAT12条目。 |- | 21 | 1 | 修改毫秒 (0-199)。 |- | 22 | 1 |创建时间的UTC偏移量 |- | 23 | 1 | 修改时间的UTC偏移量 |- | 24 | 1 | 访问时间的UTC偏移量 |- | 25 | 7 | 保留。 |} ==== 流“扩展”条目==== 它被称为扩展(extension),但100% 需要直接在 “文件” 条目之后存在。 {| {{Wikitable}} |- ! 偏移量(字节) ! 长度(字节) ! 含义 |- | 0 | 1 |条目类型=0xC0 |- | 1 | 1 |二级标志 |- | 2 | 1 | 保留 |- | 3 | 1 | 名字长度 |- | 4 | 2 |名称哈希 |- | 6 | 2 | 保留 |- | 8 | 8 | 有效数据长度。 写入大文件时,exFAT首先分配整个文件,然后在写入数据时以更新方式修改。 如果它没有有效数据长度告知用户,这种情况确实还不太好处理。 |- | 16 | 4 | 保留 |- | 20 | 4 | 第一个簇。 |- | 24 | 8 | 数据长度。 |} ====文件名条目==== {| {{Wikitable}} |- ! Offset (in bytes) ! Length (in bytes) ! Meaning |- | 0 | 1 | Entry type = 0xC1 |- | 1 | 1 | flags |- | 2 | 30 | File name characters (15 UTF16 code units). |} 要实际使用这些信息,它们通常按如下顺序出现: - 文件条目/File entry - 流扩展条目/Stream extension entry - 文件名条目/File name entry - (附加文件名条目/Additional file name entries) 文件条目包含文件元数据信息,流扩展名告诉你它是如何存储的,文件名条目告诉你它叫什么。 再也没有8.3的名字要求了。 读取文件时,流扩展辅助标志中的第二位指示它是否存储为范围,或者你是否需要使用FAT表。 如果设置了,则文件是连续的,FAT不是最新的;如果设置明确,则FAT是准确的,需要使用(但仍然可以说它是连续的)。 ==== 长文件名 ==== 长文件名条目 “总是” 具有它们所属的常规8.3条目。 长文件名条目总是放在其8.3条目之前。 以下是长文件名条目的格式。 {| {{Wikitable}} |- ! 偏移量 (以字节为单位) ! 长度(字节) ! 含义 |- | 0 | 1 | 本条目在长文件名条目的顺序。 此值帮助你知道此条目中的字符应放置在文件名的何处。 |- | 1 | 10 |该项的前5,2字节字符。 |- | 11 | 1 | 属性。总是等于0x0F。(长文件名属性) |- | 12 | 1 |长条目型。名称条目为零。 |- | 13 | 1 |文件创建时短文件名生成的校验和。 在分区安装在不支持长文件名的系统上的情况下,短文件名可以更改而无需更改长文件名。 |- | 14 | 12 |此条目的接下来6个2字节字符。 |- | 26 | 2 |始终为零。 |- | 28 | 4 | 本条目最后的2,2字节字符。 |} ==编程指南== 本节旨在为你提供有关在FAT文件系统上执行的常见功能的信息。 === 读取引导扇区 === 引导扇区始终位于逻辑扇区编号0处。 你可以将引导扇区读入数组并以这种方式访问其成员,也可以将其读入结构并通过结构进行访问。 无论哪种方式,引导扇区中的值都需要随时可用,以便在FAT文件系统中执行许多操作。 下面是C语言中一些引导扇区结构的示例。 <source lang="C"> typedef struct fat_extBS_32 { //extended fat32 stuff unsigned int table_size_32; unsigned short extended_flags; unsigned short fat_version; unsigned int root_cluster; unsigned short fat_info; unsigned short backup_BS_sector; unsigned char reserved_0[12]; unsigned char drive_number; unsigned char reserved_1; unsigned char boot_signature; unsigned int volume_id; unsigned char volume_label[11]; unsigned char fat_type_label[8]; }__attribute__((packed)) fat_extBS_32_t; typedef struct fat_extBS_16 { //extended fat12 and fat16 stuff unsigned char bios_drive_num; unsigned char reserved1; unsigned char boot_signature; unsigned int volume_id; unsigned char volume_label[11]; unsigned char fat_type_label[8]; }__attribute__((packed)) fat_extBS_16_t; typedef struct fat_BS { unsigned char bootjmp[3]; unsigned char oem_name[8]; unsigned short bytes_per_sector; unsigned char sectors_per_cluster; unsigned short reserved_sector_count; unsigned char table_count; unsigned short root_entry_count; unsigned short total_sectors_16; unsigned char media_type; unsigned short table_size_16; unsigned short sectors_per_track; unsigned short head_side_count; unsigned int hidden_sector_count; unsigned int total_sectors_32; //一旦驱动器真正知道这是什么类型的FAT,就会将其强制转换为特定类型。 unsigned char extended_section[54]; }__attribute__((packed)) fat_BS_t; </source> 可以从引导扇区中提取的重要信息包括: '''总扇区数量(包括VBR):''' <source lang="C"> total_sectors = (fat_boot->total_sectors_16 == 0)? fat_boot->total_sectors_32 : fat_boot->total_sectors_16; </source> '''扇区中的FAT大小:''' <source lang="C"> fat_size = (fat_boot->table_size_16 == 0)? fat_boot_ext_32->table_size_16 : fat_boot->table_size_16; </source> '''根目录的大小 (除非你有FAT32,在这种情况下,大小将为0):''' <source lang="C"> root_dir_sectors = ((fat_boot->root_entry_count * 32) + (fat_boot->bytes_per_sector - 1)) / fat_boot->bytes_per_sector; </source> 这一计算结果将得到汇总。32是FAT目录的大小(字节)。 '''第一个数据扇区(即可以存储目录和文件的第一个扇区):''' <source lang="C"> first_data_sector = fat_boot->reserved_sector_count + (fat_boot->table_count * fat_size) + root_dir_sectors; </source> '''The first sector in the File Allocation Table:''' <source lang="C"> first_fat_sector = fat_boot->reserved_sector_count; </source> '''数据扇区总数: ''' <source lang="C"> data_sectors = fat_boot->total_sectors - (fat_boot->reserved_sector_count + (fat_boot->table_count * fat_size) + root_dir_sectors); </source> '''簇总数:''' <source lang="C"> total_clusters = data_sectors / fat_boot->sectors_per_cluster; </source> 这进行了取整。 ''' 此文件系统的FAT类型: ''' <source lang="C"> if (sectorsize == 0) { fat_type = ExFAT; } else if(total_clusters < 4085) { fat_type = FAT12; } else if(total_clusters < 65525) { fat_type = FAT16; } else { fat_type = FAT32; } </source> ===读取目录=== 读取目录的第一步是查找和读取根目录。 在FAT 12或FAT 16卷上,根目录位于文件分配表之后的固定位置: <source lang="C"> first_root_dir_sector = first_data_sector - root_dir_sectors; </source> 在FAT32和exFAT中,根目录出现在给定簇的数据区域中,可以是簇链。 在exFAT中,它不能编码为扩展(extent),并且将始终存在于FAT中。 <source lang="C"> root_cluster_32 = extBS_32->root_cluster; </source> 对于每个给定的簇号,我们可以计算它的第一个扇区 (相对于分区的偏移量): <source lang="C"> first_sector_of_cluster = ((cluster - 2) * fat_boot->sectors_per_cluster) + first_data_sector; </source> 将正确的簇加载到内存后,下一步是读取并解析其中的所有条目。 每个条目为32字节长。 对于每个32字节条目,这是执行的流程: # 如果条目的第一个字节等于0,则此目录中不再有文件/目录。 FirstByte==0, 完成。 FirstByte!=0, 转到2。 # 如果条目的第一个字节等于0xE5,则该条目未使用。 FirstByte==0xE5, 转到8, FirstByte!=0xE5, 转到3。 # 这个条目是长文件名条目吗? 如果条目的第11个字节等于0x0F,则它是一个长文件名条目。 否则,不是。 11thByte==0x0F, 转到4。 11thByte!=0x0F, 转到5。 # 将长文件名的部分读入临时缓冲区。 Goto 8. # 使用本页上方的表格解析此条目的数据。 保存数据以供以后使用将是一个好主意。 可能在虚拟文件系统结构中。 转到6 # 临时缓冲区中是否有长文件名? 是,转到7。 否,转到8 # 将长文件名应用于你刚刚读取的条目,并清除临时缓冲区。转到8 转到8 # 递增指针和/或计数器,并检查下一个条目。(转到1) 应重复此过程,直到从簇中读取所有条目。 然后,你应该检查簇链中在此簇之后是否还有另一个簇,或者这是否是链中的最后一个簇。 有关更多信息,请参见 [[#跟进簇链|下面的跟进簇链部分]] 和 [[#File_Allocation_Table|FAT]] 部分。 你应该对链中的每个簇执行上述过程,然后执行该过程,直到链中不再剩下簇。 然后,你可以检查你刚才读取的条目中是否有任何是目录。 如果是,则应从存储在条目中的第一个簇号开始,以相同的方式读取它们。 ===跟进簇链=== 跟进簇链有两个基本步骤。 第一步是找出在链中的当前链接之后是否还有另一个 “链接” (簇)。 第二步是实际使用从FAT读取的值来读取下一个扇区。 以下是基本思想: # 从FAT中提取 _current _ 簇的值。(有关如何准确提取值的详细信息,请使用文件分配表上的上一节。) 转到数字2 # 此群集是否标记为链中的最后一个群集?(同样,更多细节请参见上述章节)是,转到4。否,转到3号 # 读取提取的值代表的簇,返回进行更多的目录解析。 # 已找到簇链的末端。我们这里的工作完成了。:) ===读取扩展extents=== 在exFAT上,文件可以在其标志中设置一个位,以指示其存储为extent-based存储。 这意味着整个文件是连续的,并且文件大小加上第一个簇指示 (整个) 文件在哪里。 FAT条目将包含垃圾,不可信。 要阅读此内容,请执行与上面相同的计算,只是你可以在每个步骤中假设下一个簇是数值上的下一个簇,并且已经为文件大小分配了足够的扇区。 == 创建一个新的FAT文件系统 == 通常在开发过程中,你希望使用FAT文件系统创建磁盘映像。 这有两种常见的方法,一种是使用直接在映像上工作的实用程序,另一种是使用[[Loopback Device]]并使用操作系统自己的驱动程序来处理映像。 不太常见的替代方法是在驱动器中安装实际磁盘。 最好用的buildscript工具是[[MTools]]- 它可以使用-i参数直接在磁盘映像上执行所有操作,并以这种方式提供与文件相关的每个DOS命令,仅以<tt>m</tt>为前缀。 它还可以使用配置文件以DOS方式访问驱动器,例如,你可以将A: 和C: 用作实际驱动器。 该工具可以在Windows环境下直接构建,并包含在许多linux软件包管理器中。 仅限Linux的开发人员通常可以使用一些sudo和玩一些权限魔术,将[[Loopback device]]与<TT>mkdosfs</TT>或<TT>mkfs.vfa</TT>以及分区编辑结合起来实现自动化。 此方法的可移植性较差,因为命令通常无法在Linux之外重用。 一些开发人员还犯了错误,将-F传递给mkdosf,试图选择一个FAT大小,这通常会导致创建一个损坏的文件系统,因为结果不符合[#读取引导扇区|FAT大小的官方规则]]。 Windows用户可以将[[Virtual Floppy Drive|VFD]]用于loopback设备。 它带有GUI,但代价是无法在脚本中适当自动化。 ==另见== ===论坛主题=== * 从原始位到目录列表 (发布的[[Topic:11247| 论坛]]代码) * [[Topic:13993|论坛]]中的公共域FAT32代码 * [[Topic:21155|论坛]]中的FAT12/FAT16引导扇区代码 === 外部链接 === * [http://www.osdever.net/downloads/docs/fatgen103.zip FAT32 File System Specification] - 来自Cottontail OS开发库 * [http://download.microsoft.com/download/1/6/1/161ba512-40e2-4cc9-843a-923143f3456c/fatgen103.doc FAT32 File System Specification] - 来自Microsoft(文档,但采用教程样式,包含许多代码示例) * [http://board.flatassembler.net/topic.php?t=12680 About an error in the above specification] * http://scottie.20m.com/fat.htm * http://www.maverick-os.dk/FileSystemFormats/FAT12_FileSystem.html * http://www.pjrc.com/tech/8051/ide/fat32.html * [http://web.archive.org/web/20170112194555/http://www.viralpatel.net/taj/tutorial/fat.php Intro into Sectors and Addressing] * http://elm-chan.org/fsw/ff/00index_e.html - 简单 (V)FAT12/16/32读/写库与良好的文档 * http://gitorious.org/unix-stuff/fat-util - 用于读取、删除和解压缩FAT12、16和32上的文件的实用程序 * http://www.larwe.com/zws/products/dosfs/index.html - 与FAT12/16/32兼容的文件系统驱动程序 * http://www.isdaman.com/alsos/protocols/fats/nowhere/FAT.HTM [[Category:Filesystems]] [[de:FAT]]
本页使用的模板:
模板:Filesystems
(
查看源代码
)
模板:If
(
查看源代码
)
模板:Main
(
查看源代码
)
模板:Show1
(
查看源代码
)
模板:SmallNavBox
(
查看源代码
)
模板:Wikitable
(
查看源代码
)
返回至“
FAT
”。
导航菜单
个人工具
登录
命名空间
页面
讨论
变体
已展开
已折叠
查看
阅读
查看源代码
查看历史
更多
已展开
已折叠
搜索
导航
首页
最近更改
随机页面
MediaWiki帮助
工具
链入页面
相关更改
特殊页面
页面信息