YAFFS2(Yet Another Flash File System 2)是专为NAND Flash设计的日志结构文件系统,其存储结构针对Flash特性(如擦除块、坏块管理、磨损均衡)进行了深度优化。以下是其核心存储结构的详细解析:
一、物理存储单元划分
层级 | 描述 |
---|---|
块(Block) | NAND Flash的基本擦除单位(通常128KB~2MB),YAFFS2以块为单位管理空间。 |
页(Page/Chunk) | 块内的最小编程单位(通常512B~4KB),每个页包含数据区和OOB(Out-Of-Band)区。 |
二、YAFFS2存储结构核心组成
1. 页(Chunk)布局
每个页分为两部分:
- 数据区(Data Area):存储文件数据或元数据。
- OOB区(Spare Area):存储YAFFS2的标签(Tags)和ECC校验码。
OOB区标签结构(yaffs_tags_t):
struct yaffs_tags {
uint32_t chunk_id; // 块ID(文件内逻辑偏移)
uint32_t obj_id; // 对象ID(文件/目录唯一标识)
uint32_t serial_number; // 序列号(用于垃圾回收)
uint16_t n_bytes; // 有效数据长度(非整页时使用)
// ... 其他字段(如ECC)
};
2. 对象头(Object Header)
- 功能:描述文件/目录的元数据(如名称、权限、时间戳)。
- 存储位置:文件/目录的第一个数据页。
- 结构示例:
struct yaffs_obj_header { uint32_t type; // 对象类型(文件、目录、符号链接等) uint32_t parent_id; // 父目录对象ID uint32_t file_size; // 文件大小(仅文件有效) uint32_t equiv_id; // 硬链接等效ID char name[YAFFS_MAX_NAME_LENGTH]; // 对象名称 // ... 时间戳、权限等 };
3. 文件数据存储
- 数据块链:文件数据按逻辑顺序分散在多个页中,通过
chunk_id
递增链接。 - 稀疏文件支持:未写入的区域不占用物理存储。
4. 超级块(Superblock)
- 位置:固定存储在第一个有效块的首个页。
- 内容:
struct yaffs_super_block { uint32_t magic; // 魔数0x5941FF32("YAFF2"的ASCII编码) uint32 version; // YAFFS2版本号 uint32 checkpt_start; // 检查点起始块 uint32 n_blocks; // 设备总块数 // ... 其他参数(页大小、OOB大小等) };
5. 检查点(Checkpoint)
- 功能:加速挂载,保存关键元数据(如Tnode树、对象关系)。
- 存储:连续占用多个块,包含序列化后的系统状态。
三、存储布局示例
假设一个NAND Flash设备参数如下:
- 块大小:128KB(64页/块,每页2KB)
- 文件示例:根目录(ID=1)、文件A(ID=2,大小5KB)
物理存储示意图:
块0(超级块):
页0: [Superblock Magic][版本][检查点位置]... (OOB: Tags标记为超级块)
块1(根目录):
页0: [Object Header: 类型=目录, 名称="/", parent_id=0] (OOB: obj_id=1, chunk_id=0)
块2(文件A):
页0: [Object Header: 类型=文件, 名称="A", size=5120] (OOB: obj_id=2, chunk_id=0)
页1: [文件数据 0-2048字节] (OOB: obj_id=2, chunk_id=1)
页2: [文件数据 2048-4096字节] (OOB: obj_id=2, chunk_id=2)
页3: [文件数据 4096-5120字节] (OOB: obj_id=2, chunk_id=3, n_bytes=1024)
块3(日志块):
页0: [删除操作日志] (OOB: 日志标记)
四、关键机制与Flash特性适配
1. 坏块管理
- 工厂坏块:通过扫描OOB标记识别并跳过。
- 运行时坏块:写入失败时标记,数据迁移至新块。
2. 磨损均衡
- 冷热数据分离:频繁更新的元数据(如目录)分散存储在不同块。
- 贪心算法:优先选择擦除次数少的块分配新数据。
3. 垃圾回收
- 策略:后台任务回收无效页(
chunk_id
已过时的页)。 - 优先级:选择无效页比例高的块优先擦除。
4. 日志结构写入
- 顺序写入:数据总是追加写入到新页,旧页标记为无效。
- 原子操作:通过日志页确保元数据操作的完整性。
五、OOB区域详细使用
OOB偏移 | 内容 | 大小(字节) |
---|---|---|
0-7 | YAFFS2 Tags | 8 |
8-15 | ECC校验码(可选) | 8 |
16- | 保留(厂商特定) | 剩余空间 |
Tags编码示例(小端模式):
Offset 0x00: chunk_id = 0x00000001 // 逻辑块ID
Offset 0x04: obj_id = 0x00000002 // 对象ID=2
Offset 0x08: serial_number = 0x1234 // 序列号
Offset 0x0C: n_bytes = 0x0800 // 有效数据长度2048
六、与YAFFS1的存储差异
特性 | YAFFS1 | YAFFS2 |
---|---|---|
页大小支持 | 仅512B页 | 支持大页(2KB/4KB) |
对象头存储 | 每个对象占用连续页 | 允许分散存储 |
检查点 | 无 | 支持检查点加速挂载 |
数据压缩 | 不支持 | 可选LZ77压缩 |
七、调试与分析工具
- nanddump:导出Flash原始数据及OOB内容。
nanddump -f yaffs.img -o /dev/mtd0
- unyaffs2:解析YAFFS2镜像文件。
- Hex编辑器:手动分析对象头和Tags结构。
通过理解YAFFS2的存储结构,开发者可以优化文件系统性能、实现数据恢复工具或定制Flash驱动适配。