F2FS之磁盘NAT和SIT布局及相关函数分析

本文详细介绍了F2FS文件系统中NAT(Node Address Table)和SIT(Segment Information Table)的数据布局,包括它们的备份策略和位图管理。NAT按segment备份,SIT则按block备份,两者都有两份副本以确保数据安全性。文章还解析了获取当前有效NAT和SIT块的函数,以及如何在位图中判断、设置和清除位的方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

NAT和SIT数据简介

关于NAT和SIT简介部分请参考
https://blog.youkuaiyun.com/u011649400/article/details/106955060

https://blog.youkuaiyun.com/u011649400/article/details/102490983

SIT的实际磁盘布局

在这里插入图片描述
SIT区域的实际构图如上所示。以f2fs_sit_block为单位进行存储,共有2blocks个f2fs_sit_block,其中blocks=sit_info->sit_blocks,即有效块数,剩下的blocks个块为备份。每个f2fs_sit_block的构成如下图所示,每个f2fs_sit_block中有55个f2fs_sit_entry,每个f2fs_sit_entry描述了一个segment的信息。
在这里插入图片描述
为什么会有2
blocks个块呢? f2fs为了防止宕机对元数据造成不可恢复的损害,所以sit/nat这种元数据有着两个副本,但是这两个副本只有一个是表示最新的数据,f2fs通过保存在cp pack中的sit_nat_version_bitmap来指示哪个才是最新的。

//根据位图获得当前最新版本的segno的f2fs_sit_block
static struct page *get_current_sit_page(struct f2fs_sb_info *sbi,
					unsigned int segno)
{
	struct sit_info *sit_i = SIT_I(sbi);
	unsigned int offset = SIT_BLOCK_OFFSET(sit_i, segno);
	block_t blk_addr = sit_i->sit_base_addr + offset;

	check_seg_range(sbi, segno);

	/* calculate sit block address */
	if (f2fs_test_bit(offset, sit_i->sit_bitmap))
		blk_addr += sit_i->sit_blocks;

	return get_meta_page(sbi, blk_addr);
}

从上述函数中可以看出unsigned int offset = SIT_BLOCK_OFFSET(sit_i, segno);这里#define SIT_BLOCK_OFFSET(sit_i, segno) (segno / SIT_ENTRY_PER_BLOCK),即offset为segno与起始段号的块偏移。

	if (f2fs_test_bit(offset, sit_i->sit_bitmap))
		blk_addr += sit_i->sit_blocks;

如果在位图上置位了,则后一个f2fs_sit_block才是最新的,而后一个f2fs_sit_block等于前一个f2fs_sit_block+总有效块数blocks,所以跟上面的物理布局是对应的。
下面这个函数是计算block_addr处的f2fs_sit_block的备份块的地址。

static inline pgoff_t next_sit_addr(struct f2fs_sb_info *sbi,
						pgoff_t block_addr)
{
	struct sit_info *sit_i = SIT_I(sbi);
	block_addr -= sit_i->sit_base_addr;
	if (block_addr < sit_i->sit_blocks)//如果此block在前面的SIT中
		block_addr += sit_i->sit_blocks;
	else
		block_addr -= sit_i->sit_blocks;//如果此block在后面的SIT中

	return block_addr + sit_i->sit_base_addr;
}

下面在再介绍一个跟物理位置有关的函数:

static struct page *get_next_sit_page(struct f2fs_sb_info *sbi,
					unsigned int start)
{
	struct sit_info *sit_i = SIT_I(sbi);
	struct page *src_page, *dst_page;
	pgoff_t src_off, dst_off;
	void *src_addr, *dst_addr;

	src_off = current_sit_addr(sbi, start);//根据start得到sit位图上有效f2fs_sit_block的块地址
	dst_off = next_sit_addr(sbi, src_off);//得到备份副本中的f2fs_sit_block的块地址

	/* get current sit block page without lock */
	src_page = get_meta_page(sbi, src_off);//从磁盘上读取根据start得到sit位图上有效f2fs_sit_block
	dst_page = grab_meta_page(sbi, dst_off);//从磁盘上读取得到备份副本中的f2fs_sit_block的块地址
	BUG_ON(PageDirty(src_page));

	src_addr = page_address(src_page);
	dst_addr = page_address(dst_page);
	memcpy(dst_addr, src_addr, PAGE_CACHE_SIZE);//将有效f2fs_sit_block复制到备份
	/*这里解释一下为什么要memcpy:因为新写的entry只能在旧块上写,而其他不修改的entry必须也得是最新的*/
	set_page_dirty(dst_page);//设置备份为脏
	f2fs_put_page(src_page, 1);

	set_to_next_sit(sit_i, start);//将sit 位图上更换有效置位

	return dst_page;
}

此函数将segno(是该块的第一个段号)所在的f2fs_sit_block从磁盘里读出来,实际上是读的旧版所在block,但是已经被新版本更新过。

NAT的实际磁盘布局

在这里插入图片描述
NAT的实际磁盘布局如图所示。与SIT按block为单位备份不同的是,NAT按照segment为单位进行备份,前一个segment的备份紧跟在其后面,每个segment是有512个f2fs_nat_block,而每个f2fs_nat_block有455个f2fs_nat_entry。每个f2fs_nat_entry描述一个node块及其块地址的信息。
在这里插入图片描述
通过下面的函数即可看出该物理布局:

static inline pgoff_t current_nat_addr(struct f2fs_sb_info *sbi, nid_t start)
{
	struct f2fs_nm_info *nm_i = NM_I(sbi);
	pgoff_t block_off;
	pgoff_t block_addr;
	int seg_off;

	block_off = NAT_BLOCK_OFFSET(start);//先找到这是第几个f2fs_nat_block
	seg_off = block_off >> sbi->log_blocks_per_seg;//根据block_off得到f2fs_nat_block在NAT区域的第seg_off个segment

	block_addr = (pgoff_t)(nm_i->nat_blkaddr +//NAT区域的起始地址加上segment和block的偏移
		(seg_off << sbi->log_blocks_per_seg << 1) +//段偏移需要*2,因为是按照段进行备份的
		(block_off & ((1 << sbi->log_blocks_per_seg) - 1)));//段内的块偏移不需要翻倍

	if (f2fs_test_bit(block_off, nm_i->nat_bitmap))//如果块位图上被置位了,就是表明后一个段的块才有效
		block_addr += sbi->blocks_per_seg;//加上一个segment的块数

	return block_addr;
}

该函数根据f2fs_nat_block的起始nid和有效位图获取其磁盘块地址。重点在于:

	block_addr = (pgoff_t)(nm_i->nat_blkaddr +//NAT区域的起始地址加上segment和block的偏移
		(seg_off << sbi->log_blocks_per_seg << 1) +//段偏移需要*2,因为是按照段进行备份的
		(block_off & ((1 << sbi->log_blocks_per_seg) - 1)));//段内的块偏移不需要翻倍

因为最终的块地址=段偏移+段内块偏移,又由于是按照段进行备份的,所以计算出段偏移后需要x2,而段内偏移不需要翻倍。
下面这个函数是计算block_addr处的f2fs_nat_block的备份块的地址。

static inline pgoff_t next_nat_addr(struct f2fs_sb_info *sbi,
						pgoff_t block_addr)
{
	struct f2fs_nm_info *nm_i = NM_I(sbi);

	block_addr -= nm_i->nat_blkaddr;
	if ((block_addr >> sbi->log_blocks_per_seg) % 2)//如果是奇数段偏移,则block_addr为后一个段的块
		block_addr -= sbi->blocks_per_seg;
	else
		block_addr += sbi->blocks_per_seg;//如果是偶数段段偏移,则block_addr为前一个段的块

	return block_addr + nm_i->nat_blkaddr;
}

下面在再介绍一个跟物理位置有关的函数

static struct page *get_next_nat_page(struct f2fs_sb_info *sbi, nid_t nid)
{
	struct page *src_page;
	struct page *dst_page;
	pgoff_t src_off;
	pgoff_t dst_off;
	void *src_addr;
	void *dst_addr;
	struct f2fs_nm_info *nm_i = NM_I(sbi);

	src_off = current_nat_addr(sbi, nid);//根据nid得到nat位图上有效f2fs_nat_block的块地址
	dst_off = next_nat_addr(sbi, src_off);//得到备份副本中的f2fs_nat_block的块地址

	/* get current nat block page with lock */
	src_page = get_meta_page(sbi, src_off);//从磁盘上读取根据start得到nat位图上有效f2fs_nat_block

	/* Dirty src_page means that it is already the new target NAT page. */
	if (PageDirty(src_page))
		return src_page;

	dst_page = grab_meta_page(sbi, dst_off);//从磁盘上读取得到备份副本中的f2fs_nat_block的块地址

	src_addr = page_address(src_page);
	dst_addr = page_address(dst_page);
	memcpy(dst_addr, src_addr, PAGE_CACHE_SIZE);//将有效f2fs_nat_block复制到备份
	/*这里解释一下为什么要memcpy:因为新写的entry只能在旧块上写,而其他不修改的entry必须也得是最新的*/
	set_page_dirty(dst_page);//设置备份为脏
	f2fs_put_page(src_page, 1);

	set_to_next_nat(nm_i, nid);//将nat 位图上更换有效置位

	return dst_page;
}

关于NAT和SIT的有效位图

struct f2fs_nm_info { //node manager 信息
	block_t nat_blkaddr;		/* base disk address of NAT区域在磁盘上的起始地址 */
	nid_t max_nid;			/* maximum possible node ids */
	nid_t init_scan_nid;		/* the first nid to be scanned */
	nid_t next_scan_nid;		/* the next nid to be scanned */

	/* NAT cache management */
	struct radix_tree_root nat_root;/* root of the nat entry cache (nat entry缓存树的根) */
	rwlock_t nat_tree_lock;		/* protect nat_tree_lock */
	unsigned int nat_cnt;		/* the # of cached nat entries */
	struct list_head nat_entries;	/* cached nat entry list (clean) */
	struct list_head dirty_nat_entries; /* cached nat entry list (dirty) */

	/* free node ids management */
	struct list_head free_nid_list;	/* a list for free nids */
	spinlock_t free_nid_list_lock;	/* protect free nid list */
	unsigned int fcnt;		/* the number of free node id */
	struct mutex build_lock;	/* lock for build free nids */

	/* for checkpoint */
	char *nat_bitmap;		/* NAT bitmap pointer */
	int bitmap_size;		/* bitmap size */
};
struct sit_info {
	const struct segment_allocation *s_ops;

	block_t sit_base_addr;		/* start block address of SIT area */
	block_t sit_blocks;		/* # of blocks used by SIT area */
	block_t written_valid_blocks;	/* # of valid blocks in main area */
	char *sit_bitmap;		/* SIT bitmap pointer */
	unsigned int bitmap_size;	/* SIT bitmap size */

	unsigned long *dirty_sentries_bitmap;	/* bitmap for dirty sentries 脏segment条目的bitmap*/
	unsigned int dirty_sentries;		/* # of dirty sentries */
	unsigned int sents_per_block;		/* # of SIT entries per block */
	struct mutex sentry_lock;		/* to protect SIT cache */
	struct seg_entry *sentries;		/* SIT segment-level cache //segment条目缓存,下标相对于free_segmap_info中start_segno*/
	struct sec_entry *sec_entries;		/* SIT section-level cache */

	/* for cost-benefit algorithm in cleaning procedure */
	unsigned long long elapsed_time;	/* elapsed time after mount */
	unsigned long long mounted_time;	/* mount time */
	unsigned long long min_mtime;		/* min. modification time */
	unsigned long long max_mtime;		/* max. modification time */
};

在sbi->f2fs_nm_info->nat_bitmap中是NAT的位图,sbi->sit_info->sit_bitmap为SIT的位图。

struct f2fs_checkpoint {
	__le64 checkpoint_ver;		/* checkpoint block version number */
	__le64 user_block_count;	/* # of user blocks */
	__le64 valid_block_count;	/* # of valid blocks in main area */
	__le32 rsvd_segment_count;	/* # of reserved segments for gc */
	__le32 overprov_segment_count;	/* # of overprovision segments */
	__le32 free_segment_count;	/* # of free segments in main area */

	/* information of current node segments */
	__le32 cur_node_segno[MAX_ACTIVE_NODE_LOGS];
	__le16 cur_node_blkoff[MAX_ACTIVE_NODE_LOGS];
	/* information of current data segments */
	__le32 cur_data_segno[MAX_ACTIVE_DATA_LOGS];
	__le16 cur_data_blkoff[MAX_ACTIVE_DATA_LOGS];
	__le32 ckpt_flags;		/* Flags : umount and journal_present */
	__le32 cp_pack_total_block_count;	/* total # of one cp pack */
	__le32 cp_pack_start_sum;	/* start block number of data summary */
	__le32 valid_node_count;	/* Total number of valid nodes */
	__le32 valid_inode_count;	/* Total number of valid inodes */
	__le32 next_free_nid;		/* Next free node number */
	__le32 sit_ver_bitmap_bytesize;	/* Default value 64 */
	__le32 nat_ver_bitmap_bytesize; /* Default value 256 */
	__le32 checksum_offset;		/* checksum校验和 offset inside cp block */
	__le64 elapsed_time;		/* mounted time */
	/* allocation type of current segment */
	unsigned char alloc_type[MAX_ACTIVE_LOGS];

	/* SIT and NAT version bitmap */
	unsigned char sit_nat_version_bitmap[1];
} __packed;

位图在磁盘上是存储在f2fs_checkpoint里的sit_nat_version_bitmap:

//确定ckpt的sit_nat_version_bitmap中,nat和sit位图的位置
static inline void *__bitmap_ptr(struct f2fs_sb_info *sbi, int flag)
{
	struct f2fs_checkpoint *ckpt = F2FS_CKPT(sbi);
	int offset = (flag == NAT_BITMAP) ?
			le32_to_cpu(ckpt->sit_ver_bitmap_bytesize) : 0;
	return &ckpt->sit_nat_version_bitmap + offset;
}

由上述函数可以看出,sit和NAT位图在sit_nat_version_bitmap中是紧挨着,其中sit在前部分,nat在后部分。

判断位图中某个位置是否置位的函数:

//addr是char(8bit)类型数组的位图,nr为偏移量,得到nr处的位图状况
static inline int f2fs_test_bit(unsigned int nr, char *addr)
{
	int mask;

	addr += (nr >> 3);//找到nr属于哪个字节
	mask = 1 << (7 - (nr & 0x07));//nr & 0x07就是nr%8,7-他=从后面数的第几位,然后将1左移这些位就得到了nr处mask
	return mask & *addr;//拿着mask与该字节处做与,就得到了该位的状况
}

还有置位和清除位的函数:

//segment内增加块,根据segment内偏移nr置位图
static inline int f2fs_set_bit(unsigned int nr, char *addr)
{
	int mask;
	int ret;

	addr += (nr >> 3);
	mask = 1 << (7 - (nr & 0x07));
	ret = mask & *addr;
	*addr |= mask;
	return ret;
}
//segment内删除块,根据segment内偏移nr置位图
static inline int f2fs_clear_bit(unsigned int nr, char *addr)
{
	int mask;
	int ret;

	addr += (nr >> 3);
	mask = 1 << (7 - (nr & 0x07));
	ret = mask & *addr;
	*addr &= ~mask;
	return ret;
}
<think>我们正在讨论F2FS文件系统中的`f2fs_node`结构体初始化问题。根据用户的问题,他明确要求查找`f2fs_node`结构体在何时被初始化的具体函数。 回顾之前的讨论,我们知道`f2fs_node`是磁盘上的结构,代表一个inode在磁盘上的存储格式(包括直接指针、间接指针等)。它的初始化发生在文件或目录创建时,以及文件系统挂载时(从磁盘加载)。 现在,我们需要具体到初始化这个结构体的函数。根据F2FS源码,我们可以分析: 1. **文件创建时**:当创建一个新文件或目录时,需要初始化一个新的inode。在F2FS中,这个操作会调用`f2fs_new_inode`函数。在这个函数中,会分配一个新的inode号,并初始化磁盘上的inode结构(即`f2fs_node`)。 2. **具体初始化函数**:在`f2fs_new_inode`函数中,会进一步调用`init_inode_metadata`函数来初始化inode的元数据。而`f2fs_node`的初始化实际上是在写入磁盘时完成的,具体来说,是在`f2fs_do_write_inode`函数中,该函数会将内存中的inode信息(`f2fs_inode_info`)转换为磁盘上的`f2fs_node`结构并写入磁盘。 3. **写入磁盘**:在`f2fs_do_write_inode`函数中,会调用`f2fs_write_inode`函数(该函数在用户提供的引用[1]中提到)来将inode写入磁盘。写入的内容就是`f2fs_node`结构。 4. **挂载时初始化**:挂载时,`f2fs_node`结构体是从磁盘读取到内存的,而不是初始化(因为已经在磁盘上存在)。读取操作通过`f2fs_get_node_page`函数完成,该函数会读取磁盘上的node page(包含`f2fs_node`)到内存。 因此,`f2fs_node`结构体的初始化(即创建新的inode并写入磁盘)主要发生在`f2fs_new_inode`函数中,并通过`f2fs_do_write_inode`写入磁盘。 具体步骤: - 在`f2fs_new_inode`函数中,调用`new_inode_page`函数来分配一个新的node page(即一个`f2fs_node`的磁盘空间)。 - 然后,通过`init_inode_metadata`函数初始化inode的元数据(包括设置`f2fs_node`的各个字段)。 - 最后,通过`f2fs_do_write_inode`将inode写入磁盘,此时`f2fs_node`被写入磁盘。 所以,初始化`f2fs_node`结构体的函数主要是`init_inode_metadata`(设置内容)`f2fs_do_write_inode`(写入磁盘)。 另外,用户提供的引用[2]中提到了`sync_node_pages`函数,该函数用于将脏的node page(即包含`f2fs_node`的页面)写回磁盘,但这属于写回过程,而不是初始化。 因此,回答用户问题:`f2fs_node`结构体的初始化发生在`f2fs_new_inode`函数中,具体初始化操作在`init_inode_metadata`函数中完成,然后通过`f2fs_do_write_inode`写入磁盘。 注意:在内存中,`f2fs_node`结构体实际上是通过`f2fs_get_node_page`从磁盘读取到内存的node page中,然后通过`inline_data`等条件进行解析。但创建新的`f2fs_node`(即初始化)是在文件创建时。 总结: - 创建时初始化:`f2fs_new_inode` -> `init_inode_metadata` -> 设置`f2fs_node`的各个字段 -> 通过`f2fs_do_write_inode`写入磁盘。 - 挂载时加载:通过`f2fs_get_node_page`读取磁盘上的`f2fs_node`到内存。 现在,我们给出具体回答。</think>### `f2fs_node`结构体的初始化函数分析F2FS文件系统中,`f2fs_node`结构体(磁盘级inode)的初始化主要发生在**文件/目录创建****文件系统挂载**两个阶段。以下是具体实现函数流程: --- #### 1. **文件/目录创建时的初始化(核心场景)** 当创建新文件或目录时,通过以下函数链完成`f2fs_node`的初始化: ```c f2fs_create() / f2fs_mkdir() └── f2fs_new_inode() // 分配新inode号 └── new_inode_page() // 分配磁盘空间 └── f2fs_init_inode_metadata() // 核心初始化函数[^1] ├── set_page_dirty(node_page) // 标记为脏页 └── f2fs_do_write_inode() // 最终写入磁盘 ``` 关键函数说明: - **`f2fs_init_inode_metadata()`** 这是初始化`f2fs_node`的核心函数,负责: - 设置基础元数据(`i_mode`, `i_size`, `i_links`等) - 初始化数据块指针(直接/间接指针) - 生成唯一inode编号 ```c // 伪代码逻辑 void f2fs_init_inode_metadata(struct inode *inode) { struct f2fs_inode *ri = F2FS_INODE(node_page); // 获取f2fs_node指针 ri->i_mode = cpu_to_le16(inode->i_mode); // 设置权限 ri->i_size = 0; // 初始文件大小=0 ri->i_links = cpu_to_le32(1); // 初始链接数=1 // ...初始化其他字段... } ``` - **`f2fs_do_write_inode()`** 通过`f2fs_write_inode()`将内存中的`f2fs_node`写入磁盘[^1]: ```c int f2fs_do_write_inode(struct inode *inode) { struct f2fs_inode_info *fi = F2FS_I(inode); struct page *node_page = get_node_page(sbi, inode->i_ino); // 获取磁盘f2fs_wait_on_page_writeback(node_page, NODE, true); // 等待写回完成 set_page_dirty(node_page); // 标记脏页 f2fs_put_page(node_page, 1); // 释放页引用 } ``` --- #### 2. **文件系统挂载时的初始化** 挂载时通过Checkpoint机制加载已有的`f2fs_node`: ```c f2fs_fill_super() └── build_node_manager() // 初始化节点管理器 └── sync_node_pages() // 同步节点页[^2] └── f2fs_get_node_page() // 从磁盘读取节点页 └── read_node_page() // 实际读盘函数 ``` 关键流程: - `read_node_page()`提交bio请求读取磁盘块: ```c int read_node_page(struct page *page) { struct f2fs_node *rn = page_address(page); // 获取f2fs_node内存映射 struct f2fs_io_info fio = { .type = NODE, .op = REQ_OP_READ, .page = page }; f2fs_submit_page_bio(&fio); // 触发磁盘读取 } ``` - 读取的磁盘数据直接映射到`struct f2fs_node`内存结构中 --- ### 关键区别:初始化 vs 加载 | 操作类型 | 函数路径 | 触发条件 | 本质行为 | |----------|--------------------------|-----------------------|------------------------| | **初始化** | `f2fs_init_inode_metadata` | 文件/目录创建 | 构造**新**磁盘inode结构 | | **加载** | `read_node_page` | 文件系统挂载/文件访问 | 读取**已有**磁盘数据 | > **注意**:`f2fs_node`是磁盘持久化结构,其初始化仅在文件创建时发生一次,后续修改通过`f2fs_write_inode()`更新[^1]。 --- ### 典型调用栈示例 **文件创建场景**: ```plaintext 用户调用 touch test.txt → VFS: vfs_create() → F2FS: f2fs_create() → f2fs_new_inode() → new_inode_page() // 分配磁盘页 → f2fs_init_inode_metadata() // 初始化f2fs_node → f2fs_do_write_inode() // 写入磁盘[^1] ``` **挂载场景**: ```plaintext mount -t f2fs /dev/sda1 /mnt → f2fs_fill_super() → build_node_manager() → sync_node_pages() // 同步节点页[^2] → f2fs_get_node_page() → read_node_page() // 读取磁盘f2fs_node ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值