F2FS源码分析系列文章

主目录
一、文件系统布局以及元数据结构
二、文件数据的存储以及读写
三、文件与目录的创建以及删除(未完成)
四、垃圾回收机制
五、数据恢复机制
  1. 数据恢复的原理以及方式
  2. 后滚恢复和Checkpoint的作用与实现
  3. 前滚恢复和Recovery的作用与实现(未完成)
六、重要数据结构或者函数的分析

Checkpoint的作用与实现

后滚恢复即恢复到上一个Checkpoint点的元数据状态,因此F2FS需要在特定的时刻将Checkpoint的数据写入到磁盘中。

Checkpoint的时机

CP是一个开销很大的操作,因此合理选取CP时机,能够很好地提高性能。CP的触发时机有:

前台GC(FG_GC)
FASTBOOT
UMOUNT
DISCARD
RECOVERY
TRIM
周期进行

因此F2FS有几个宏表示CP的触发原因:

#define  CP_UMOUNT   0x00000001
#define CP_FASTBOOT 0x00000002
#define CP_SYNC     0x00000004
#define CP_RECOVERY 0x00000008
#define CP_DISCARD  0x00000010
#define CP_TRIMMED  0x00000020

大部分情况下,都是触发 CP_SYNC 这个宏的CP。

Checkpoint的核心流程

Checkpoint的入口函数以及核心数据结构

struct cp_control {int reason;         /* Checkpoint的原因,大部分情况下是CP_SYNC */__u64 trim_start;   /* CP处理后的数据的block起始地址 */__u64 trim_end;     /* CP处理后的数据的block结束地址 */__u64 trim_minlen;  /* CP处理后的最小长度 */
};int f2fs_write_checkpoint(struct f2fs_sb_info *sbi, struct cp_control *cpc)

下面分段分析 CP_SYNC 原因的 f2fs_write_checkpoint 函数的核心流程。

f2fs_write_checkpoint(struct f2fs_sb_info *sbi, struct cp_control *cpc)
{struct f2fs_checkpoint *ckpt = F2FS_CKPT(sbi); //从sbi读取当前CP的数据结构...err = block_operations(sbi); //将文件系统的所有操作都停止...f2fs_flush_merged_writes(sbi); // 将暂存的所有BIO刷写到磁盘...ckpt_ver = cur_cp_version(ckpt); // 获取当前CP的versionckpt->checkpoint_ver = cpu_to_le64(++ckpt_ver); // 给当前CP version加1// 更新元数据的NAT区域f2fs_flush_nat_entries(sbi, cpc); // 刷写所有nat entries到磁盘// 更新元数据的SIT区域f2fs_flush_sit_entries(sbi, cpc); // 刷写所有sit entries到磁盘,处理dirty prefree segments// 更新元数据的Checkpoint区域以及Summary区域err = do_checkpoint(sbi, cpc); // checkpoint核心流程f2fs_clear_prefree_segments(sbi, cpc); // 清除dirty prefree segments的dirty标记unblock_operations(sbi); //恢复文件系统的操作...f2fs_update_time(sbi, CP_TIME); // 更新CP的时间...
}

Checkpoint涉及的子函数的分析

暂存BIO的回写

一般情况下,文件系统与设备的交互的开销是比较大的,因此一些文件系统为了减少交互的开销,都会尽可能将更多的page合并在一个bio中,再提交到设备,进而减少交互的次数。F2FS中,在sbi中使用了struct f2fs_bio_info结构用于减少交互次数,它的核心是缓存一个bio,将即将回写的page都保存到这个bio中,等到bio尽可能满再回写进入磁盘。它在sbi的声明如下:

struct f2fs_sb_info {...struct f2fs_bio_info *write_io[NR_PAGE_TYPE]; // NR_PAGE_TYPE表示HOW/WARM/COLD不同类型的数据...
}

在Checkpoint流程中,必须要回写暂存的page,以获得系统最新的稳定状态信息,它调用了函数是 f2fs_flush_merged_writesf2fs_flush_merged_writes 函数调用了f2fs_submit_merged_write分别回写了DATA、NODE、META的信息。然后会调用__submit_merged_write_cond函数,这个函数会遍历HOW/WARM/COLD对应的sbi->write_io进行回写,最后调用__submit_merged_bio函数,从sbi->write_io得到bio,submit到设备中。

void f2fs_flush_merged_writes(struct f2fs_sb_info *sbi)
{f2fs_submit_merged_write(sbi, DATA);f2fs_submit_merged_write(sbi, NODE);f2fs_submit_merged_write(sbi, META);
}void f2fs_submit_merged_write(struct f2fs_sb_info *sbi, enum page_type type)
{__submit_merged_write_cond(sbi, NULL, 0, 0, type, true);
}static void __submit_merged_write_cond(struct f2fs_sb_info *sbi,struct inode *inode, nid_t ino, pgoff_t idx,enum page_type type, bool force)
{enum temp_type temp;if (!force && !has_merged_page(sbi, inode, ino, idx, type))return;for (temp = HOT; temp < NR_TEMP_TYPE; temp++) { // 遍历不同的HOT/WARM/COLD类型就行回写__f2fs_submit_merged_write(sbi, type, temp);/* TODO: use HOT temp only for meta pages now. */if (type >= META)break;}
}static void __f2fs_submit_merged_write(struct f2fs_sb_info *sbi,enum page_type type, enum temp_type temp)
{enum page_type btype = PAGE_TYPE_OF_BIO(type);struct f2fs_bio_info *io = sbi->write_io[btype] + temp; // temp可以计算属于HOT/WARM/COLD对应的sbi->write_iodown_write(&io->io_rwsem);/* change META to META_FLUSH in the checkpoint procedure */if (type >= META_FLUSH) {io->fio.type = META_FLUSH;io->fio.op = REQ_OP_WRITE;io->fio.op_flags = REQ_META | REQ_PRIO | REQ_SYNC;if (!test_opt(sbi, NOBARRIER))io->fio.op_flags |= REQ_PREFLUSH | REQ_FUA;}__submit_merged_bio(io);up_write(&io->io_rwsem);
}static void __submit_merged_bio(struct f2fs_bio_info *io)
{struct f2fs_io_info *fio = &io->fio;if (!io->bio)return;bio_set_op_attrs(io->bio, fio->op, fio->op_flags);if (is_read_io(fio->op))trace_f2fs_prepare_read_bio(io->sbi->sb, fio->type, io->bio);elsetrace_f2fs_prepare_write_bio(io->sbi->sb, fio->type, io->bio);__submit_bio(io->sbi, io->bio, fio->type); // 从f2fs_io_info得到bio,提交到设备io->bio = NULL;
}

NAT区域的脏数据回写

f2fs_flush_nat_entriesf2fs_flush_sit_entries 的作用是将暂存在ram的nat entry合sit entry都回写到Journal或磁盘当中:

f2fs_flush_nat_entries函数

修改node的信息会对对应的nat_entry进行修改,同时nat_entry会被设置为脏,加入到nm_i->nat_set_root的radix tree中。Checkpoint会对脏的nat_entry进行回写,完成元数据的更新。

首先声明了一个list变量LIST_HEAD(sets),然后通过一个while循环,将nat_entry_set按一个set为单位,对脏的nat_entry进行提取,每次提取SETVEC_SIZE个,然后保存到setvec[SETVEC_SIZE]中,然后对setvec中的每一个nat_entry_set,按照一定条件加入到LIST_HEAD(sets)的链表中。最后针对LIST_HEAD(sets)nat_entry_set,执行__flush_nat_entry_set函数,对脏数据进行回写。__flush_nat_entry_set有两种回写方法,第一种是写入到curseg的journal中,第二种是直接找到对应的nat block,回写到磁盘中。

void f2fs_flush_nat_entries(struct f2fs_sb_info *sbi, struct cp_control *cpc)
{struct f2fs_nm_info *nm_i = NM_I(sbi);struct curseg_info *curseg = CURSEG_I(sbi, CURSEG_HOT_DATA);struct f2fs_journal *journal = curseg->journal;struct nat_entry_set *setvec[SETVEC_SIZE];struct nat_entry_set *set, *tmp;unsigned int found;nid_t set_idx = 0;LIST_HEAD(sets);if (!nm_i->dirty_nat_cnt)return;down_write(&nm_i->nat_tree_lock);/** __gang_lookup_nat_set 这个函数就是从radix tree读取set_idx开始,* 连续读取SETVEC_SIZE这么多个nat_entry_set,保存在setvec中* 然后按照一定条件,通过__adjust_nat_entry_set函数加入到LIST_HEAD(sets)链表中* */while ((found = __gang_lookup_nat_set(nm_i,set_idx, SETVEC_SIZE, setvec))) {unsigned idx;set_idx = setvec[found - 1]->set + 1;for (idx = 0; idx < found; idx++)__adjust_nat_entry_set(setvec[idx], &sets,MAX_NAT_JENTRIES(journal));}/** flush dirty nats in nat entry set* 遍历这个list所有的nat_entry_set,然后写入到curseg->journal中* */list_for_each_entry_safe(set, tmp, &sets, set_list)__flush_nat_entry_set(sbi, set, cpc);up_write(&nm_i->nat_tree_lock);/* Allow dirty nats by node block allocation in write_begin */
}

__flush_nat_entry_set有两种回写的方式,第一种是写入到curseg的journal中,第二种是回写到nat block中。

第一种写入方式通常是由于curseg有足够的journal的情况下的写入,首先遍历nat_entry_set中的所有nat_entry,然后根据nid找到curseg->journal中对应的nat_entry的位置,跟着将被遍历的nat_entry的值赋予给curseg->journal的nat_entry,通过raw_nat_from_node_info完成curseg的nat_entry的更新。

第二种写入方式在curseg没有足够的journal的时候触发,首先根据nid找到NAT区域的对应的f2fs_nat_block,然后通过get_next_nat_page读取出来,然后通过raw_nat_from_node_info进行更新。

static void __flush_nat_entry_set(struct f2fs_sb_info *sbi,struct nat_entry_set *set, struct cp_control *cpc)
{struct curseg_info *curseg = CURSEG_I(sbi, CURSEG_HOT_DATA);struct f2fs_journal *journal = curseg->journal;nid_t start_nid = set->set * NAT_ENTRY_PER_BLOCK; // 根据set number找到对应f2fs_nat_blockbool to_journal = true;struct f2fs_nat_block *nat_blk;struct nat_entry *ne, *cur;struct page *page = NULL;/** there are two steps to flush nat entries:* #1, flush nat entries to journal in current hot data summary block.* #2, flush nat entries to nat page.*/if (enabled_nat_bits(sbi, cpc) ||!__has_cursum_space(journal, set->entry_cnt, NAT_JOURNAL)) //当curseg的journal空间不够了,就刷写到磁盘中to_journal = false;if (to_journal) {down_write(&curseg->journal_rwsem);} else {page = get_next_nat_page(sbi, start_nid); /* 根据nid找到管理这个nid的f2fs_nat_block */nat_blk = page_address(page);f2fs_bug_on(sbi, !nat_blk);}/** flush dirty nats in nat entry set* 遍历所有的nat_entry** nat_entry只存在于内存当中,具体在磁盘保存的是f2fs_entry_block* */list_for_each_entry_safe(ne, cur, &set->entry_list, list) {struct f2fs_nat_entry *raw_ne;nid_t nid = nat_get_nid(ne);int offset;f2fs_bug_on(sbi, nat_get_blkaddr(ne) == NEW_ADDR);if (to_journal) {// 搜索当前的journal中nid所在的位置offset = f2fs_lookup_journal_in_cursum(journal,NAT_JOURNAL, nid, 1);f2fs_bug_on(sbi, offset < 0);raw_ne = &nat_in_journal(journal, offset); // 从journal中取出f2fs_nat_entry的信息nid_in_journal(journal, offset) = cpu_to_le32(nid); // 更新journal的nid} else {raw_ne = &nat_blk->entries[nid - start_nid]; /* 拿到nid对应的nat_entry地址,下面开始填数据 */}raw_nat_from_node_info(raw_ne, &ne->ni); // 将node info的信息更新到journal中后者磁盘中nat_reset_flag(ne); // 清除需要CP的标志__clear_nat_cache_dirty(NM_I(sbi), set, ne); // 从dirty list清除处理后的entryif (nat_get_blkaddr(ne) == NULL_ADDR) { // 如果对应nid已经是被无效化了,则释放add_free_nid(sbi, nid, false, true);} else {spin_lock(&NM_I(sbi)->nid_list_lock);update_free_nid_bitmap(sbi, nid, false, false); // 更新可用的nat的bitmapspin_unlock(&NM_I(sbi)->nid_list_lock);}}if (to_journal) {up_write(&curseg->journal_rwsem);} else {__update_nat_bits(sbi, start_nid, page);f2fs_put_page(page, 1);}/* Allow dirty nats by node block allocation in write_begin */if (!set->entry_cnt) {radix_tree_delete(&NM_I(sbi)->nat_set_root, set->set);kmem_cache_free(nat_entry_set_slab, set);}
}

SIT区域的脏数据回写

f2fs_flush_sit_entries函数

主要过程跟 f2fs_flush_nat_entries 类似,将dirty的seg_entry刷写到journal或sit block中

void f2fs_flush_sit_entries(struct f2fs_sb_info *sbi, struct cp_control *cpc)
{struct sit_info *sit_i = SIT_I(sbi);unsigned long *bitmap = sit_i->dirty_sentries_bitmap;struct curseg_info *curseg = CURSEG_I(sbi, CURSEG_COLD_DATA);struct f2fs_journal *journal = curseg->journal;struct sit_entry_set *ses, *tmp;struct list_head *head = &SM_I(sbi)->sit_entry_set;bool to_journal = true;struct seg_entry *se;down_write(&sit_i->sentry_lock);if (!sit_i->dirty_sentries)goto out;/** add and account sit entries of dirty bitmap in sit entry* set temporarily** 遍历所有dirty的segment的segno,* 找到对应的sit_entry_set,然后保存到sbi->sm_info->sit_entry_set*/add_sits_in_set(sbi);/** if there are no enough space in journal to store dirty sit* entries, remove all entries from journal and add and account* them in sit entry set.*/if (!__has_cursum_space(journal, sit_i->dirty_sentries, SIT_JOURNAL))remove_sits_in_journal(sbi);/** there are two steps to flush sit entries:* #1, flush sit entries to journal in current cold data summary block.* #2, flush sit entries to sit page.* 遍历list中的所有segno对应的sit_entry_set*/list_for_each_entry_safe(ses, tmp, head, set_list) {struct page *page = NULL;struct f2fs_sit_block *raw_sit = NULL;unsigned int start_segno = ses->start_segno;unsigned int end = min(start_segno + SIT_ENTRY_PER_BLOCK,(unsigned long)MAIN_SEGS(sbi));unsigned int segno = start_segno; /* 找到 */if (to_journal &&!__has_cursum_space(journal, ses->entry_cnt, SIT_JOURNAL))to_journal = false;if (to_journal) {down_write(&curseg->journal_rwsem);} else {page = get_next_sit_page(sbi, start_segno); /* 访问磁盘,从磁盘获取到f2fs_sit_block */raw_sit = page_address(page); /* 根据segno获得f2fs_sit_block,然后下一步将数据写入这个block当中 */}/** flush dirty sit entries in region of current sit set* 遍历segno~end所有dirty的seg_entry* */for_each_set_bit_from(segno, bitmap, end) {int offset, sit_offset;se = get_seg_entry(sbi, segno); /* 根据segno从SIT缓存中获取到seg_entry,这个缓存是F2FS初始化的时候,将全部seg_entry读入创建的 */if (to_journal) {offset = f2fs_lookup_journal_in_cursum(journal,SIT_JOURNAL, segno, 1);f2fs_bug_on(sbi, offset < 0);segno_in_journal(journal, offset) =cpu_to_le32(segno);seg_info_to_raw_sit(se,&sit_in_journal(journal, offset)); // 更新journal的数据check_block_count(sbi, segno,&sit_in_journal(journal, offset));} else {sit_offset = SIT_ENTRY_OFFSET(sit_i, segno);seg_info_to_raw_sit(se,&raw_sit->entries[sit_offset]); // 更新f2fs_sit_block的数据check_block_count(sbi, segno,&raw_sit->entries[sit_offset]);}__clear_bit(segno, bitmap); // 从dirty map中除名sit_i->dirty_sentries--;ses->entry_cnt--;}if (to_journal)up_write(&curseg->journal_rwsem);elsef2fs_put_page(page, 1);f2fs_bug_on(sbi, ses->entry_cnt);release_sit_entry_set(ses);}f2fs_bug_on(sbi, !list_empty(head));f2fs_bug_on(sbi, sit_i->dirty_sentries);
out:up_write(&sit_i->sentry_lock);/** 通过CP的时机,将暂存在dirty_segmap的dirty的segment信息,更新到free_segmap中* 而且与接下来的do_checkpoint完成的f2fs_clear_prefree_segments有关系,因为 这里处理完了* dirty prefree segments,所以在f2fs_clear_prefree_segments这个函数将它的dirty标记清除* */set_prefree_as_free_segments(sbi);
}static inline struct seg_entry *get_seg_entry(struct f2fs_sb_info *sbi,unsigned int segno)
{struct sit_info *sit_i = SIT_I(sbi);return &sit_i->sentries[segno];
}static void set_prefree_as_free_segments(struct f2fs_sb_info *sbi)
{struct dirty_seglist_info *dirty_i = DIRTY_I(sbi);unsigned int segno;mutex_lock(&dirty_i->seglist_lock);/** 遍历dirty_seglist_info->dirty_segmap[PRE],然后执行__set_test_and_free* */for_each_set_bit(segno, dirty_i->dirty_segmap[PRE], MAIN_SEGS(sbi))__set_test_and_free(sbi, segno); /* 根据segno更新free_segmap的可用信息 */mutex_unlock(&dirty_i->seglist_lock);
}static inline void __set_test_and_free(struct f2fs_sb_info *sbi,unsigned int segno)
{struct free_segmap_info *free_i = FREE_I(sbi);unsigned int secno = GET_SEC_FROM_SEG(sbi, segno);unsigned int start_segno = GET_SEG_FROM_SEC(sbi, secno);unsigned int next;spin_lock(&free_i->segmap_lock);/** free_i->free_segmap用这个bitmap表示这个segment是否是dirty* 如果这个segno对应的segment位置等于0,代表不是dirty,不作处理* 如果这个segno对应的位置等于1,表示这个segment是dirty的,那么在当前的free_segment+1,更新最新的free_segment信息* */if (test_and_clear_bit(segno, free_i->free_segmap)) {free_i->free_segments++;next = find_next_bit(free_i->free_segmap,start_segno + sbi->segs_per_sec, start_segno);if (next >= start_segno + sbi->segs_per_sec) {if (test_and_clear_bit(secno, free_i->free_secmap))free_i->free_sections++;}}spin_unlock(&free_i->segmap_lock);
}

Checkpoint区域的回写

上述分别描述了对NAT和SIT的回写与更新,而do_checkpoint是针对Checkpoint区域的更新。Checkpoint主要涉及两部分,第一部分f2fs_checkpoint结构的更新,第二部分是curseg的summary数据的回写。在分析这个函数之前,需要知道元数据的Checkpoint区域在磁盘中是如何保存的,磁盘的保存结构如下:

             +---------------------------------------------------------------------------------------------------+| f2fs_checkpoint | data summaries | hot node summaries | warm node summaries | cold node summaries |+---------------------------------------------------------------------------------------------------+.                 .             .                                   .               .                 compacted summaries                 .        +----------------+-------------------+----------------+|  nat journal   |    sit journal    | data summaries |+----------------+-------------------+----------------+.                  normal summaries                   .        +----------------+-------------------+----------------+|                    data summaries                   |+----------------+-------------------+----------------+

其中f2fs_checkpoint、hot/warm/cold node summaries都分别占用一个block的空间。f2fs为了减少Checkpoint的写入开销,将data summaries被设计为可变的。它包含两种写入方式,一种是compacted summaries写入,另一种是normal summaries写入。compacted summaries可以在一次Checkpoint中,减少1~2个page的写入。

do_checkpoint函数

下面是简化的do_checkpoint函数核心流程,如下所示:

static int do_checkpoint(struct f2fs_sb_info *sbi, struct cp_control *cpc)
{// 第一部分,根据curseg,修改f2fs_checkpoint结构的信息...for (i = 0; i < NR_CURSEG_NODE_TYPE; i++) {ckpt->cur_node_segno[i] =cpu_to_le32(curseg_segno(sbi, i + CURSEG_HOT_NODE));ckpt->cur_node_blkoff[i] =cpu_to_le16(curseg_blkoff(sbi, i + CURSEG_HOT_NODE));ckpt->alloc_type[i + CURSEG_HOT_NODE] =curseg_alloc_type(sbi, i + CURSEG_HOT_NODE);}//printk("[do-checkpoint] point 3\n");for (i = 0; i < NR_CURSEG_DATA_TYPE; i++) {ckpt->cur_data_segno[i] =cpu_to_le32(curseg_segno(sbi, i + CURSEG_HOT_DATA));ckpt->cur_data_blkoff[i] =cpu_to_le16(curseg_blkoff(sbi, i + CURSEG_HOT_DATA));ckpt->alloc_type[i + CURSEG_HOT_DATA] =curseg_alloc_type(sbi, i + CURSEG_HOT_DATA);}// 第二部分,根据curseg,修改summary的信息...data_sum_blocks = f2fs_npages_for_summary_flush(sbi, false);if (data_sum_blocks < NR_CURSEG_DATA_TYPE)__set_ckpt_flags(ckpt, CP_COMPACT_SUM_FLAG);else__clear_ckpt_flags(ckpt, CP_COMPACT_SUM_FLAG);f2fs_write_data_summaries(sbi, start_blk); // 将data summary以及里面的journal写入磁盘/* * node summaries的写回只有在启动和关闭F2FS的时候才会执行,* 如果出现的宕机的情况下,就会失去了UMOUNT的标志,也会失去了所有的NODE SUMMARY* F2FS会进行根据上次checkpoint的情况进行恢复*/if (__remain_node_summaries(cpc->reason)) {f2fs_write_node_summaries(sbi, start_blk); // 将node summary以及里面的journal写入磁盘start_blk += NR_CURSEG_NODE_TYPE;}commit_checkpoint(sbi, ckpt, start_blk); // 将修改后的checkpoint区域的数据提交到设备,对磁盘的元数据进行更新...return 0;
}

首先,第一部分主要是针对元数据区域的f2fs_checkpoint结构的修改,其实包括将curseg的当前segno,blkoff等写入到f2fs_checkpoint中,以便下次重启时可以根据这些信息,重建curseg。

接下来重点讨论,Checkpoint区域的summary的回写,在分析流程之前,需要分析compacted summaries和normal summaries的差别。

compacted summaries和normal summaries
通过查看curseg的结构可以知道,curseg管理了(NODE,DATA) X (HOT,WARM,COLD)总共6个的segment,因此也需要管理这6个segment对应的f2fs_summary_block

因此一般情况下,每一次checkpoint时候,应该需要回写6种类型的f2fs_summary_block,即6个block到磁盘。

为了减少这部分回写的开销,f2fs针对DATA类型f2fs_summary_block设计了一种compacted summary block。一般情况下,DATA需要回写3个f2fs_summary_block到磁盘(HOT,WARM,COLD),但是如果使用了compacted summary block,大部分情况下只需要回写1~2个block。

compacted summary block被设计为通过1~2个block保存当前curseg所有的元信息,它的核心设计是将HOW WARM COLD DATA的元信息混合保存:

混合类型Journal保存
compacted summary block分别维护了一个公用的nat journal,以及sit journal,HOT WARM COLD类型的Journal都会混合保存进入两个journal结构中。

在满足COMPACTED的条件下,系统启动时,F2FS会从磁盘中读取这两个Journal到内存中,分别保存在HOT以及COLD所在的curseg->journal中。

不同类型的journal会在CP时刻,通过f2fs_flush_sit_entries函数写入到HOT或者COLD对应的curseg->journal区域中。如果HOT或者COLD对应的curseg->journal区域的空间不够了,就将不同类型的journal保存的segment的信息,直接写入到对应的sit entry block中。

接下来将HOT或者COLD对应的curseg->journal包装为compacted block回写到cp区域中。

混合类型Summary保存
b) 以及将HOT,WARM,COLD三种类型的summary混合保存同一个data summaries数组中,它们的差别如下:

compacted summary block (4KB)
+------------------+
|nat journal       |
|sit journal       |
|data sum[439]     | data summaries数组大小是439
+------------------+
|                  | 如果需要,会接上一个纯summary数组的block
| data sum[584]    | data summaries数组大小是584
|                  |
+------------------+normal summary block,表示三种类型的DATA的summary
+--------------------+
|hot data journal    |
|hot data summaries  | data summaries数组大小是512
|                    |
+--------------------+
|warm data journal   |
|warm data summaries | data summaries数组大小是512
|                    |
+--------------------+
|cold data journal   |
|cold data summaries | data summaries数组大小是512
|                    |
+--------------------+

根据上面的描述,不同类型的summary block的可以保存的summary的大小,可以得到
HOT,WARM,COLD DATA这三种类型,如果目前加起来仅使用了

  1. 少于439的block(只修改了439个f2fs_summary),那么可以通过compacted回写方式进行回写,即通过一个compacted summary block完成回写,需要回写1个block。
  2. 大于439,少于439+584=1023个block,那么可以通过compacted回写方式进行回写,即可以通过compacted summary block加一个纯summary block的方式保存所有信息,需要回写2个block。
  3. 大于1023的情况下,即和normal summary block同样的回写情况,那么就会使用normal summary block的回写方式完成回写,即回写3个block。(因为大于1023情况下,如果继续使用compacted回写,最差的情况下要回写4个block)

接下来进行代码分析:


// 根据需要回写的summary的数目,返回需要写回的block的数目,返回值有1、2、3
data_sum_blocks = f2fs_npages_for_summary_flush(sbi, false); // 如果data_sum_blocks = 1 或者 2,则表示回写1个或者2个block,则设置CP_COMPACT_SUM_FLAG标志
if (data_sum_blocks < NR_CURSEG_DATA_TYPE) // NR_CURSEG_DATA_TYPE = 3__set_ckpt_flags(ckpt, CP_COMPACT_SUM_FLAG);
else__clear_ckpt_flags(ckpt, CP_COMPACT_SUM_FLAG);// 然后将summary写入磁盘
f2fs_write_data_summaries(sbi, start_blk); // 将data summary以及里面的journal写入磁盘

f2fs_write_data_summaries函数会判断一下是否设置了CP_COMPACT_SUM_FLAG标志,采取不同的方法写入磁盘

void f2fs_write_data_summaries(struct f2fs_sb_info *sbi, block_t start_blk)
{if (is_set_ckpt_flags(sbi, CP_COMPACT_SUM_FLAG))write_compacted_summaries(sbi, start_blk);elsewrite_normal_summaries(sbi, start_blk, CURSEG_HOT_DATA);
}

write_compacted_summaries函数会根据上述的compacted block的数据分布,将数据写入到磁盘中

static void write_compacted_summaries(struct f2fs_sb_info *sbi, block_t blkaddr)
{struct page *page;unsigned char *kaddr;struct f2fs_summary *summary;struct curseg_info *seg_i;int written_size = 0;int i, j;int datatypes = CURSEG_COLD_DATA;
#ifdef CONFIG_F2FS_COMPRESSIONdatatypes = CURSEG_BG_COMPR_DATA;
#endifpage = f2fs_grab_meta_page(sbi, blkaddr++);kaddr = (unsigned char *)page_address(page);memset(kaddr, 0, PAGE_SIZE);/* Step 1: write nat cache */seg_i = CURSEG_I(sbi, CURSEG_HOT_DATA); // 第一步写nat的journalmemcpy(kaddr, seg_i->journal, SUM_JOURNAL_SIZE);written_size += SUM_JOURNAL_SIZE;/* Step 2: write sit cache */seg_i = CURSEG_I(sbi, CURSEG_COLD_DATA);memcpy(kaddr + written_size, seg_i->journal, SUM_JOURNAL_SIZE); // 第二步写sit的journalwritten_size += SUM_JOURNAL_SIZE;/* Step 3: write summary entries */for (i = CURSEG_HOT_DATA; i <= datatypes; i++) { // 开始写summaryunsigned short blkoff;seg_i = CURSEG_I(sbi, i);if (sbi->ckpt->alloc_type[i] == SSR)blkoff = sbi->blocks_per_seg;elseblkoff = curseg_blkoff(sbi, i);for (j = 0; j < blkoff; j++) {if (!page) { // 如果f2fs compacted block写不下,则创建一个纯summary的blockpage = f2fs_grab_meta_page(sbi, blkaddr++);kaddr = (unsigned char *)page_address(page);memset(kaddr, 0, PAGE_SIZE);written_size = 0;}summary = (struct f2fs_summary *)(kaddr + written_size);*summary = seg_i->sum_blk->entries[j];written_size += SUMMARY_SIZE;if (written_size + SUMMARY_SIZE <= PAGE_SIZE -SUM_FOOTER_SIZE)continue;set_page_dirty(page); // 如果超过了compaced sum block可以承载的极限,就设置这个block是脏,等待回写f2fs_put_page(page, 1);page = NULL;}}if (page) {set_page_dirty(page);f2fs_put_page(page, 1);}
}

write_normal_summaries函数则是简单地将按照HOT/WARM/COLD的顺序写入到checkpoint区域中

static void write_normal_summaries(struct f2fs_sb_info *sbi,block_t blkaddr, int type)
{int i, end;if (IS_DATASEG(type))end = type + NR_CURSEG_DATA_TYPE;elseend = type + NR_CURSEG_NODE_TYPE;for (i = type; i < end; i++) // 根据 HOW WARM COLD 都写入磁盘write_current_sum_page(sbi, i, blkaddr + (i - type));
}static void write_current_sum_page(struct f2fs_sb_info *sbi,int type, block_t blk_addr)
{struct curseg_info *curseg = CURSEG_I(sbi, type);struct page *page = f2fs_grab_meta_page(sbi, blk_addr);struct f2fs_summary_block *src = curseg->sum_blk;struct f2fs_summary_block *dst;dst = (struct f2fs_summary_block *)page_address(page);memset(dst, 0, PAGE_SIZE);mutex_lock(&curseg->curseg_mutex);down_read(&curseg->journal_rwsem);memcpy(&dst->journal, curseg->journal, SUM_JOURNAL_SIZE);up_read(&curseg->journal_rwsem);memcpy(dst->entries, src->entries, SUM_ENTRY_SIZE);memcpy(&dst->footer, &src->footer, SUM_FOOTER_SIZE);mutex_unlock(&curseg->curseg_mutex);set_page_dirty(page);f2fs_put_page(page, 1);
}

F2FS源码分析-5.2 [数据恢复流程] 后滚恢复和Checkpoint的作用与实现相关推荐

  1. F2FS源码分析系列文章目录

    一.文件系统布局以及元数据结构 总体结构 Superblock区域 Checkpoint区域 Segment Infomation Table区域(SIT) Node Address Table区域( ...

  2. F2FS源码分析-2.2 [F2FS 读写部分] F2FS的一般文件写流程分析

    F2FS源码分析系列文章 主目录 一.文件系统布局以及元数据结构 二.文件数据的存储以及读写 F2FS文件数据组织方式 一般文件写流程 一般文件读流程 目录文件读流程(未完成) 目录文件写流程(未完成 ...

  3. F2FS源码分析-2.3 [F2FS 读写部分] F2FS的一般文件读流程分析

    F2FS源码分析系列文章 主目录 一.文件系统布局以及元数据结构 二.文件数据的存储以及读写 F2FS文件数据组织方式 一般文件写流程 一般文件读流程 目录文件读流程(未完成) 目录文件写流程(未完成 ...

  4. F2FS源码分析-1.6 [F2FS 元数据布局部分] Segment Summary Area-SSA结构

    F2FS源码分析系列文章 主目录 一.文件系统布局以及元数据结构 总体结构 Superblock区域 Checkpoint区域 Segment Infomation Table区域(SIT) Node ...

  5. F2FS源码分析-2.1 [F2FS 读写部分] F2FS文件数据组织方式以及物理地址的映射

    F2FS源码分析系列文章 主目录 一.文件系统布局以及元数据结构 二.文件数据的存储以及读写 F2FS文件数据组织方式 一般文件写流程 一般文件读流程 目录文件读流程(未完成) 目录文件写流程(未完成 ...

  6. F2FS源码分析-1.3 [F2FS 元数据布局部分] Checkpoint结构

    F2FS源码分析系列文章 主目录 一.文件系统布局以及元数据结构 总体结构 Superblock区域 Checkpoint区域 Segment Infomation Table区域(SIT) Node ...

  7. F2FS源码分析-6.6 [其他重要数据结构以及函数] F2FS的重命名过程-f2fs_rename函数

    F2FS源码分析系列文章 主目录 一.文件系统布局以及元数据结构 二.文件数据的存储以及读写 三.文件与目录的创建以及删除(未完成) 四.垃圾回收机制 五.数据恢复机制 六.重要数据结构或者函数的分析 ...

  8. F2FS源码分析-1.4 [F2FS 元数据布局部分] Segment Infomation Table-SIT结构

    F2FS源码分析系列文章 主目录 一.文件系统布局以及元数据结构 总体结构 Superblock区域 Checkpoint区域 Segment Infomation Table区域(SIT) Node ...

  9. F2FS源码分析-1.2 [F2FS 元数据布局部分] Superblock结构

    F2FS源码分析系列文章 主目录 一.文件系统布局以及元数据结构 总体结构 Superblock区域 Checkpoint区域 Segment Infomation Table区域(SIT) Node ...

最新文章

  1. nginx的启动初始化过程(一)
  2. 信息系统项目管理知识--信息系统建设
  3. python递归方式和普通方式实现输出和查询斐波那契数列
  4. 搭建FastDFS分布式文件方式一(Docker版本)
  5. pythonlbp纹理提取_Python + OpenCV 实现LBP特征提取的示例代码
  6. 短视频技术详解:Android端的短视频开发技术
  7. linux输入命令对话框,linux里命令的对话框whiptail
  8. 安装python要注意什么_安装python注意事项
  9. inner join 和join的区别_left join、right join和join ???
  10. java基础输入_java基础之标准输入
  11. 计算机论文与护理,快速护理论文范文
  12. Android-将RGB彩色图转换为灰度图
  13. 王者荣耀是用什么代码变成MOBA游戏的,该怎么学?有前途吗?
  14. 【IDE】【WebStorm】html排版设置head和body缩进
  15. OptaPlanner快速开始
  16. Spring Boot使用qq邮箱实现验证码发送
  17. 如何将图片表格转换为excel表格?
  18. windows无法连接到打印机?三个方法连接打印机(Win10系统)
  19. 【Android笔记】Android 使用高德SDK获取定位
  20. 关于STM32串口波特率的产生,以及USARTDIV写入到USART_BRR寄存器的值

热门文章

  1. 【代码】第11章 APP的爬取,appium打开微信朋友圈
  2. OSChina 周一乱弹 —— 济南源创会特刊
  3. 最受IT公司欢迎的 30 款开源软件
  4. VMware、配置VMware vSphere 6.0 vMotion、DRS、HA和FT
  5. 合数(数论基础概念)
  6. 爬虫 - 收藏集 - 掘金
  7. SCP 从Linux下载文件到Windows本地
  8. 大数据-孩子学习成绩分析
  9. win10电脑耳机没有声音 如何在不重启电脑情况下耳机重新有声音
  10. 20135108李泽源 Java实验一