1. 程式人生 > >f2fs系列文章truncate

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;
}