f2fs系列文章——缓存summary写入磁盘的问题

时间:2021-03-11 10:01:45

    这篇文章讲述一下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个字节全部都置为了零。