在我最近分析的ext2_get_blocks()函数分析中碰到了ext2_blks_to_allocate()这个过程,虽然实现的很简单,但是从整个写入流程来看,也是相当有意思的。

这个函数的意义非常明确,即计算本次需要分配多少个数据块。可能有人不禁要问了,这还需要计算么,调用者不是在参数中就直接告诉了我需要写多少个字节么,你直接按这么多来分配就是了,其实,要是真能实现这个目标就好了。在ext2上做起来却困难重重,让我们来分析下原因:
如果目前文件还足够小,小到根本无需使用间接索引来保存逻辑块到物理块的映射,直接使用inode中的i_data[0 ~ 11]数组来保存逻辑块至物理块的映射。举例来说,如果当前文件大小为4KB,那我们知道,i_data[0~3]这四项已经被索引填充了(如果当前物理块大小为1KB),而此时上层突然来了个4KB ~1MB + 4KB(起始位置~结束位置)的写请求(其实如果真的来说,ext2_get_blocks()的调用者是不太会传入这么大的请求的,为什么?因为ext2_get_blocks()的调用者是将应用层的写请求划分成page为单位,对每个page再来调用ext2_get_blocks(),而linux上默认page大小是4KB,但即使是这样,仍然有可能产生下面描述的问题),根据之前的代码我们知道,这时候需要优先使用直接块索引方法,而1MB大小的请求是无法完全利用直接块来索引的(当前直接块最多还能索引8KB的文件内容)这个时候应该怎么办,以哪个为准来分配空间?
对于使用间接块映射文件物理块的情况来说,问题也是一样,假如当前的间接块已经处于快满的状态而无力映射调用者传入的那么多数量的磁盘块时,这个时候怎么办?
通过上面的分析我们知道,产生该问题的本质原因是ext2传统的逻辑块至物理块的映射方式可能不足以灵活地应付上层的各种需求,这个时候我们就需要作一些折中。解决办法就是调用我们这里分析的ext2_blks_to_allocate()来确定到底分配多少个物理块。

该函数的计算方法也比较简单,根据当前映射模式来确定采用该映射方式还能分配多少个物理块,废话不多说,让我们来直接考察该函数的实现吧。

static int
ext2_blks_to_allocate(Indirect * branch, int k, unsigned long blks,
        int blocks_to_boundary)
{
    unsigned long count = 0;

    /*
     * Simple case, [t,d]Indirect block(s) has not allocated yet
     * then it's clear blocks on that path have not allocated
     */
    if (k > 0) {
        /* right now don't hanel cross boundary allocation */
        if (blks < blocks_to_boundary + 1)
            count += blks;
        else
            count += blocks_to_boundary + 1;
        return count;
    }

    count++;
    while (count < blks && count <= blocks_to_boundary
        && le32_to_cpu(*(branch[0].p + count)) == 0) {
        count++;
    }
    return count;
}

该函数个参数的意义是:

  • branch:这个代表了映射断裂的分支位置信息;
  • k:表示间接块的数量,即是否需要分配间接块;
  • blks:代表调用者想要分配的数据块数量;
  • blocks_to_boundary:代表我本次所能分配磁盘块数量的上限

在了解了上面几个参数的意义之后,我想这个函数理解起来就不是那么费劲了,如果本次写入需要使用间接块来映射的话,那本次所能分配的物理块的数量取决于该间接块中尚能映射的物理块数;而如果本次写入不需要间接块接入的话,那本次所能分配的物理块数取决于直接映射方式剩余的索引数量(即i_data[0 ~ 11]中空闲项的个数)。
所以,这里的关键还是在于blocks_to_boundary这个参数值的确定。我们不妨追根溯源,看看这个参数是在那里被定值的。

static int ext2_block_to_path(struct inode *inode,
            long i_block, int offsets[4], int *boundary)
{
    //ptrs:每个间接块内可保存的指针数量
    int ptrs = EXT2_ADDR_PER_BLOCK(inode->i_sb);
    int ptrs_bits = EXT2_ADDR_PER_BLOCK_BITS(inode->i_sb);
    const long direct_blocks = EXT2_NDIR_BLOCKS,
        indirect_blocks = ptrs,
        double_blocks = (1 << (ptrs_bits * 2));
    int n = 0;
    int final = 0;

    if (i_block < 0) {
        ext2_msg(inode->i_sb, KERN_WARNING,
            "warning: %s: block < 0", __func__);
    } else if (i_block < direct_blocks) {
        offsets[n++] = i_block;
        final = direct_blocks;
    } else if ( (i_block -= direct_blocks) < indirect_blocks) {
        offsets[n++] = EXT2_IND_BLOCK;
        offsets[n++] = i_block;
        final = ptrs;
    } else if ((i_block -= indirect_blocks) < double_blocks) {
        offsets[n++] = EXT2_DIND_BLOCK;
        offsets[n++] = i_block >> ptrs_bits;
        offsets[n++] = i_block & (ptrs - 1);
        final = ptrs;
    } else if (((i_block -= double_blocks) >> (ptrs_bits * 2)) < ptrs) {
        offsets[n++] = EXT2_TIND_BLOCK;
        offsets[n++] = i_block >> (ptrs_bits * 2);
        offsets[n++] = (i_block >> ptrs_bits) & (ptrs - 1);
        offsets[n++] = i_block & (ptrs - 1);
        final = ptrs;
    } else {
        ext2_msg(inode->i_sb, KERN_WARNING,
            "warning: %s: block is too big", __func__);
    }
    //boundary到底是什么意思,为什么需要这样一个东西
    if (boundary)
        *boundary = final - 1 - (i_block & (ptrs - 1));

    return n;

经过一番不懈探索,我们发现该参数在ext2_block_to_path()这个函数中被设置的。这是一个比较容易理解的函数,其实就是根据逻辑块号来确定到底采用几级映射,返回值为1,2,3,4分别代表直接映射,一级间接映射,二级间接映射,三级间接映射,同时在offsets[]中存储索引路径。在函数的最后有一个计算
*boundary = final – 1 – (i_block & (ptrs – 1))
可能直接看这个表达式不太好理解,我们不妨列举这样一种情况:

假如当前写的逻辑块号为18,那么通过上面计算知道,必须使用一级间接索引的方式来索引文件。假设磁盘块大小为1024字节,因此,final = ptrs = 1024 / 4 = 256(即一级索引磁盘块中可存储256个物理磁盘块号),此时i_block = 18 – 12 = 6, i_block & (1024 – 1) = 6(这表示逻辑块号为18的块其物理块号的索引位于一级间接块的偏移量为6的位置),因此*boundary = 256 -1 – 6 = 249,这个比较容易理解了,就是该间接块当前最多能索引多少个物理块(从6之后的索引都可以使用,一直到最大索引255处)。

对于其他的索引方式,理解起来也是一样,有兴趣的可以参照上面的思路比划下,就不再赘述了。

知道了当次能分配的物理块上限,以及调用者需要分配的物理块数,那么我们上面的ext2_blocks-to_allocate()的理解起来就简单了,无非就是取两者中的较小嘛,至少核心思想就是这样。

  1. 拉黑?就算拉黑又如何!如果爱,拉黑就能忘了吗?如果爱,拉黑就能走出那段阴影吗? 如果不爱,不需要拉黑!