手写FAT文件系统:Rust Hypervisor Firmware的FAT12/16/32目录遍历实现原理

手写FAT文件系统:Rust Hypervisor Firmware的FAT12/16/32目录遍历实现原理

【免费下载链接】rust-hypervisor-firmware 【免费下载链接】rust-hypervisor-firmware 项目地址: https://gitcode.com/gh_mirrors/ru/rust-hypervisor-firmware

你有没有想过,一台虚拟机在没有 BIOS、没有 UEFI 的情况下,是怎么从磁盘里找到并启动 Linux 内核的?答案就藏在一个不起眼却至关重要的模块里——FAT文件系统Rust Hypervisor Firmware(rust-hypervisor-firmware)是一个用 Rust 编写的极简虚拟化固件,它不依赖 TianoCore/edk2 这类复杂固件,而是自己手写了一套支持 FAT12/16/32 目录遍历与文件读取的代码,从磁盘镜像中直接加载引导程序。这篇文章将带你深入 src/fat.rs,逐行拆解这套目录遍历的实现原理,让新手也能看懂"手写 FAT 文件系统"这件事到底是怎么做到的。

为什么虚拟机固件需要自己实现 FAT 文件系统?📦

现代 UEFI 固件动辄上百万行代码,而 rust-hypervisor-firmware 的目标恰恰相反——用最少的代码完成"从磁盘引导内核"这一件事。它的启动链路是这样的:

  1. 通过 virtio-blk 协议读取磁盘扇区(src/block.rs
  2. 解析 GPT 分区表,找到 EFI 系统分区(src/part.rs
  3. 用自研 FAT 文件系统遍历目录、读取内核文件src/fat.rs
  4. 按 Boot Loader Specification 解析引导配置,加载 bzImage(src/loader.rs

整条链路在 src/main.rsboot_from_device 函数中一气呵成。可以说,FAT 文件系统实现是这个固件的"心脏",没有它,固件连内核文件都读不到。

FAT 文件系统结构速览:从引导扇区到数据区 🗂️

要理解目录遍历,先要理解 FAT 磁盘的物理布局。一张 FAT 格式的磁盘从上到下依次是:

区域说明
保留区(Reserved)第一个扇区是引导扇区(Boot Sector),存放 BPB 参数
FAT 表区记录每个簇(Cluster)的下一跳地址,构成"簇链"
根目录区FAT12/16 的根目录固定在这里;FAT32 的根目录只是一个普通目录
数据区真正的文件内容,按簇(Cluster)为单位分配

固件在 src/fat.rsinit() 函数中读取引导扇区,把关键字段解析出来:每扇区字节数、每簇扇区数、FAT 表个数、FAT 表大小、根目录项数等。注意这些字段都是通过 #[repr(C, packed)] 结构体直接映射到扇区字节上的,这正是 Rust 做底层开发的优雅之处——结构体即协议

FAT12、FAT16、FAT32 的判定方法

不同 FAT 类型的主要区别是 FAT 表项宽度不同,而判定依据是数据区的簇数量

  • 簇数 < 0xFF5(4085)→ FAT12
  • 簇数 < 0xFFF5(65525)→ FAT16
  • 否则 → FAT32
self.fat_type = if self.clusters < FAT12_MAX {
    FatType::FAT12
} else if self.clusters < FAT16_MAX {
    FatType::FAT16
} else {
    FatType::FAT32
};

一个文件在磁盘上占用若干簇,这些簇通过 FAT 表串联成一条"簇链"。读文件的过程,本质上就是沿着簇链逐个簇地读下去。这是 FAT 系列文件系统最核心的思想。

FAT 目录项(Directory Entry)的底层结构 📄

目录在 FAT 里不是一个特殊对象,它就是一个特殊的文件,其内容是一连串 32 字节的目录项。每个 32 字节的短文件名目录项(Short Name Entry)结构如下:

偏移长度字段说明
011name8.3 短文件名(8 字节名 + 3 字节扩展名)
111flags属性:0x10 表示目录,0x0F 表示 LFN 长文件名项
202cluster_high起始簇号高 16 位(FAT32 使用)
262cluster_low起始簇号低 16 位
284size文件大小(字节)

src/fat.rsFatDirectory 结构体中,这些字段被一一映射。目录项的名称、起始簇号、大小这三个信息,就是遍历目录的全部"弹药"。

8.3 短文件名与 LFN 长文件名解析

传统 DOS 只支持 8.3 短文件名,但现代磁盘上到处都是长文件名。FAT 的解决方案是:在真正的短文件名目录项之前,插入一个或多个 LFN(Long File Name)目录项,属性标记为 0x0F,每项携带 13 个 UTF-16 字符,多个 LFN 项按序号逆序排列。

固件在 src/fat.rs 定义了 FatLongNameEntry,并在 next_entry() 中按序号把字符拼接回 long_entry 数组,最后通过 ucs2_to_ascii 转成 ASCII(src/fat.rs)。这就是为什么固件既能读 BOOTX64.EFI 这样的短名,也能读 longfilenametest 这样的长名。

目录遍历核心算法:next_entry 一步步怎么走 🚶

现在到了本文的重头戏——目录遍历。固件把"读一个目录项"封装成了 Directory::next_entry()src/fat.rs),它的处理逻辑相当清晰:

  1. 调用 read_next 读取当前扇区(512 字节,正好 16 个目录项)
  2. 逐项检查,遇到三种特殊标记:
    • name[0] == 0x00:目录结束标记,返回 EndOfFile
    • name[0] == 0xE5:目录项已被删除,跳过
    • flags == 0x0F:LFN 长文件名项,拼接到 long_entry 后继续
  3. 遇到正常目录项,组合出 DirectoryEntry(文件名、类型、簇号、大小)返回

跨簇遍历:目录太大放不下怎么办?

一个目录也可能占用多个簇。当当前簇的扇区读完后,read_nextsrc/fat.rs)会调用 next_cluster 沿着 FAT 表跳到下一个簇继续读:

if self.sector >= self.filesystem.sectors_per_cluster {
    match self.filesystem.next_cluster(self.cluster.unwrap()) {
        Ok(new_cluster) => {
            self.cluster = Some(new_cluster);
            self.sector = 0;
            self.offset = 0;
        }
        ...
    }
}

配合 seek(0) 重置遍历位置,目录可以反复扫描,这正是后面路径解析的基础。测试用例 test_fat_large_directorysrc/fat.rs)专门验证了跨簇大目录的遍历能力。

FAT 表簇链遍历:FAT12/16/32 三种读取方式 ⛓️

簇链是 FAT 的灵魂,而 next_cluster()src/fat.rs)用三个分支优雅地处理了三种 FAT 类型,这也是手写 FAT 文件系统中最考验细节的地方:

  • FAT12:每 1.5 字节存一个表项,实现时先按 cluster + cluster / 2 定位偏移,再根据簇号奇偶性取低 12 位或高 12 位,还要处理偏移恰好落在扇区末尾的跨界情况(读取下一个扇区的第一个字节)。
  • FAT16:每 2 字节一个表项,直接按小端序读出即可,表项值 ≥ 0xFFF8 表示文件结束。
  • FAT32:每 4 字节一个表项,但只取低 28 位(& 0x0FFF_FFFF),高 4 位保留。

文件读取器 File::readsrc/fat.rs)每次读满一个簇的扇区后,就调用 next_cluster 跳转到下一个簇,直到表项值达到结束标记。值得称赞的是,它还精细地处理了最后一个扇区只有部分有效数据的情况,返回的实际字节数不会超出文件大小。

从根目录到任意路径:open 路径解析实战 🔍

有了逐项遍历的能力,路径解析就水到渠成了。Filesystem::opensrc/fat.rs)从根目录出发,open_fromsrc/fat.rs)把路径按 /\ 切分成逐级目录名:

  1. 在当前位置逐项扫描目录,用 compare_name大小写不敏感的短名匹配(src/fat.rs
  2. 匹配到目录且路径未结束 → 进入该子目录继续循环
  3. 匹配到文件 → 返回 File 节点

测试用例 test_fat_open 中,固件成功解析了 \EFI\BOOT\BOOTX64.EFI 并定位到簇号 166、大小 92789 字节的文件,验证了整条路径解析链路的正确性。这套实现同时兼容正反斜杠,对 Windows 风格路径也游刃有余。

完整启动链路:固件如何用 FAT 加载内核 🚀

理解了目录遍历,再回头看完整启动流程就一目了然了。boot_from_device 找到 EFI 分区后创建 Filesystem 并调用 init(),随后交给 loader::load_default_entry

  1. 打开 /loader/loader.conf,读取 default 字段(src/loader.rs
  2. 用 glob 模式匹配 /loader/entries/ 下的 .conf 文件(src/loader.rs
  3. 解析 linuxinitrdoptions 三行配置
  4. 通过 fs.open() 打开内核和 initrd,加载进内存后跳转执行

如果 BLS 引导失败,固件会优雅降级:直接打开 \EFI\BOOT\BOOTX64.EFI,用内置的 PE32+ 加载器启动 shim + GRUB2。无论哪条路,都离不开FAT 目录遍历这块基石。

总结 📝

从引导扇区解析到目录项读取,从 FAT 表簇链追踪到长文件名拼接,rust-hypervisor-firmware 用不到一千行代码,就在 no_std 的裸机环境下完整实现了 FAT12/16/32 文件系统的读取能力。对于想学习文件系统原理、Rust 底层编程或虚拟化固件开发的读者来说,src/fat.rs 就是一份不可多得的"活教材"——它没有层层抽象,把 FAT 最本质的数据结构直接映射到内存里,清晰、完整、可运行。如果你也想体验"手写 FAT 文件系统"的快感,不妨 clone 下来跑一遍它的单元测试,亲手感受从扇区字节到完整文件路径的奇妙旅程。

【免费下载链接】rust-hypervisor-firmware 【免费下载链接】rust-hypervisor-firmware 项目地址: https://gitcode.com/gh_mirrors/ru/rust-hypervisor-firmware

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值