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