f2fs系列文章——快取summary寫入磁碟的問題
這篇文章講述一下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個位元組全部都置為了零。