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