f2fs系列文章truncate

    这篇文章讲f2fs文件系统的截断,在调用这个函数之前会设置inode的i_size,这个函数完成在文件中i_size之后的数据的删除。其起始的函数是f2fs_truncate。

    f2fs_truncate:检查inode的mode,如果不是REG或者是目录或者是LNK,那么直接返回。然后再调用f2fs_may_inline_data检查文件是否可以以内联的形式存放,如果不行,调用f2fs_convert_inline_inode来将内联的数据转换成正常索引的形式(目前还不知道这个地方有什么用)。接着调用truncate_blocks来进行真正的截断。最后修改inode的修改时间i_mtime,然后将inode设置为dirty。

int f2fs_truncate(struct inode *inode)
{
	int err;

	if (!(S_ISREG(inode->i_mode) || S_ISDIR(inode->i_mode) || S_ISLNK(inode->i_mode)))
		return 0;

	trace_f2fs_truncate(inode);

	if (!f2fs_may_inline_data(inode)) {
		err = f2fs_convert_inline_inode(inode);
		if (err)
			return err;
	}

	err = truncate_blocks(inode, i_size_read(inode), true);
	if (err)
		return err;

	inode->i_mtime = inode->i_ctime = current_time(inode);
	f2fs_mark_inode_dirty_sync(inode);
	return 0;
}

    truncate_blocks:完成真正的所有的截断。首先计算截断位置下一个block的块索引free_from,然后调用get_node_page读取inode对应的f2fs_inode。f2fs_has_inline_data检查是否存放的是内联数据,如果是就调用truncate_inline对内联数据进行truncate操作,然后马上将修改后的f2fs_inode进行set_dirty操作。如果没有内联数据就按照正常索引的形式进行截断:首先通过set_new_dnode和get_dnode_of_data来获取free_from所在的dnode,通过计算得到dnode中大于等于free_from的块地址的个数count。如果dnode中的ofs或者是当前的dnode是f2fs_inode,那么就调用函数truncate_data_blocks_range把当前dnode(这里拥有923个块地址的f2fs_inode姑且也算一个dnode)中索引大于等于free_from的块地址全部删除,上述操作是为了删除部分的块地址来消除dnode中的零头,后面的删除可以以dnode为单位进行删除了。接下来的截断就由truncate_inode_blcoks来完成剩余的block的删除,最后调用truncate_partial_data_page对from所在的block中剩余的部分进行块内的截断。

int truncate_blocks(struct inode *inode, u64 from, bool lock)
{
	struct f2fs_sb_info *sbi = F2FS_I_SB(inode);
	unsigned int blocksize = inode->i_sb->s_blocksize;
	struct dnode_of_data dn;
	pgoff_t free_from;
	int count = 0, err = 0;
	struct page *ipage;
	bool truncate_page = false;

	trace_f2fs_truncate_blocks_enter(inode, from);

	free_from = (pgoff_t)F2FS_BYTES_TO_BLK(from + blocksize - 1);
	if (free_from >= sbi->max_file_blocks)
		goto free_partial;
	if (lock)
		f2fs_lock_op(sbi);
	ipage = get_node_page(sbi, inode->i_ino);
	if (IS_ERR(ipage)) {
		err = PTR_ERR(ipage);
		goto out;
	}

	if (f2fs_has_inline_data(inode)) {
		if (truncate_inline_inode(ipage, from))
			set_page_dirty(ipage);
		f2fs_put_page(ipage, 1);
		truncate_page = true;
		goto out;
	}

	set_new_dnode(&dn, inode, ipage, NULL, 0);
	err = get_dnode_of_data(&dn, free_from, LOOKUP_NODE_RA);
	if (err) {
		if (err == -ENOENT)
			goto free_next;
		goto out;
	}
	count = ADDRS_PER_PAGE(dn.node_page, inode);
	count -= dn.ofs_in_node;
	f2fs_bug_on(sbi, count < 0);
	if (dn.ofs_in_node || IS_INODE(dn.node_page)) {
		truncate_data_blocks_range(&dn, count);
		free_from += count;
	}
	
	f2fs_put_dnode(&dn);
free_next:
	err = truncate_inode_blocks(inode, free_from);
out:
	if (lock)
		f2fs_unlock_op(sbi);
free_partial:
	if (!err)
		err = truncate_partial_data_page(inode, from, truncate_page);

	trace_f2fs_truncate_blocks_exit(inode, err);
	return err;
}

    truncate_inline_inode首先检查截断的位置from是否大于MAX_INLINE_DATA,这是最大的内联字节数。如果大于这个就直接返回。否则计算f2fs_inode中的内联数据起始地址,然后将存放内联数据的空间中的from后面的全部置零,也就是删除,最后将f2fs_inode进行set_dirty操作。

bool truncate_inline_inode(struct page *ipage, u64 from)
{
	void *addr;

	if (from >= MAX_INLINE_DATA)
		return false;

	addr = inline_data_addr(ipage);

	f2fs_wait_on_page_writeback(ipage, NODE, true);
	memset(addr + from, 0, MAX_INLINE_DATA - from);
	set_page_dirty(ipage);
	return true;
}

    truncate_data_blocks_range计算dnode中的ofs的实际地址,然后对dnode中剩余的块地址进行遍历,如果其本身就是NULL_ADDR,那就直接跳过。如果不是,那就首先将其修改为NULL_ADDR,然后将其更新到dnode中,接着调用invalidate_blocks函数修改文件系统元数据sit。如果删除了部分的block,那就更新一下extent。

int truncate_data_blocks_range(struct dnode_of_data *dn, int count)
{
	struct f2fs_sb_info *sbi = F2FS_I_SB(dn->inode);
	struct f2fs_node *raw_node;
	int nr_free = 0, ofs = dn->ofs_in_node, len = count;
	__le32 *addr;

	raw_node = F2FS_NODE(dn->node_page);
	addr = blkaddr_in_node(raw_node) + ofs;
	for (; count > 0; count--, addr++, dn->ofs_in_node++) {
		block_t blkaddr = le32_to_cpu(*addr);
		if (blkaddr == NULL_ADDR)
			continue;
		dn->data_blkaddr = NULL_ADDR;
		set_data_blkaddr(dn);
		invalidate_blocks(sbi, blkaddr);
		if (dn->ofs_in_node == 0 && IS_INODE(dn->node_page))
			clear_inode_flag(dn->inode, FI_FIRST_BLOCK_WRITTEN);
		nr_free++;
	}

	if (nr_free) {
		pgoff_t fofs;
		fofs = start_bidx_of_node(ofs_of_node(dn->node_page), dn->inode) + ofs;
		f2fs_update_extent_cache_range(dn, fofs, 0, len);
		dec_valid_block_count(sbi, dn->inode, nr_free);
	}
	dn->ofs_in_node = ofs;

	f2fs_update_time(sbi, REQ_TIME);
	trace_f2fs_truncate_data_blocks_range(dn->inode, dn->nid, dn->ofs_in_node, nr_free);
	return nr_free;
}

    truncate_partial_data_page:首先判断加入删除的页内偏移为零并且没有该页没有缓存,那么就直接返回没截下来如果缓存了from所在的block,那么就找到该块,如果找到这个块并且是最新的,那么直接跳转到下面进行页内截断,如果不满足,那就直接返回。如果没有缓存,那就通过函数get_lock_data_page来读取from所在block,然后将这个块中from到结束的一段全部置零也就是删除。也就是这个函数完成的是块内数据的删除操作。

static int truncate_partial_data_page(struct inode *inode, u64 from, bool cache_only)
{
	unsigned offset = from & (PAGE_SIZE - 1);
	pgoff_t index = from >> PAGE_SHIFT;
	struct address_space *mapping = inode->i_mapping;
	struct page *page;

	if (!offset && !cache_only)
		return 0;

	if (cache_only) {
		page = find_lock_page(mapping, index);
		if (page && PageUptodate(page))
			goto truncate_out;
		f2fs_put_page(page, 1);
		return 0;
	}

	page = get_lock_data_page(inode, index, true);
	if (IS_ERR(page))
		return 0;
truncate_out:
	f2fs_wait_on_page_writeback(page, DATA, true);
	zero_user(page, offset, PAGE_SIZE - offset);
	if (!cache_only || !f2fs_encrypted_inode(inode) || !S_ISREG(inode->i_mode))
		set_page_dirty(page);
	f2fs_put_page(page, 1);
	return 0;
}

    truncate_inode_blocks:首先调用get_node_path来确定截断的位置level及offset这些,由于我们需要截断的位置所处的dnode、indnode是不能删除的,所以我们先对其进行处理一下。对于截断位置from在f2fs_inode中属于1级的也就是对应两个dnode,由于在truncate_blocks以及对齐到dnode了,所以直接跳过。如果截断位置from在f2fs_inode中属于2级的也就是对应两个indnode,如果对应offset==0也就是现在的对应的位置在一个全新的indnode,这时不要担心删除截断位置对应的indnode。同理如果截断位置from在f2fs_inode中属于3级的也就是对应dindnode,如果对应offset==0也就是现在的对应的位置在一个全新的indnode,这时不要担心删除截断位置对应的indnode。对于2级和3级,如果在offset!=0的情况下,那么需要删除一定数量的dnode来达到与indnode对齐的目的。这个是通过函数truncate_partial_nodes完成的。解决了这个问题之后,需要进行最后的全部清洗了,这个仍然是分级进行处理,如果是1级也就是对应两个dnode,那就直接调用函数truncate_dnode对dnode中的全部块地址以及dnode本身的删除,然后是循环进入2级的删除。如果是2级也就是对应两个indnode,那么调用truncate_nodes对indnode中的dnode及indnode本身的删除,然后循环进入3级的删除。如果是3级也就是对应dindnode,调用对dindnode中的indnode及indnode本身的删除。在上述删除的过程中,每次循环删除之后,如果offset==0,也就是删除的是一个完整的级别的block,那么此时在f2fs_inode中对应的nid也应该置0了。

int truncate_inode_blocks(struct inode *inode, pgoff_t from)
{
	struct f2fs_sb_info *sbi = F2FS_I_SB(inode);
	int err = 0, cont = 1;
	int level, offset[4], noffset[4];
	unsigned int nofs = 0;
	struct f2fs_inode *ri;
	struct dnode_of_data dn;
	struct page *page;

	trace_f2fs_truncate_inode_blocks_enter(inode, from);

	level = get_node_path(inode, from, offset, noffset);
	page = get_node_page(sbi, inode->i_ino);
	if (IS_ERR(page)) {
		trace_f2fs_truncate_inode_blocks_exit(inode, PTR_ERR(page));
		return PTR_ERR(page);
	}
	set_new_dnode(&dn, inode, page, NULL, 0);
	unlock_page(page);
	ri = F2FS_INODE(page);
	switch (level) {
	case 0:
	case 1:
		nofs = noffset[1];
		break;
	case 2:
		nofs = noffset[1];
		if (!offset[level - 1])
			goto skip_partial;
		err = truncate_partial_nodes(&dn, ri, offset, level);
		if (err < 0 && err != -ENOENT)
			goto fail;
		nofs += 1 + NIDS_PER_BLOCK;
		break;
	case 3:
		nofs = 5 + 2 * NIDS_PER_BLOCK;
		if (!offset[level - 1])
			goto skip_partial;
		err = truncate_partial_nodes(&dn, ri, offset, level);
		if (err < 0 && err != -ENOENT)
			goto fail;
		break;
	default:
		BUG();
	}

skip_partial:
	while (cont) {
		dn.nid = le32_to_cpu(ri->i_nid[offset[0] - NODE_DIR1_BLOCK]);
		switch (offset[0]) {
		case NODE_DIR1_BLOCK:
		case NODE_DIR2_BLOCK:
			err = truncate_dnode(&dn);
			break;

		case NODE_IND1_BLOCK:
		case NODE_IND2_BLOCK:
			err = truncate_nodes(&dn, nofs, offset[1], 2);
			break;

		case NODE_DIND_BLOCK:
			err = truncate_nodes(&dn, nofs, offset[1], 3);
			cont = 0;
			break;

		default:
			BUG();
		}
		if (err < 0 && err != -ENOENT)
			goto fail;
		if (offset[1] == 0 &&
				ri->i_nid[offset[0] - NODE_DIR1_BLOCK]) {
			lock_page(page);
			BUG_ON(page->mapping != NODE_MAPPING(sbi));
			f2fs_wait_on_page_writeback(page, NODE, true);
			ri->i_nid[offset[0] - NODE_DIR1_BLOCK] = 0;
			set_page_dirty(page);
			unlock_page(page);
		}
		offset[1] = 0;
		offset[0]++;
		nofs += err;
	}
fail:
	f2fs_put_page(page, 0);
	trace_f2fs_truncate_inode_blocks_exit(inode, err);
	return err > 0 ? 0 : err;
}

    truncate_partial_nodes,这个函数只可能是由truncate_inode_blocks来调用,这个函数用来完成截断与indnode的对齐,也就是这个函数调用之后,后面的删除可以以indnode为单位进行删除了。首先调用get_node_page和get_nid来获取截断位置所在的indnode在f2fs_inode的nid或者dindnode中的nid。然后调用ra_node_pages对截断位置及之后的dnode进行与读取。接着对这些dnode进行遍历调用,检查对应的dnode的nid!=0就调用函数truncate_dnode对该dnode进行删除。在完成了这些删除之后,然后检查这次的删除是不是在offset==0,也就是从indnode的开始删除的(但是根据调用的情况,这个是不存在的),如果是这种情况那么将这个indnode本身也删除了。接着更新offset进入下一个全新的indnode。

static int truncate_partial_nodes(struct dnode_of_data *dn,
			struct f2fs_inode *ri, int *offset, int depth)
{
	struct page *pages[2];
	nid_t nid[3];
	nid_t child_nid;
	int err = 0;
	int i;
	int idx = depth - 2;

	nid[0] = le32_to_cpu(ri->i_nid[offset[0] - NODE_DIR1_BLOCK]);
	if (!nid[0])
		return 0;

	for (i = 0; i < idx + 1; i++) {
		pages[i] = get_node_page(F2FS_I_SB(dn->inode), nid[i]);
		if (IS_ERR(pages[i])) {
			err = PTR_ERR(pages[i]);
			idx = i - 1;
			goto fail;
		}
		nid[i + 1] = get_nid(pages[i], offset[i + 1], false);
	}

	ra_node_pages(pages[idx], offset[idx + 1], NIDS_PER_BLOCK);
	for (i = offset[idx + 1]; i < NIDS_PER_BLOCK; i++) {
		child_nid = get_nid(pages[idx], i, false);
		if (!child_nid)
			continue;
		dn->nid = child_nid;
		err = truncate_dnode(dn);
		if (err < 0)
			goto fail;
		if (set_nid(pages[idx], i, 0, false))
			dn->node_changed = true;
	}

	if (offset[idx + 1] == 0) {
		dn->node_page = pages[idx];
		dn->nid = nid[idx];
		truncate_node(dn);
	} else {
		f2fs_put_page(pages[idx], 1);
	}
	offset[idx]++;
	offset[idx + 1] = 0;
	idx--;
fail:
	for (i = idx; i >= 0; i--)
		f2fs_put_page(pages[i], 1);

	trace_f2fs_truncate_partial_nodes(dn->inode, nid, depth, err);

	return err;
}


    truncate_dnode:主要完成dnode的数据块地址和本身的删除。首先调用get_node_page读取到该dnode,然后truncate_data_blocks来完成对dnode中的数据块的删除。接着调用truncate_node来删除dnode本身。

static int truncate_dnode(struct dnode_of_data *dn)
{
	struct page *page;

	if (dn->nid == 0)
		return 1;
	page = get_node_page(F2FS_I_SB(dn->inode), dn->nid);
	if (IS_ERR(page) && PTR_ERR(page) == -ENOENT)
		return 1;
	else if (IS_ERR(page))
		return PTR_ERR(page);
	dn->node_page = page;
	dn->ofs_in_node = 0;
	truncate_data_blocks(dn);
	truncate_node(dn);
	return 1;
}

    truncate_data_blocks:通过调用truncate_data_blocks_range来完成一个dnode中的所有的数据块地址的删除。

void truncate_data_blocks(struct dnode_of_data *dn)
{
	truncate_data_blocks_range(dn, ADDRS_PER_BLOCK);
}

    truncate_nodes:主要完成node本身的删除。首先调用get_node_info获得nid对应的node_info,接着检查i_blocks。接着调用函数invalidate_blocks修改文件系统元数据sit,将dnode对应的块地址无效掉,然后调用dec_valid_node_count更新有效的node的数量。然后调用set_node_addr函数将node_info中的块地址设置为NULL_ADDR(这个函数有把这个node_indo置为dirty)。接着检查删除的node是否为inode,如果是inode,那么首先调用remove_orphan_inode从孤儿inode中删除(由于文件inode的删除首先会将inode加入到orphaninode中)。然后是调用dec_valid_inode_count更新有效inode数量,接着调用f2fs_inode_synced来解除一些链表的联系(f2fs缓存机制维护了很多的链表)。接着将该node的dirty标志清除,因为删除了没有再同步的需要了,最后调用invalidate_mapping_pages删掉node_mapping中的页缓存。

static void truncate_node(struct dnode_of_data *dn)
{
	struct f2fs_sb_info *sbi = F2FS_I_SB(dn->inode);
	struct node_info ni;

	get_node_info(sbi, dn->nid, &ni);
	if (dn->inode->i_blocks == 0) {
		f2fs_bug_on(sbi, ni.blk_addr != NULL_ADDR);
		goto invalidate;
	}
	f2fs_bug_on(sbi, ni.blk_addr == NULL_ADDR);
	invalidate_blocks(sbi, ni.blk_addr);
	dec_valid_node_count(sbi, dn->inode);
	set_node_addr(sbi, &ni, NULL_ADDR, false);
	if (dn->nid == dn->inode->i_ino) {
		remove_orphan_inode(sbi, dn->nid);
		dec_valid_inode_count(sbi);
		f2fs_inode_synced(dn->inode);
	}
invalidate:
	clear_node_page_dirty(dn->node_page);
	set_sbi_flag(sbi, SBI_IS_DIRTY);
	f2fs_put_page(dn->node_page, 1);
	invalidate_mapping_pages(NODE_MAPPING(sbi), dn->node_page->index, dn->node_page->index);
	dn->node_page = NULL;
	trace_f2fs_truncate_node(dn->inode, dn->nid, ni.blk_addr);
}

    truncate_nodes:这个函数主要完成indnode和dindirect的删除。首先get_node_page读取需要删除的nid所对应的indnode或者dindnode。然后ra_node_pages来对nid下面的dnode或者indnode进行预读。对于删除indnode的情况,对该node中的nid进行遍历,如果nid==0,那么直接跳过;如果nid!=0,那就调用truncate_dnode对该dnode进行删除;接着调用set_nid将indnode中的该位置的nid修改为0。删除dindnode跟上述的删除indnode的情况是差不多的,只是在删除dnode的时候是调用truncate_nodes递归删除掉indnode。最后如果删除的ofs==0,那说明删除的是一个全新的dindnode或indnode。那就调用truncate_node将这个dindnode或者indnode本身也删除。

static int truncate_nodes(struct dnode_of_data *dn, unsigned int nofs, int ofs, int depth)
{
	struct dnode_of_data rdn = *dn;
	struct page *page;
	struct f2fs_node *rn;
	nid_t child_nid;
	unsigned int child_nofs;
	int freed = 0;
	int i, ret;

	if (dn->nid == 0)
		return NIDS_PER_BLOCK + 1;

	trace_f2fs_truncate_nodes_enter(dn->inode, dn->nid, dn->data_blkaddr);

	page = get_node_page(F2FS_I_SB(dn->inode), dn->nid);
	if (IS_ERR(page)) {
		trace_f2fs_truncate_nodes_exit(dn->inode, PTR_ERR(page));
		return PTR_ERR(page);
	}

	ra_node_pages(page, ofs, NIDS_PER_BLOCK);

	rn = F2FS_NODE(page);
	if (depth < 3) {
		for (i = ofs; i < NIDS_PER_BLOCK; i++, freed++) {
			child_nid = le32_to_cpu(rn->in.nid[i]);
			if (child_nid == 0)
				continue;
			rdn.nid = child_nid;
			ret = truncate_dnode(&rdn);
			if (ret < 0)
				goto out_err;
			if (set_nid(page, i, 0, false))
				dn->node_changed = true;
		}
	} else {
		child_nofs = nofs + ofs * (NIDS_PER_BLOCK + 1) + 1;
		for (i = ofs; i < NIDS_PER_BLOCK; i++) {
			child_nid = le32_to_cpu(rn->in.nid[i]);
			if (child_nid == 0) {
				child_nofs += NIDS_PER_BLOCK + 1;
				continue;
			}
			rdn.nid = child_nid;
			ret = truncate_nodes(&rdn, child_nofs, 0, depth - 1);
			if (ret == (NIDS_PER_BLOCK + 1)) {
				if (set_nid(page, i, 0, false))
					dn->node_changed = true;
				child_nofs += ret;
			} else if (ret < 0 && ret != -ENOENT) {
				goto out_err;
			}
		}
		freed = child_nofs;
	}

	if (!ofs) {
		dn->node_page = page;
		truncate_node(dn);
		freed++;
	} else {
		f2fs_put_page(page, 1);
	}
	trace_f2fs_truncate_nodes_exit(dn->inode, freed);
	return freed;

out_err:
	f2fs_put_page(page, 1);
	trace_f2fs_truncate_nodes_exit(dn->inode, ret);
	return ret;
}

猜你喜欢

转载自blog.csdn.net/WaterWin/article/details/79791726