f2fs文件系统的页缓存

时间:2022-12-06 09:58:55

f2fs有三种inode,meta_inode,node_inode和普通的文件inode,前两种inode只存在于vfs层,且数量只有一个。meta_inode对应于SIT,NAT,SSA,checkpoint和super block这些文件系统元数据,node inode对应于main area的node segment的数据,普通文件inode读写的区域对应于main area的data segment。所以,所有的f2fs数据的读写都是通过vfs层的inode的。这些inode的保存在f2fs_sb_info中,在文件系统初始化时f2fs_fill_super生成。再看inode的初始化过程,在函数inode.c/f2fs_iget中:

struct inode *f2fs_iget(struct super_block *sb, unsigned long ino)
{
struct f2fs_sb_info *sbi = F2FS_SB(sb);
struct inode *inode;
int ret = 0;

inode = iget_locked(sb, ino);
if (!inode)
return ERR_PTR(-ENOMEM);

if (!(inode->i_state & I_NEW)) {
trace_f2fs_iget(inode);
return inode;
}
if (ino == F2FS_NODE_INO(sbi) || ino == F2FS_META_INO(sbi))
goto make_now;

ret = do_read_inode(inode);
if (ret)
goto bad_inode;
make_now:
if (ino == F2FS_NODE_INO(sbi)) {
inode->i_mapping->a_ops = &f2fs_node_aops;
mapping_set_gfp_mask(inode->i_mapping, GFP_F2FS_ZERO);
} else if (ino == F2FS_META_INO(sbi)) {
inode->i_mapping->a_ops = &f2fs_meta_aops;
mapping_set_gfp_mask(inode->i_mapping, GFP_F2FS_ZERO);
} else if (S_ISREG(inode->i_mode)) {
inode->i_op = &f2fs_file_inode_operations;
inode->i_fop = &f2fs_file_operations;
inode->i_mapping->a_ops = &f2fs_dblock_aops;
} else if (S_ISDIR(inode->i_mode)) {
inode->i_op = &f2fs_dir_inode_operations;
inode->i_fop = &f2fs_dir_operations;
inode->i_mapping->a_ops = &f2fs_dblock_aops;
mapping_set_gfp_mask(inode->i_mapping, GFP_F2FS_ZERO);
} else if (S_ISLNK(inode->i_mode)) {
inode->i_op = &f2fs_symlink_inode_operations;
inode->i_mapping->a_ops = &f2fs_dblock_aops;
} else if (S_ISCHR(inode->i_mode) || S_ISBLK(inode->i_mode) ||
S_ISFIFO(inode->i_mode) || S_ISSOCK(inode->i_mode)) {
inode->i_op = &f2fs_special_inode_operations;
init_special_inode(inode, inode->i_mode, inode->i_rdev);
} else {
ret = -EIO;
goto bad_inode;
}
unlock_new_inode(inode);
trace_f2fs_iget(inode);
return inode;

bad_inode:
iget_failed(inode);
trace_f2fs_iget_exit(inode, ret);
return ERR_PTR(ret);
}
初始化过程区别对待了meta_inode和node_inode,再看看它们的address_space_operation:

const struct address_space_operations f2fs_node_aops = {	.writepage	= f2fs_write_node_page,	.writepages	= f2fs_write_node_pages,	.set_page_dirty	= f2fs_set_node_page_dirty,	.invalidatepage	= f2fs_invalidate_node_page,	.releasepage	= f2fs_release_node_page,};
const struct address_space_operations f2fs_meta_aops = {	.writepage	= f2fs_write_meta_page,	.writepages	= f2fs_write_meta_pages,	.set_page_dirty	= f2fs_set_meta_page_dirty,};
也许大家会觉得奇怪,为什么只有writepage而没有readpage呢,那它们是怎么读磁盘上的数据呢?文件系统实现了两个函数node.c/get_node_page和checkpoint.c/get_meta_page,这两个函数的流程类似,都是首先使用grab_cache_page去获取特定位置的页,grab_cache_page首先会查找address_space的基数树,如果找到该页则返回,如果找不到,则从伙伴系统分配一个新的页;然后调用f2fs_readpage读取磁盘上该页的内容。

struct page *get_meta_page(struct f2fs_sb_info *sbi, pgoff_t index)
{
struct address_space *mapping = sbi->meta_inode->i_mapping;
struct page *page;
repeat:
page = grab_cache_page(mapping, index);
if (!page) {
cond_resched();
goto repeat;
}
if (PageUptodate(page))
goto out;

if (f2fs_readpage(sbi, page, index, READ_SYNC))
goto repeat;

lock_page(page);
if (page->mapping != mapping) {
f2fs_put_page(page, 1);
goto repeat;
}
out:
mark_page_accessed(page);
return page;
}
get_meta_page的参数index,直接对应了该页在磁盘上的块地址,所以可以直接作为f2fs_readpage的参数。以读取SSA的一个实例看,如功能函数get_sum_page,该函数实现很简单,通过GET_SUM_BLOCK(sbi, segno)获取summary page的块地址,该宏展开为

#define GET_SUM_BLOCK(sbi, segno)				\
((sbi->sm_info->ssa_blkaddr) + segno)
sbi->sm_info->ssa_blkaddr是SSA区域的起始地址,segno是segment number,由于一个segment的summary存储在一个page上,因此直接相加就是块地址。

struct page *get_sum_page(struct f2fs_sb_info *sbi, unsigned int segno)
{
return get_meta_page(sbi, GET_SUM_BLOCK(sbi, segno));
}
而get_node_page稍稍有点不同:
struct page *get_node_page(struct f2fs_sb_info *sbi, pgoff_t nid){	struct address_space *mapping = sbi->node_inode->i_mapping;	struct page *page;	int err;repeat:	page = grab_cache_page(mapping, nid);	if (!page)		return ERR_PTR(-ENOMEM);	err = read_node_page(page, READ_SYNC);	if (err < 0)		return ERR_PTR(err);	else if (err == LOCKED_PAGE)		goto got_it;	lock_page(page);	if (!PageUptodate(page)) {		f2fs_put_page(page, 1);		return ERR_PTR(-EIO);	}	if (page->mapping != mapping) {		f2fs_put_page(page, 1);		goto repeat;	}got_it:	BUG_ON(nid != nid_of_node(page));	mark_page_accessed(page);	return page;}
该函数的参数是node的number,在f2fs中,node number的实际块地址在NAT中存储,因此还需要读取NAT的数据,获得该node的块地址,这些工作在read_node_page中完成。

总的来说,f2fs通过这三种inode把它的全部数据都管理起来,不得不说这是非常巧妙的设计啊