ext4 extent详解2之内核源码详解

news/2024/11/18 0:37:06/

在查看本文前,希望先查看《ext4 extent详解1之示意图演示》这篇文章,有助于理解本文。本文内核源码版本3.10.96,详细内核详细源码注释见https://github.com/dongzhiyan-stack/kernel-code-comment。

什么时候会用到ext4 extent B+树呢?我们看一个函数流程ext4_readpage()->mpage_readpages()->ext4_get_block()->_ext4_get_block()->ext4_map_blocks()->ext4_ext_map_blocks(),这是一个典型的ext4文件系统读文件的流程。里边有一个核心操作是,把应用层读文件的逻辑地址转成实际保存文件数据的物理块地址,有个这个物理块地址,才能从磁盘读到文件数据。而ext4_ext_map_blocks()正是完成文件逻辑地址与物理块地址映射的核心函数。这里先把ext4_ext_map_blocks()函数整体流程图贴下,然后结合上篇文章里ext4  extent B+树的形成过程,结合源码讲解一下函数流程。

 最后执行ext4_ext_insert_extent()把新的ext4_extent插入到ext4 extent b+树,ext4_ext_insert_extent()函数是个重点函数,逻辑非常复杂,它的源码流程简单画下:

 本文将以ext4_ext_map_blocks()函数为切入点,详细讲解把怎么找到文件的逻辑块地址映射的物理块地址,完成文件逻辑块地址与物理块地址的映射,然后把表示这个映射关系的ext4_extent结构插入ext4_extent b+树。在正式开始讲解前,把下边讲解源码时高频出现的词汇单独列下:

  1. map:本次参与文件逻辑块地址映射的 struct ext4_map_blocks 结构
  2. map->m_lblk:待映射的起始逻辑块地址
  3. map->m_len:待映射的逻辑块个数,或者说逻辑块地址需映射的物理块个数
  4. depth ext_depth(inode): ext4 extent b+树深度
  5. ex:原型是struct ext4_extent *ex,执行ext4_ext_find_extent(...map->m_lblk...)后被赋值叶子节点中起始逻辑块地址最接近map->m_lblkext4_extent结构
  6. ex->ee_block ee_blockex这个ext4_extent结构的起始逻辑块地址
  7. ee_lenex映射的连续物理块个数
  8. ee_startex的起始逻辑块地址映射的起始物理块号

1:ext4_ext_map_blocks()函数源码解析

ext4_ext_map_blocks()函数源码如下:

  1. int ext4_ext_map_blocks(handle_t *handle, struct inode *inode,
  2.             struct ext4_map_blocks *map, int flags)
  3. {
  4.     struct ext4_ext_path *path = NULL;
  5.     struct ext4_extent newex, *ex, *ex2;
  6.     struct ext4_sb_info *sbi = EXT4_SB(inode->i_sb);
  7.     ext4_fsblk_t newblock = 0;
  8.     int free_on_err = 0, err = 0, depth, ret;
  9.     unsigned int allocated = 0, offset = 0;
  10.     unsigned int allocated_clusters = 0;
  11.     struct ext4_allocation_request ar;
  12.     ext4_io_end_t *io = ext4_inode_aio(inode);
  13.     ext4_lblk_t cluster_offset;
  14.     int set_unwritten = 0;
  15.     .........
  16.     /*ext4 extent B+树每一层索引节点(包含根节点)中找到起始逻辑块地址最接近传入的起始逻辑块地址map->m_lblkext4_extent_idx结构保存到path[ppos]->p_idx.然后找到最后一层的叶子节点中最接近传入的起始逻辑块地址map->m_lblkext4_extent结构,保存到path[ppos]->p_ext。这个ext4_extent才包含了逻辑块地址和物理块地址的映射关系。*/
  17.     path = ext4_ext_find_extent(inode, map->m_lblk, NULL);
  18.     //ext4 extent B+树深度
  19.     depth = ext_depth(inode);
  20.     //指向起始逻辑块地址最接近map->m_lblkext4_extent
  21.     ex = path[depth].p_ext;
  22.     if (ex) {
  23.         //ext4_extent结构代表的起始逻辑块地址
  24.         ext4_lblk_t ee_block = le32_to_cpu(ex->ee_block);
  25.         //ext4_extent结构代表的起始物理块地址
  26.         ext4_fsblk_t ee_start = ext4_ext_pblock(ex);
  27.         unsigned short ee_len;
  28.        
  29.         //ex的逻辑块地址映射的物理块个数
  30.         ee_len = ext4_ext_get_actual_len(ex);
  31.         //如果map->m_lblkex的逻辑块地址范围内
  32.         if (in_range(map->m_lblk, ee_block, ee_len)) {
  33.             //newblock : map->m_lblk这个起始逻辑块地址对应的物理块地址
  34.             newblock = map->m_lblk - ee_block + ee_start;
  35.             //map->m_lblk(ee_block+ee_len)这个范围的物理块个数
  36.             allocated = ee_len - (map->m_lblk - ee_block);
  37.             /*ex已经初始化过直接goto out返回,否则执行下边的ext4_ext_handle_uninitialized_extents()*/
  38.             if (!ext4_ext_is_uninitialized(ex))
  39.                 goto out;
  40.            
  41.             /*ex的逻辑块地址进行分割,高版本内核函数名称改为 ext4_ext_handle_unwritten_extents()*/
  42.             ret = ext4_ext_handle_uninitialized_extents(
  43.         }
  44.     }
  45.     ..........
  46.     //设置newex的起始逻辑块号,newex是针对本次映射分配的ext4_extent结构
  47.     newex.ee_block = cpu_to_le32(map->m_lblk);
  48.     .........
  49.     //找到map->m_lblk映射的目标起始物理块地址并返回给ar.goal
  50.     ar.goal = ext4_ext_find_goal(inode, path, map->m_lblk);
  51.     //ar.logical是起始逻辑块地址map->m_lblk
  52.     ar.logical = map->m_lblk;
  53.     .......
  54.     //offset测试时0
  55.     offset = EXT4_LBLK_COFF(sbi, map->m_lblk);
  56.     //本次映射需分配的物理块个数,即allocated
  57.     ar.len = EXT4_NUM_B2C(sbi, offset+allocated);
  58.     //物理块起始地址,offset0
  59.     ar.goal -= offset;
  60.     ar.logical -= offset;
  61.     .......
  62.     /*分配map->m_len个物理块,这就是newex逻辑块地址映射的map->m_len个物理块,并返回这map->m_len个物理块的起始物理块号newblock。测试结果 newblock ar.goal有时相等,有时不相等。本次映射的起始逻辑块地址是map->m_lblk,映射物理块个数map->m_lenext4_mb_new_blocks()除了要找到newblock这个起始逻辑块地址,还得保证找到newblock打头的连续map->m_len个物理块,必须是连续的,这才是更重要的。*/
  63.     newblock = ext4_mb_new_blocks(handle, &ar, &err);
  64.     ..............
  65. got_allocated_blocks:
  66.     /*设置本次映射的map->m_len个物理块的起始物理块号(newblock)newexnewex是针对本次映射分配的ext4_extent结构*/
  67.     ext4_ext_store_pblock(&newex, newblock + offset);//offset0
  68.     /*设置newex映射的物理块个数,与执行ext4_ext_mark_initialized()标记ex已初始化一个效果*/
  69.     newex.ee_len = cpu_to_le16(ar.len);
  70.     .........
  71.     if (!err)//newex这个插入ext4 extent B+
  72.         err = ext4_ext_insert_extent(handle, inode, path,
  73.                      &newex, flags);
  74.     ..........
  75. out:
  76.     ..........
  77.     map->m_flags |= EXT4_MAP_MAPPED;
  78.     //本次起始逻辑块地址map->m_lblk映射的起始物理块号
  79.     map->m_pblk = newblock;
  80.     /*本次逻辑块地址完成映射的物理块数,并不能保证allocated等于传入的map->m_len,还有可能小于*/
  81.     map->m_len = allocated;
  82.    
  83.     //返回成功映射的物理块个数
  84.     return err ? err : allocated;
  85. }

ext4_ext_map_blocks()函数主要流程有如下几点:

1:先执行ext4_ext_find_extent(),试图找到逻辑块地址最接近map->m_lblk的索引节点ext4_extent_idr结构和叶子节点ext4_extent结构并保存到path[]。ext4_ext_find_extent()函数源码下文详解。如果找到匹配的叶子节点ext4_extent结构,则ex = path[depth].p_ext保存这个找到的ext4_extent结构。此时if (ex)成立,如果map->m_lblk在ex的逻辑块地址范围内,即if (in_range(map->m_lblk, ee_block, ee_len))成立,则执行里边代码newblock = map->m_lblk - ee_block + ee_start和allocated = ee_len - (map->m_lblk - ee_block),通过ex已经映射的逻辑块地址和物理块地址找到map->m_lblk映射的起始物理块,allocated是找到的映射的物理块个数。简单说,本次要映射的起始逻辑块地址map->m_lblkex的逻辑块地址范围内,那就可以借助ex这个ext4_extent已有的逻辑块地址与物理块地址映射关系,找到map->m_lblk映射的起始物理块地址,并找到已经映射过的allocated个物理块

2:如果ex是已初始化状态,则if (!ext4_ext_is_uninitialized(ex))成立,直接goto out 。否则ex未初始化状态,则要执行ext4_ext_handle_uninitialized_extents()->ext4_ext_convert_to_initialized()对ex的逻辑块地址进行分割,还有概率创建新的索引节点和叶子节点。高版本内核 ext4_ext_handle_uninitialized_extents()函数名称改为 ext4_ext_handle_unwritten_extents(),需注意,ext4_ext_convert_to_initialized()源码下文详解。

3:继续ext4_ext_map_blocks()代码,如果ex = path[depth].p_ext是NULL,则if (ex)不成立。则执行下文newblock = ext4_mb_new_blocks(handle, &ar, &err)函数,针对本次需映射的起始逻辑块地址map->m_lblk和需映射的逻辑块个数map->m_len,分配map->m_len个连续的物理块并返回这map->m_len个物理块的第一个物理块号给newblock。

4:接着先执行newex.ee_block = cpu_to_le32(map->m_lblk)赋值本次要映射的起始逻辑块地址,newex是针对本次逻辑块地址映射创建的ext4_extent结构。然后执行ext4_ext_store_pblock(&newex, newblock + offset)把本次逻辑块地址map->m_lblk ~( map->m_lblk+ map->m_len)映射的起始物理块号newblock保存到newex的ee_start_lo和ee_start_hi。并执行newex.ee_len = cpu_to_le16(ar.len)把成功映射的物理块数保存到newex.ee_len,即map->m_len。

5:执行err = ext4_ext_insert_extent(handle, inode, path,&newex, flags)把代表本次逻辑块地址映射关系的newex插入到ext4  extent b+树。

下一节讲解ext4_ext_find_extent()函数源码。

2:ext4_ext_find_extent()函数源码解析

ext4_ext_find_extent()函数源码如下:

  1. struct ext4_ext_path * ext4_ext_find_extent(struct inode *inode, ext4_lblk_t block,
  2.                     struct ext4_ext_path *path)//block是传入的起始逻辑块地址
  3. {
  4.     struct ext4_extent_header *eh;
  5.     struct buffer_head *bh;
  6.     short int depth, i, ppos = 0, alloc = 0;
  7.     int ret;
  8.     //ext4_inode_info->i_data数组得到ext4 extent B+树的根节点
  9.     eh = ext_inode_hdr(inode);
  10.     //xt4 extent B+树深度
  11.     depth = ext_depth(inode);
  12.     if (!path) {
  13.         //按照B+树的深度分配ext4_ext_path结构
  14.         path = kzalloc(sizeof(struct ext4_ext_path) * (depth + 2),
  15.                 GFP_NOFS);
  16.         if (!path)
  17.             return ERR_PTR(-ENOMEM);
  18.         alloc = 1;
  19.     }
  20.     path[0].p_hdr = eh;
  21.     path[0].p_bh = NULL;
  22.     i = depth;
  23.     while (i) {
  24.         /*利用二分法在ext4 extent B+path[ppos]->p_hdr[]后边的ext4_extent_idx[]数组中,找到起始逻辑块地址最接近blockext4_extent_idx结构。path[ppos]->p_idx指向这个ext4_extent_idx*/
  25.         ext4_ext_binsearch_idx(inode, path + ppos, block);
  26.         //通过索引节点ext4_extent_idx结构的ei_leaf_loei_leaf_hi成员计算出的物理块号,这个物理块保存了下层叶子节点或者索引节点4K数据
  27.         path[ppos].p_block = ext4_idx_pblock(path[ppos].p_idx);
  28.         path[ppos].p_depth = i;//逻辑块地址接近map->m_lblk的索引节点或叶子节点所在ext4 extent B+树中的层数
  29.         path[ppos].p_ext = NULL;
  30.         /*path[ppos].p_block是保存了下层叶子节点或者索引节点4K数据,bh映射指向这个物理块*/
  31.         bh = sb_getblk(inode->i_sb, path[ppos].p_block);
  32.         ..................
  33.         /*eh指向当前索引节点对应的 下层的索引节点或者叶子节点的头结点,注意,是当前ppos索引节点下层的索引节点或者叶子节点*/
  34.         eh = ext_block_hdr(bh);
  35.         //索引节点层数加1
  36.         ppos++;
  37.         ..............
  38.         /*上边ppos++了,ppos代表下一层索引节点或者叶子节点了。path[ppos].p_bh指向新的ppos这一层 索引节点或者叶子节点 4K数据的物理块 映射的bh*/
  39.         path[ppos].p_bh = bh;
  40.         //path[ppos].p_bh指向新的ppos这一层索引节点或者叶子节点的头结构
  41.         path[ppos].p_hdr = eh;
  42.         i--;
  43.         ................
  44.     }
  45.     path[ppos].p_depth = i;
  46.     path[ppos].p_ext = NULL;
  47.     path[ppos].p_idx = NULL;
  48.     /*利用二分法在ext4 extent B+path[ppos]->p_hdr[]后边的ext4_extent[]数组中,找到起始逻辑块地址最接近blockext4_extent,令path[ppos]->p_ext指向这个ext4_extent。如果叶子结点没有一个有效的ext4_extent结构,则path[ppos]->p_ext保持NULL*/
  49.     ext4_ext_binsearch(inode, path + ppos, block)
  50.     if (path[ppos].p_ext)
  51.         /*ext4_extent结构的ee_start_hiee_start_lo成员计算出的物理块号,这个物理块号是ext4_extent的逻辑块地址映射的的起始物理块号*/
  52.         path[ppos].p_block = ext4_ext_pblock(path[ppos].p_ext);
  53.     ext4_ext_show_path(inode, path);
  54.     return path;
  55. err:
  56.     ext4_ext_drop_refs(path);
  57.     if (alloc)
  58.         kfree(path);
  59.     return ERR_PTR(ret);
  60. }

该函数根据ext4 extent B+树的根节点的ext4_extent_header,先找到每一层索引节点中起始逻辑块地址最接近传入的逻辑块地址block的ext4_extent_idx保存到path[ppos]->p_idx.然后找到最后一层的叶子节点中起始逻辑块地址最接近传入的逻辑块地址block的ext4_extent,保存到path[ppos]->p_ext,这个ext4_extent才包含了逻辑块地址和物理块地址的映射关系。注意,找到这些起始逻辑块地址接近block的ext4_extent_idx和ext4_extent的起始逻辑块地址<=block,在block的左边,必须这样!将来把block对应的ext4_extent插入ext4 extent B+树时,也是插入到这些ext4_extent_idx和ext4_extent的右边。ext4 extent B+树索引节点和叶子节点中的ext4_extent_idx和ext4_extent的逻辑块地址从左到右依次增大,顺序排布。

下边举例讲解,先看下这个示意图,在第一篇讲解ext4_extent 的文章出现过。在执行ext4_ext_map_blocks()函数时,待映射的起始逻辑块地址是map->m_lblk,需要映射的逻辑块个数是map->m_len。再简单说下ext4_ext_find_extent()函数的作用,简单说:根据传入的起始逻辑块地址map->m_lblk,在ext4  extent b+树中从根节点到索引节点再到叶子节点,找到起始逻辑块地址最接近map->m_lblk的并且小于等于map->m_lblk的ext4_extent_idx和ext4_extent保存到struct ext4_ext_path *path[]数组。下边用示意图举个例子:

 假设待映射的起始逻辑块地址map->m_lblk是5,则根节点起始逻辑块地址最接近map->m_lblk的并且小于等于map->m_lblk的ext4_extent_idx是c0,索引节点中起始逻辑块地址最接近map->m_lblk的并且小于等于map->m_lblk的ext4_extent_idx是d0,叶子节点中起始逻辑块地址最接近map->m_lblk的并且小于等于map->m_lblk的ext4_extent是e0。则进行如下赋值

  1. path[0].p_idx = c0//指向根节点起始逻辑块地址最接近map->m_lblk的并且小于等于map->m_lblkext4_extent_idx
  2. path[1].p_idx = d0//指向索引节点中起始逻辑块地址最接近map->m_lblk的并且小于等于map->m_lblkext4_extent_idx
  3. path[2].p_ext = e0//指向叶子节点中起始逻辑块地址最接近map->m_lblk的并且小于等于map->m_lblkext4_extent

struct ext4_ext_path *path[]结构体定义如下:

  1. struct ext4_ext_path {
  2.     /*ext4_ext_find_extent()中赋值,是索引节点时,是由ext4_extent_idx结构的ei_leaf_loei_leaf_hi成员计算出的物理块号,这个物理块保存了下层叶子节点或者索引节点4K数据。是叶子节点时,是由ext4_extent结构的ee_start_hiee_start_lo成员计算出的物理块号,这个物理块号是ext4_extent的逻辑块地址映射的的起始物理块号*/
  3.     ext4_fsblk_t            p_block;
  4.     //当前索引节点或者叶子节点处于ext4 extent B+树第几层。ext4 extent B+树没有索引节点或者叶子节点时层数是0
  5.     __u16               p_depth;
  6.     //起始逻辑块地址最接近map->m_lblkext4_extent
  7.     struct ext4_extent      *p_ext;
  8.     //起始逻辑块地址最接近传map->m_lblkext4_extent_idx
  9.     struct ext4_extent_idx      *p_idx;
  10.     //指向ext4 extent B+索引节点和叶子节点的头结点结构体
  11.     struct ext4_extent_header   *p_hdr;
  12.     //保存索引节点或者叶子节点4K数据的物理块映射的bh
  13.     struct buffer_head      *p_bh;
  14. };
  15. struct ext4_extent_idx {
  16.     //起始逻辑块地址
  17.     __le32  ei_block;  
  18.     /*ei_leaf_loei_leaf_hi一起计算出物理块号,这个物理块保存下层叶子节点或者索引节点4K数据。没错,索引节点ext4_extent_idx结构的ei_leaf_loei_leaf_hi保存了下层索引节点或者叶子节点的物理块号,索引节点的ext4_extent_idx通过其ei_leaf_loei_leaf_hi成员指向下层的索引节点或者叶子节点。这点非常重要*/
  19.     __le32  ei_leaf_lo;
  20.     __le16  ei_leaf_hi;
  21.     __u16   ei_unused;
  22. };
  23. struct ext4_extent {
  24.     //起始逻辑块地址
  25.     __le32  ee_block;
  26.     //逻辑块映射的连续物理块个数
  27.     __le16  ee_len;    
  28.     //ee_start_hiee_start_lo一起计算出起始逻辑块地址映射的起始物理块地址
  29.     __le16  ee_start_hi;   
  30.     __le32  ee_start_lo;   
  31. };

我们这里只展示了对它的成员p_idxp_ext赋值,这两个成员最关键,其他成员没展示。下一节讲解ext4_ext_convert_to_initialized ()函数。

3:ext4_ext_convert_to_initialized ()函数源码解析

ext4_ext_convert_to_initialized ()函数源码如下:

  1. static int ext4_ext_convert_to_initialized(handle_t *handle,
  2.                        struct inode *inode,
  3.                        struct ext4_map_blocks *map,
  4.                        struct ext4_ext_path *path,
  5.                        int flags)
  6. {
  7.     //ext4 extent B+树深度
  8.     depth = ext_depth(inode);
  9.     //指向ext4 extent B+树叶子节点头结构ext4_extent_header
  10.     eh = path[depth].p_hdr;
  11.     /*ext4 extent B+树叶子节点,指向起始逻辑块地址最接近map->m_lblkext4_extent*/
  12.     ex = path[depth].p_ext;
  13.     //ex这个ext4_extent的起始逻辑块地址
  14.     ee_block = le32_to_cpu(ex->ee_block);
  15.     //ex这个ext4_extent映射的物理块个数
  16.     ee_len = ext4_ext_get_actual_len(ex);
  17.        
  18.     //要映射的起始逻辑块地址map->m_lblk等于ex的起始逻辑块地址
  19.     if ((map->m_lblk == ee_block) &&
  20.         (map_len < ee_len) &&/*要求映射的物理块数map_len要小于ex已经映射的物理块数ee_len*/
  21.         /*ex是指向叶子节点第2个及以后ext4_extent结构*/
  22.         (ex > EXT_FIRST_EXTENT(eh))) {
  23.           ...........
  24.             /*下边是重新划分ex这个ext4_extent结构的逻辑块地址范围,把之前ee_block~ee_block+map_len划分给abut_ex这个ext4_extentex新的逻辑块地址范围是(ee_block + map_len)~(ee_block + ee_len)ex映射的逻辑块(物理块)个数减少了map_len个,abut_ex的增加了map_len*/
  25.             ex->ee_block = cpu_to_le32(ee_block + map_len);//设置新的逻辑块首地址
  26.             ext4_ext_store_pblock(ex, ee_pblk + map_len);//设置新的物理块首地址
  27.             ex->ee_len = cpu_to_le16(ee_len - map_len);//设置新的映射的物理块个数
  28.             /*ex这个ext4_extent设置"uninitialized"标记,这是重点*/
  29.             ext4_ext_mark_uninitialized(ex);
  30.             //abut_ex映射的物理块个数增加map_len
  31.             abut_ex->ee_len = cpu_to_le16(prev_len + map_len);
  32.             //allocatedabut_ex增多的逻辑块个数
  33.             allocated = map_len;
  34.           ...........
  35.     }
  36.     //要映射的结束逻辑块地址map->m_lblk+map_len等于ex的结束逻辑块地址ee_block + ee_len
  37.     else if (((map->m_lblk + map_len) == (ee_block + ee_len)) &&
  38.            (map_len < ee_len) &&    /*L1*///要求映射的物理块数map_len要小于ex已经映射的物理块数ee_len
  39.            ex < EXT_LAST_EXTENT(eh)) {  /*L2*///ex是指向叶子节点最后一个ext4_extent结构
  40.         ...........
  41.             /*下边这是把ex的逻辑块范围(ex->ee_block + ee_len - map_len)~(ex->ee_block + ee_len)map_len个逻辑块合并到后边的abut_ex,合并后abut_ex的逻辑块范围是(ex->ee_block + ee_len - map_len)~(next_lblk+next_len),ex的逻辑块范围缩小为ex->ee_block~(ee_len - map_len)*/
  42.             abut_ex->ee_block = cpu_to_le32(next_lblk - map_len);
  43.             ext4_ext_store_pblock(abut_ex, next_pblk - map_len);//设置新的物理块首地址
  44.             //ex映射的逻辑块个数减少了map_len
  45.             ex->ee_len = cpu_to_le16(ee_len - map_len);
  46.             /*标记ex"uninitialized"状态,这是重点,ex还是未初始化状态*/
  47.             ext4_ext_mark_uninitialized(ex);
  48.             //abut_ex逻辑块个数增大了map_len
  49.             abut_ex->ee_len = cpu_to_le16(next_len + map_len);
  50.             //abut_ex逻辑块个数增加了map+len
  51.             allocated = map_len;
  52.         ...........    
  53.     }
  54.     .............
  55.     if (allocated) {/*allocated0说明abut_ex逻辑块范围吞并了ex map_len个逻辑块*/
  56.         /*ext4 extent叶子节点变为abut_ex,原来的ex废弃了,隐藏知识点*/
  57.         path[depth].p_ext = abut_ex;
  58.         goto out;//退出该函数
  59.     } else
  60.         /*allocated=(ee_len+ee_block) - map->m_lblk。如果abut_ex没有吞并ex的逻辑块,allocatedmap->m_lblkex结束逻辑块地址之间的逻辑块数*/
  61.         allocated = ee_len - (map->m_lblk - ee_block);
  62.     ...........
  63.     //重点,把ex的逻辑块地址进行分割
  64.     allocated = ext4_split_extent(handle, inode, path,
  65.                       &split_map, split_flag, flags);
  66.                      
  67.     return err ? err : allocated;
  68. }

该函数逻辑比较简单,主要功能总结如下:如果本次要映射的物理块数(或者逻辑块数)map->len小于ex已经映射的逻辑块数ee_len,则尝试把ex的map->len的逻辑块合并到它前边或者后边的ext4_extent结构(即abut_ex)。合并条件苛刻,需要二者逻辑块地址和物理块地址紧挨着等等。如果合并成功直接从ext4_ext_convert_to_initialized()函数返回。否则执行ext4_split_extent()把ex的逻辑块地址进程分割成2段或者3段,分割出的以map->m_lblk为起始地址且共allocated个逻辑块的逻辑块范围就是我们需要的,这allocated个逻辑块可以保证映射了物理块。但allocated<=map->len,即并不能保证map要求映射的map->len个逻辑块全映射完成。注意,ext4_split_extent()对ex分割后,还剩下其他1~2段逻辑块范围,则要把它们对应的ext4_extent结构插入的ext4_extent B+树。

该函数里重点执行的ext4_split_extent()函数,下节讲解

4:ext4_split_extent()函数源码解析

ext4_split_extent()函数源码如下:

  1. static int ext4_split_extent(handle_t *handle,
  2.                   struct inode *inode,
  3.                   struct ext4_ext_path *path,
  4.                   struct ext4_map_blocks *map,
  5.                   int split_flag,
  6.                   int flags)
  7. {
  8.     ext4_lblk_t ee_block;
  9.     struct ext4_extent *ex;
  10.     unsigned int ee_len, depth;
  11.     int err = 0;
  12.     int uninitialized;
  13.     int split_flag1, flags1;
  14.     int allocated = map->m_len;
  15.     depth = ext_depth(inode);
  16.     ex = path[depth].p_ext;
  17.     ee_block = le32_to_cpu(ex->ee_block);
  18.     ee_len = ext4_ext_get_actual_len(ex);
  19.     //ex是否是未初始化状态
  20.     uninitialized = ext4_ext_is_uninitialized(ex);
  21.     /*如果map的结束逻辑块地址小于ex的结束逻辑块地址,则执行ext4_split_extent_at()ex的逻辑块地址分割为ee_block~(map->m_lblk+map->m_len)(map->m_lblk+map->m_len)~(ee_block + ee_len)*/
  22.     if (map->m_lblk + map->m_len < ee_block + ee_len) {
  23.         split_flag1 = split_flag & EXT4_EXT_MAY_ZEROOUT;
  24.        
  25.         flags1 = flags | EXT4_GET_BLOCKS_PRE_IO;//flag加上EXT4_GET_BLOCKS_PRE_IO标记
  26.         /*如果ex有未初始化标记,则split_flag1被加上EXT4_EXT_MARK_UNINIT1EXT4_EXT_MARK_UNINIT2标记。EXT4_EXT_MARK_UNINIT1是标记分割的前半段ext4_extent未初始化状态,EXT4_EXT_MARK_UNINIT2是标记分割的后半段ext4_extent未初始化状态*/
  27.         if (uninitialized)
  28.             split_flag1 |= EXT4_EXT_MARK_UNINIT1 |
  29.                        EXT4_EXT_MARK_UNINIT2;
  30.        
  31.         if (split_flag & EXT4_EXT_DATA_VALID2)
  32.             split_flag1 |= EXT4_EXT_DATA_VALID1;
  33.         /*map->m_lblk + map->m_len这个逻辑块地址为分割点,把path[depth].p_ext指向的ext4_extent结构(ex)的逻辑块范围ee_block~(ee_block+ee_len)分割成ee_block~(map->m_lblk + map->m_len)(map->m_lblk + map->m_len)~(ee_block+ee_len),然后把后半段map->m_lblk + map->m_len)~(ee_block+ee_len)对应的ext4_extent结构添加到ext4 extent B+*/
  34.         err = ext4_split_extent_at(handle, inode, path,
  35.                 map->m_lblk + map->m_len, split_flag1, flags1);
  36.         if (err)
  37.             goto out;
  38.     } else {
  39.         /*到这里,说明map的结束逻辑块地址大于ex的结束逻辑块地址,则allocated=(ee_len+ee_block)-map->m_lblk,即本次映射map只能用到ex逻辑块范围里的allocated个逻辑块,下边if (map->m_lblk >= ee_block)肯定成立,则执行ext4_split_extent_at()ex的逻辑块范围分割成ee_block~map->m_lblk map->m_lblk~(ee_block + ee_len)map->m_lblk~(ee_block + ee_len)map本次映射的逻辑块,没有达到map->len*/
  40.         allocated = ee_len - (map->m_lblk - ee_block);
  41.     }
  42.     .................
  43.     /*上边可能把ex的逻辑块范围分割了,这里重新再ext4 extent B+树查找逻辑块地址范围接近map->m_lblk的索引节点和叶子结点*/
  44.     path = ext4_ext_find_extent(inode, map->m_lblk, path);
  45.     .................
  46.     depth = ext_depth(inode);
  47.     ex = path[depth].p_ext;
  48.     //ex是否是未初始化状态
  49.     uninitialized = ext4_ext_is_uninitialized(ex);
  50.     split_flag1 = 0;
  51.    
  52.     /*如果map的起始逻辑块地址大于等于ex的起始逻辑块地址,以map->m_lblk为分割点,再次分割新的ex逻辑块范围*/
  53.     if (map->m_lblk >= ee_block) {
  54.         split_flag1 = split_flag & EXT4_EXT_DATA_VALID2;
  55.         /*如果ex有未初始化标记,则split_flag1被加上EXT4_EXT_MARK_UNINIT1标记,EXT4_EXT_MARK_UNINIT1是标记分割的前半段ext4_extent未初始化状态*/
  56.         if (uninitialized) {
  57.             split_flag1 |= EXT4_EXT_MARK_UNINIT1;
  58.             split_flag1 |= split_flag & (EXT4_EXT_MAY_ZEROOUT |
  59.                              EXT4_EXT_MARK_UNINIT2);
  60.         }
  61.         /*map->m_lblk这个逻辑块地址为分割点,把path[depth].p_ext指向的ext4_extent结构(ex)的逻辑块范围ee_block~(ee_block+ee_len)分割成ee_block~map->m_lblkmap->m_lblk~(ee_block+ee_len),然后把后半段map->m_lblk~(ee_block+ee_len)对应的ext4_extent结构添加到ext4 extent B+树。*/
  62.         err = ext4_split_extent_at(handle, inode, path,
  63.                 map->m_lblk, split_flag1, flags);
  64.         if (err)
  65.             goto out;
  66.     }
  67.     .................
  68. out:
  69.     return err ? err : allocated;
  70. }

该函数主要分两种情况:

4.1 :map->m_lblk +map->m_len 小于ee_block + ee_len时的分割

如果 map->m_lblk +map->m_len 小于ee_block + ee_len,即map的结束逻辑块地址小于ex的结束逻辑块地址。则把ex的逻辑块范围分割成3段ee_block~map->m_lblk 和 map->m_lblk~(map->m_lblk +map->m_len) 和 (map->m_lblk +map->m_len)~(ee_block + ee_len)。这种情况,就能保证本次要求映射的map->m_len个逻辑块都能完成映射,即allocated =map->m_len。具体细节是:

1:if (map->m_lblk + map->m_len < ee_block + ee_len)成立,split_flag1 |= EXT4_EXT_MARK_UNINIT1|EXT4_EXT_MARK_UNINIT2,然后执行ext4_split_extent_at()以map->m_lblk + map->m_len这个逻辑块地址为分割点,把path[depth].p_ext指向的ext4_extent结构(即ex)的逻辑块范围ee_block~(ee_block+ee_len)分割成ee_block~(map->m_lblk + map->m_len)和(map->m_lblk + map->m_len)~(ee_block+ee_len)这两个ext4_extent。

2:前半段的ext4_extent还是ex,只是映射的逻辑块个数减少了(ee_block+ee_len)-(map->m_lblk + map->m_len)。后半段的是个新的ext4_extent。因为split_flag1 |= EXT4_EXT_MARK_UNINIT1|EXT4_EXT_MARK_UNINIT2,则还要标记这两个ext4_extent结构"都是未初始化状态"。然后把后半段 (map->m_lblk + map->m_len)~(ee_block+ee_len)对应的ext4_extent结构添加到ext4 extent B+树。回到ext4_split_extent()函数,ext4_ext_find_extent(inode, map->m_lblk, path)后path[depth].p_ext大概率还是老的ex。

3: if (map->m_lblk >= ee_block)肯定成立,里边的if (uninitialized)成立,if (uninitialized)里边的split_flag1 |= EXT4_EXT_MARK_UNINIT1,可能不会加上EXT4_EXT_MARK_UNINIT2标记。因为split_flag1 |= split_flag & (EXT4_EXT_MAY_ZEROOUT |EXT4_EXT_MARK_UNINIT2),接着再次执行ext4_split_extent_at(),以map->m_lblk这个逻辑块地址为分割点,把path[depth].p_ext指向的ext4_extent结构(即ex)的逻辑块范围ee_block~(ee_block+ee_len)分割成ee_block~map->m_lblk和map->m_lblk~(ee_block+ee_len)两个ext4_extent结构。

前半段的ext4_extent结构还是ex,但是逻辑块数减少了(ee_block+ee_len)-map->m_lblk个。因为此时split_flag1有EXT4_EXT_MARK_UNINIT1标记,可能没有EXT4_EXT_MARK_UNINIT2标记,则再对ex加上"未初始化状态",后半段的ext4_extent可能会被去掉"未初始化状态",因为split_flag1可能没有EXT4_EXT_MARK_UNINIT2标记。接着,把后半段的ext4_extent结构添加到ext4 extent B+树。这里有个特例,就是 if (map->m_lblk >= ee_block)里的map->m_lblk == ee_block,即map的要映射的起始逻辑块地址等于ex的起始逻辑块地址,则执行ext4_split_extent_at()函数时,不会再分割ex,里边if (split == ee_block)成立,会执行ext4_ext_mark_initialized(ex)标记ex是"初始化状态",ex终于转正了。

4.2: map->m_lblk +map->m_len 大于等于ee_block + ee_len时的分割

如果 map->m_lblk +map->m_len 大于等于ee_block + ee_len,即map的结束逻辑块地址大于ex的结束逻辑块地址。则把ex的逻辑块范围分割成2段ee_block~map->m_lblk 和 map->m_lblk~(ee_block + ee_len),这种情况,不能保证本次要求映射的map->m_len个逻辑块都完成映射。只能映射 (ee_block + ee_len) - map->m_lblk个逻辑块,即allocated =(ee_block + ee_len) - map->m_lblk。这个分割过程就是4.2节的第3步,看4.2节的第3步节就行。

ext4_split_extent()里重点执行的是ext4_split_extent_at()函数,它完成对ex逻辑块地址的分割,下文讲解。

5:ext4_split_extent_at()函数源码解析

ext4_split_extent_at()函数源码如下:

  1. static int ext4_split_extent_at(handle_t *handle,
  2.                  struct inode *inode,
  3.                  struct ext4_ext_path *path,
  4.                  ext4_lblk_t split,
  5.                  int split_flag,
  6.                  int flags)
  7. {
  8.     //ext4 extent B+树深度
  9.     depth = ext_depth(inode);
  10.     /*ext4 extent B+树叶子节点中起始逻辑块地址最接近map->m_lblk这个起始逻辑块地址的ext4_extent*/
  11.     ex = path[depth].p_ext;
  12.     //ex这个ext4_extent代表的起始逻辑块地址
  13.     ee_block = le32_to_cpu(ex->ee_block);
  14.     //ex这个ext4_extent代表的映射的物理块个数
  15.     ee_len = ext4_ext_get_actual_len(ex);
  16.     /*ee_blockex起始逻辑块地址,split是分割点的逻辑块地址,split大于ee_block,二者都在ex这个ext4_extent的逻辑块范围内。newblock是分割点的逻辑块地址对应的物理块地址*/
  17.     newblock = split - ee_block + ext4_ext_pblock(ex);
  18.     ...........
  19.     //分割点的逻辑块地址等于ex起始逻辑块地址,不用分割
  20.     if (split == ee_block) {
  21.         if (split_flag & EXT4_EXT_MARK_UNINIT2)
  22.             ext4_ext_mark_uninitialized(ex);//"UNINIT2"标记就要标记ex "uninitialized"
  23.         else
  24.             ext4_ext_mark_initialized(ex);//标记ex初始化
  25.         if (!(flags & EXT4_GET_BLOCKS_PRE_IO))
  26.             //尝试把ex前后的ext4_extent结构的逻辑块和物理块地址合并到ex
  27.             ext4_ext_try_to_merge(handle, inode, path, ex);
  28.         /*ext4_extent映射的逻辑块范围可能发生变化了,标记对应的物理块映射的bh或者文件inode*/
  29.         err = ext4_ext_dirty(handle, inode, path + path->p_depth);
  30.         goto out;
  31.     }
  32.     /*下边这是把ex的逻辑块分割成两部分(ee_block~split)(split~ee_block+ee_len)。分割后,ex新的逻辑块范围是(ee_block~split)ex2的逻辑块范围是(split~ee_block+ee_len)*/
  33.     //orig_ex先保存ex原有数据
  34.     memcpy(&orig_ex, ex, sizeof(orig_ex));
  35.     /*重点,标记ex->ee_len为映射的block数,这样ex就是被标记初始化状态了,因为ex->ee_len只要不是没被标记EXT_INIT_MAX_LEN,就是初始化状态,但是一旦下边执行ext4_ext_mark_uninitialized(ex)ex又成未初始化状态了*/
  36.     ex->ee_len = cpu_to_le16(split - ee_block);
  37.     if (split_flag & EXT4_EXT_MARK_UNINIT1)
  38.         ext4_ext_mark_uninitialized(ex);//EXT4_EXT_MARK_UNINIT1标记再把ex标记未初始化
  39.     .............
  40.     ex2 = &newex;//ex2就是ex分割后的后半段的逻辑块范围对应的ext4_extent结构
  41.     ex2->ee_block = cpu_to_le32(split);//ex2的逻辑块起始地址,分割点的逻辑块地址
  42.     ex2->ee_len   = cpu_to_le16(ee_len - (split - ee_block));//ex2逻辑块个数
  43.     ext4_ext_store_pblock(ex2, newblock);//ex2的起始物理块地址
  44.     if (split_flag & EXT4_EXT_MARK_UNINIT2)
  45.         ext4_ext_mark_uninitialized(ex2);//标记ex2未初始化状态
  46.     //ex分割的后半段ext4_extent结构即ex2添加到ext4 extent B+树,重点函数
  47.     err = ext4_ext_insert_extent(handle, inode, path, &newex, flags);
  48.     ..........
  49. }

ext4_split_extent_at()函数逻辑简单多了,主要作用是:以split这个逻辑块地址为分割点,把path[depth].p_ext指向的ext4_extent结构(即ex)的逻辑块范围ee_block~(ee_block+ee_len)分割成ee_block~split和split~(ee_block+ee_len),然后把后半段split~(ee_block+ee_len)对应的ext4_extent结构添加到ext4 extent B+树。

ext4_split_extent_at()里执行的ext4_ext_insert_extent()才是重点函数,它负责把一个ext4_extent插入ext4_extent b+树,流程是相当复杂,下文讲解:

6:ext4_ext_insert_extent()函数源码解析

ext4_ext_insert_extent()函数源码如下:

  1. int ext4_ext_insert_extent(handle_t *handle, struct inode *inode,
  2.                 struct ext4_ext_path *path,
  3.                 struct ext4_extent *newext, int flag)//newext正是要插入extent B+数的ext4_extent
  4. {
  5.     //ext4 extent B+树深度
  6.     depth = ext_depth(inode);
  7.     /*ext4 extent B+树叶子节点中起始逻辑块地址最接近map->m_lblk这个起始逻辑块地址的ext4_extent*/
  8.     ex = path[depth].p_ext;
  9.     eh = path[depth].p_hdr;
  10.    
  11.     /*下判断newexexex前边的ext4_extent结构、ex后边的ext4_extent结构逻辑块地址范围是否紧挨着,是的话才能将二者合并。但能合并还要符合一个苛刻条件:参与合并的两个ext4_extent必须是initialized状态,否则无法合并*/
  12.     if (ex && !(flag & EXT4_GET_BLOCKS_PRE_IO)) {
  13.         ................
  14.         if (ext4_can_extents_be_merged(inode, ex, newext)) {
  15.             //newext的逻辑块地址范围合并到ex
  16.             ex->ee_len = cpu_to_le16(ext4_ext_get_actual_len(ex)
  17.                     + ext4_ext_get_actual_len(newext));
  18.             if (uninitialized)
  19.                 ext4_ext_mark_uninitialized(ex);//标记ex未初始化
  20.             eh = path[depth].p_hdr;
  21.            
  22.             nearex = ex;//nearexex
  23.            
  24.             goto merge;//跳转到merge分支
  25.             ...............
  26.         }
  27. prepend:
  28.         if (ext4_can_extents_be_merged(inode, newext, ex)) {
  29.             ...........
  30.                         //ex没有初始化过则uninitialized = 1
  31.             if (ext4_ext_is_uninitialized(ex))
  32.                 uninitialized = 1;
  33.             //ex的逻辑块地址范围合并到newext,还是以ex为母体
  34.             ex->ee_block = newext->ee_block;
  35.             //更新ex映射的的起始物理块地址为newext的映射的起始物理块地址
  36.             ext4_ext_store_pblock(ex, ext4_ext_pblock(newext));
  37.             //ex->ee_len增加newext的逻辑块(物理块)个数
  38.             ex->ee_len = cpu_to_le16(ext4_ext_get_actual_len(ex)
  39.                     + ext4_ext_get_actual_len(newext));
  40.             if (uninitialized)
  41.                 ext4_ext_mark_uninitialized(ex);
  42.             eh = path[depth].p_hdr;
  43.            
  44.             nearex = ex;//nearexex
  45.            
  46.             goto merge;//跳转到merge分支
  47.             ...............
  48.         }
  49.     }
  50.     .............
  51.     depth = ext_depth(inode);
  52.     eh = path[depth].p_hdr;
  53.     /*eh->eh_maxext4_extent B+树叶子节点最大ext4_extent个数,这是测试path[depth].p_hdr所在叶子节点的ext4_extent结构是否爆满,没有爆满才会跳到has_space分支*/
  54.     if (le16_to_cpu(eh->eh_entries) < le16_to_cpu(eh->eh_max))
  55.         goto has_space;
  56.    
  57.     /*如果要插入的newext起始逻辑块地址大于ext4 extent B+树叶子节点最后一个ext4_extent结构的,说明当前的叶子节点逻辑块地址范围太小了*/
  58.     if (le32_to_cpu(newext->ee_block) > le32_to_cpu(fex->ee_block))
  59.         /*回到上层找到起始逻辑块地址更大的索引节点,这个索引节点还必须得有空闲的ext4_extent_idx*/
  60.         next = ext4_ext_next_leaf_block(path);
  61.     if (next != EXT_MAX_BLOCKS) {//成立说明找到了合适的ext4_extent_idx
  62.     {
  63.         /*nextext4 extent B+树新找到的索引节点ext4_extent_idx的起始逻辑块地址,这个逻辑块地址更大,本次要插入的newext的逻辑块地址在这个ext4_extent_idx的逻辑块地址范围内。下边是根据next这个逻辑地址,在ext4 extent B+树,从上层到下层,一层层找到起始逻辑块地址最接近next的索引节点ext4_extent_idx结构和叶子节点ext4_extent结构,保存到npath[]*/
  64.         npath = ext4_ext_find_extent(inode, next, NULL);
  65.         ............
  66.         //按照next这个逻辑块地址找到的新的叶子节点的ext4_extent_header头结构
  67.         eh = npath[depth].p_hdr;
  68.         /*叶子节点已使用的ext4_extent个数没有超过eh->eh_max成立,即叶子节点ext4_extent没有爆满*/
  69.         if (le16_to_cpu(eh->eh_entries) < le16_to_cpu(eh->eh_max)) {
  70.             //path指向按照next这个逻辑块地址找到的struct ext4_ext_path
  71.             path = npath;
  72.             //跳到has_space分支,把newext插入到按照next这个逻辑块地址找到的叶子节点
  73.             goto has_space;
  74.         }
  75.     }
  76.     /*到这里说明ext4_extent B+叶子节点空间不够了/
  77.    
  78.     //重点函数,创建新的叶子节点或者索引节点*/
  79.     err = ext4_ext_create_new_leaf(handle, inode, flags, path, newext);
  80.     depth = ext_depth(inode);
  81.     eh = path[depth].p_hdr;
  82.    
  83. /*到这里,最新的path[depth].p_ext所在叶子节点肯定有空闲的ext4_extent,即空闲位置可以存放newext这个ext4_extent结构,则直接把newext插入到叶子节点某个合适的ext4_extent位置处*/
  84. has_space:
  85.     /*nearex指向起始逻辑块地址最接近 newext->ee_block这个起始逻辑块地址的ext4_extentnewext是本次要ext4 extent b+树的ext4_extent*/
  86.     nearex = path[depth].p_ext;
  87.     if (!nearex) {//path[depth].p_ext所在叶子节点还没有使用过一个ext4_extent结构
  88.         //nearex指向叶子节点第一个ext4_extent结构,newext就插入到这里
  89.         nearex = EXT_FIRST_EXTENT(eh);
  90.     }
  91.     else {
  92.         //newext的起始逻辑块地址大于nearex的起始逻辑块地址
  93.         if (le32_to_cpu(newext->ee_block)
  94.                > le32_to_cpu(nearex->ee_block)) {
  95.             nearex++;//nearex++指向后边的一个ext4_extent结构
  96.         } else {
  97.             /* Insert before */
  98.         }
  99.         /*这是计算nearex这个ext4_extent结构到叶子节点最后一个ext4_extent结构(有效的)之间的ext4_extent结构个数。注意"有效的"3个字,比如叶子节点只使用了一个ext4_extent,则EXT_LAST_EXTENT(eh)是叶子节点第一个ext4_extent结构。*/
  100.         len = EXT_LAST_EXTENT(eh) - nearex + 1;
  101.         if (len > 0) {
  102.             /*这是把nearex这个ext4_extent结构 ~ 最后一个ext4_extent结构(有效的)之间的所有ext4_extent结构的数据整体向后移动一个ext4_extent结构大小,腾出原来nearex这个ext4_extent结构的空间,下边正是把newext插入到这里,这样终于把newex插入ext4_extent B+树了*/
  103.             memmove(nearex + 1, nearex,
  104.                 len * sizeof(struct ext4_extent));
  105.         }
  106.     }
  107.     /*下边是把newext的起始逻辑块地址、起始物理块起始地址、逻辑块地址映射的物理块个数等信息赋值给nearex,相当于把newext添加到叶子节点原来nearex的位置。然后叶子节点ext4_extent个数加1path[depth].p_ext指向newext*/
  108.    
  109.     //叶子节点ext4_extent个数加1
  110.     le16_add_cpu(&eh->eh_entries, 1);
  111.     //相当于path[depth].p_ext指向newext
  112.     path[depth].p_ext = nearex;
  113.     //nearex->ee_block赋值为newext起始逻辑块地址
  114.     nearex->ee_block = newext->ee_block;
  115.     //newext起始物理块地址赋值给nearex
  116.     ext4_ext_store_pblock(nearex, ext4_ext_pblock(newext));
  117.     //nearex->ee_len赋值为newext
  118.     nearex->ee_len = newext->ee_len;
  119.    
  120. merge:
  121.     if (!(flag & EXT4_GET_BLOCKS_PRE_IO))
  122.       /*尝试把ex后的ext4_extent结构的逻辑块和物理块地址合并到ex。并且,如果ext4_extent B+树深度是1,并且叶子结点有很少的ext4_extent结构,则尝试把叶子结点的ext4_extent结构移动到root节点,节省空间而已*/
  123.         ext4_ext_try_to_merge(handle, inode, path, nearex);
  124.     ............
  125.     return err;
  126. }

先对该函数功能做个整体总结:首先尝试把newext合并到ex(即path[depth].p_ext)、或者(ex+1)、或者(ex-1)指向的ext4_extent结构,合并条件很苛刻,合并成功则直接返回。接着看ext4 extent B+树中与newext->ee_block(这个要插入B+树的ext4_extent结构的起始逻辑块地址)有关的叶子节点是否ext4_extent结构爆满,即是否有空闲entry。没有空闲entry的话就执行ext4_ext_create_new_leaf()创建新的索引节点和叶子节点,这样就可以保证ext4_ext_create_new_leaf()->ext4_ext_find_extent()执行后path[depth].p_ext指向的ext4_extent结构所在的叶子节点有空闲entry,可以存放newext。接着是该函数has_space分支,只是简单的把newex插入path[depth].p_ext前后的ext4_extent位置处。最后还会执行ext4_ext_try_to_merge()尝试把ex后的ext4_extent结构的逻辑块和物理块地址合并到ex,还会尝试把叶子结点的ext4_extent结构移动到root节点,节省空间。

什么时机会执行ext4_ext_insert_extent()函数?两种情况:

1:ext4_ext_map_blocks()为map在ext4 extent B+树找不到逻辑块地址接近的ext4_extent结构,则为map分配一个新的ext4_extent结构,然后执行ext4_ext_insert_extent()把这个新的ext4_extent结构插入ext4 extent B+树。

2:在ext4_split_extent_at()中,把path[depth].p_ext指向的ext4_extent结构(即ex)的逻辑块范围分割成两段,把后半段逻辑块范围对应的ext4_extent结构执行ext4_ext_insert_extent()插入ext4 extent B+树。

它里边执行的ext4_ext_create_new_leaf()函数及后续执行的ext4_ext_split()和ext4_ext_grow_indepth()函数才是隐藏boss,把这几个函数看懂,才算理解了ext4 extent b+树是怎么形成。下文依次讲解这些函数

7:ext4_ext_create_new_leaf()函数源码解析

首先是ext4_ext_create_new_leaf()函数,源码如下:

  1. static int ext4_ext_create_new_leaf(handle_t *handle, struct inode *inode,
  2.                     unsigned int flags,
  3.                     struct ext4_ext_path *path,
  4.                     struct ext4_extent *newext)
  5. {
  6.     struct ext4_ext_path *curp;
  7.     int depth, i, err = 0;
  8. repeat:
  9.     //ext4 extent B+树深度
  10.     i = depth = ext_depth(inode);
  11.     //curp首先指向ext4 extent B+树叶子节点
  12.     curp = path + depth;
  13.    
  14.     /*while是从ext4 extent B+树叶子节点开始,向上一直到索引节点,看索引节点或者叶子节点的ext4_extent_idxext4_extent个数是否大于最大限制eh_max,超出限制EXT_HAS_FREE_INDEX(curp)返回0,否则返回1.从该while循环退出时,有两种可能,1:curpNULLcurp指向的索引节点或叶子节点有空闲ext4_extent_idxext4_extent可使用,2:i0ext4 extent B+树索引节点或叶子节点ext4_extent_idxext4_extent个数爆满,没有空闲ext4_extent_idxext4_extent可使用*/
  15.     while (i > 0 && !EXT_HAS_FREE_INDEX(curp)) {
  16.         i--;
  17.         curp--;
  18.     }
  19.     /*ext4 extent B+树索引节点或者叶子节点有空闲ext4_extent_idxext4_extent可使用。此时的i表示ext4 extent B+树哪一层有空闲ext4_extent_idxext4_extent可使用。newext是要插入ext4_extent B+树的ext4_extent,插入ext4_extent B+树的第i层的叶子节点或者第i层索引节点下边的叶子节点*/
  20.     if (EXT_HAS_FREE_INDEX(curp)) {
  21.           /*凡是执行到ext4_ext_split()函数,说明ext4 extent B+树中与newext->ee_block有关的叶子节点ext4_extent结构爆满了。于是从ext4 extent B+at那一层索引节点到叶子节点,针对每一层都创建新的索引节点,也创建叶子节点。还会尝试把索引节点path[at~depth].p_hdr指向的ext4_extent_idx结构的后边的ext4_extent_idx结构和path[depth].p_ext指向的ext4_extent结构后边的ext4_extent结构,移动到新创建的叶子节点和索引节点。这样就能保证ext4 extent B+树中,与newext->ee_block有关的叶子节点有空闲entry,就能存放newext这个ext4_extent结构了。*/
  22.         err = ext4_ext_split(handle, inode, flags, path, newext, i);
  23.         .................
  24.         /*ext4_ext_split()ext4_extent B+树做了重建和分割,这里再次在ext4_extent B+树查找起始逻辑块地址接近newext->ee_block的索引节点和叶子结点*/
  25.         path = ext4_ext_find_extent(inode,
  26.                     (ext4_lblk_t)le32_to_cpu(newext->ee_block),
  27.                     path);
  28.     } else {
  29.     /*到这个分支,ext4 extent B+树索引节点的ext4_extent_idx和叶子节点的ext4_extent个数全爆满,没有空闲ext4_extent_idxext4_extent可使用,就是说ext4 extent B+树全爆满了,只能增加执行ext4_ext_grow_indepth()增加ext4 extent B+树叶子节点或者索引节点了*/
  30.         /*针对newext->ee_block分配一个新的物理块,作为新的索引节点或者叶子节点添加到ext4 extent B+树根节点下方,这样相当于跟ext4 extent B+树增加了一层新的节点*/
  31.         err = ext4_ext_grow_indepth(handle, inode, flags, newext);
  32.         .......................
  33.         /*到这里,ext4 extent B+树根节点下方增加了一层新的索引或者叶子节点,再重新在ext4 extent B+find_extent*/
  34.         path = ext4_ext_find_extent(inode,
  35.                    (ext4_lblk_t)le32_to_cpu(newext->ee_block),
  36.                     path);
  37.         depth = ext_depth(inode);
  38.         /*如果path[depth].p_hdr指向的叶子结点保存ext4_extent结构达到eh_max,即叶子节点ext4_extent还是爆满,则goto repeat寻找有空闲ext4_extent_idr的索引节点,然后分割ext4 extent B+*/
  39.         if (path[depth].p_hdr->eh_entries == path[depth].p_hdr->eh_max) {
  40.             /* now we need to split */
  41.             goto repeat;
  42.         }
  43.     }
  44. out:
  45.     return err;
  46. }
  47. }

先做个整体总结:执行到该函数,说明ext4 extent B+树中与newext->ee_block有关的叶子节点ext4_extent结构爆满了,需要扩容。首先尝试搜索叶子节点上的每一层索引节点有没有空闲entry的,有的话记录这一层索引节点的深度是at。接着执行ext4_ext_split():从ext4 extent B+树at那一层索引节点到叶子节点,针对每一层都创建新的索引节点,也创建叶子节点。还会尝试把索引节点path[at~depth-1].p_hdr指向的ext4_extent_idx结构的后边的ext4_extent_idx结构和path[depth].p_ext指向的ext4_extent结构后边的ext4_extent结构,移动到新创建的叶子节点和索引节点。这样就可能保证ext4 extent B+树中,与newext->ee_block有关的叶子节点有空闲entry,能存放newext。

如果ext4 extent B+树索引节点的ext4_extent_idx结构也爆满了,则执行ext4_ext_grow_indepth()在ext4 extent B+树root节点下的创建一层新的索引节点(或者叶子节点)。此时ext4 extent B+树第2层的索引节点(或者叶子节点)是空的,可以存放多个ext4_extent_idx结构,即有空闲entry了。然后大概率goto repeat处,执行ext4_ext_split()分割创建索引节点和叶子节点。总之,从ext4_ext_create_new_leaf()函数返回前,里边执行的ext4_ext_find_extent()找打的path[depth].p_ext指向的叶子节点有空闲entry,可以存放newext。

里边重点执行了ext4_ext_split()和ext4_ext_grow_indepth()函数,下文讲解:

8:ext4_ext_split()函数源码解析

ext4_ext_split()感觉是最重要最负责最难以理解的一个函数,源码如下:

  1. static int ext4_ext_split(handle_t *handle, struct inode *inode,
  2.               unsigned int flags,
  3.               struct ext4_ext_path *path,
  4.               /*newext是要插入ext4_extent B+树的ext4_extent,在ext4_extent B+树的第at层插入newext,第at层的索引节点有空闲entry*/
  5.               struct ext4_extent *newext, int at)
  6. {
  7.     /*path[depth].p_extext4 extent B+树叶子节点中,逻辑块地址最接近map->m_lblk这个起始逻辑块地址的ext4_extent*/
  8.     if (path[depth].p_ext != EXT_MAX_EXTENT(path[depth].p_hdr)) {
  9.         /*path[depth].p_ext不是叶子节点最后一个ext4_extent结构,那以它后边的ext4_extent结构path[depth].p_ext[1]的起始逻辑块地址作为分割点border*/
  10.         border = path[depth].p_ext[1].ee_block;
  11.     } else {
  12.         /*这里说明path[depth].p_ext指向的是叶子节点最后一个ext4_extent结构*/
  13.         border = newext->ee_block;
  14.     }
  15.    
  16.     /*依照ext4_extent B+树层数分配depthext4_fsblk_t的数组,下边保存分配的物理块号*/
  17.     ablocks = kzalloc(sizeof(ext4_fsblk_t) * depth, GFP_NOFS);
  18.     /*分配(depth - at)个物理块,newext是在ext4 extent B+的第at层插入,从at层到depth层,每层分配一个物理块*/
  19.     for (a = 0; a < depth - at; a++) {
  20.         /*每次从ext4文件系统元数据区分配一个物理块,返回它的物理块号,4K大小,保存ext4 extent B+树索引节点的头结构ext4_extent_header+Next4_extent_idx或者或者叶子结点ext4_extent_header+Next4_extent结构*/
  21.         newblock = ext4_ext_new_meta_block(handle, inode, path,
  22.                            newext, &err, flags);
  23.         ..............
  24.         //分配的物理块的块号保存到ablocks数组
  25.         ablocks[a] = newblock;
  26.     }
  27.     .............
  28.     //bh映射newblock物理块号,这是叶子节点的物理块
  29.     bh = sb_getblk(inode->i_sb, newblock);
  30.     if (unlikely(!bh)) {
  31.         err = -ENOMEM;
  32.         goto cleanup;
  33.     }
  34.     ...........
  35.     /*neh指向新分配的叶子节点首内存的头结构ext4_extent_header,下边对新分配的叶子节点头结构ext4_extent_header进行初始化*/
  36.     neh = ext_block_hdr(bh);
  37.     neh->eh_entries = 0;
  38.     neh->eh_max = cpu_to_le16(ext4_ext_space_block(inode, 0));
  39.     neh->eh_magic = EXT4_EXT_MAGIC;
  40.     neh->eh_depth = 0;
  41.     ...........
  42.     /*path[depth].p_ext后边的ext4_extent结构到叶子节点最后一个ext4_extent结构之间,一共有mext4_extent结构*/
  43.     m = EXT_MAX_EXTENT(path[depth].p_hdr) - path[depth].p_ext++;
  44.     ext4_ext_show_move(inode, path, newblock, depth);
  45.     if (m) {
  46.         struct ext4_extent *ex;
  47.         //ex指向上边新分配的叶子节点的第一个ext4_extent结构
  48.         ex = EXT_FIRST_EXTENT(neh);
  49.         //老的叶子节点path[depth].p_ext后的mext4_extent结构移动到上边新分配的叶子节点
  50.         memmove(ex, path[depth].p_ext, sizeof(struct ext4_extent) * m);
  51.         //新分配的叶子节点增加了mext4_extent结构
  52.         le16_add_cpu(&neh->eh_entries, m);
  53.     }
  54.     ............
  55.     /*ext4_extent B+at那一层的索引节点到最后一层索引节点之间的层数,就是从at开始有多少层索引节点*/
  56.     k = depth - at - 1;
  57.    
  58.     /*i初值是ext4_extent B+树最后一层索引节点的层数,就是叶子节点上边的那层索引节点*/
  59.     i = depth - 1;
  60.     /*循环k次保证把at那一层的ext4_extent B+树索引节点到最后一层索引节点中,每一层索引节点path[i].p_idx指向的ext4_extent_idx结构到最后一个ext4_extent_idx结构之间的ext4_extent_idx结构,都复制到上边新创建的索引节点的物理块中,物理块号是ablocks[--a]newblockbh映射这个物理块。neh指向这个索引节点头ext4_extent_header结构,fidx是这个索引节点第一个ext4_extent_idx结构。注意,是从ext4_extent B+树最下层的索引节点向上开始复制,因为i的初值是depth - 1,这是ext4_extent B+树最下边一层索引节点的层数*/
  61.     while (k--) {
  62.         oldblock = newblock;
  63.         /*新取出一个物理块对应的块号newblock,这个物理块将来保存新创建的索引节点ext4_extent_header头结构+Next4_extent_idx结构的4K数据*/
  64.         newblock = ablocks[--a];
  65.         //newblock物理块映射的bh
  66.         bh = sb_getblk(inode->i_sb, newblock);
  67.         ................
  68.         //neh指向newblock这个物理块映射的bh的内存首地址,这是索引节点的头ext4_extent_header结构
  69.         neh = ext_block_hdr(bh);
  70.         //索引节点有效的ext4_extent_idx个数初值是1
  71.         neh->eh_entries = cpu_to_le16(1);
  72.         neh->eh_magic = EXT4_EXT_MAGIC;
  73.         //计算索引结点能容纳的ext4_extent_idx结构个数
  74.         neh->eh_max = cpu_to_le16(ext4_ext_space_block_idx(inode, 0));
  75.         //索引节点所处ext4 extent B+树的层数
  76.         neh->eh_depth = cpu_to_le16(depth - i);
  77.         //fidx指向索引节点的第一个ext4_extent_idx结构
  78.         fidx = EXT_FIRST_INDEX(neh);
  79.         //fidx的起始逻辑块地址是上边的分割点逻辑块地址border
  80.         fidx->ei_block = border;
  81.         /*把下一层叶子节点或者下一层索引节点的物理块号保存到当前索引节点第一个ext4_extent_idx结构的ei_leaf_loei_leaf_hi成员中。后续可以通过这个ext4_extent_idx结构的ei_leaf_loei_leaf_hi成员找到它指向的下一层的索引节点或者叶子节点*/
  82.         ext4_idx_store_pblock(fidx, oldblock);
  83.         ................
  84.         /*计算 path[i].p_hdr这一层索引节点中,从path[i].p_idx指向的ext4_extent_idx结构到最后一个ext4_extent_idx结构之间ext4_extent_idx个数*/
  85.         m = EXT_MAX_INDEX(path[i].p_hdr) - path[i].p_idx++;
  86.         ................
  87.         if (m) {
  88.             /*path[i].p_idx后边的mext4_extent_idx结构赋值到newblock这个物理块对应的索引节点开头的第1ext4_extent_idx的后边,即fidx指向的ext4_extent_idx后边。这里是++fid,即fidx指向的索引节点的第2ext4_extent_idx位置处,这是向索引节点第2ext4_extent_idx处及后边复制mext4_extent_idx结构*/
  89.             memmove(++fidx, path[i].p_idx,
  90.                 sizeof(struct ext4_extent_idx) * m);
  91.             //newblock这个物理块对应的新的索引节点增加了mext4_extent_idx结构
  92.             le16_add_cpu(&neh->eh_entries, m);
  93.         }
  94.         .............
  95.         if (m) {
  96.             /*path[i].p_hdr指向的老的ext4 extent B+树那一层索引节点减少了mext4_extent_idx结构*/
  97.             le16_add_cpu(&path[i].p_hdr->eh_entries, -m);
  98.         }
  99.         /*i--进入下次循环,就会把上一层ext4_extent B+树索引节点的path[i].p_idx指向的ext4_extent_idx结构到最后一个ext4_extent_idx结构之间所有的ext4_extent_idx结构,复制到ablocks[--a]newblock这个物理块映射bh*/
  100.         i--;
  101.     }
  102.     ................
  103.     /*把新的索引节点的ext4_extent_idx结构(起始逻辑块地址border,物理块号newblock)插入到ext4 extent B+at那一层索引节点(path + at)->p_idx指向的ext4_extent_idx结构前后。*/
  104.     err = ext4_ext_insert_index(handle, inode, path + at,
  105.                     le32_to_cpu(border), newblock);
  106.     ................               
  107.     return err;
  108. }

1:首先确定ext4 extent B+树的分割点逻辑地址border。如果path[depth].p_ext不是ext4_extent B+树叶子节点节点最后一个ext4 extent结构,则分割点逻辑地址border是path[depth].p_ext后边的ext4_extent起始逻辑块地址,即border=path[depth].p_ext[1].ee_block。否则border是新插入ext4 extent B+树的ext4_extent的起始逻辑块地址,即newext->ee_block。

2:因为ext4_extent B+树at那一层索引节点有空闲entry,则针对at~depth(B+树深度)之间的的每一层索引节点和叶子节点都分配新的索引节点和叶子结点,每个索引节点和叶子结点都占一个block大小(4K),分别保存N个ext4_extent_idx结构和N个ext4_extent结构,还有ext4_extent_header。在while (k--)那个循环,这些新分配的索引节点和叶子节点中,B+树倒数第2层的那个索引节点的第一个ext4_extent_idx的物理块号成员(ei_leaf_lo和ei_leaf_hi)记录的新分配的保存叶子结点4K数据的物理块号(代码是ext4_idx_store_pblock(fidx, oldblock)),第一个ext4_extent_idx的起始逻辑块地址是border(代码是fidx->ei_block = border)。B+树倒数第3层的那个索引节点的第一个ext4_extent_idx的物理块号成员记录的是保存倒数第2层的索引节点4K数据的物理块号,这层索引节点第一个ext4_extent_idx的起始逻辑块地址是border(代码是fidx->ei_block = border)........其他类推。at那一层新分配的索引节点(物理块号是newblock,起始逻辑块地址border),执行ext4_ext_insert_index()插入到ext4_extent B+树at层原有的索引节点(path + at)->p_idx指向的ext4_extent_idx结构前后的ext4_extent_idx结构位置处。插入过程是:把(path + at)->p_idx指向的索引节点的ext4_extent_idx结构后的所有ext4_extent_idx结构向后移动一个ext4_extent_idx结构大小,这就在(path + at)->p_idx指向的索引节点的ext4_extent_idx处腾出了一个空闲的ext4_extent_idx结构大小空间,新分配的索引节点就是插入到这里。

3:要把ext4_extent B+树原来的at~depth层的 path[i].p_idx~path[depth-1].p_idx指向的ext4_extent_idx结构后边的所有ext4_extent_idx结构 和 path[depth].p_ext指向的ext4_extent后的所有ext4_extent结构都对接移动到上边针对ext4_extent B+树at~denth新分配索引节点和叶子节点物理块号映射bh内存。这是对原有的ext4 extent B+树进行扩容的重点。

上边的解释没有诠释到本质。直击灵魂,为什么会执行到ext4_ext_insert_extent()->ext4_ext_create_new_leaf()->ext4_ext_split()?有什么意义?首先,ext4_split_extent_at()函数中,把path[depth].p_ext指向的ext4_extent结构(即ex)的逻辑块范围分割成两段,两个ext4_extent结构。前边的ext4_extent结构还是ex,只是逻辑块范围减少了。而后半段ext4_extent结构即newext就要插入插入到到ext4 extent B+树。到ext4_ext_insert_extent()函数,如果此时ex所在叶子节点的ext4_extent结构爆满了,即if (le16_to_cpu(eh->eh_entries) < le16_to_cpu(eh->eh_max))不成立,但是if (le32_to_cpu(newext->ee_block) > le32_to_cpu(fex->ee_block))成立,即newext的起始逻辑块地址小于ex所在叶子节点的最后一个ext4_extent结构的起始逻辑块地址,则执行next = ext4_ext_next_leaf_block(path)等代码,回到上层索引节点,找到起始逻辑块地址更大的索引节点和叶子节点,如果新的叶子节点的ext4_extent结构还是爆满,那就要执行ext4_ext_create_new_leaf()增大ext4_extent B+树层数了。

来到ext4_ext_create_new_leaf()函数,从最底层的索引节点开始向上搜索,找到有空闲entry的索引节点。如果找到则执行ext4_ext_split()。如果找不到则执行ext4_ext_grow_indepth()在ext4_extent B+树root节点增加一层索引节点(或叶子节点),然后也执行ext4_ext_split()。

当执行到ext4_ext_split(),at一层的ext4_extent B+树有空闲entry,则以从at层到叶子节点那一层,创建新的索引节点和叶子节点,建立这些新的索引节点和叶子节点彼此的物理块号的联系。我们假设ext4_ext_split()的if (path[depth].p_ext != EXT_MAX_EXTENT(path[depth].p_hdr))成立,则这样执行:向新分配的叶子节点复制m个ext4_extent结构时,复制的第一个ext4_extent结构不是path[depth].p_ext,而是它后边的 path[depth].p_ext[1]这个ext4_extent结构。并且,下边新创建的索引节点的第一个ext4_extent_idx结构的起始逻辑器块地址都是border,即path[depth].p_ext[1]的逻辑块地址,也是path[depth].p_ext[1].ee_block。然后向新传创建的索引节点的第2个ext4_extent_idx结构处及之后复制m个ext4_extent_idx结构。新传创建的索引节点的第一个ext4_extent_idx的起始逻辑块地址是border,单独使用,作为分割点的ext4_extent_idx结构。如此,后续执行ext4_ext_find_extent(newext->ee_block)在老的ext4_extent B+树找到的path[depth].p_ext指向的ext4_extent还是老的,但是path[depth].p_ext后边的m个ext4_extent结构移动到了新分配的叶子节点,path[depth].p_ext所在叶子节点就有空间了,newext就插入到path[depth].p_ext指向的ext4_extent叶子节点后边。这段代码在ext4_ext_insert_extent()的has_space 的if (!nearex)........} else{......}的else分支。

如果ext4_ext_split()的if (path[depth].p_ext != EXT_MAX_EXTENT(path[depth].p_hdr))不成立,则这样执行:不会向新分配的叶子节点复制ext4_extent结构,m是0,因为path[depth].p_ext就是叶子节点最后一个ext4_extent结构,下边的m = EXT_MAX_EXTENT(path[depth].p_hdr) - path[depth].p_ext++=0。并且,下边新创建的索引节点的第一个ext4_extent_idx结构

的起始逻辑器块地址都是newext->ee_block。这样后续执行ext4_ext_find_extent()在ext4_extent B+树就能找到起始逻辑块地址是newext->ee_block的层层索引节点了,完美匹配。那叶子节点呢?这个分支没有向新的叶子节点复制ext4_extent结构,空的,ext4_ext_find_extent()执行后,path[ppos].depth指向新的叶子节点的头结点,此时直接令该叶子节点的第一个ext4_extent结构的逻辑块地址是newext->ee_block,完美!这段代码在ext4_ext_insert_extent()的has_space 的if (!nearex)分支。

注意,这是ext4_extent B+树叶子节点增加增加的第一个ext4_extent结构,并且第一个ext4_extent结构的起始逻辑块地址与它上边的索引节点的ext4_extent_idx的起始逻辑块地址都是newext->ee_block,再上层的索引节点的ext4_extent_idx的起始逻辑块地址也是newext->ee_block,直到第at层。

因此,我们看到ext4_ext_split()最核心的作用是:at一层的ext4_extent B+树有空闲entry,则从at层开始创建新的索引节点和叶子节点,建立这些新的索引节点和叶子节点彼此的物理块号联系。然后把path[depth].p_ext后边的ext4_extent结构移动到新的叶子节点,把path[at~depth-1].p_idx这些索引节点后边的ext4_extent_idx结构依次移动到新创建的索引节点。这样要么老的path[depth].p_ext所在叶子节点有了空闲的ext4_extent entry,把newex插入到老的path[depth].p_ext所在叶子节点后边即可。或者新创建的at~denth的索引节点

和叶子节点,有大量空闲的entry,这些索引节点的起始逻辑块地址还是newext->ee_block,则直接把newext插入到新创建的叶子节点第一个ext4_extent结构即可。

最后,对ext4_ext_split简单总结: 凡是执行到ext4_ext_split()函数,说明ext4 extent B+树中与newext->ee_block有关的叶子节点ext4_extent结构爆满了。于是从ext4 extent B+at那一层索引节点到叶子节点,针对每一层都创建新的索引节点,也创建叶子节点。还会尝试把索引节点path[at~depth].p_hdr指向的ext4_extent_idx结构的后边的ext4_extent_idx结构和path[depth].p_ext指向的ext4_extent结构后边的ext4_extent结构,移动到新创建的叶子节点和索引节点。这样可能保证ext4 extent B+树中,与newext->ee_block有关的叶子节点有空闲entry,能存放newext

下边介绍里边最后执行的ext4_ext_insert_index()函数。

9:ext4_ext_insert_index ()函数源码解析

ext4_ext_insert_index()函数源码如下:

  1. static int ext4_ext_insert_index(handle_t *handle, struct inode *inode,
  2.                  struct ext4_ext_path *curp,
  3.                  int logical, ext4_fsblk_t ptr)
  4. {
  5.     struct ext4_extent_idx *ix;
  6.     int len, err;
  7.     ................
  8.     /*curp->p_idxext4 extent B+树起始逻辑块地址最接近传入的起始逻辑块地址map->m_lblkext4_extent_idx结构,现在是把新的ext4_extent_idx(起始逻辑块地址是logical,起始物理块号ptr)插入到curp->p_idx指向的ext4_extent_idx结构前后*/
  9.     if (logical > le32_to_cpu(curp->p_idx->ei_block)) {
  10.         /*待插入的ext4_extent_idx结构起始逻辑块地址logical大于curp->p_idx的起始逻辑块地址, 就要插入curp->p_idx这个ext4_extent_idx后边,(curp->p_idx + 1)这个ext4_extent_idx后边。插入前,下边memmove先把(curp->p_idx+1)后边的所有ext4_extent_idx结构全向后移动一个ext4_extent_idx结构大小,然后把新的ext4_extent_idx插入到curp->p_idx + 1位置处*/
  11.         ix = curp->p_idx + 1;
  12.     } else {
  13.         /*待插入的ext4_extent_idx结构起始逻辑块地址logical更小,就插入到curp->p_idx这个ext4_extent_idx前边。插入前,下边memmove先把curp->p_idx后边的所有ext4_extent_idx结构全向后移动一个ext4_extent_idx结构大小,然后把新的ext4_extent_idx插入到curp->p_idx位置处*/
  14.         ix = curp->p_idx;
  15.     }
  16.     /*ixcurp->p_idx或者(curp->p_idx+1)lenix这个索引节点的ext4_extent_idx结构到索引节点最后一个ext4_extent_idx结构(有效的)之间所有的ext4_extent_idx结构个数。注意,EXT_LAST_INDEX(curp->p_hdr)是索引节点最后一个有效的ext4_extent_idx结构,如果索引节点只有一个ext4_extent_idx结构,那EXT_LAST_INDEX(curp->p_hdr)就指向这第一个ext4_extent_idx结构*/
  17.     len = EXT_LAST_INDEX(curp->p_hdr) - ix + 1;
  18.     if (len > 0) {
  19.         //ix后边的lenext4_extent_idx结构向后移动一次ext4_extent_idx结构大小
  20.         memmove(ix + 1, ix, len * sizeof(struct ext4_extent_idx));
  21.     }
  22.     //现在ix指向ext4_extent_idx结构是空闲的,用它保存要插入的逻辑块地址logial和对应的物理块号。相当于把本次要插入ext4 extent b+树的ext4_extent_idx插入到ix指向的ext4_extent_idx位置处
  23.     ix->ei_block = cpu_to_le32(logical);
  24.     ext4_idx_store_pblock(ix, ptr);
  25.     //索引节点有效的ext4_extent_idx增加一个,因为刚才新插入了一个ext4_extent_idx
  26.     le16_add_cpu(&curp->p_hdr->eh_entries, 1);
  27.     return err;
  28. }

这个函数简单多了:把新的索引节点ext4_extent_idx结构(起始逻辑块地址logical,物理块号ptr)插入到ext4 extent B+树curp->p_idx指向的ext4_extent_idx结构前后。插入的本质很简单,把curp->p_idx或者(curp->p_idx+1)后边的所有ext4_extent_idx结构全向后移动一个ext4_extent_idx结构大小,把新的ext4_extent_idx插入curp->p_idx或者(curp->p_idx+1)原来的位置。

最后讲解ext4_ext_grow_indepth()函数源码

10:ext4_ext_grow_indepth()函数源码解析

ext4_ext_grow_indepth()函数源码如下:

  1. static int ext4_ext_grow_indepth(handle_t *handle, struct inode *inode,
  2.                  unsigned int flags,
  3.                  struct ext4_extent *newext)
  4. {
  5.     struct ext4_extent_header *neh;
  6.     struct buffer_head *bh;
  7.     ext4_fsblk_t newblock;
  8.     int err = 0;
  9.     /*分配一个新的物理块,返回物理块号newblock。这个物理块4K大小,是本次新创建的索引节点或者叶子节点,将来会保存索引节点的头结构ext4_extent_header+Next4_extent_idx或者或者叶子结点ext4_extent_header+Next4_extent结构*/
  10.     newblock = ext4_ext_new_meta_block(handle, inode, NULL,
  11.         newext, &err, flags);
  12.     if (newblock == 0)
  13.         return err;
  14.     //bh映射到newblock这个物理块
  15.     bh = sb_getblk(inode->i_sb, newblock);
  16.     ..................
  17.     /*ext4 extent B+树的根节点的数据(头结构ext4_extent_header+4ext4_extent_idx或者叶子结点ext4_extent_header+4ext4_extent结构)复制到bh->b_data。相当于把根节点的数据复制到上边新创建的物理块,腾空根节点*/
  18.     memmove(bh->b_data, EXT4_I(inode)->i_data,
  19.         sizeof(EXT4_I(inode)->i_data));
  20.     /*neh指向bh首地址,这些内存的数据是前边向bh->b_data复制的根节点的头结构ext4_extent_header*/
  21.     neh = ext_block_hdr(bh);
  22.    
  23.     //如果ext4 extent B+树有索引节点,neh指向的内存作为索引节点
  24.     if (ext_depth(inode))
  25.         neh->eh_max = cpu_to_le16(ext4_ext_space_block_idx(inode, 0));
  26.     else//如果ext4 extent B+树没有索引节点,只有根节点,neh指向的内存作为叶子结点
  27.         neh->eh_max = cpu_to_le16(ext4_ext_space_block(inode, 0));
  28.     neh->eh_magic = EXT4_EXT_MAGIC;
  29.     ...................
  30.     //现在neh又指向ext4 extent B+根节点
  31.     neh = ext_inode_hdr(inode);
  32.     //根节点现在只有一个叶子节点的ext4_extent结构在使用或者只有一个索引节点的ext4_extent_idx结构在使用
  33.     neh->eh_entries = cpu_to_le16(1);
  34.     /*这是把前边新创建的索引节点或者叶子节点的物理块号newblock记录到根节点第一个ext4_extent_idx结构的ei_leaf_loei_leaf_hi成员。这样就建立了根节点与新创建的物理块号是newblock的叶子结点或索引节点的联系。因为通过根节点第一个ext4_extent_idx结构的ei_leaf_loei_leaf_hi成员,就可以找到这个新创建的叶子节点或者索引节点的物理块号newblock*/
  35.     ext4_idx_store_pblock(EXT_FIRST_INDEX(neh), newblock);
  36.     //如果neh->eh_depth0,说明之前ext4 extent B+树深度是0,即只有根节点
  37.     if (neh->eh_depth == 0) {
  38.         /*以前B+树只有根节点,没有索引节点。现在根节点作为索引节点,这是计算根节点最多可容纳的ext4_extent_idx结构个数,4*/
  39.         neh->eh_max = cpu_to_le16(ext4_ext_space_root_idx(inode, 0));
  40.         /*以前B+树只有根节点,没有索引节点,根节点都是ext4_extent结构,现在B+树根节点下添加了newblock这个叶子节点。根节点成了根索引节点,因此原来第一个ext4_extent结构要换成ext4_extent_idx结构,下边赋值就是把原来的根节点第一个ext4_extent的起始逻辑块地址赋值给现在根节点的第一个ext4_extent_idx的起始逻辑块地址*/
  41.         EXT_FIRST_INDEX(neh)->ei_block =
  42.             EXT_FIRST_EXTENT(neh)->ee_block;
  43.     }
  44.     .............
  45.     //ext4 extent B+树增加了一层索引节点或叶子结点,即物理块号是newblock的那个,树深度加1
  46.     le16_add_cpu(&neh->eh_depth, 1);
  47.     .............
  48.     return err;
  49. }

这个函数只是针对ex->ee_block分配一个新的物理块,作为新的索引节点或者叶子节点添加到ext4 extent B+树根节点下方,这样相当于跟ext4 extent B+树增加了一层新的节点。然后建立这个新分配的节点与根节点的联系即可,相对简单多了。

最后做个总结,ext4_extent 内核源码似乎并不多,但是它的逻辑相当复杂,很绕,尤其是ext4_extent b+树的增长、分割、创建的叶子节点和索引节点。看到最后,觉得这部分代码算法设计的很巧妙,并不是太多的代码实现了如此复杂的功能,牛!


http://www.ppmy.cn/news/8380.html

相关文章

(函数介绍)puts()函数

功能介绍 1. puts()函数用来向标准输出设备屏幕输出字符串并换行。 2. 函数的参数就是一个起始的地址&#xff0c;然后就从这个地址开始一直输出字符串&#xff0c;直到碰到\0就停止&#xff0c;然后这个\0是不进行输出的&#xff0c;是不能够算在里面的。与此同时&#xff…

Java算法_LeetCode26:删除排序数组中的重复项

LeetCode26&#xff1a;删除排序数组中的重复项 给你一个 升序排列 的数组 nums &#xff0c;请你 原地 删除重复出现的元素&#xff0c;使每个元素 只出现一次 &#xff0c;返回删除后数组的新长度。元素的 相对顺序 应该保持 一致 。 由于在某些语言中不能改变数组的长度&a…

异步任务执行:workqueue与taskwork

workqueue workqueue就是用来异步执行逻辑的内核组件。异步执行是很常见的需求,workqueue组件为异步执行抽象出三个概念: work:指需要异步执行的任务 woker:处理work的异步执行上下文,就是一个内核线程 workqueue:work的链表,异步执行需求者产生work,将work加入到w…

周赛326总结

周赛326总结 感谢力扣&#xff0c;第一次全部做完&#xff0c;耗时43分37秒&#xff0c;很满足了&#xff0c;虽然是题比较简单&#xff0c;新的一年继续努力呀&#xff01;&#xff01; 计能整除数字的位数 Given an integer num, return the number of digits in num that …

Leetcode 36. 有效的数独

请你判断一个 9 x 9 的数独是否有效。只需要 根据以下规则 &#xff0c;验证已经填入的数字是否有效即可。 数字 1-9 在每一行只能出现一次。数字 1-9 在每一列只能出现一次。数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。&#xff08;请参考示例图&#xff09;注意…

pycharm-qt5-designer1

pycharm-qt5-designer1一: designer界面介绍1. 新建模板二: 控件箱简介1. Layouts 布局2. Spacers 间隔(透明)3. Button4. Item views5. Item Widgets 条目控件6. Containers 容器7. input Widgets 输入控件8. Display Widgets 显示控件三: 控件属性简介1. sizePolicy: 控件大小…

我用Python做了个动图生成器,把一千个MM生成了GIF设置桌面,只为每天愉悦心情

文章目录序言代码实战序言 现在的年轻人都开始每天保温杯里泡枸杞&#xff0c;这怎么能行呢&#xff1f; 想要每天过的好&#xff0c;美女必然少不了&#xff0c;每天看美女&#xff0c;只为了愉悦心情&#xff0c;心情好了&#xff0c;才长寿。 于是怀揣着愉悦心情的想法&am…

图像简单运算

图像运算是以图像为单位对图像进行数学操作&#xff0c;运算对象以像素点为基本单位&#xff0c;运算结果为一幅灰度分布与原图像不同的新图像。 算术运算与逻辑运算 算术运算与逻辑运算中每次只涉及一个空间像素的位置&#xff0c;所以可以”原地操作“&#xff08;在&#xf…