文件系统如何在块设备上可靠写入元数据并实现原子小写入?
你提到的单比特标志位翻转需要读写整块的问题,核心在于磁盘硬件的原子写入粒度就是物理块大小(比如你说的512字节),但现代文件系统通过多种成熟机制,在不牺牲抗崩溃性的前提下优化这类小操作:
依赖磁盘原子块写入特性
几乎所有现代磁盘都保证单个物理块的写入是原子的——要么整个块写入成功,要么完全失败,不会出现半写状态。所以哪怕你只修改了块里的1个比特,写入整块的操作在硬件层面就是不可拆分的,崩溃后要么旧块完整保留,要么新块完全生效,完全符合你要的高可靠性。没必要纠结“只写1比特”,因为硬件本身就不支持更小的原子写入单元。日志式文件系统的日志缓冲
Ext3/Ext4、NTFS这类日志文件系统,会把元数据的修改操作先写入一个独立的日志区块(同样按块写入)。等日志写入确认后,再去更新实际的元数据块。崩溃恢复时,系统会检查日志:如果日志里的操作已经完成,就把修改同步到元数据;如果未完成,直接丢弃该日志操作,确保元数据始终处于一致状态。对于标志位翻转这类操作,日志只需要记录对应的完整块写入动作,恢复时直接重建该块即可。写时复制(COW)从根源规避读改写
Btrfs、ZFS这类COW文件系统彻底避免了传统的读-改-写流程。当需要修改元数据(包括翻转标志位)时,系统会先复制整个元数据块,在副本上完成所有修改,然后原子性地更新指向该元数据的上层指针。这个指针更新是单个块级的原子操作,崩溃后要么旧指针有效(指向旧元数据),要么新指针有效(指向修改后的副本),绝不会出现元数据损坏。而且COW天然支持元数据冗余副本(比如ZFS的镜像或RAID-Z),进一步强化可靠性。元数据打包与对齐优化
多数文件系统会把多个小元数据结构(比如你说的64字节文件大小、版本标志位)打包到同一个物理块中,并且严格对齐物理块边界,避免跨块写入(跨块就无法保证原子性)。比如把文件大小、版本标志、inode的其他属性都塞进同一个inode块,修改时直接写入整个inode块即可,既保证了原子性,也避免了额外的IO开销。
内容的提问来源于stack exchange,提问作者Marco

