Btrfs在COW时为了保证效率会让多个线程在一个trans内并发的进行读写,所以只有当所有的线程都退出trans时才会在btrfs_commit_transaction中将dirty data真正落盘。这样如果当用户只是想刷写某一个inode,如果只采用cow,他需要等待所有线程都退出trans后将所有dirty data都落盘而不是只落盘inode,这样会导致fsync inode花费相当长的时间。所以在这种情况下Btrfs 会通过log_root进行journal的方式保证inode的一致性。
sync
当用户为了保证文件的一致性,通过fsync同步文件会将inode更新的meta_data和extent_data保存在log_root_tree中(根据情况也会保存其parent(inode unlink)和其child(child_dir会保存所有数据,child_reg只会保存inode_item,ref,xattr))。如果需要full_commit或者log失败,将会通过btrfs_commit_transaction将所有的更改提交。
int btrfs_sync_file(struct file *file, loff_t start, loff_t end, int datasync)
{
... ...
/*将所有delalloc的段进行cow,这样就可以将dirty之后的数据log。虽然这里面有包含数据的submit_bio,但是因为super没落盘所以这些数据都不会生效(并且是raid,并不能保证这些数据会比log先落盘)*/
ret = start_ordered_ops(inode, start, end);
... ...
/*
下面两种情况不会commit log
1.inode已经提交到log中
2.inode已经提交完成(last_trans_committed是在btrfs_commit_transaction 落盘super之后更新的,last_trans<last_trans_committed在满足下面条件下,表示super是最新的)
-full_sync前面会等待所有的ordered都落盘完成,即last_trans是在full_sync中同步ordered时btrfs_finish_ordered_io更新的
-如果段内没有需要落盘的ordered
*/
if (btrfs_inode_in_log(inode, root->fs_info->generation) ||
(BTRFS_I(inode)->last_trans <=
root->fs_info->last_trans_committed &&
(full_sync ||
!btrfs_have_ordered_extents_in_range(inode, start, len)))) {
clear_bit(BTRFS_INODE_NEEDS_FULL_SYNC,
&BTRFS_I(inode)->runtime_flags);
mutex_unlock(&inode->i_mutex);
goto out;
}
... ...
trans = btrfs_start_transaction(root, 0);
... ...
/*
功能分析:将inode的所有修改;inode parent(因为inode unlink)修改;inode dir_item.ino的修改记录在log_root中
函数分析:
1.last_trans_log_full_commit是要求记录的trans作为full_commit(btrfs_commit_transaction),如果这个trans没做commit,那么不用记录log
2.check_parent_dirs_for_sync会检查所有parent的last_unlink_trans(记录inode unlink的trans,unlink会改变inode.refs),因为parent unlink必须得更新parent_parent,所以直接将trans设置为last_trans_log_full_commit
3.start_log_trans为root设置log_root并设置整个fs_info.log_root_tree
4.btrfs_log_inode在log_root中记录所有和inode相关的item
-btrfs_get_logged_extents将inode.ordered_tree上在段start~end内的ordered链接到logged_list上
-drop_objectid_items会删除log中0~max_type之间所有的inode item;btrfs_truncate_inode_items会删除log中min_type~-1之间所有的inode item,包括extent_data
-循环通过btrfs_search_forward找到大于当前trans,大于min_key的path。通过copy_items复制从min_key~max_key之间trans在当前trans之前的所有item
注:1.LOG_INODE_EXISTS会复制到xattr_item,LOG_INODE_ALL会复制所有的inode item(包含extent_data_item,由btrfs_finish_ordered_io可知extent_data是在所有的数据都落盘之后才会添加的)
2.copy_item不仅会复制所有的item,还会将hole的extent_data_item补全(没有disk_bytenr)
-btrfs_log_all_xattrs会复制所有的xattr到log中(上面的循环忽略了所有的xattr)
-btrfs_log_changed_extents会遍历inode.extent_tree.modified_extents,log_one_extent会将log中原来的extent_data,而将新的disk_bytenr链接到extent_data中
-如果是dir,还需要通过log_directory_changes将root中对应的dir_item/dir_index写入到log_root中,并为段内添加dir_log_item/dir_log_index
-更新inode的logged_trans,last_log_commit
5.如果last_unlink_trans没有被提交,需要通过btrfs_log_all_parents找到所有引用inode的parent,并通过btrfs_log_inode将parent记录到log中(这里会递归的记录所有unlink的parent,但是check_parent_dirs_for_sync有check,保证parent不会被unlink)
6.将inode所在的dentry path都通过LOG_INODE_EXISTS方式更新其item,并没有更新其extent_data
7.如果inode是dir并且其dir_item有更新(log_directory_changes中检查到dir_item有更新会设置ctx.log_new_dentries),通过log_new_dir_dentries通过btrfs_search_forward查找出更新的dir_item,通过btrfs_log_inode将dir_item.inode log(如果是dir会LOG_INODE_ALL,否则只会LOG_INODE_EXISTS),如果dir_item.inode中的dir_item有更新会递归的log其子inode
8.如果出错了,会btrfs_set_log_full_commit,通过btrfs_commit_transaction的方式提交。
*/
ret = btrfs_log_dentry_safe(trans, root, dentry, start, end, &ctx);
... ...
if (ret != BTRFS_NO_LOG_SYNC) {
if (!ret) {
/*
功能分析:刷写整个log_root_tree,并更新super
函数分析:
1.btrfs_write_marked_extents刷写root.dirty_log_pages链表上的标记的extent刷写到磁盘
注:在btrfs_init_new_buffer中会将log中申请的extent标记为对应的mark(根据不同的trans_id会设置成不同的mark,这样可以让两个log同时运行)
2.update_log_root更新log_root.root_item
3.btrfs_write_marked_extents会进一步刷写整个log_root_tree.dirty_log_pages上被标记为extent_new和extent_dirty的extent(主要是被更新的log_root)
4.btrfs_wait_marked_extents等待root和log_root_tree上所有被标记的extent全部落盘(等待tree_block落盘)
5.btrfs_wait_logged_extents等待btrfs_log_inode过程中记录的ordered data全部落盘(等待inode数据落盘,这样log_root中的extent_data的索引就不会落空)
6.write_all_supers刷写更新log_root_tree之后的super
*/
ret = btrfs_sync_log(trans, root, &ctx);
if (!ret) {
ret = btrfs_end_transaction(trans, root);
goto out;
}
}
/*如果记录到log中失败,会先等待所有的ordered落盘,然后通过btrfs_commit_transaction落盘所有的dirty data*/
if (!full_sync) {
ret = btrfs_wait_ordered_range(inode, start, len);
... ...
}
ret = btrfs_commit_transaction(trans, root);
}
}
fsck
因为btrfs_commit_transaction在提交更改时会通过btrfs_free_log_root_tree释放所有的log。所以当btrfs重启时发现有log_root_tree,就是inode最新并且没有提交的更改。在replay log的过程中会删除root多余,添加log新增的,并且重新计算inode.i_nlink,最后通过btrfs_commit_transaction将所有的更新落盘。
int btrfs_recover_log_trees(struct btrfs_root *log_root_tree)
{
... ...
/*
功能分析:采用深度优先遍历log_root_tree所有block,通过wc.process_func和wc.free处理block
函数分析:
1.walk_down_log_tree会从当前(eb,slot)开始一直走最左边的路线到leaf,然后遍历leaf的所有slot执行wc.process_func(如果要求wc.free,会将leaf释放),完成之后退出(返回0表示正常退出)
2.walk_up_log_tree会从leaf开始向上读取到parent(如果parent的slot都处理完成,通过wc.process_func和wc.free处理parent,然后继续向上遍历parent)。如果遍历到parent返回0;遍历到root所有的block都处理完返回1
3.如果root还没处理,通过wc.process_func和wc.free处理
4.这里wc.process_func是process_one_buffer,wc.free没置上
-wc.pin置上,会将tree_block和data所在的free_space添加到pinned_extent中
注:1.将log tree_block所在空间添加到fs_info.pinned_extents中(这个空间会在commit_trans中通过btrfs_finish_extent_commit释放)
2.如果是extent_data会通过__exclude_logged_extent将disk_bytenr从block_group中的free_space删除(如果free_space没有cache到内存中,先在freed_extents记录,然后在caching_thread中add_new_free_space不添加对应free_space后,将freed_extents释放)
-如果wc.write置上,会写eb;wc.wait置上会等待eb写完
*/
ret = walk_log_tree(trans, log_root_tree, &wc);
... ...
while (1) {
/*查找log_root_tree上查找所有的log_root。found_key.objecti都是BTRFS_TREE_LOG_OBJECTID(记录在root.log_root中),其offset指向源root的objectid(记录在wc.replay_dest中)*/
ret = btrfs_search_slot(NULL, log_root_tree, &key, path, 0, 0);
... ...
/*
1.如果wc.stage在LOG_WALK_PIN_ONLY(0),会通过process_one_buffer将log_root上所有的tree_block和data使用的空间添加到pinned_extent中
2.后续会依次replay inodes, dir_index, all,这些状态都会通过replay_one_buffer处理所有的log leaf
-如果是repaly inodes,删除root中inode在log中不存在的xattr、dir_item,并将log中的inode_item写入root中
1.replay_xattr_deletes在root中遍历inode所有的xattr(虽然data里面存储的是dir_item+data,但是里面存储的是inode的属性),如果发现log中没有对应的dir_item那么在root中将root.inode.xattr删除
2.如果inode是dir,replay_dir_deletes查看log中所有的log_dir_item/log_dir_index的范围,然后在此范围内在root中查找对应的dir_item,check_item_in_log查看root中的dir_item在log中是否存在,如果不存在添加orphan_item(link_to_fixup_dir添加,并递增i_nlink),并unlink dir_item.ino(在unlink之前递增ino.i_nlink,避免inode被删除)
3.overwrite_item用log中的inode_item替换root中的
4.insert_orphan_item在regular inode添加orphan_item, link_to_fixup_dir为inode添加orphan_item递增i_nlink,两者的objectid不同
-如果replay dir_index,replay_one_dir_item修复root中缺失的dir_index,dir_item
1.replay_one_name查看在log中的dir_item在源root中是否存在dir_item和inode
-如果dir_item不存在,直接插入(这里直插入dir_index)
-如果dir_item存在但是不等
-如果inode不存在(inode在repaly inodes时已经恢复了,但是下面的步骤在sync dir时没同步其child foo),先退出
* mkdir testdir
* touch testdir/foo
* touch testdir/bar
* sync
*
* ln testdir/bar testdir/bar_link
* ln testdir/foo testdir/foo_link
* xfs_io -c "fsync" testdir/bar
*
* <power failure>
-如果inode存在,将dir_item删除后,并插入新的dir_index
-dir_item存在并相等,不做处理退出
2.如果有在root中添加dir_item,link_to_fixup_dir为dir.ino添加orphan_item并递增i_nlink
-如果是replay all,将log中的xattr,ref/ext_ref,dir_item,extent_data恢复到root中
1.overwrite_item将log中所有的xattr写到root中
注:在replay inode时删除root中多余的xattr,这里添加root中缺失的xattr
2.add_inode_ref检查ref_item/ext_ref_item中所有引用,unlink root中多余的inode ref,并且将不在root但是log中的dir_item通过btrfs_add_link添加到root中。最后overwrite_item用log中的ref_item和ext_ref_item更新root
注:1.ext_ref是在ref将一个leaf的空间用完之后,就是用ext_ref(参考btrfs_insert_inode_ref-->btrfs_search_slot-->split_leaf(extend))
2.__add_inode_ref除了删除root中相对于log多余的ref/ext_ref(因为在replay inode中,inode.i_nlink已经变成log中的i_nlink,所以在unlink_inode时会递增i_nlink时inode_item.i_nlink不改变)外,还删除root中原有的dir_item/dir_index。
3.如果在log中但是不在root中,__add_inode_ref会通过btrfs_unlink_inode删除在root中但不在log中所有的ref;如果log中所有都在root中,overwrite_item会删除root中多余的ref/ext_ref
3.replay_one_extent恢复inode extent_data数据,并计算csum
4.replay_one_dir_item如果发现replay dir_index或者add_inode_ref恢复的dit_item恢复错了,将其删除
注:tree_root下只有dir_item,但是其他root(fs_root)下既有dir_item又有dir_index,所以在replay dir_index过程中就将fs_root下的dir_item和dir_index通过insert_one_name插入到root中,但是在tree_root下将不会插入任何dir_item
*/
ret = walk_log_tree(trans, log, &wc);
... ...
/*
功能分析:最后,为log中新加的,log与root冲突的inode,重新计算i_nlink
函数分析:
1.link_to_fixup_dir处理的inode,最终都会重新计算i_nlink
-log中所有的inode_item
-log中新加的inode.dir_item.ino
-在log中不存在,但是在root中存在的inode.dir_item.ino
-root中和log不一致的inode.dir_item.ino
2.fixup_inode_link_count统计inode所有的ref/ext_ref,根据ref重新设置i_nlink。如果dir的i_nlink不被引用,通过replay_dir_deletes删除所有的dir_item并且将dir_item.ino也重新计算i_nlink
*/
if (!ret && wc.stage == LOG_WALK_REPLAY_ALL) {
ret = fixup_inode_link_counts(trans, wc.replay_dest,
path);
}
}
... ...
/*依次执行wc.stage状态机*/
if (wc.pin) {
wc.pin = 0;
wc.process_func = replay_one_buffer;
wc.stage = LOG_WALK_REPLAY_INODES;
goto again;
}
/* step three is to replay everything */
if (wc.stage < LOG_WALK_REPLAY_ALL) {
wc.stage++;
goto again;
}
... ...
/*释放pinned block,并提交恢复的block*/
ret = btrfs_commit_transaction(trans, fs_info->tree_root);
... ...
}