CS140e: (3) 文件系统

CS140e: (3) 文件系统

这次作业的主题是 FAT32 文件系统的实现。

第一部分是实现 memory allocator, 这样之后就可以用需要在堆上分配内存的数据结构了。但是在这之前要实现一个更好的 panic 取代之前一 panic 就 abort 的行为。这里就引入了 Language Items 的概念,就是通过标注,通知编译器在特定情况下插入对我们选择的函数的调用。完成后 panic 就可以显示一些有用的信息,方便调试了。过程中发现因为从 ttywrite 发送完 kernel image 到 screen 接上去之间有时间差,会错过一些输出,所以我修改了下 bootloader, 收到 kernel 后不立即启动,等 screen 接上按任意键启动,这样就不会错过任何输出了。

接下来,要实现对 ARM tags 的读取,以获得可用内存的信息。读取的过程中要用到 union, 这是一个 unsafe 的东西,也进行了封装,可以说这已经成为了这门课程的一种习惯了。

之后为 allocator 实现了一些工具函数,包括对齐和获取可用内存的范围。期间对比了 C 的allocator 接口和 Rust 的 allocator 接口,个人认为 Rust 中申请者可以指定对齐,无疑比起 C 钦定一个对齐值更灵活,但是这样的灵活性也给 allocator 实现带来了不小的麻烦。Rust 中申请者需要自己记住申请内存的有关信息(大小,对齐)以在释放时提供,其实逻辑上是更合理的,也简化了 allocator 的职责。

接下来是实现 allocator 了,实现的 allocator 最终通过 #[global_allocator] 的标注注册。一共实现了两种 allocator, 第一个是最 naive 的,无脑向后面分配,完全不回收。第二种使用一系列链表维护不同大小类别的空间。(这里用到了 intrusive linked list 其实为之后留下了坑)利用 #[path] 标注可以在两种实现间切换,可以说是非常巧妙了。

第二大部分是实现 FAT32, 主要是照着 FAT32 文档实现,因为 FAT32 毕竟是实际应用的文件系统,还是有点麻烦的,花了不少时间。这里也体现出了 CS140e 的试验性,一度给出的测试是错的,似乎是标准实现没有正确处理被删掉的文件,列出目录内容的时候也会列出,让人有点困扰。

第三部分首先是需要封装 SD card driver, 属于简单的 Rust FFI 教学。值得注意的是, sd_readsector() 如果出错,会将错误信息写入全局变量 sd_err, 这不是线程安全的,所以需要加锁保护一下。

接下来需要让 SD card driver 和之前的 FAT32 相结合,本来感觉不复杂,但是真正上树莓派测试却出现了诡异的错误,运行到一半就跑不下去了。我原以为是 FAT32 实现的问题,但将 SD 卡用 dd 做成镜像到本地测试(因为树莓派上调试不便),却发现一切正常。用 panic/kprintln 调了一天,终于把出错范围缩小到 memory allocator 里 intrusive linked list 的某处 unsafe 里的一个内存写。原来 arm 对非对齐的内存访问是会 fault 的。那么测试时又为何不会出现这个问题呢?因为 x86 不对齐也可以访问。因为 intrusive linked list 会在分配的地址空间写入一个 usize, 因此每块分配的内存都至少需要满足 usize 的大小和对齐。修复后便可以正常工作了。(中间有个小插曲,我一度认为 64 位下 usize 是 4 bytes, 但实际是 8 bytes)但是回顾 FAT32 的实现,对齐问题并没有根本上解决:实现中 block 在内存中是以 [u8] 存储,其对齐为 1, 这样转换为 FAT32 的一些 struct 后,对某些 field 的读取可能是非对齐的。不过由于这些 field 的对齐要求都没有 usize 严,所以并不会暴露出来。由于我不知道怎么限制 [u8] 的对齐,所以这个修复还处于坑掉的状态。希望知道怎么解决的朋友教我一下。

最后一部分是实现 cd, ls, pwd 和 cat 这几个命令,有了之前的铺垫,实现都不复杂。

通过调试这个作业的经历,我深深感受到了 unsafe 其实不好写,要写对需要考虑的东西很多,尤其是需要跨平台的时候。想想之前写的东西,又有多少是真正 safe 的呢。

编辑于 2018-03-16

文章被以下专栏收录