这篇文章讲述一下f2fs文件系统中缓存在内存中的f2fs_summary写入磁盘的问题,这个涉及到f2fs_summary写入磁盘的时机、位置以及curseg在do_checkpoint中的写入问题和mount的时候的恢复curseg的问题。
首先,对于curseg_info中的f2fs_summary_block有两种方式同步到page cache中。一种是在curseg_info进行替换时调用change_curseg或者new_curseg时调用write_sum_page将curseg_info中的f2fs_summary_block同步到page cache中。第二种是在do_checkpoint的时候调用write_data_summaries或者write_node_summaries将其同步到page cache中。
static void new_curseg(struct f2fs_sb_info *sbi, int type, bool new_sec)
{
struct curseg_info *curseg = CURSEG_I(sbi, type);
unsigned int segno = curseg->segno;
int dir = ALLOC_LEFT;
write_sum_page(sbi, curseg->sum_blk, GET_SUM_BLOCK(sbi, segno));
if (type == CURSEG_WARM_DATA || type == CURSEG_COLD_DATA)
dir = ALLOC_RIGHT;
if (test_opt(sbi, NOHEAP))
dir = ALLOC_RIGHT;
get_new_segment(sbi, &segno, new_sec, dir);
curseg->next_segno = segno;
reset_curseg(sbi, type, 1);
curseg->alloc_type = LFS;
}
static void change_curseg(struct f2fs_sb_info *sbi, int type, bool reuse)
{
struct dirty_seglist_info *dirty_i = DIRTY_I(sbi);
struct curseg_info *curseg = CURSEG_I(sbi, type);
unsigned int new_segno = curseg->next_segno;
struct f2fs_summary_block *sum_node;
struct page *sum_page;
write_sum_page(sbi, curseg->sum_blk, GET_SUM_BLOCK(sbi, curseg->segno));
__set_test_and_inuse(sbi, new_segno);
mutex_lock(&dirty_i->seglist_lock);
__remove_dirty_segment(sbi, new_segno, PRE);
__remove_dirty_segment(sbi, new_segno, DIRTY);
mutex_unlock(&dirty_i->seglist_lock);
reset_curseg(sbi, type, 1);
curseg->alloc_type = SSR;
__next_free_blkoff(sbi, curseg, 0);
if (reuse) {
sum_page = get_sum_page(sbi, new_segno);
sum_node = (struct f2fs_summary_block *)page_address(sum_page);
memcpy(curseg->sum_blk, sum_node, SUM_ENTRY_SIZE);
f2fs_put_page(sum_page, 1);
}
}
static void write_sum_page(struct f2fs_sb_info *sbi,
struct f2fs_summary_block *sum_blk, block_t blk_addr)
{
update_meta_page(sbi, (void *)sum_blk, blk_addr);
}
void update_meta_page(struct f2fs_sb_info *sbi, void *src, block_t blk_addr)
{
struct page *page = grab_meta_page(sbi, blk_addr);
void *dst = page_address(page);
if (src)
memcpy(dst, src, PAGE_SIZE);
else
memset(dst, 0, PAGE_SIZE);
set_page_dirty(page);
f2fs_put_page(page, 1);
}
static int do_checkpoint(struct f2fs_sb_info *sbi, struct cp_control *cpc)
{
...
write_data_summaries(sbi, start_blk);
...
if (__remain_node_summaries(cpc->reason)) {
write_node_summaries(sbi, start_blk);
start_blk += NR_CURSEG_NODE_TYPE;
}
update_meta_page(sbi, ckpt, start_blk);
...
}
这里两种方式的page cache对应的是不同的位置,第一种最后调用的update_meta_page中的grab_meta_page的blk_addr对应的是该f2fs_summary_block在SSA区域对应的位置;第二种调用的update_meta_page中的start_blk对应的是cp pack区域对应的位置,而且对于node的summary只有在umount和fastboot两种情况才会写入cp pack区域。
static inline bool __remain_node_summaries(int reason)
{
return (reason == CP_UMOUNT || reason == CP_FASTBOOT);
}
对于第一种情况落到SSA,由于已经换了curseg,所以之后访问这个f2fs_summary_block就是访问SSA区域,所以不会出现任何问题。但是第二种情况,访问这个f2fs_summary_block是通过curseg_info来访问的,而curseg_info的summary涉及到了宕机问题。对于data的summary,这个由于在do_checkpoint的时候会将这些写进cp pack区域,恢复的时候可以恢复到上一个checkpoint的数据。但是对于node的summary在do_checkpoint的时候没有写入,那么宕机之后就没有可恢复的数据。但是实际上在mount的时候node的curseg_info的恢复时在基于segno和next_blk在上次do_checkpoint保存的情况下,对这个segno对应的segment的[0,next_blk]的node进行读取,用其中的footer里面的数据来恢复node对应的curseg_info的f2fs_summary_block。
static int read_normal_summaries(struct f2fs_sb_info *sbi, int type)
{
...
if (IS_NODESEG(type)) {
if (__exist_node_summaries(sbi)) {
struct f2fs_summary *ns = &sum->entries[0];
int i;
for (i = 0; i < sbi->blocks_per_seg; i++, ns++) {
ns->version = 0;
ns->ofs_in_node = 0;
}
} else {
int err;
err = restore_node_summary(sbi, segno, sum);
if (err) {
f2fs_put_page(new, 1);
return err;
}
}
}
...
}
int restore_node_summary(struct f2fs_sb_info *sbi, unsigned int segno, struct f2fs_summary_block *sum)
{
struct f2fs_node *rn;
struct f2fs_summary *sum_entry;
block_t addr;
int bio_blocks = MAX_BIO_BLOCKS(sbi);
int i, idx, last_offset, nrpages;
last_offset = sbi->blocks_per_seg;
addr = START_BLOCK(sbi, segno);
sum_entry = &sum->entries[0];
for (i = 0; i < last_offset; i += nrpages, addr += nrpages) {
nrpages = min(last_offset - i, bio_blocks);
ra_meta_pages(sbi, addr, nrpages, META_POR, true);
for (idx = addr; idx < addr + nrpages; idx++) {
struct page *page = get_tmp_page(sbi, idx);
rn = F2FS_NODE(page);
sum_entry->nid = rn->footer.nid;
sum_entry->version = 0;
sum_entry->ofs_in_node = 0;
sum_entry++;
f2fs_put_page(page, 1);
}
invalidate_mapping_pages(META_MAPPING(sbi), addr, addr + nrpages);
}
return 0;
}
关于node的curseg_info的这种机制可能是为了减少check_point的开销(也不是很大啊),但是重新启动的开销就可能很大(读取了大量的node)。在恢复的时候,由于对于node的f2fs_summary,其reserved[3]或者version和ofs_in_node对应的3个字节全部都置为了零。