F2FS之读取最新的checkpoint块

在F2FS文件系统中为了读取最新的CP块给超级块对象中的sbi->ckpt,是调用int get_valid_checkpoint(struct f2fs_sb_info *sbi)函数 。

int get_valid_checkpoint(struct f2fs_sb_info *sbi)
{
	struct f2fs_checkpoint *cp_block;
	struct f2fs_super_block *fsb = sbi->raw_super;//磁盘超级块
	struct page *cp1, *cp2, *cur_page;
	unsigned long blk_size = sbi->blocksize;
	unsigned long long cp1_version = 0, cp2_version = 0;
	unsigned long long cp_start_blk_no;

	sbi->ckpt = kzalloc(blk_size, GFP_KERNEL);
	if (!sbi->ckpt)
		return -ENOMEM;
	/*
	 * Finding out valid cp block involves read both
	 * sets( cp pack1 and cp pack 2)
	 */
	cp_start_blk_no = le32_to_cpu(fsb->cp_blkaddr);//检查点的开始块地址
	cp1 = validate_checkpoint(sbi, cp_start_blk_no, &cp1_version);

	/* The second checkpoint pack should start at the next segment */
	//第二个检查点包应从下一个segment开始
	cp_start_blk_no += 1 << le32_to_cpu(fsb->log_blocks_per_seg);
	cp2 = validate_checkpoint(sbi, cp_start_blk_no, &cp2_version);

	if (cp1 && cp2) {//如果两个检查点都有效,则选择最新的检查点
		if (ver_after(cp2_version, cp1_version))
			cur_page = cp2;
		else
			cur_page = cp1;
	} else if (cp1) {
		cur_page = cp1;
	} else if (cp2) {
		cur_page = cp2;
	} else {
		goto fail_no_cp;
	}

	cp_block = (struct f2fs_checkpoint *)page_address(cur_page);//将最新的CP赋值给CP块
	memcpy(sbi->ckpt, cp_block, blk_size);//将新的CP块赋值给超级块内存对象

	f2fs_put_page(cp1, 1);
	f2fs_put_page(cp2, 1);
	return 0;

fail_no_cp:
	kfree(sbi->ckpt);
	return -EINVAL;
}

分析一下这个函数:首先先给sbi->ckpt创建一个块大小的内存空间。接着从磁盘超级块的fsb->cp_blkaddr中获取磁盘CP的起始地址。然后调用validate_checkpoint(sbi, cp_start_blk_no, &cp1_version)检查第一个CP是否有效 ,该函数会从磁盘中读取对应的f2fs_checkpoint结构(大小为一个块),并判断该CP是否有效,如果有效则返回对应的page,无效则返回null,细节见下面的函数分析。接着将cp_start_blk_no加上一个segment,因为在磁盘上第二个CP在第二个segment,同样调用validate_checkpoint(sbi, cp_start_blk_no, &cp1_version)检查第二个CP是否有效。随后根据两次调用的结果,cp1和cp2判断是否都有返回值,并且根据version的大小选择最新的f2fs_checkpoint。得到最新的f2fs_checkpoint即cur_page后,将cur_page做类型转换后复制到超级块的sbi->ckpt处即可。

下面分析一下上面调用的validate_checkpoint

static struct page *validate_checkpoint(struct f2fs_sb_info *sbi,
				block_t cp_addr, unsigned long long *version)
{
	struct page *cp_page_1, *cp_page_2 = NULL;
	unsigned long blk_size = sbi->blocksize;//块大小
	struct f2fs_checkpoint *cp_block;//磁盘检查点块
	unsigned long long cur_version = 0, pre_version = 0;
	unsigned int crc = 0;
	size_t crc_offset;

	/* Read the 1st cp block in this CP pack */
	cp_page_1 = get_meta_page(sbi, cp_addr);//从磁盘上读取cp_addr处的检查点页

	/* get the version number */
	cp_block = (struct f2fs_checkpoint *)page_address(cp_page_1);//checkpoint块
	crc_offset = le32_to_cpu(cp_block->checksum_offset);//校验和偏移量
	if (crc_offset >= blk_size)
		goto invalid_cp1;

	crc = *(unsigned int *)((unsigned char *)cp_block + crc_offset);//读取CRC校验码值
	if (!f2fs_crc_valid(crc, cp_block, crc_offset))
		goto invalid_cp1;//校验和失效

	pre_version = le64_to_cpu(cp_block->checkpoint_ver);//第一个checkpoint的版本号

	/* Read the 2nd cp block in this CP pack *///cp pack可能是2个segment,第一个cp在第一个块,第2个cp在最后一个块
	cp_addr += le32_to_cpu(cp_block->cp_pack_total_block_count) - 1;//第二个检查点地址为第一个检查点地址加自己占有的块数
	cp_page_2 = get_meta_page(sbi, cp_addr);//从磁盘上读取cp_addr处的检查点页

	cp_block = (struct f2fs_checkpoint *)page_address(cp_page_2);
	crc_offset = le32_to_cpu(cp_block->checksum_offset);
	if (crc_offset >= blk_size)
		goto invalid_cp2;

	crc = *(unsigned int *)((unsigned char *)cp_block + crc_offset);
	if (!f2fs_crc_valid(crc, cp_block, crc_offset))
		goto invalid_cp2;

	cur_version = le64_to_cpu(cp_block->checkpoint_ver);

	if (cur_version == pre_version) {//比较两个检查点的版本号相等否
		*version = cur_version;
		f2fs_put_page(cp_page_2, 1);
		return cp_page_1;
	}
invalid_cp2:
	f2fs_put_page(cp_page_2, 1);
invalid_cp1:
	f2fs_put_page(cp_page_1, 1);
	return NULL;
}

该函数从页缓存中读取有效检查点的块,version为有效的版本号,判断该cp是否有效。首先cp_page_1 = get_meta_page(sbi, cp_addr)从磁盘上读取cp_addr处的检查点页,也就是该CP的第一个f2fs_checkpoint块然后读取他的校验和并判断是否有效,随后获得他的pre_version版本号。随后再来读取该CP的第二个f2fs_checkpoint块,首先获得他的块地址cp_addr += le32_to_cpu(cp_block->cp_pack_total_block_count) - 1;因为第二个f2fs_checkpoint块在CP的最后一个块,随后同理得到第二个f2fs_checkpoint块的版本号。比较两个f2fs_checkpoint块的版本号是否相同,如果相同,则表明该CP有效,并返回第一个f2fs_checkpoint块,否则该CP无效,返回NULL。

<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、付费专栏及课程。

余额充值