__ext2_truncate_blocks解析

来源:互联网 发布:gis软件平台 编辑:程序博客网 时间:2024/06/05 10:51

        之前的几篇博客中我们都在研究ext2文件系统是怎样为文件分配空闲磁盘块来存储文件数据或者充当间接块的,今天我们来看看ext2的如何截断文件的:即,当ext2如何删除那些我们不需要的数据块了。

        所谓的截断文件指的是:一个特定长度的文件,我们从某个位置开始丢弃后面的数据,之前的数据依然保留。对具体文件系统来说,截断数据主要意味着两件事情:1. 文件大小发生变化;2. 文件被截断部分之前占用的数据块(对ext2来说可能还包含间接块)释放,让其他文件可以使用。

        看看我们今天要分析函数的名字就能知道,该函数主要处理如何截断文件数据。前面我们说过,所谓的截断,其主要工作就是释放文件占用的磁盘块,但这里并不是释放文件所有的磁盘块,我们只释放从被截断位置处至文件结束这部分数据块,当然由于ext2特殊的映射方式,我们不仅需要截断数据块,还可能包括映射这些数据块的间接索引块。

        同样,在下面的解析过程中,我们通过举一个事例来理解该函数的实现,照例,我们将函数的实现罗列如下:

static void __ext2_truncate_blocks(struct inode *inode, loff_t offset){__le32 *i_data = EXT2_I(inode)->i_data;struct ext2_inode_info *ei = EXT2_I(inode);int addr_per_block = EXT2_ADDR_PER_BLOCK(inode->i_sb);int offsets[4];Indirect chain[4];Indirect *partial;__le32 nr = 0;int n;long iblock;unsigned blocksize;blocksize = inode->i_sb->s_blocksize;//这里是有点儿讲究的//offset + blocksize - 1主要是考虑到从offset所在的下一块开始//释放,因为如果offset  != 0意味着offset 所在的块是不能//被释放的,只能从下一个块开始释放iblock = (offset + blocksize-1) >> EXT2_BLOCK_SIZE_BITS(inode->i_sb);n = ext2_block_to_path(inode, iblock, offsets, NULL);if (n == 0)return;/* * From here we block out all ext2_get_block() callers who want to * modify the block allocation tree. */mutex_lock(&ei->truncate_mutex);//如果截断处从直接映射块开始//那么我们首先释放直接块//再来释放间接块映射的那部分if (n == 1) {ext2_free_data(inode, i_data+offsets[0],i_data + EXT2_NDIR_BLOCKS);goto do_indirects;}partial = ext2_find_shared(inode, n, offsets, chain, &nr);/* Kill the top of shared branch (already detached) */if (nr) {if (partial == chain)mark_inode_dirty(inode);elsemark_buffer_dirty_inode(partial->bh, inode);ext2_free_branches(inode, &nr, &nr+1, (chain+n-1) - partial);}/* Clear the ends of indirect blocks on the shared branch */while (partial > chain) {ext2_free_branches(inode,   partial->p + 1,   (__le32*)partial->bh->b_data+addr_per_block,   (chain+n-1) - partial);mark_buffer_dirty_inode(partial->bh, inode);brelse (partial->bh);partial--;}do_indirects:/* Kill the remaining (whole) subtrees */switch (offsets[0]) {default:nr = i_data[EXT2_IND_BLOCK];if (nr) {i_data[EXT2_IND_BLOCK] = 0;mark_inode_dirty(inode);ext2_free_branches(inode, &nr, &nr+1, 1);}case EXT2_IND_BLOCK:nr = i_data[EXT2_DIND_BLOCK];if (nr) {i_data[EXT2_DIND_BLOCK] = 0;mark_inode_dirty(inode);ext2_free_branches(inode, &nr, &nr+1, 2);}case EXT2_DIND_BLOCK:nr = i_data[EXT2_TIND_BLOCK];if (nr) {i_data[EXT2_TIND_BLOCK] = 0;mark_inode_dirty(inode);ext2_free_branches(inode, &nr, &nr+1, 3);}case EXT2_TIND_BLOCK:;}ext2_discard_reservation(inode);mutex_unlock(&ei->truncate_mutex);}
        让我们首先看看这个函数的参数:
  1. inode:代表被截断的文件;
  2. offset:从offset该位置开始截断,注意offset以字节为单位,后面必须根据offset计算其所在块号。
        让我们举例来理解这个函数的执行流程,照例,我们将这部分代码分为几个大的段落,首先来看看第一部分在干嘛:
//这里是有点儿讲究的//offset + blocksize - 1主要是考虑到从offset所在的下一块开始//释放,因为如果offset  != 0意味着offset 所在的块是不能//被释放的,只能从下一个块开始释放iblock = (offset + blocksize-1) >> EXT2_BLOCK_SIZE_BITS(inode->i_sb);n = ext2_block_to_path(inode, iblock, offsets, NULL);if (n == 0)return;/* * From here we block out all ext2_get_block() callers who want to * modify the block allocation tree. */mutex_lock(&ei->truncate_mutex);//如果截断处从直接映射块开始//那么我们首先释放直接块//再来释放间接块映射的那部分if (n == 1) {ext2_free_data(inode, i_data+offsets[0],i_data + EXT2_NDIR_BLOCKS);goto do_indirects;}
        这是第一种情况:被截断文件偏移offset处于直接映射处,这种截断的处理方式应该算是比较简单的,我们只需要首先释放直接映射的数据块,然后再释放所有的间接映射方式所映射的数据块以及间接映射块即可。所以在代码中有个判断:
      (if(n == 1)) ext2_free_data();goto do_indirects;
        这个判断即是处理上述情形,如果映射深度为1,首先释放那些被直接映射方式映射的直接数据块。处理完成以后,我们就可以释放被间接块映射的数据块和间接块了(如果文件足够大,需要使用间接映射才能索引),所以一个goto转到了do_indirects。下面的一个事例说明了这种情况。
        假如当前文件大小为300KB,文件系统块大小为1KB。因此,我们知道必须使用二级间接映射才可以索引整个文件,其中,直接映射方式能索引12KB大小文件数据,一级间接索引可索引256KB文件数据,剩下的(300KB-12KB - 256KB = 32KB)数据必须通过二级间接索引方式来索引,整个映射关系如下图所示:


        上图是当前文件的映射方式,我们可以清楚地看到,只有使用二级间接索引的方式才能索引到所有文件数据块。现在假如我们要将文件截断成4KB大小,应该怎么做?

        根据上面的代码,在本例中,我们很容易知道我们需要回收哪些磁盘块:1. 直接块Block5 ~ Block12,这些是直接映射方式使用的磁盘块;2. 一级间接索引方式索引的数据块和间接索引快,上图中为Block13 ~ Block268,同时还有一级间接索引块;3. 二级间接索引块索引的数据块Block269 ~ Block300,加上二级间接索引块和一级间接索引块。 总的来说,我们一共需要回收的数据块+索引块有300+1+1+1 = 303个。下图中用X代表了我们需要释放的磁盘块。(下面的图不是非常准确,因为只有前Block1 ~ Block4是被保留的,Block5开始就要被释放了,但由于空间关系,没有画出来,特此注释)


        那假如说我们要截断的部分不是直接映射方式能索引到的,该怎么办呢?我们会直接进入下面这个分支:

partial = ext2_find_shared(inode, n, offsets, chain, &nr);/* Kill the top of shared branch (already detached) */if (nr) {if (partial == chain)mark_inode_dirty(inode);elsemark_buffer_dirty_inode(partial->bh, inode);ext2_free_branches(inode, &nr, &nr+1, (chain+n-1) - partial);}/* Clear the ends of indirect blocks on the shared branch */while (partial > chain) {ext2_free_branches(inode,   partial->p + 1,   (__le32*)partial->bh->b_data+addr_per_block,   (chain+n-1) - partial);mark_buffer_dirty_inode(partial->bh, inode);brelse (partial->bh);partial--;}

        首先我们来以一个例子思考下对于这种情况我们该怎么处理,一旦明白了怎么处理,代码我们也就懂了一半。 

        假如当前文件大小为1MB,此时必须使用二级间接索引方式,如下图所示(由于画图空间的考虑,我们只画出了二级索引的映射情况,将直接映射方式和一级间接映射没有画出,特此说明)


        现在假如我要从882KB处开始截断,即从Block882处开始,以后所有的文件数据块(Block882 ~ Block1023)都将被释放,在前面的函数中我们计算了Block882的映射深度(2),offsets[0]同时也记录了几级映射,当前应该是13。因此,我们需要释放的磁盘块索引路径一定是从i_data[13]处开始。一级映射和直接块映射路径上面的索引块和直接数据块我们无需释放。而且我们需要明白,该二级映射路径上也不是所有的块都需要被释放,本例中,只有上图标X的索引块或者数据块才需要被释放。所以我想我们首先第一步得确认我们需要回收哪些索引块和数据块。

        其实释放可以按照如下思路来进行:首先我们释放二级映射路径上的需要释放的磁盘块,对本例来说,释放Block882 ~ Block1023,然后释放该路径上必须释放的间接块,本例中因为我们二级间接映射路径上的一级间接块只分配了三个,而且我们截断的起始块(882)正好是位于最后一个间接块映射范围,因此,我们不必释放任何一个一级间接块,同样,二级间接块我们也不能释放。

        因此,以上的函数段的作用总结起来就是:将被截断块所在的映射路径上需要被释放的直接数据块或者间接块回收。但是注意:这里我们只回收某一级映射路径上磁盘块(截断起始块所在的映射路径,如本例中的二级映射路径)。

        但是请注意:我们上面只是处理了某一级映射路径上的磁盘块,如上面代码中的第一段中的直接映射块处理,第二段中的二级映射路径上磁盘块的释放。但如果文件足够大,那么我们还得需要处理接下来映射路径上的磁盘块释放,如上面的第二个例子,假如文件为1G,那么我们还需要三级映射,而上面的代码只处理了二级映射路径上的磁盘块,所以我们还得处理三级映射路径上的磁盘块释放。

        接下来的第三部分代码就是干这个事情的,需要注意的是:这部分代码处理的是某个映射路径上所有被分配的磁盘块,也意味着该路径上的所有分配的磁盘块均将被释放。其实现如下:

do_indirects:/* Kill the remaining (whole) subtrees */switch (offsets[0]) {default:nr = i_data[EXT2_IND_BLOCK];if (nr) {i_data[EXT2_IND_BLOCK] = 0;mark_inode_dirty(inode);ext2_free_branches(inode, &nr, &nr+1, 1);}case EXT2_IND_BLOCK:nr = i_data[EXT2_DIND_BLOCK];if (nr) {i_data[EXT2_DIND_BLOCK] = 0;mark_inode_dirty(inode);ext2_free_branches(inode, &nr, &nr+1, 2);}case EXT2_DIND_BLOCK:nr = i_data[EXT2_TIND_BLOCK];if (nr) {i_data[EXT2_TIND_BLOCK] = 0;mark_inode_dirty(inode);ext2_free_branches(inode, &nr, &nr+1, 3);}case EXT2_TIND_BLOCK:;}ext2_discard_reservation(inode);mutex_unlock(&ei->truncate_mutex);}

        这段代码的主要作用是释放某个映射路径上的所有已分配磁盘块。offsets[0]记录了该映射路径的起始块号,但是我们需要注意一点:我们要映射的路径是位于offsets[0]的下一级映射,因为offsets[0]所在的映射路径上的需要被删除的磁盘块已经被释放了。如上面的事例2,offsets[0]=13也即二级映射路径上的磁盘块已经处理过了,我们需要处理的是三级映射路径上被分配磁盘块的释放。所以你看上面代码的时候一定需要注意这点。

        其实,这段代码实现也比较简单了,判断如果这一级映射路径存在(即nr != 0),那么就调用ext2_free_branches()来释放这一级映射路径上分配过的磁盘块。而且可以事先透露下,ext2_free_branches()是用递归实现的。即从该映射路径起始一直会释放到映射路径结束的地方。关于该函数的实现,我觉得是没必要再专门写一篇博客来阐述了,理解了整个流程以后,读起来其实算是比较容易的。

        至此,我们算是理解了ext2文件系统的截断流程,在阅读代码的时候,一定得事先考虑清楚其中需要处理的问题,有了截断的逻辑,我们才能更容易地理解这部分代码。

原创粉丝点击