F2FS nat entry涉及的数据结构(linux 5.18.11)
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 < 0);
/* 获取journal中的ne */
raw_ne = &nat_in_journal(journal, offset);
nid_in_journal(journal, offset) = cpu_to_le32(nid);
}else {
raw_ne = &nat_blk->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/normal summaries到内存中。
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