在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。