恒美微站 Logo 恒美微站
  • 首页
  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心
  • 联系我们

FAT12根目录遍历:从裸机扇区到文件系统索引链路

  • 首页
  • 资讯中心
  • /
  • FAT12根目录遍历:从裸机扇区到文件系统索引链路

相关资讯

Sunshine游戏串流5步搭建指南 2026/9/4 16:18:19
BlenderMCP:自然语言一句话建 3D 2026/9/4 16:18:19
基于YOLOv11的乡村道路障碍物检测:从数据构建到边缘部署全流程实战 2026/9/4 16:18:19

最新资讯

Spring Cloud Gateway实战:从零构建微服务API网关与避坑指南
基于STM32与ATT7022E的高精度单相电能计量模块全链路设计
昇腾大模型训练调优实战:从迁移到性能优化的完整指南
奥迪A4L第四代蓝牙数字钥匙改装全攻略:原理、安装与排错指南
AI视频搜索技术解析:从多模态向量检索到Python语义搜索Demo
STM32步进电机梯形加减速驱动实现:从算法原理到工程实践

今日推荐

爬虫防护实操:出海网站拦截恶意采集、垃圾爬虫、无效刷量,CDN 精准防护落地指南
STM32H743 SPI从机DMA双缓冲通信实战
CPU开盖降温教程:20元成本让温度直降30度的原理与实践

本周热门

备战数据库管理工程师校招:索引、事务、备份恢复核心考点解析
数字电路时序基石:深入理解建立时间与保持时间
蓝桥杯国赛超声波测距机:从单片机原理到嵌入式系统实战

本月精选

自研推理加速器Redwood:两周内实现PyTorch模型高效部署的实战教程
V4L2摄像头采集实战:从camera_client.rar到出图全流程解析
从“谁发明了钢琴键”到知识问答智能体:RAG与记忆工程实践

FAT12根目录遍历:从裸机扇区到文件系统索引链路

发布时间:2026/9/4 16:23:19
FAT12根目录遍历:从裸机扇区到文件系统索引链路 如果你已经写出了一个能在裸机上启动的 kernel屏幕能打印字符段寄存器也切换到位下一步往往会产生一个很自然的念头让 kernel 从磁盘上读取一个文本文件解析里面的配置项或者直接显示出来。听起来只是比读扇区多了一步真正动手时才会撞到一面墙——磁盘上没有“文件”。你手里只有一堆编号从 0 到 2879 的扇区每个扇区 512 字节bootloader 阶段可以约定“kernel 固定放在第几个扇区”但一份文件如果分散在多个扇区、甚至多个簇里没有目录、没有索引你就完全不知道下一个数据块在哪里。FAT12 就是解决这个问题的最基础方案。这篇博客记录的是 OS 开发入门里一个很值得做的实验在实模式 kernel 中遍历 FAT12 根目录解析出文件名、扩展名、文件大小、属性以及起始簇号。这个实验表面上是写一个扫描循环真正练到的是把磁盘从“线性扇区集合”理解成“有结构的文件空间”。如果你只是照着别人的代码抄一遍看到几个文件名打印出来就收工那收获会非常有限。每一步计算都要能解释清楚为什么数据区起点是这个公式为什么 FAT 表项要跨字节读取为什么目录项里没有文件内容。1. 这个实验真正让你跨过的坎是把磁盘从“扇区集合”看成“文件系统”1.1 kernel 里为什么不能直接“打开文件”在应用开发里open(config.txt)是一个系统调用用户态程序只需要指定路径剩下的解析目录、读取索引、分配缓冲区、处理磁盘错误全部由内核完成。但是在写自己的 kernel 时没有现成的运行时兜底。你面对的就是一块裸磁盘甚至可能只是一份软盘镜像文件。bootloader 能加载 kernel靠的是“固定位置”约定编译期知道 kernel 镜像会写到磁盘的某个扇区于是直接用 BIOS 中断按 CHS 或 LBA 地址读取。这在小 kernel、固定大小、固定扇区的场景下没问题。可一旦 kernel 需要读取配置项、读取用户数据或者加载更大的模块这个模式就崩了。因为你不可能每次编译都去计算“配置文件放在哪个扇区”更不可能保证文件内容不会跨多个扇区。文件系统存在的意义就是在物理扇区之上建立一层索引。这层索引让数据可以被定位、被枚举、被按名字访问。在 PC 平台历史上FAT12 最早出现在软盘上结构足够简单没有 inode 位图、没有日志、没有 B 树一共就是几个区域加一张表。拿它入门可以看清楚文件系统最本质的工作把块设备上无序的扇区组织成有名字、有大小、有内容边界的数据对象。1.2 FAT12 恰好是理解索引结构的最佳入口FAT12 整个文件系统由几个连续区域构成保留区第一个扇区是引导扇区里面放着 BPB 参数、FAT 表区、根目录区、数据区。要定位一个文件需要经历一条清晰的索引链路从引导扇区的 BPB 参数里读出各区域的位置和大小。根据参数计算根目录区起始扇区。在根目录区里按 32 字节一个目录项找到目标文件名。从目录项里取出起始簇号。沿 FAT 表顺藤摸瓜找到文件占用的所有簇。把簇换算成数据区扇区读出文件内容。这也是为什么很多人会建议把 FAT12 处理好之后再考虑 FAT16/FAT32 或者 ext4。FAT 家族的簇、目录项、数据区划分一脉相承ext4 虽然多了很多机制但“元数据描述数据位置”的思想是一样的。这个实验里你不需要一开始就完成第 5、6 步。先把“根目录遍历”跑通能看到文件名和起始簇号已经是在和文件系统真正交互了。2. 动手之前先把 FAT12 的四个区域在脑子里摆出来2.1 BPB 参数整个文件系统操作的地基FAT12 的第一个扇区是引导扇区前 512 字节里既包含启动代码也包含一个叫 BPBBIOS Parameter Block的参数区域。文件系统的任何计算都从 BPB 开始。如果你写死了“根目录在扇区 19”那只是恰好匹配 1.44MB 软盘换一个镜像、换一张磁盘结果就是乱码甚至死循环。以 1.44MB 软盘为例BPB 里最重要的几个字段如下字段偏移字节长度常见值每扇区字节数0x0B2512每簇扇区数0x0D11保留扇区数0x0E21FAT 表数量0x1012根目录项数0x112224总扇区数16位0x1322880如果为 0再看 0x20 的 32 位字段每 FAT 扇区数0x1629从这些字段可以算出三个关键位置// 常见表达式具体类型按实模式环境调整 uint16_t fat_region_sectors bpb.fat_count * bpb.sectors_per_fat; uint16_t root_dir_start bpb.reserved_sectors fat_region_sectors; uint16_t root_dir_sectors (bpb.root_entry_count * 32 bpb.bytes_per_sector - 1) / bpb.bytes_per_sector; uint16_t data_start root_dir_start root_dir_sectors;1.44MB 软盘代入后root_dir_start 1 2 * 9 19root_dir_sectors 224 * 32 / 512 14根目录区占用第 19 到第 32 扇区data_start 33。关键不是记住这几个数字而是理解公式。保留扇区数不一定总是 1FAT 表数量不一定总是 2根目录项数也不一定总是 224。凡是能从 BPB 读出来的值都不要在代码里写死。2.2 目录项不是文件内容而是文件元数据的入口根目录区是由一组 32 字节的目录项组成的。每个目录项描述一个文件或子目录但它并不保存文件内容它保存的是“文件元数据”文件名、属性、起始簇号、文件大小。一个典型的 FAT12 目录项结构如下偏移长度含义08文件名不足 8 字节用空格填充83扩展名111属性字节1210保留字段Windows NT 下可能用于大小写信息222最后写入时间242最后写入日期262起始簇号FAT12 中低 16 位284文件大小字节属性字节的低 6 位含义要熟记0x01只读0x02隐藏0x04系统0x08卷标0x10子目录0x20归档0x0F长文件名分块条目LFN基础实验可以先跳过另外还有两个特殊标记目录项第一个字节是0x00表示后面再没有有效目录项扫描到它就应该结束第一个字节是0xE5表示这个目录项对应的文件已经被删除应跳过。这里最容易误解的是目录项里只有“起始簇号”没有内容。文件内容在哪、下一个数据块是什么都要靠 FAT 表去查。目录项只是入口。2.3 FAT 表把文件内容串成链的分配表FAT 表是一张表项数组FAT12 里每个表项占 12 位。每个表项对应数据区中的一个簇表项的值表示这个簇的分配状态或者“下一个簇号”。0x000空闲簇0xFF0 - 0xFF6保留簇0xFF7坏簇0xFF8 - 0xFFF文件的最后一个簇结束标记其他值下一个簇号可以把它理解成一个集中存储的链表每个节点的 next 指针不在节点内部而是统一放在 FAT 表里。文件系统把文件数据按簇分配第一个簇号写在目录项里后续簇号沿 FAT 表逐项读取。用簇而不是扇区做分配单位核心原因是减少索引开销。1.44MB 软盘每簇只有 1 个扇区如果每簇 4 个扇区FAT 表项数量会减少到四分之一但内部碎片会变大。FAT12 选择 12 位表项同样是空间换复杂度——表项小就能在有限空间里描述更多簇。3. 根目录遍历的完整流程从扇区号到目录项3.1 先算出根目录区的位置和大小在实模式 kernel 里读取磁盘通常还是通过 BIOS 中断int 13h的AH02h按扇区读取。读软盘时需要把 LBA 扇区号转换成 CHS柱面/磁头/扇区地址因为 BIOS 调用接受的是 CHS 参数。转换公式是cylinder lba / (sectors_per_track * heads); head (lba / sectors_per_track) % heads; sector (lba % sectors_per_track) 1; // 扇区号从 1 开始1.44MB 软盘通常每磁道 18 扇区2 个磁头每面 80 磁道。如果你的实验环境是 QEMU、Bochs 这类模拟器也可以直接读软盘镜像的内存副本先跳过 CHS 转换专心验证文件系统逻辑。能跑通后再把底层读取换成真正的中断调用这样问题定位更清晰。读取根目录区的流程是分配一块内存缓冲区大小至少是root_dir_sectors * 512字节。从root_dir_start开始连续读取root_dir_sectors个扇区。把缓冲区按 32 字节切分成目录项数组。这里需要注意根目录项数是 224根目录区占用 14 个扇区正好能放下224 * 32 7168字节也就是 14 个扇区。如果用其他格式的镜像根目录项数不同占用扇区数要按公式重新计算。3.2 逐个检查目录项空闲、删除、卷标、子目录还是普通文件遍历逻辑的关键是判断顺序。建议按以下顺序处理每个目录项如果第 0 字节是0x00停止扫描后面都是空闲项。如果第 0 字节是0xE5文件已删除跳过。如果属性字节是0x0F长文件名分块基础实验跳过。如果属性字节含0x08卷标不是文件跳过。如果属性字节含0x10子目录可以先打印信息不进入。否则普通文件提取文件名、大小、起始簇号。一个简单的 C 语言风格示例uint8_t *root_dir (uint8_t *)ROOT_BUF; for (int i 0; i bpb.root_entry_count; i) { uint8_t *entry root_dir i * 32; if (entry[0] 0x00) break; // 结束标记 if (entry[0] 0xE5) continue; // 已删除 if (entry[11] 0x0F) continue; // LFN 条目 if (entry[11] 0x08) continue; // 卷标 if (entry[11] 0x10) { // 子目录 print_dir_name(entry); continue; } uint8_t name[13]; format_83_name(entry, name); // 处理 8.3 文件名 uint16_t cluster entry[26] | (entry[27] 8); uint32_t size entry[28] | (entry[29] 8) | (entry[30] 16) | ((uint32_t)entry[31] 24); printf(%s size%u cluster%u\n, name, size, cluster); }8.3 文件名处理时要注意文件名字段和扩展名都用空格填充显示前要把空格去掉中间补一个点。比如文件名是KERNEL BIN要格式化成KERNEL.BIN。3.3 实验验证拿你的输出和系统工具对比遍历代码写完怎么确认结果是对的最好用系统里现成的工具对比。Linux 下可以用mcopy、mdir来读取 FAT12 镜像mdir -i floppy.img或者直接file floppy.img确认镜像格式。你还需要一个已知内容的 FAT12 镜像。用 Linux 的mkfs.fat可以生成dd if/dev/zero offloppy.img bs512 count2880 mkfs.fat -F 12 floppy.img mcopy -i floppy.img test.txt ::TEST.TXT然后把镜像挂载到 kernel 模拟环境里运行你的内核看打印的文件名是不是和mdir输出一致。这一步非常关键一旦文件名、大小、起始簇号能和宿主机工具对得上说明 BPB 解析、扇区读取、目录项结构都对了。验证时不要只看“程序没有崩溃”就算成功。文件系统层最容易出现的错误是“数据错但流程正常”——遍历顺序没问题但算出来的起始扇区位置偏了一个打印出来的文件名全是乱码。4. FAT 簇链从找到文件到真正知道数据在哪4.1 目录项里为什么只保存起始簇号目录项里保存的是起始簇号而不是起始扇区号这一点很多初学者会困惑。原因是文件系统的分配单位是“簇”不是“扇区”。簇可以包含 1 个或多个连续扇区FAT12 软盘常见每簇 1 扇区FAT32 常见每簇 8 扇区甚至更多。用簇作为分配单位之后FAT 表的表项数量明显减少也让文件增长变得容易。当一个文件装不下当前簇时文件系统就在 FAT 表里找一个空闲簇把当前簇的 FAT 表项指向新簇号。这样文件内容可能分散在磁盘不同位置但通过 FAT 表链可以完整串联。把簇号转换成数据区的扇区号公式是uint32_t sector data_start (cluster - 2) * sectors_per_cluster;为什么是cluster - 2因为 FAT 表项 0 和 1 有含义实际数据簇从 2 开始。这一点如果漏掉读出来的扇区位置会整体偏移两个簇。4.2 12 位表项的读取跨字节边界的坑FAT12 的每个表项占 12 位1.5 字节这就意味着表项不是从字节边界开始的。读取时要做偏移计算和位掩码。第 n 个 FAT 表项在 FAT 表中的字节偏移是uint16_t offset n n / 2;接下来分两种情况n 是偶数从offset开始取 16 位低 12 位是第 n 个表项。n 是奇数从offset开始取 16 位高 12 位是第 n 个表项。代码示例uint16_t fat_get_next(uint8_t *fat, uint16_t cluster) { uint16_t offset cluster cluster / 2; uint16_t entry fat[offset] | (fat[offset 1] 8); if (cluster 1) { return entry 4; // 奇数高 12 位 } else { return entry 0x0FFF; // 偶数低 12 位 } }这个细节是 FAT12 最容易写错的地方。错一个位读出来的下一个簇号就会完全不对整个文件内容跟着乱。建议先在调试器里打开 FAT 表数据手动算几个表项确认代码逻辑正确后再继续。4.3 顺着簇链读取完整文件内容拿到起始簇号后读取整个文件内容的循环很直观uint16_t cluster start_cluster; while (cluster 2 cluster 0xFF8) { uint32_t sector data_start (cluster - 2) * sectors_per_cluster; read_sectors(buffer, sector, sectors_per_cluster); // 处理 buffer 中的文件数据 cluster fat_get_next(fat_buf, cluster); }循环终止条件要注意正常文件链最后遇到0xFF8 - 0xFFF表示文件结束。如果遇到0xFF7说明某个簇被标记为坏簇需要报错。如果遇到0xFF0 - 0xFF6属于保留值罕见但需要处理。单文件遍历跑通之后可以试着做一个更完整的实验给定一个文件名遍历根目录找到它沿 FAT 链把内容读到内存再以十六进制形式打印。这个实验做完你就已经实现了最简单的open和read的雏形。5. 实验里最容易翻车的几个细节很多错误不会报错只会错数据5.1 BPB 参数不要写死用公式计算而不是调试值把根目录起始扇区写成 19在 1.44MB 软盘上没问题但这会成为陷阱。换一个 720KB 软盘每 FAT 扇区数、总扇区数都不同根目录区位置就会变。如果代码里到处是魔法数字排错时会非常痛苦。更合理的做法是kernel 启动时从引导扇区把 BPB 读进一个结构体之后所有文件系统计算都从结构体取值。哪怕当前只支持 1.44MB 软盘代码结构也是可扩展的。5.2 设备上有两份 FAT但很多实验只用第一份FAT 文件系统为了安全会在磁盘上保存两份 FAT 表。实验阶段只读第一份没有大问题但要意识到第二份的存在。如果你的镜像格式被其他工具改写过或者 FAT1 出现坏扇区FAT2 就是备用数据源。调试时可以对比 FAT1 和 FAT2 的字节内容。正常情况下它们应该完全一致如果不一样可能镜像本身有问题或者你对 FAT 区的起始位置理解错了。5.3 小端序、字段偏移、8.3 命名三个容易一起出问题的点x86 是小端序FAT12 里的多字节字段也是小端存储。读 16 位起始簇号时应该手动拼接uint16_t cluster entry[26] | (entry[27] 8);不要直接写成*(uint16_t *)(entry 26)。在实模式下未对齐访问通常不会报错但在其他架构或更严格的模拟环境下行为可能不同。手动拼字节也更符合“我在解析一个外部数据格式”的思维方式。8.3 文件名同样容易踩坑。文件名字段不足部分用空格填充不能直接把 8 个字符打印出来。常见的处理是先按长度复制并按需合并扩展名再截断尾部空格。5.4 先只做根目录不要一上来就处理子目录和 LFN子目录遍历和长文件名解析是后续实验的内容。根目录遍历还没跑通时就把 LFN 和子目录逻辑掺进来错误来源会成倍增加。建议实验 1 的验收标准就三条能列出镜像根目录下的全部普通文件名。能正确解析每个文件的大小和起始簇号。输出结果和宿主机工具一致。遇到 LFN 条目直接跳过遇到子目录只打印名称不进入。等这一层稳定了再扩展去处理子目录遍历那才是实验 2、实验 3 的事。6. 完成遍历之后下一阶段该补什么6.1 从遍历到读取文件内容才是真正的“能访问文件”根目录遍历只是拿到文件入口。要真正“读取文件”还需要把起始簇号、FAT 链、数据区扇区读取串起来。建议按这个顺序继续实现一个read_file_by_name(const char *name, uint8_t *buf)。先只支持根目录下的固定文件例如读取README.TXT。把文件内容逐字节打印和宿主机里的原始文件对比。再逐步支持子目录路径以及更通用的文件名解析。这样每一步的输入输出都可控出错时能快速定位是目录解析的问题、FAT 链读取的问题还是扇区读取的问题。6.2 先跑通再抽象别急着设计通用的文件系统接口看到这里你可能已经开始想是不是应该设计一个open/read/close接口再加一层文件描述符管理甚至做内存缓存想法没错但顺序很重要。如果连 FAT12 基本遍历都没做对抽象出来的接口会掩盖底层逻辑问题。你写了一个open()结果返回错误码你根本分不清是文件系统解析错误、磁盘读取错误还是接口本身的边界条件错误。先用最原始的方式把从“扇区编号”到“文件字节流”的完整链路走通。然后再考虑把这段逻辑封装成接口。这也是很多 OS 开发教程会把实验拆成多个阶段的原因——先见森林再见树木最后把两者合起来。6.3 从 FAT12 到 VFS这次实验在整条学习路径里的位置当你把 FAT12 的文件遍历、内容读取都做完再往后看文件系统演进就会清楚很多。FAT16、FAT32 本质上做的事和 FAT12 一样只是簇号从 16 位扩展到 32 位根目录从固定大小变成可扩展区域。再到 VFS虚拟文件系统它是在各种具体文件系统之上抽象出一层统一接口让open、read、write不需要感知底层是 FAT、ext4 还是网络文件系统。而这次实验对你个人的价值在于你亲手把“字节块”翻译成了“文件”。以后无论遇到多复杂的文件系统概念你都能知道它最终要解决的还是同一个问题——让数据可以被命名、被定位、被按顺序读取。跑通根目录遍历不是终点。真正的收获是你已经能把“磁盘块”和“文件数据”之间的映射关系讲清楚并且知道为什么文件系统需要区、表、目录项、簇链这些结构。这个理解比打出一串文件名值钱得多。

关于恒美微站

恒美微站专注于为个体商户、工作室提供极简自助建站服务,让每个人都能轻松拥有专业网站。

快速链接

  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心

服务项目

  • 可视化建站
  • 拖拽编辑
  • 主题定制
  • SEO 优化
  • 网站托管

联系方式

  • 📍 地址:北京市朝阳区建国路 88 号
  • 📞 电话:400-888-8888
  • ✉️ 邮箱:info@hmyw.cn
  • 🕐 时间:周一至周日 9:00-18:00

© 2024 恒美微站 hmyw.cn 版权所有 | 京 ICP 备 12345678 号