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)初始化过程区别对待了meta_inode和node_inode,再看看它们的address_space_operation:
{
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);
}
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)get_meta_page的参数index,直接对应了该页在磁盘上的块地址,所以可以直接作为f2fs_readpage的参数。以读取SSA的一个实例看,如功能函数get_sum_page,该函数实现很简单,通过GET_SUM_BLOCK(sbi, segno)获取summary page的块地址,该宏展开为
{
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;
}
#define GET_SUM_BLOCK(sbi, segno) \sbi->sm_info->ssa_blkaddr是SSA区域的起始地址,segno是segment number,由于一个segment的summary存储在一个page上,因此直接相加就是块地址。
((sbi->sm_info->ssa_blkaddr) + segno)
struct page *get_sum_page(struct f2fs_sb_info *sbi, unsigned int segno)而get_node_page稍稍有点不同:
{
return get_meta_page(sbi, GET_SUM_BLOCK(sbi, segno));
}
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把它的全部数据都管理起来,不得不说这是非常巧妙的设计啊