这篇文章讲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;
}