linux_f2fs_super_cp_recv

Linux v3.8-rc1

super.c

f2fs_alloc_inode // 从缓存中申请内存后,初始化具体的inode信息并设定标志,返回vfs_inode

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/* struct inode 和 struct super_block 在/include/linux/fs.h中*/
static struct inode *f2fs_alloc_inode(struct super_block *sb)
{
struct f2fs_inode_info *fi;

/* 调用kmem_cache_alloc从缓存中申请内存。
* f2fs_inode_cachep是可以指向任意kmem_cache类型的结构体指针
*
* 内存分配掩码Get Free Page Mask
* GFP_NOFS:分配内存时,禁止任何文件系统操作
* __GFP_ZERO:分配器在分配成功时,将返回填充字节0的页
*/
fi = kmem_cache_alloc(f2fs_inode_cachep, GFP_NOFS | __GFP_ZERO);
if (!fi)
return NULL;

init_once((void *) fi);

/* Initilize f2fs-specific inode info */
fi->vfs_inode.i_version = 1;
atomic_set(&fi->dirty_dents, 0);
fi->current_depth = 1;
fi->is_cold = 0;
rwlock_init(&fi->ext.ext_lock);

// 设定索引节点的标志(FI_NEW_INODE为枚举类型,表明新分配的索引节点)
set_inode_flag(fi, FI_NEW_INODE);

return &fi->vfs_inode;
}

f2fs_i_callback // 进程结束后把进程信息存放到缓存中

调用container_of(macro在include/linux/kernel.h中)使指针head指向inode结构体的成员变量i_rcu, 把整个inode结构体的指针都存放在struct inode *inode,再调用F2FS_I函数通过inode指针指向f2fs_inode_info中的成员vfs_inode,通过inode指针返回结构体f2fs_inode_info的起始地址。然后调用kmem_cache_free函数把前面返回的结构体地址保存到f2fs_inode_cachep。

f2fs_destroy_inode // 销毁旧的inode指针

f2fs_put_super // 释放内存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
static void f2fs_put_super(struct super_block *sb)
{
/*
* F2FS_SB in /fs/f2fs/f2fs.h
* 把sb指向结构体super_block的子数据s_fs_info指针并将其赋给sbi
* s_fs_info指向特定文件系统的超级块信息
*/
struct f2fs_sb_info *sbi = F2FS_SB(sb);

#ifdef CONFIG_F2FS_STAT_FS // 如果宏已经定义,则编译下面代码
if (sbi->s_proc) {
f2fs_stat_exit(sbi);
remove_proc_entry(sb->s_id, f2fs_proc_root);
}
#endif
stop_gc_thread(sbi); // 停止gc线程

write_checkpoint(sbi, false, true);

iput(sbi->node_inode); // 如果索引节点使用计数达到零,则索引节点将被释放或销毁
iput(sbi->meta_inode);

/* destroy f2fs internal modules */
destroy_gc_manager(sbi);
destroy_node_manager(sbi);
destroy_segment_manager(sbi);

/*
* kfree释放先前分配的内存
* sbi->ckpt:kmalloc返回的指针。 如果sbi->ckpt为NULL,则不执行任何操作
*/
kfree(sbi->ckpt);

sb->s_fs_info = NULL;
brelse(sbi->raw_super_buf);
kfree(sbi);
}

f2fs_statfs // 获取f2fs使用情况

如block大小和数目。可使用的有效block数目,有效的inode数目,有效的node数目

f2fs_show_options // 属性显示

如有操作,如“后台清理”、“关闭前滚”、“无堆分配”等情况出现,向seq流中写入相应的字符串如”,background_gc_on”

parse_options // 解析属性,与f2fs_show_options关联,认为两者与项目无关

max_file_size // 通过索引节点中的地址指针,直接块中的地址指针,间接块中的节点ID计算最大文件大小

sanity_check_raw_super // 原始super的健全性检查

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
static int sanity_check_raw_super(struct f2fs_super_block *raw_super)
{
unsigned int blocksize;

// 检查魔数magic是否为F2FS_SUPER_MAGIC
if (F2FS_SUPER_MAGIC != le32_to_cpu(raw_super->magic))
return 1;
blocksize = 1 << le32_to_cpu(raw_super->log_blocksize);

/*
* 检查块大小和页大小是否一致(f2fs文件系统的设计上是一致的)
*/
if (blocksize != PAGE_CACHE_SIZE)
return 1;
return 0;
}

sanity_check_ckpt // checkpoint的健全性检查

如果ckpt, sit, nat, ssa各自的segment数之和加上gc的保留段数 小于等于 segment总数,则通过健全性检查

init_sb_info // 对f2fs_sb_info进行初始化

f2fs_fill_super // 读取磁盘的前端区域的数据,对F2FS元区域数据进行初始化

f2fs_fill_super 是加载F2FS文件系统的第一步,主要作用是读取磁盘的前端区域的数据,对F2FS元区域数据进行初始化。

1 首先调用kzalloc为特定f2fs的超级块信息分配内存
2 设定一个临时的块大小
3 调用sb_bread读取原始超级块的信息
4 调用set_opt初始化FS参数
5 调用parse_options解析安装属性
6 对原始super进行健全性检查
7 初始化特定的超级块信息,如用于gc, cp, write_inode, writepages的互斥量
8 调用f2fs_iget获取元空间的索引节点
9 对cp做健全性检查
10 调用init_orphan_info初始化超级块orphan信息
11 依次调用build_segment_manager, build_node_manager, build_gc_manager设置f2fs内部模块
12 调用recover_orphan_inodes恢复孤立节点,有则释放它们
13 调用f2fs_iget获取根索引节点和目录项
14 调用recover_fsync_data恢复(与数据的同步写入磁盘相关)
15 运行后台GC线程

这个初始化函数中与后滚恢复相关的函数有get_valid_checkpointbuild_segment_manager,分别对应f2fs_checkpoint相关的数据和curseg相关的数据

get_valid_checkpoint // 恢复f2fs_checkpoint

定义在fs/f2fs/checkpoint.c中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
// struct f2fs_sb_info在/fs/f2fs/f2fs.h中
int get_valid_checkpoint(struct f2fs_sb_info *sbi)
{
struct f2fs_checkpoint *cp_block;

/*
* sbi指向f2fs_sb_info结构体的子数据raw_super, 是一个原始超级块指针
* raw_super指向f2fs_super_block类型的任意结构体
* 这里相当于fsb就是原来的超级块指针
*/
struct f2fs_super_block *fsb = sbi->raw_super;
struct page *cp1, *cp2, *cur_page;
unsigned long blk_size = vsbi->blocksize;
unsigned long long cp1_version = 0, cp2_version = 0;
unsigned long long cp_start_blk_no;

/*
* kzalloc在include/linux/slab.h中
* blk_size是需要分配的内存大小, GFP_KERNEL为要分配的内存类型
* 查询"内存分配掩码"知: GFP_KERNEL是一种常规的分配方式,可能会阻塞。
* 这个标志在睡眠安全时用在进程的长下文代码中。
* 为了获取调用者所需的内存,内核会尽力而为。这个标志应该是首选标志.
* ckpt是原始checkpoint指针, 指向f2fs_checkpoint类型的任意结构体
*/
sbi->ckpt = kzalloc(blk_size, GFP_KERNEL); // 分配f2fs_checkpoint的堆空间

// 如上述操作未完成, 说明内存不足
if (!sbi->ckpt)
return -ENOMEM;
/*
* 找到并读取两个cp
*/
cp_start_blk_no = le64_to_cpu(fsb->start_segment_checkpoint); // 从sbi获得checkpoint的起始地址
cp1 = validate_checkpoint(sbi, cp_start_blk_no, &cp1_version); // 读取该地址并检查其合法性

/* The second checkpoint pack should start at the next segment */
cp_start_blk_no += 1 << le32_to_cpu(fsb->log_blocks_per_seg);
cp2 = validate_checkpoint(sbi, cp_start_blk_no, &cp2_version);

if (cp1 && cp2) { // 根据版本的高低决定使用哪个版本的cp
if (ver_after(cp2_version, cp1_version))
cur_page = cp2;
else
cur_page = cp1;
} else if (cp1) {
cur_page = cp1;
} else if (cp2) {
cur_page = cp2;
} else {
goto fail_no_cp;
}

// page_address返回页面的虚拟地址, 这个cur_page就是以上获取的正在使用的checkpoint
cp_block = (struct f2fs_checkpoint *)page_address(cur_page);
memcpy(sbi->ckpt, cp_block, blk_size); // 复制数据到sbi中,用于运行中的管理

/* f2fs_put_page在/fs/f2fs/f2fs.h中
* 查看!PageLocked(cp1)和!PageLocked(cp1)时的堆栈信息.
* 解除cp1和cp2的锁定并释放页面缓存
*/
f2fs_put_page(cp1, 1);
f2fs_put_page(cp2, 1);
return 0;

fail_no_cp:
kfree(sbi->ckpt);
return -EINVAL;
}

build_segment_manager // 恢复curseg

恢复curseg的功能主要在build_segment_manager函数的build_curseg函数中完成
build_segment_manager和build_curseg定义在fs/f2fs/segment.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
static int build_curseg(struct f2fs_sb_info *sbi)
{
struct curseg_info *array;
int i;

array = kzalloc(sizeof(*array) * NR_CURSEG_TYPE, GFP_KERNEL);
if (!array)
return -ENOMEM;

SM_I(sbi)->curseg_array = array;

// 初始化curseg的空间
for (i = 0; i < NR_CURSEG_TYPE; i++) {
mutex_init(&array[i].curseg_mutex);
array[i].sum_blk = kzalloc(PAGE_CACHE_SIZE, GFP_KERNEL);
if (!array[i].sum_blk)
return -ENOMEM;
array[i].segno = NULL_SEGNO;
array[i].next_blkoff = 0;
}

// 从磁盘中,读取恢复curseg的信息
return restore_curseg_summaries(sbi);
}
restore_curseg_summaries // 读取curseg

function in /fs/f2fs/segment.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
static int restore_curseg_summaries(struct f2fs_sb_info *sbi)
{
int type = CURSEG_HOT_DATA;

/*
* 检查CP_COMPACT_SUM_FLAG的标志,这个标志用于检查是否按COMPACTED的方式读取data summary
* is_set_ckpt_flags fs/f2fs/f2fs.h, line 467 (as a function)
*
*/
if (is_set_ckpt_flags(F2FS_CKPT(sbi), CP_COMPACT_SUM_FLAG)) {
/* restore for compacted data summary */
if (read_compacted_summaries(sbi))
return -EINVAL;
type = CURSEG_HOT_NODE;
}

/*
* 如果没有COMPACTED标识,则DATA和NODE都使用NORMAL的方式进行恢复
*/
for (; type <= CURSEG_COLD_NODE; type++)
if (read_normal_summaries(sbi, type))
return -EINVAL;
return 0;
}
read_normal_summaries //读取summaries

这个函数对于F2FS的正常关闭,重新启动时读取的summary的方式都是类似的,都是根据HOT/WARM/COLD的顺序,读取对应的block,然后将数据保存到curseg对应的类型当中。这里重点考虑出现了宕机的情况的恢复


checkpoint.c

grab_meta_page 和 get_meta_page// 获取元页面

不同之处在于,grab_meta_page在返回给定缓存中给定索引处的锁定页面后,调用wait_on_page_writeback等待页面完成回写再SetPageUptodate更新页面,然后返回page
get_meta_page,在返回给定缓存中给定索引处的锁定页面后,调用mark_page_accessed将页面标记为可访问,然后返回page


f2fs_write_meta_page f2fs_write_meta_pages sync_meta_pages与写页面相关


f2fs_set_meta_page_dirty // 设置元页面为脏

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
static int f2fs_set_meta_page_dirty(struct page *page)
{
struct address_space *mapping = page->mapping;
struct f2fs_sb_info *sbi = F2FS_SB(mapping->host->i_sb);

SetPageUptodate(page); // 更新页面
/*
* 如果该page不是脏页,则调用__set_page_dirty_nobuffers将页面设置为脏,但不是所有缓冲区都设置为脏
*/
if (!PageDirty(page)) {
__set_page_dirty_nobuffers(page);
inc_page_count(sbi, F2FS_DIRTY_META);
F2FS_SET_SB_DIRT(sbi);
return 1;
}
return 0;
}

check_orphan_space // 检查孤立空间大小

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int check_orphan_space(struct f2fs_sb_info *sbi)
{
unsigned int max_orphans;
int err = 0;

/*
* 考虑段中的512个块,cp和log段摘要需要5个块
* 剩余的块用于保留有限的orphan entries,为cp pack保留一个段,我们最多可以有1020 * 507个orphan entries
* F2FS_ORPHANS_PER_BLOCK用于孤立索引节点的管理(#define F2FS_ORPHANS_PER_BLOCK 1020)
*/
max_orphans = (sbi->blocks_per_seg - 5) * F2FS_ORPHANS_PER_BLOCK;
mutex_lock(&sbi->orphan_inode_mutex); // 获取互斥量
if (sbi->n_orphans >= max_orphans)
err = -ENOSPC;
mutex_unlock(&sbi->orphan_inode_mutex); // 释放此前被锁定的互斥量
return err;
}

add_orphan_inode

remove_orphan_inode

recover_orphan_inode // 恢复孤立索引节点

1
2
3
4
5
6
7
8
9
static void recover_orphan_inode(struct f2fs_sb_info *sbi, nid_t ino)
{
struct inode *inode = f2fs_iget(sbi->sb, ino); // 获取元空间的索引节点
BUG_ON(IS_ERR(inode)); // 查看 inode指针未指向最后一个page 时的堆栈内容
clear_nlink(inode); // 直接将索引节点的链接数清零

/* 调用iput截断所有数据 */
iput(inode);
}

recover_orphan_inodes

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
int recover_orphan_inodes(struct f2fs_sb_info *sbi)
{
block_t start_blk, orphan_blkaddr, i, j;

// 如果umount和journal_present标志值为0则直接return 0
if (!(F2FS_CKPT(sbi)->ckpt_flags & CP_ORPHAN_PRESENT_FLAG))
return 0;

sbi->por_doing = 1;
// 获取块起始地址和orphan块地址
start_blk = __start_cp_addr(sbi) + 1;
orphan_blkaddr = __start_sum_addr(sbi) - 1;

for (i = 0; i < orphan_blkaddr; i++) {
struct page *page = get_meta_page(sbi, start_blk + i);
struct f2fs_orphan_block *orphan_blk;

orphan_blk = (struct f2fs_orphan_block *)page_address(page);
for (j = 0; j < le32_to_cpu(orphan_blk->entry_count); j++) {
nid_t ino = le32_to_cpu(orphan_blk->ino[j]);
recover_orphan_inode(sbi, ino);
}
f2fs_put_page(page, 1);
}
/* clear Orphan Flag */
F2FS_CKPT(sbi)->ckpt_flags &= (~CP_ORPHAN_PRESENT_FLAG);
sbi->por_doing = 0;
return 0;
}

get_valid_checkpoint // 获取有效的checkpoint

f2fs_fill_super中已介绍

do_checkpoint

首先刷新所有的NAT/SIT页面,然后根据curseg修改checkpoint的信息和summary的信息。
修改checkpoint:对f2fs_checkpoint的修改主要是把curseg的当前segno,blkoff等写入到f2fs_checkpoint中,以便下次重启时可以根据这些信息,重建curseg。

summary的回写:根据需要回写的summary的数目,返回需要写回的block的数目data_sum_blocks,如果data_sum_blocks = 1 或者 2,则表示回写1个或者2个block,则设置CP_COMPACT_SUM_FLAG标志。

1
2
3
4
5
data_sum_blocks = npages_for_summary_flush(sbi);
if (data_sum_blocks < 3)
ckpt->ckpt_flags |= CP_COMPACT_SUM_FLAG;
else
ckpt->ckpt_flags &= (~CP_COMPACT_SUM_FLAG);

然后调用write_data_summaries将summary写入磁盘。
write_data_summaries(sbi, start_blk);//将data summary以及里面的journal写入磁盘
write_data_summaries函数会判断一下是否设置了CP_COMPACT_SUM_FLAG标志,采取不同的方法写入磁盘。

write_checkpoint // 写checkpoint

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
void write_checkpoint(struct f2fs_sb_info *sbi, bool blocked, bool is_umount)
{
struct f2fs_checkpoint *ckpt = F2FS_CKPT(sbi);//从sbi读取当前cp的数据结构
unsigned long long ckpt_ver;

if (!blocked) {
mutex_lock(&sbi->cp_mutex);
block_operations(sbi);//将文件系统的所有操作停止
}

/*
*按照DATA, NODE, META的顺序将数据从page同步写入到磁盘
*(META包括了SIT/NAT/checkpoint)
*这里写入磁盘的meta并不是最新的meta
*(可能是在此之前使用fsync/fdatasync时触发了checkpoint,meta还没有完全写入磁盘时起到阻塞的作用)
*最新的meta还只保存在cache中
*/
f2fs_submit_bio(sbi, DATA, true);
f2fs_submit_bio(sbi, NODE, true);
f2fs_submit_bio(sbi, META, true);

/*
* 完成数据的写入后更新更新版本号
* 以便于SIT entries和seg summaries写入正确的位置
*/
ckpt_ver = le64_to_cpu(ckpt->checkpoint_ver);
ckpt->checkpoint_ver = cpu_to_le64(++ckpt_ver);

/* 更新元数据的NAT区域, SIT区域
* 刷写所有nat entries, sit entries到磁盘
*/
flush_nat_entries(sbi);
flush_sit_entries(sbi);

reset_victim_segmap(sbi);

/* unlock all the fs_lock[] in do_checkpoint()
* 调用do_checkpoint()将最新的元数据Checkpoint区域以及Summary区域写入磁盘
*/
do_checkpoint(sbi, is_umount);

// 恢复文件系统的操作
unblock_operations(sbi);
mutex_unlock(&sbi->cp_mutex);
}

recovery.c

space_for_roll_forward // 是否可以前滚回复

1
2
3
4
5
6
7
8
bool space_for_roll_forward(struct f2fs_sb_info *sbi)
{
// 最后有效块数与分配有效块数之和小于等于用户块数,返回true
if (sbi->last_valid_block_count + sbi->alloc_valid_block_count
> sbi->user_block_count)
return false;
return true;
}

get_fsync_inode // 获取fsync索引节点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 返回一个entry(获取fsync索引节点)
static struct fsync_inode_entry *get_fsync_inode(struct list_head *head,
nid_t ino) // typedef u32 nid_t
{
struct list_head *this;
struct fsync_inode_entry *entry;

// 遍历列表,struct list_head做循环游标,head为列表头
list_for_each(this, head) {
// 获取目录项的结构
entry = list_entry(this, struct fsync_inode_entry, list);

// entry指向vfs inode指针指向的结构体inode中的子数据i_ino与给定ino(inode number)相等则返回目录项
if (entry->inode->i_ino == ino)
return entry;
}
return NULL;
}

recover_dentry // 恢复目录项

恢复目录项
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
static int recover_dentry(struct page *ipage, struct inode *inode)
{
// kmap函数将分配到的高端内存映射到永久内存映射区域
struct f2fs_node *raw_node = (struct f2fs_node *)kmap(ipage);
struct f2fs_inode *raw_inode = &(raw_node->i);
struct dentry dent, parent;
struct f2fs_dir_entry *de;
struct page *page;
struct inode *dir;
int err = 0;

if (!raw_node->footer.dentry)
goto out;

// 获取inode所属文件的超级块指针和父索引节点号
dir = f2fs_iget(inode->i_sb, le32_to_cpu(raw_inode->i_pino));
if (IS_ERR(dir)) {
err = -EINVAL;
goto out;
}

parent.d_inode = dir; // 与该目录项关联的inode
dent.d_parent = &parent; // 父目录的目录项

// 目录项名称
dent.d_name.len = le32_to_cpu(raw_inode->i_namelen);
dent.d_name.name = raw_inode->i_name;

// 根据以上信息找到目录项
de = f2fs_find_entry(dir, &dent.d_name, &page);
if (de) {
kunmap(page);
f2fs_put_page(page, 0);
} else {
f2fs_add_link(&dent, inode);
}
iput(dir);
out:
kunmap(ipage);
return err;
}

recover_inode // 恢复inode

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
static int recover_inode(struct inode *inode, struct page *node_page)
{
// 获取节点页面的映射虚拟地址
void *kaddr = page_address(node_page);
struct f2fs_node *raw_node = (struct f2fs_node *)kaddr;
struct f2fs_inode *raw_inode = &(raw_node->i);

inode->i_mode = le32_to_cpu(raw_inode->i_mode); // 文件类型和访问权限
i_size_write(inode, le64_to_cpu(raw_inode->i_size)); // inode所代表的文件大小
inode->i_atime.tv_sec = le32_to_cpu(raw_inode->i_atime); // 文件最后一次访问时间
inode->i_ctime.tv_sec = le32_to_cpu(raw_inode->i_ctime); // inode最后一次修改时间
inode->i_mtime.tv_sec = le32_to_cpu(raw_inode->i_mtime); // 文件最后一次修改时间

return recover_dentry(node_page, inode);
}

find_fsync_dnodes // 找到所有的可以恢复的dnode对应的inode

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
static int find_fsync_dnodes(struct f2fs_sb_info *sbi, struct list_head *head)
{
unsigned long long cp_ver = le64_to_cpu(sbi->ckpt->checkpoint_ver);// cp版本
struct curseg_info *curseg;
struct page *page;
block_t blkaddr;
int err = 0;

/* 获取当前段的节点页面
* CURSEG_I因为是基于dnode进行恢复,因此是WARM NODE
*/
curseg = CURSEG_I(sbi, CURSEG_WARM_NODE);
blkaddr = START_BLOCK(sbi, curseg->segno) + curseg->next_blkoff;

/* 读页面 */
page = alloc_page(GFP_NOFS | __GFP_ZERO);
if (IS_ERR(page))
return PTR_ERR(page);
lock_page(page);

while (1) {
struct fsync_inode_entry *entry;

if (f2fs_readpage(sbi, page, blkaddr, READ_SYNC))
goto out;

if (cp_ver != cpver_of_node(page)) // 比较从cp版本
goto out;

if (!is_fsync_dnode(page)) // 前滚恢复只能恢复被fsync的node page
goto next;

/*
* 如果是NULL,则表示inode不在list中
* 如不是NULL,则表示这个inode已经在list中,不需要加入了
*/
entry = get_fsync_inode(head, ino_of_node(page));
if (entry) {
entry->blkaddr = blkaddr;
// 如果是dentry的inode,则先恢复
if (IS_INODE(page) && is_dent_dnode(page))
set_inode_flag(F2FS_I(entry->inode),
FI_INC_LINK);
} else {
if (IS_INODE(page) && is_dent_dnode(page)) {
if (recover_inode_page(sbi, page)) {
err = -ENOMEM;
goto out;
}
}

/* add this fsync inode to the list */
entry = kmem_cache_alloc(fsync_entry_slab, GFP_NOFS);
if (!entry) {
err = -ENOMEM;
goto out;
}

INIT_LIST_HEAD(&entry->list);
list_add_tail(&entry->list, head);

entry->inode = f2fs_iget(sbi->sb, ino_of_node(page));
if (IS_ERR(entry->inode)) {
err = PTR_ERR(entry->inode);
goto out;
}
entry->blkaddr = blkaddr;
}
if (IS_INODE(page)) {
err = recover_inode(entry->inode, page);
if (err)
goto out;
}
next:
/* check next segment */
blkaddr = next_blkaddr_of_node(page);
ClearPageUptodate(page);
}
out:
unlock_page(page);
__free_pages(page, 0);
return err;
}

destroy_fsync_dnodes // 销毁fsync dnodes

check_index_in_prev_nodes // 检查前向节点的索引

do_recover_data // 恢复data page和node page

首先调用start_bidx_of_node函数,把当前node page的起始块索引赋给start
set_new_dnode建立一个vfs dnode,get_dnode_of_data初始化刚刚建立的dnode相关信息。
wait_on_page_writeback等待页面回写
get_node_info从node page获取节点信息
datablock_addr获取文件名和索引
check_index_in_prev_nodes检查具有以上获取的索引的前向节点
通过调用recover_data_pageupdate_extent_cache写入虚拟数据页
调用recover_node_page恢复node page

recover_data // 恢复数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
static void recover_data(struct f2fs_sb_info *sbi,
struct list_head *head, int type)
{
unsigned long long cp_ver = le64_to_cpu(sbi->ckpt->checkpoint_ver);
struct curseg_info *curseg;
struct page *page;
block_t blkaddr;

/* get node pages in the current segment */
curseg = CURSEG_I(sbi, type);
blkaddr = NEXT_FREE_BLKADDR(sbi, curseg);

/* read node page */
page = alloc_page(GFP_NOFS | __GFP_ZERO);
if (IS_ERR(page))
return;
lock_page(page);

while (1) {
struct fsync_inode_entry *entry;

if (f2fs_readpage(sbi, page, blkaddr, READ_SYNC))
goto out;

if (cp_ver != cpver_of_node(page))
goto out;

// 从inodelist中取出一个entry
entry = get_fsync_inode(head, ino_of_node(page));
if (!entry)
goto next;

// 进行恢复
do_recover_data(sbi, entry->inode, page, blkaddr);

if (entry->blkaddr == blkaddr) {
iput(entry->inode);
list_del(&entry->list);
kmem_cache_free(fsync_entry_slab, entry);
}
next:
/* check next segment */
blkaddr = next_blkaddr_of_node(page);
ClearPageUptodate(page);
}
out:
unlock_page(page);
__free_pages(page, 0);

allocate_new_segments(sbi);
}

recover_fsync_data // 恢复fsync数据

①首先通过find_fsync_dnodes函数找到所有的可以恢复的dnode对应的inode(有可能dnode就是inode本身),放入到一个list中。

find_fsync_dnodes的执行流程:通过调用CURSEG_ISTART_BLOCK函数获取当前段(segment)的node pages,再调用alloc_page读取node pages,把可恢复的dnode对应的inode添加到list中,再检查下个段(segment)。

②恢复数据:恢复inode list里面的所有的node page



----------- 本文结束 -----------




0%