我们前面的博客中仔细阐述了ext2_allocate_branch()的实现原理,其主要作用是分配写所需的直接和间接块,并建立映射关系,但仔细分析后,我们可以发现,这种映射关系建立的不是很彻底,即有些该做的还未做,那么这些未做的留到哪了呢?就在本文所要分析的ext2_splice_branch()函数中来实现。

首先,让我们来看看到底ext2_allocate_branch()中到底做了哪些,而哪些事情没有做:
ext2_allocate_branch()做了的事情:

  • 分配间接块和直接块;
  • 如果分配了间接块,那么链接了间接块的索引(即索引路径上的间接块都初始化并在相应的偏移设置到下一级间接块的索引);
  • 如果分配了间接块,那么最后一个间接块到直接块的映射也建立起来了。

下图是2对应的情况:

ext2_splice_branch解析

该图中,由于分配了间接块(红色数据块)而使得间接块的映射建立成功了,但是我们注意到,在映射断裂的地方(ei->i_data[13])它和二级间接块的映射彼时还未建立起来,只是将二级间接块的块号记录在断裂的数据结构中了,即branch[0].key,branch[0].p则记录了该块号最终应该被写入的位置(本例中是i_data[13])。

下图则描述了另外一种情形下ext2_allocate_branch做了哪些以及哪些尚未完成:

ext2_splice_branch解析

此时,在ext2_allocate_branch()中无需分配间接块,而是直接分配了两个数据块(右侧蓝色块),但在ext2_allocate_branch()实现时,并未将这两个数据块和索引块建立起映射,而只是在断裂位置处记录了分配的数据块号(branch[0].key的值)。

尚未完成的事情:

  • 如果没有分配间接块,那么从间接块到 直接数据块的索引就没有建立;
  • 在链接断裂的地方,其索引并没有建立。

因此,通过上面的两张图,我们知道了ext2_allocate_branch什么还没有做,我们就明白了ext2_splice_branch()到底应该需要做些什么。

static void ext2_splice_branch(struct inode *inode,
            long block, Indirect *where, int num, int blks)
{
    int i;
    struct ext2_block_alloc_info *block_i;
    ext2_fsblk_t current_block;

    block_i = EXT2_I(inode)->i_block_alloc_info;

    /* XXX LOCKING probably should have i_meta_lock ?*/
    /* That's it */

    *where->p = where->key;

    /*
     * Update the host buffer_head or inode to point to more just allocated
     * direct blocks blocks
     */
    /* num == 0意味着之前没有分配间接块,
    ** blks > 1意味着之前分配了超过1个直接块
    ** blks = 1的情况已经在上面代码
    ** *where->p = where->key中处理过了
    */
    if (num == 0 && blks > 1) {
        current_block = le32_to_cpu(where->key) + 1;
        for (i = 1; i < blks; i++)
            *(where->p + i ) = cpu_to_le32(current_block++);
    }

    /*
     * update the most recently allocated logical & physical block
     * in i_block_alloc_info, to assist find the proper goal block for next
     * allocation
     */
    if (block_i) {
        block_i->last_alloc_logical_block = block + blks - 1;
        block_i->last_alloc_physical_block =
                le32_to_cpu(where[num].key) + blks - 1;
    }

    /* We are done with atomic stuff, now do the rest of housekeeping */

    /* had we spliced it onto indirect block? */
    if (where->bh)
        mark_buffer_dirty_inode(where->bh, inode);

    inode->i_ctime = CURRENT_TIME_SEC;
    mark_inode_dirty(inode);
}

让我们来看看这个函数的每一步都是在干什么:

  1. *where->p = where->key:修复映射断裂,在索引块的特定偏移位置(where->p指向)记录被索引块的块号(无论被索引块是间接索引块还是直接数据块),这样,映射断裂处的断裂就不复存在了;
  2. 接下来的if(num == 0 && blks > 1)判断:这是针对上图2锁描述的情况:
    num=0意味着前面没有分配间接块,而blks > 1则意味着分配了数据块,因为在上图2中索引块到数据块的映射并未建立,而上面的一句(*where->p = where->key)只是建立了第一个数据块的映射关系,如果当前分配的数据块数量大于1,那我们还必须将剩余的数据块的映射关系建立起来。而建立也很简单,
    current_block = le32_to_cpu(where->key) + 1;
    for (i = 1; i < blks; i++)
    *(where->p + i ) = cpu_to_le32(current_block++);
    这是建立在分配的数据块一定是连续的基础之上的。

到目前为止,整个映射关系才算是真正地建立起来,我们才可以使用。
接下来的是一些无关痛痒的倒也简单的功能,如修改预分配结构(因为本次新分配了数据块,所以需要更新预分配结构中的某些成员),另外因为可能修改间接块中的内容,所以必须将间接块所在的buffer_head标记为dirty,以等待合适的时机写入物理磁盘。