Btrfs之NV(二):log root

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);
	... ...
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值