ext2文件系统在为一次写分配数据块之前,一般会在处理过程中为块分配器提供一个参考意见(即最好选择哪个磁盘块分配),其选择的标准是尽量将文件的数据块连续,并且将文件的inode所在块和数据块尽量分配在一起,以加速文件读写过程。有了这一原则作为指导思想,我们在理解其逻辑时就简单了很多。

另外,ext2文件系统为了进一步优化,提出了“预分配”这一思想,其核心就是一次分配时并非只分配一个数据块,而是多分配一些数据块,这些数据块的特征是物理位置上连续,这样,只要后续来的分配请求在逻辑上连续,我们就能保证其在物理位置上同样连续,最终还是提升文件的读写效率。

有了前面基础知识的铺垫,我们来看看ext2_find_goal()函数的庐山真面目。

static inline ext2_fsblk_t ext2_find_goal(struct inode *inode, long block,  
                      Indirect *partial)  
{  
    struct ext2_block_alloc_info *block_i;  

    block_i = EXT2_I(inode)->i_block_alloc_info;  

    /* 
     * try the heuristic for sequential allocation, 
     * failing that at least try to get decent locality. 
     */  
    /* 如果和上一次分配的逻辑块号连续 
    ** 那么就从上一次分配的物理块号之后的 
    ** 那个物理块开始分配 
    */  
    if (block_i && (block == block_i->last_alloc_logical_block + 1)  
        && (block_i->last_alloc_physical_block != 0)) {  
        return block_i->last_alloc_physical_block + 1;  
    }  

    return ext2_find_near(inode, partial);  
}

从上面的代码中可以看到,首先判断该文件的预分配情况,如果已经为其建立预分配结构,而且恰巧本次写入和上次写入的逻辑块连续,那么没啥好说的,直接用后面的那个物理块吧,so easy,妈妈再也不用担心我不懂了。

如果很不巧的是无法满足顺序读写这一优良特性,那么只好老老实实地去找到一个最合适的建议数据块了,那就让我们来看看到底怎么个寻找最合适物理块的方法吧。

static ext2_fsblk_t ext2_find_near(struct inode *inode, Indirect *ind)  
{  
    struct ext2_inode_info *ei = EXT2_I(inode);  
    __le32 *start = ind->bh ? (__le32 *) ind->bh->b_data : ei->i_data;  
    __le32 *p;  
    ext2_fsblk_t bg_start;  
    ext2_fsblk_t colour;  

    /* Try to find previous block */  
    for (p = ind->p - 1; p >= start; p--)  
        if (*p)  
            return le32_to_cpu(*p);  

    /* No such thing, so let's try location of indirect block */  
    if (ind->bh)  
        return ind->bh->b_blocknr;  

    /* 
     * It is going to be refered from inode itself? OK, just put it into 
     * the same cylinder group then. 
     */  
    bg_start = ext2_group_first_block_no(inode->i_sb, ei->i_block_group);  
    colour = (current->pid % 16) *  
            (EXT2_BLOCKS_PER_GROUP(inode->i_sb) / 16);  
    return bg_start + colour;  
} 

在这个函数中我们不得不考虑一种很恶心的情况,那就是如果ind是在间接块的位置处断链应该怎么办,让我们还是从最简单的考虑起吧:

  • 如果是第一次写,写的起始逻辑块号为0,这时start是ei->i_data那个数组起始地址,这时候没办法了,因为之前还没数据块,你从哪连续去,所以最好的建议块号就是inode所在的后面的那个物理块了,这样可满足数据块和inode连续,当然内核在实现的时候玩了点小花样,将数据块和inode放在了同一个块组中(回想下ext2的磁盘布局吧),但可能只是在块组中按照某个算法选择一个物理块。
  • 如果是非第一次写,但文件可能还比较小,还无需用到间接块,那此时参数ind=NULL,start同样是ei->i_data[]数组起始地址,这个时候好办,我们不就是想把这次的数据块和之前的尽量放在连续的物理块中么,那好办啊,我们从之前写入的逻辑块开始找就是了,找到有分配物理块的,我们就将这个物理块号作为最理想的物理块了,也不甚难理解;
  • 如果非第一次写,并且此时可能文件比较大了,采用直接映射的方式可能已经装不下该文件了,那只能采用间接映射的方式,那么此时很可能在一级间接映射处断链了,即第一级间接磁盘块已经分配,但是保存数据的物理磁盘块尚未分配,那此时合理的动作是从本次写的逻辑块之前的那个逻辑块开始查找,如果其物理块已经分配,那么使用该块号,一直找到第一级间接磁盘块的起始位置处。

在上面3的情况下,如果一直找到第一级间接磁盘块的起始处依然没有找到想要的(如当前正写的逻辑块正好位于一级间接磁盘块的第一个索引处),那这时候也没有什么好的建议了,你不是需要连续嘛,就用一级磁盘块的块号作为最理想的目标吧。

注意:我们这里寻找的是一个建议磁盘块号,而这个磁盘块很有可能已经被占用,但我们这里并不care,我们要找的就是一个能与之前的物理块连续的磁盘块,至于它到底可不可用会在接下来继续作判断。