数据存储:clean状态的ne和数据结构关系图大的原则
数据存储:clean状态的ne和数据结构关系图大的原则entry(简称ne)在代码中涉及多个数据结构,先上图。ne涉及的数据结构关系图layout中的(持久化存储);蓝色部分是内存数据结构。比如在文件尾部写新数据;lseek到hole区域,等等。eg1:在文件尾部写新数据,分配了新的block比如更新文件数据,,被更新的数据块已经存在与disk上。除非被路径10或11回收,否则ne一直作为缓存存在系统中。
nat entry(简称ne)在代码中涉及多个数据结构,先上图。
图1 ne涉及的数据结构关系图
大的原则
1)红色部分是disk layout中的(持久化存储);蓝色部分是内存数据结构。
2)系统中所有的ne,缓存在nat cache中(radix tree管理),用于快速检索ne。
3)所有clean状态的ne,通过clean list管理。
4)一个NAT block中的所有ne,归属于同一个set;所有的set通过radix tree管理。引入set的目的是为了在将dirty ne刷入disk时,属于同一个block的呢会被一起写入block,提高性能和存储设备寿命。
5)系统内存压力大时,f2fs_balance_fs会回收clean状态的ne。
6)dirty ne的block add是NEW_ADDR时,说明是刚分配的,checkpoint时不需要写回存储设备,也是出于性能、寿命考虑。
7)journal是能用尽量用的,即使checkpoint后,journal中也会存在一些ne,这些ne还没有写回nat block。这也是出于性能考虑,如果不用journal缓存ne,那么ne就需要写回nat block,这会导致频繁的写disk;而用journal则可以将所有的ne一次性写入checkpoint的cp pack区域。
图1执行路径描述
1)挂载f2fs场景。从disk layout的cp pack区域读入journal,存于struct curseg_info->journal中。
f2fs_fill_super –>f2fs_build_segment_manager –>build_curseg –>restore_curseg_summaries –>read_compacted_summaries 或 read_normal_summaries
cp pack、compatced/normal summaries概念参考待整理文章。
2)第一次访问ne,并且disk NAT区域没有该ne场景。在内存中,这些ne是新创建的,需记录在nat cache和clean list中。
比如在文件尾部写新数据;lseek到hole区域,等等。
eg1:在文件尾部写新数据,分配了新的block
vfs_write –> new_sync_write –> call_write_iter –> f2fs_file_write_iter -> f2fs_buffered_write_iter –> generic_perform_write –> f2fs_write_begin –> prepare_write_begin –> f2fs_get_block –> f2fs_reserve_block –> f2fs_get_dnode_of_data –> f2fs_new_node_page –> set_node_addr–> __init_nat_entry
eg2:seek到文件的空洞区域
vfs_llseek –> f2fs_llseek –> f2fs_seek_block –> f2fs_get_dnode_of_data –> f2fs_new_node_page –> set_node_addr
3)第一次访问ne,这个ne存在于disk NAT区域。在内存中,这些ne是新创建的,需记录在nat cache和clean list中。
比如更新文件数据,,被更新的数据块已经存在与disk上。
vfs_write –> new_sync_write –> call_write_iter –> f2fs_file_write_iter –> f2fs_preallocate_blocks –> f2fs_map_blocks –> f2fs_get_dnode_of_data –> f2fs_new_node_page –> set_node_addr–> __init_nat_entry
4)checkpoint时,journal(内存)中的ne在mount读取后一直没访问过,并且journal(内存)中没有空间容纳脏ne了,journal(内存)中的ne将被nat cache和clean list记录起来
f2fs_write_checkpoint –>f2fs_flush_nat_entries –>
if (cpc->reason & CP_UMOUNT || !__has_cursum_space(journal, nm_i->nat_cnt[DIRTY_NAT], NAT_JOURNAL)) remove_nats_in_journal(sbi);
系统中所有的ne都会记录在nat cache中。除非被路径10或11回收,否则ne一直作为缓存存在系统中。
5)修改ne的场景,常见于做了set_node_addr操作数据存储,修改后的ne被移入dirty list
eg1:序号2中写文件场景。f2fs lfs模式更新文件数据,需新分配了一个block,所以会修改ne的block address,执行set_node_addr。
eg2:在目录中创建了一个文件。
f2fs_create –> f2fs_add_link –> f2fs_do_add_link –> f2fs_add_dentry –> f2fs_add_regular_entry –> f2fs_init_inode_metadata –> f2fs_new_inode_page –> f2fs_new_node_page –> set_node_addr –> __set_nat_cache_dirty
6)checkpoint时,journal(内存)中没有空间容纳dirty list中的ne了,不能放入journal中的ne将被写入page中
f2fs_write_checkpoint –>f2fs_flush_nat_entries –>__flush_nat_entry_set –>
if ((cpc->reason & CP_UMOUNT) || !__has_cursum_space(journal, set->entry_cnt, NAT_JOURNAL)) to_journal = false; if (to_journal) { down_write(&curseg->journal_rwsem); } else { /* 获取ne所在的nat block,读入page */ page = get_next_nat_page(sbi, start_nid); if (IS_ERR(page)) return PTR_ERR(page); nat_blk = page_address(page); f2fs_bug_on(sbi, !nat_blk); } …… /* 更新ne信息到page */ raw_ne = &nat_blk->entries[nid - start_nid]; raw_nat_from_node_info(raw_ne, &ne->ni); ……
7)checkpoint时,journal(内存)中还有空间容纳dirty list中的ne,dirty list中的ne移至dirty list
f2fs_write_checkpoint –>f2fs_flush_nat_entries –>__flush_nat_entry_set –>
/* flush dirty nats in nat entry set */ 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中有ne,则返回ne所在的位置偏移 * 如果journal中没有ne,则返回journal下一个空闲位置 */ offset = f2fs_lookup_journal_in_cursum(journal, NAT_JOURNAL, nid, 1); f2fs_bug_on(sbi, offset entries[nid - start_nid]; } /* 更新journal中ne信息 */ raw_nat_from_node_info(raw_ne, &ne->ni); nat_reset_flag(ne); __clear_nat_cache_dirty(NM_I(sbi), set, ne);
8)checkpoint时,journal中的ne写入cp pack区域(即持久化存储)
f2fs_write_checkpoint –> do_checkpoint –> f2fs_write_data_summaries –> write_compacted_summaries 或write_normal_summaries
mount f2fs时,路径1读取compatcted/normalsummaries到内存中。
9)脏元数据页写入nat block(即持久化存储)
worker_thread –> process_one_work –> wb_workfn –> wb_do_writeback –> wb_check_start_all或wb_check_background_flush –> wb_writeback –> __writeback_inodes_wb –> writeback_sb_inodes –> __writeback_single_inode –> do_writepages –> f2fs_write_meta_pages
kworker线程将脏页数据写入storage device。在大多数情况下,kworker线程周期性回写脏页数据,但也可能因系统脏页过多而主动回写。
10)系统内存压力大时,回收clean状态的ne
f2fs_balance_fs –>f2fs_balance_fs_bg –>f2fs_try_to_free_nats –>__del_from_nat_cache –>__free_nat_entry
只要内存中生成ne数据存储,都会缓存在nat cache中(最大数量10万个,见宏定义DEF_NAT_CACHE_THRESHOLD),系统内存压力大的时候,需要回收clean状态的ne,避免f2fs对系统内存的影响。checkpoint或者f2fs_write_node_pages都会触发f2fs_balance_fs_bg,然后根据系统内存使用情况决定是否回收ne。
void f2fs_balance_fs_bg(struct f2fs_sb_info *sbi, bool from_bg){ if (unlikely(is_sbi_flag_set(sbi, SBI_POR_DOING))) return; /* try to shrink extent cache when there is no enough memory */ if (!f2fs_available_free_memory(sbi, EXTENT_CACHE)) f2fs_shrink_extent_tree(sbi, EXTENT_CACHE_SHRINK_NUMBER); /* check the # of cached NAT entries */ if (!f2fs_available_free_memory(sbi, NAT_ENTRIES)) f2fs_try_to_free_nats(sbi, NAT_ENTRY_PER_BLOCK);
11)umount时回收ne
f2fs_put_super –>f2fs_destroy_node_manager –>__del_from_nat_cache –>__free_nat_entry