名词解释
-
f2fs
flash friend file system,最早由三星开发的一个对flash/SSD 友好的文件系统。 - block
f2fs中数据读写最小的切片单位,通常4K。没个block 有一个编号,每个编号对应某个偏移的物理切片。包括:
- data block 专门存放数据
- node block 专门存放Block 编号,这个编号可以视为NodeID。
- f2fs的索引层次
包括四层:
-
indoe
文件或目录的索引节点,含义同通常的inode节点,可以理解成文件或目录的最顶层索引。 -
direct pointer:
包含在inode中,地址直接指向数据块。 -
indirect node
indirect node是一个node block,里面存放的是Node ID。这个NodeID需要差NAT才能找到对应的数据Node。 - doubel indirect node:
indirect node是一个node block,里面存放的是Node ID。这个NodeID需要差NAT才能找到对应的indirect node。
- NAT
顾名思义,NAT是node address table,是file/directory内逻辑地址到物理地址转换的查找表。
背景
基于LFS的文件系统如果使用平常的多级索引数据结构,会存在wandering tree问题。比如,如果使用下面的多级索
引:
当我们对某个indirect指向的数据更新的时候,基于LFS实现,就需要更新对应的索引树,如下图所示:
显然这样一次更新数据,需要更新三级索引结构,写放大太大。这个问题就是LFS的wandring tree 问题。f2fs是如何解决这个问题的呢?
实现原理
分析上面过程,造成wandring tree的关键是上级索引直接指向了下级索引,基于LFS索引也不能原地更新,这样一旦下级索引有改动,上级索引也需要随之更新。为此,可以在上级索引和下级索引直接引入一层防火墙,来隔离这两者的相互影响,避免更新向上层传播。
f2fs里这里利用的就是NAT(Node Address Table)这个统一的表,用以实现node id和node block的地址映射。
如下图所示:
比如,我们要访问某个文件第11个block的数据块,根据pointer11,找到对应的indirect block,基于inode的索引层次,算出对应物理块地址在这个indirect block内由哪个entry索引,读取对应entry记录的NodeID,查找NAT,得到最终的物理地址。最后基于这个物理地址,访问数据。
相关数据结构
主要的数据结构包括:
-
inode
struct inode { ...... __le32 i_flags; /* file attributes */ __le32 i_pino; /* parent inode number */ __le32 i_namelen; /* file name length */ __u8 i_name[F2FS_NAME_LEN]; /* file name for SPOR */ __u8 i_dir_level; /* dentry_level for large dir */ ...... union { struct { // for what usage? __le16 i_extra_isize; /* extra inode attribute size */ __le32 i_inode_checksum;/* inode meta checksum */ __le64 i_crtime; /* creation time */ __le32 i_crtime_nsec; /* creation time in nano scale */ __le32 i_extra_end[0]; /* for attribute size calculation */ } __packed; __le32 i_addr[DEF_ADDRS_PER_INODE]; /* Pointers to data blocks */ }; __le32 i_nid[DEF_NIDS_PER_INODE]; /* direct(2), indirect(2), double_indirect(1) node id */ } __packed;
- node
struct direct_node {
__le32 addr[ADDRS_PER_BLOCK]; /* array of data block address */
} __packed;
struct indirect_node {
__le32 nid[NIDS_PER_BLOCK]; /* array of data block address */
} __packed;
struct f2fs_node {
/* can be one of three types: inode, direct, and indirect types */
union {
struct f2fs_inode i;
struct direct_node dn;
struct indirect_node in;
};
struct node_footer footer;
} __packed;
- nat
/*
* For NAT entries
*/
#define NAT_ENTRY_PER_BLOCK (PAGE_SIZE / sizeof(struct f2fs_nat_entry))
struct f2fs_nat_entry {
__u8 version; /* latest version of cached nat entry */
__le32 ino; /* inode number */
__le32 block_addr; /* block address */
} __packed;
struct f2fs_nat_block {
struct f2fs_nat_entry entries[NAT_ENTRY_PER_BLOCK];
} __packed;
NAT的更新
根据上面流程可以看到,写文件的过程中实际也会更新对应的NAT。那么,NAT 是每一次IO都需要更新吗?如果这样,也会有一次写放大,为此实际工程中可以把这些更新攥在一起落盘,减少整体写的次数。
那么如果不及时更新,掉电怎么办?可以把NAT的更新也以log strucuture 的形式追加落盘,在f2fs 做checkpoint的时候,把这些更新统一写入NAT,然后回收先前的NAT 更新日志空间,流程参考:http://xiaqichao.cn/wordpress/?p=211
//首发于http://xiaqichao.cn 欢迎光临