在查看本文前,希望先查看《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_lblk的ext4_extent结构
  6. ex->ee_block 或 ee_block:ex这个ext4_extent结构的起始逻辑块地址
  7. ee_len:ex映射的连续物理块个数
  8. ee_start:ex的起始逻辑块地址映射的起始物理块号

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_lblk的ext4_extent_idx结构保存到path[ppos]->p_idx.然后找到最后一层的叶子节点中最接近传入的起始逻辑块地址map->m_lblk的ext4_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_lblk的ext4_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. //ex的逻辑块地址映射的物理块个数
  29. ee_len = ext4_ext_get_actual_len(ex);
  30. //如果map->m_lblk在ex的逻辑块地址范围内
  31. if (in_range(map->m_lblk, ee_block, ee_len)) {
  32. //newblock : map->m_lblk这个起始逻辑块地址对应的物理块地址
  33. newblock = map->m_lblk - ee_block + ee_start;
  34. //map->m_lblk到(ee_block+ee_len)这个范围的物理块个数
  35. allocated = ee_len - (map->m_lblk - ee_block);
  36. /*ex已经初始化过直接goto out返回,否则执行下边的ext4_ext_handle_uninitialized_extents()*/
  37. if (!ext4_ext_is_uninitialized(ex))
  38. goto out;
  39. /*对ex的逻辑块地址进行分割,高版本内核函数名称改为 ext4_ext_handle_unwritten_extents()*/
  40. ret = ext4_ext_handle_uninitialized_extents(
  41. }
  42. }
  43. ..........
  44. //设置newex的起始逻辑块号,newex是针对本次映射分配的ext4_extent结构
  45. newex.ee_block = cpu_to_le32(map->m_lblk);
  46. .........
  47. //找到map->m_lblk映射的目标起始物理块地址并返回给ar.goal
  48. ar.goal = ext4_ext_find_goal(inode, path, map->m_lblk);
  49. //ar.logical是起始逻辑块地址map->m_lblk
  50. ar.logical = map->m_lblk;
  51. .......
  52. //offset测试时0
  53. offset = EXT4_LBLK_COFF(sbi, map->m_lblk);
  54. //本次映射需分配的物理块个数,即allocated
  55. ar.len = EXT4_NUM_B2C(sbi, offset+allocated);
  56. //物理块起始地址,offset是0
  57. ar.goal -= offset;
  58. ar.logical -= offset;
  59. .......
  60. /*分配map->m_len个物理块,这就是newex逻辑块地址映射的map->m_len个物理块,并返回这map->m_len个物理块的起始物理块号newblock。测试结果 newblock 和 ar.goal有时相等,有时不相等。本次映射的起始逻辑块地址是map->m_lblk,映射物理块个数map->m_len,ext4_mb_new_blocks()除了要找到newblock这个起始逻辑块地址,还得保证找到newblock打头的连续map->m_len个物理块,必须是连续的,这才是更重要的。*/
  61. newblock = ext4_mb_new_blocks(handle, &ar, &err);
  62. ..............
  63. got_allocated_blocks:
  64. /*设置本次映射的map->m_len个物理块的起始物理块号(newblock)到newex,newex是针对本次映射分配的ext4_extent结构*/
  65. ext4_ext_store_pblock(&newex, newblock + offset);//offset是0
  66. /*设置newex映射的物理块个数,与执行ext4_ext_mark_initialized()标记ex已初始化一个效果*/
  67. newex.ee_len = cpu_to_le16(ar.len);
  68. .........
  69. if (!err)//把newex这个插入ext4 extent B+树
  70. err = ext4_ext_insert_extent(handle, inode, path,
  71. &newex, flags);
  72. ..........
  73. out:
  74. ..........
  75. map->m_flags |= EXT4_MAP_MAPPED;
  76. //本次起始逻辑块地址map->m_lblk映射的起始物理块号
  77. map->m_pblk = newblock;
  78. /*本次逻辑块地址完成映射的物理块数,并不能保证allocated等于传入的map->m_len,还有可能小于*/
  79. map->m_len = allocated;
  80. //返回成功映射的物理块个数
  81. return err ? err : allocated;
  82. }

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_lblk在ex的逻辑块地址范围内,那就可以借助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[]数组中,找到起始逻辑块地址最接近block的ext4_extent_idx结构。path[ppos]->p_idx指向这个ext4_extent_idx*/
  25. ext4_ext_binsearch_idx(inode, path + ppos, block);
  26. //通过索引节点ext4_extent_idx结构的ei_leaf_lo和ei_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[]数组中,找到起始逻辑块地址最接近block的ext4_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_hi和ee_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_lblk的ext4_extent_idx
  2. path[1].p_idx = d0//指向索引节点中起始逻辑块地址最接近map->m_lblk的并且小于等于map->m_lblk的ext4_extent_idx
  3. path[2].p_ext = e0//指向叶子节点中起始逻辑块地址最接近map->m_lblk的并且小于等于map->m_lblk的ext4_extent

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

  1. struct ext4_ext_path {
  2. /*ext4_ext_find_extent()中赋值,是索引节点时,是由ext4_extent_idx结构的ei_leaf_lo和ei_leaf_hi成员计算出的物理块号,这个物理块保存了下层叶子节点或者索引节点4K数据。是叶子节点时,是由ext4_extent结构的ee_start_hi和ee_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_lblk的ext4_extent
  7. struct ext4_extent      *p_ext;
  8. //起始逻辑块地址最接近传map->m_lblk的ext4_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_lo和ei_leaf_hi一起计算出物理块号,这个物理块保存下层叶子节点或者索引节点4K数据。没错,索引节点ext4_extent_idx结构的ei_leaf_lo和ei_leaf_hi保存了下层索引节点或者叶子节点的物理块号,索引节点的ext4_extent_idx通过其ei_leaf_lo和ei_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_hi和ee_start_lo一起计算出起始逻辑块地址映射的起始物理块地址
  29. __le16  ee_start_hi;
  30. __le32  ee_start_lo;
  31. };

我们这里只展示了对它的成员p_idx和p_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_lblk的ext4_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. //要映射的起始逻辑块地址map->m_lblk等于ex的起始逻辑块地址
  18. if ((map->m_lblk == ee_block) &&
  19. (map_len < ee_len) &&/*要求映射的物理块数map_len要小于ex已经映射的物理块数ee_len*/
  20. /*ex是指向叶子节点第2个及以后ext4_extent结构*/
  21. (ex > EXT_FIRST_EXTENT(eh))) {
  22. ...........
  23. /*下边是重新划分ex这个ext4_extent结构的逻辑块地址范围,把之前ee_block~ee_block+map_len划分给abut_ex这个ext4_extent,ex新的逻辑块地址范围是(ee_block + map_len)~(ee_block + ee_len)。ex映射的逻辑块(物理块)个数减少了map_len个,abut_ex的增加了map_len个*/
  24. ex->ee_block = cpu_to_le32(ee_block + map_len);//设置新的逻辑块首地址
  25. ext4_ext_store_pblock(ex, ee_pblk + map_len);//设置新的物理块首地址
  26. ex->ee_len = cpu_to_le16(ee_len - map_len);//设置新的映射的物理块个数
  27. /*把ex这个ext4_extent设置"uninitialized"标记,这是重点*/
  28. ext4_ext_mark_uninitialized(ex);
  29. //abut_ex映射的物理块个数增加map_len个
  30. abut_ex->ee_len = cpu_to_le16(prev_len + map_len);
  31. //allocated是abut_ex增多的逻辑块个数
  32. allocated = map_len;
  33. ...........
  34. }
  35. //要映射的结束逻辑块地址map->m_lblk+map_len等于ex的结束逻辑块地址ee_block + ee_len
  36. else if (((map->m_lblk + map_len) == (ee_block + ee_len)) &&
  37. (map_len < ee_len) &&    /*L1*///要求映射的物理块数map_len要小于ex已经映射的物理块数ee_len
  38. ex < EXT_LAST_EXTENT(eh)) {  /*L2*///ex是指向叶子节点最后一个ext4_extent结构
  39. ...........
  40. /*下边这是把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)*/
  41. abut_ex->ee_block = cpu_to_le32(next_lblk - map_len);
  42. ext4_ext_store_pblock(abut_ex, next_pblk - map_len);//设置新的物理块首地址
  43. //ex映射的逻辑块个数减少了map_len个
  44. ex->ee_len = cpu_to_le16(ee_len - map_len);
  45. /*标记ex为"uninitialized"状态,这是重点,ex还是未初始化状态*/
  46. ext4_ext_mark_uninitialized(ex);
  47. //abut_ex逻辑块个数增大了map_len个
  48. abut_ex->ee_len = cpu_to_le16(next_len + map_len);
  49. //abut_ex逻辑块个数增加了map+len个
  50. allocated = map_len;
  51. ...........
  52. }
  53. .............
  54. if (allocated) {/*allocated非0说明abut_ex逻辑块范围吞并了ex map_len个逻辑块*/
  55. /*ext4 extent叶子节点变为abut_ex,原来的ex废弃了,隐藏知识点*/
  56. path[depth].p_ext = abut_ex;
  57. goto out;//退出该函数
  58. } else
  59. /*即allocated=(ee_len+ee_block) - map->m_lblk。如果abut_ex没有吞并ex的逻辑块,allocated是map->m_lblk到ex结束逻辑块地址之间的逻辑块数*/
  60. allocated = ee_len - (map->m_lblk - ee_block);
  61. ...........
  62. //重点,把ex的逻辑块地址进行分割
  63. allocated = ext4_split_extent(handle, inode, path,
  64. &split_map, split_flag, flags);
  65. return err ? err : allocated;
  66. }

该函数逻辑比较简单,主要功能总结如下:如果本次要映射的物理块数(或者逻辑块数)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. flags1 = flags | EXT4_GET_BLOCKS_PRE_IO;//flag加上EXT4_GET_BLOCKS_PRE_IO标记
  25. /*如果ex有未初始化标记,则split_flag1被加上EXT4_EXT_MARK_UNINIT1和EXT4_EXT_MARK_UNINIT2标记。EXT4_EXT_MARK_UNINIT1是标记分割的前半段ext4_extent未初始化状态,EXT4_EXT_MARK_UNINIT2是标记分割的后半段ext4_extent未初始化状态*/
  26. if (uninitialized)
  27. split_flag1 |= EXT4_EXT_MARK_UNINIT1 |
  28. EXT4_EXT_MARK_UNINIT2;
  29. if (split_flag & EXT4_EXT_DATA_VALID2)
  30. split_flag1 |= EXT4_EXT_DATA_VALID1;
  31. /*以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+树*/
  32. err = ext4_split_extent_at(handle, inode, path,
  33. map->m_lblk + map->m_len, split_flag1, flags1);
  34. if (err)
  35. goto out;
  36. } else {
  37. /*到这里,说明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个*/
  38. allocated = ee_len - (map->m_lblk - ee_block);
  39. }
  40. .................
  41. /*上边可能把ex的逻辑块范围分割了,这里重新再ext4 extent B+树查找逻辑块地址范围接近map->m_lblk的索引节点和叶子结点*/
  42. path = ext4_ext_find_extent(inode, map->m_lblk, path);
  43. .................
  44. depth = ext_depth(inode);
  45. ex = path[depth].p_ext;
  46. //ex是否是未初始化状态
  47. uninitialized = ext4_ext_is_uninitialized(ex);
  48. split_flag1 = 0;
  49. /*如果map的起始逻辑块地址大于等于ex的起始逻辑块地址,以map->m_lblk为分割点,再次分割新的ex逻辑块范围*/
  50. if (map->m_lblk >= ee_block) {
  51. split_flag1 = split_flag & EXT4_EXT_DATA_VALID2;
  52. /*如果ex有未初始化标记,则split_flag1被加上EXT4_EXT_MARK_UNINIT1标记,EXT4_EXT_MARK_UNINIT1是标记分割的前半段ext4_extent未初始化状态*/
  53. if (uninitialized) {
  54. split_flag1 |= EXT4_EXT_MARK_UNINIT1;
  55. split_flag1 |= split_flag & (EXT4_EXT_MAY_ZEROOUT |
  56. EXT4_EXT_MARK_UNINIT2);
  57. }
  58. /*以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),然后把后半段map->m_lblk~(ee_block+ee_len)对应的ext4_extent结构添加到ext4 extent B+树。*/
  59. err = ext4_split_extent_at(handle, inode, path,
  60. map->m_lblk, split_flag1, flags);
  61. if (err)
  62. goto out;
  63. }
  64. .................
  65. out:
  66. return err ? err : allocated;
  67. }

该函数主要分两种情况:

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

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

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

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_idx是ext4 extent B+树起始逻辑块地址最接近传入的起始逻辑块地址map->m_lblk的ext4_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. /*ix是curp->p_idx或者(curp->p_idx+1)。len是ix这个索引节点的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后边的len个ext4_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+N个ext4_extent_idx或者或者叶子结点ext4_extent_header+N个ext4_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+4个ext4_extent_idx或者叶子结点ext4_extent_header+4个ext4_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. //如果ext4 extent B+树有索引节点,neh指向的内存作为索引节点
  23. if (ext_depth(inode))
  24. neh->eh_max = cpu_to_le16(ext4_ext_space_block_idx(inode, 0));
  25. else//如果ext4 extent B+树没有索引节点,只有根节点,neh指向的内存作为叶子结点
  26. neh->eh_max = cpu_to_le16(ext4_ext_space_block(inode, 0));
  27. neh->eh_magic = EXT4_EXT_MAGIC;
  28. ...................
  29. //现在neh又指向ext4 extent B+根节点
  30. neh = ext_inode_hdr(inode);
  31. //根节点现在只有一个叶子节点的ext4_extent结构在使用或者只有一个索引节点的ext4_extent_idx结构在使用
  32. neh->eh_entries = cpu_to_le16(1);
  33. /*这是把前边新创建的索引节点或者叶子节点的物理块号newblock记录到根节点第一个ext4_extent_idx结构的ei_leaf_lo和ei_leaf_hi成员。这样就建立了根节点与新创建的物理块号是newblock的叶子结点或索引节点的联系。因为通过根节点第一个ext4_extent_idx结构的ei_leaf_lo和ei_leaf_hi成员,就可以找到这个新创建的叶子节点或者索引节点的物理块号newblock*/
  34. ext4_idx_store_pblock(EXT_FIRST_INDEX(neh), newblock);
  35. //如果neh->eh_depth是0,说明之前ext4 extent B+树深度是0,即只有根节点
  36. if (neh->eh_depth == 0) {
  37. /*以前B+树只有根节点,没有索引节点。现在根节点作为索引节点,这是计算根节点最多可容纳的ext4_extent_idx结构个数,4*/
  38. neh->eh_max = cpu_to_le16(ext4_ext_space_root_idx(inode, 0));
  39. /*以前B+树只有根节点,没有索引节点,根节点都是ext4_extent结构,现在B+树根节点下添加了newblock这个叶子节点。根节点成了根索引节点,因此原来第一个ext4_extent结构要换成ext4_extent_idx结构,下边赋值就是把原来的根节点第一个ext4_extent的起始逻辑块地址赋值给现在根节点的第一个ext4_extent_idx的起始逻辑块地址*/
  40. EXT_FIRST_INDEX(neh)->ei_block =
  41. EXT_FIRST_EXTENT(neh)->ee_block;
  42. }
  43. .............
  44. //ext4 extent B+树增加了一层索引节点或叶子结点,即物理块号是newblock的那个,树深度加1
  45. le16_add_cpu(&neh->eh_depth, 1);
  46. .............
  47. return err;
  48. }

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

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

ext4 extent详解2之内核源码详解相关推荐

  1. cgroup使用举例和linux内核源码详解

    cgroup的原理其实并不复杂,用法也比较简单.但是涉及的内核数据结构真的复杂,错综复杂的数据结构感觉才是cgroup真正的难点.本文结合个人学习cgroup源码的心得,尽可能以举例的形式,总结cgr ...

  2. page_to_pfn 、virt_to_page、 virt_to_phys、page、页帧pfn、内核虚拟地址、物理内存地址linux内核源码详解

    首先说说内核态虚拟地址和物理内存地址转换关系 #define PAGE_OFFSET     UL(0xffffffc000000000) /* PHYS_OFFSET - the physical ...

  3. 激光雷达的检测仿真代码详解(附Matlab源码详解)

    一.创建velodyneFileReader 本示例中的激光雷达数据来自安装在车辆上的激光雷达Velodyne HDL32E.创建velodyneFileReader对象以读取录制的PCAP文件. f ...

  4. 【Linux 内核】编译 Linux 内核 ② ( 解压内核源码 | 查询当前 Linux 内核版本号 | 进入并查看 linux 内核源码目录 )

    文章目录 一.解压内核源码 二.查询当前 Linux 内核版本号 三.进入并查看 linux 内核源码目录 一.解压内核源码 将 下载的 Linux 内核源码 linux-5.6.14.tar.gz ...

  5. Vue-Watcher观察者源码详解

    源码调试地址 https://github.com/KingComedy/vue-debugger 什么是Watcher Watcher是Vue中的观察者类,主要任务是:观察Vue组件中的属性,当属性 ...

  6. 编译天嵌E8内核源码

    不久前,买了一块天嵌E8的板,拿到资料后就试着编译一下所给的内核源码. 解压,进入源码目录,发现没有.config文件.于是: #cp config_for_E8_Linux .config 然后,# ...

  7. Redhat7.2上编译Linux内核源码

    下载linux源码包:https://git.kernel.org/pub/scm/virt/kvm/kvm.git/snapshot/kvm-4.17-1.tar.gz (这是包含kvm开发版本的l ...

  8. 【转载】ubuntu下linux内核源码阅读工具和调试方法总结

    http://blog.chinaunix.net/space.php?uid=20940095&do=blog&cuid=2377369 一 linux内核源码阅读工具 window ...

  9. 飞凌嵌入式iMX8MP 开发板试用体验--编译内核源码

    FETMX8MP-C核心板基于NXP i.MX 8M Plus处理器开发设计,该系列处理器专注于机器学习与视觉.高级多媒体以及具有高可靠性的工业自动化.旨在满足智慧城市.工业互联网.智能医疗.智慧交通 ...

最新文章

  1. 我的操作系统复习——进程(下)
  2. SecureCRT通过密钥进行SSH登录
  3. Qt QWidget实现手势缩放和平移(一)
  4. JVM实用参数(七)CMS收集器
  5. html字居右垂直设置,css文字水平垂直居中怎么设置?
  6. webpack 图片的路径与打包
  7. phpstudy的php fpm,PHP_php-fpm配置详解,php5.3自带php-fpm复制代码 代码 - phpStudy
  8. 【图像处理基础知识】中文车牌识别API-HyperLPR的应用与相关学习资料(python版)
  9. atmega328p 设置熔丝位,atmega328p 刷机
  10. ad10搜索快捷键_AD 常用快捷键
  11. Should I normalize/standardize/rescale the data
  12. 第九届山东理工大学ACM网络编程擂台赛 热身赛 sdut4076 数的价值(一)
  13. 记一次namenode关机导致的问题
  14. oracle数据库学习笔记(二)(创建表、DDL、DML、单行插入数据、多行插入数据)
  15. [MAC]Google Drive下载大文件
  16. 计算机专业大学排名(全168所学校)
  17. 计算机蓝牙快捷键,如何打开Windows10笔记本电脑的蓝牙,快捷键打开笔记本电脑的蓝牙!...
  18. Linux常见日志文件和常用命令
  19. Siri 语音识别 Speech
  20. vue实战项目仿卖座电影APP

热门文章

  1. 计算机图形学(二)输出图元_6_OpenGL曲线函数_5_其他曲线
  2. windows装linux
  3. Spring Boot连接Oracle数据库驱动加载不上的问题(pom.xml引入ojdbc报错的问题)
  4. CSS的水平居中与垂直居中
  5. CSS的水平居中、垂直居中和水平垂直居中
  6. 谈谈北京的租房和买房
  7. mdk工程的批量操作文件
  8. 【区块链与密码学】第9-8讲:群签名在区块链中的应用 II
  9. WampServer图标为黄色,无法启动的解决办法及思路
  10. 饥荒怎么自动订阅服务器,steam饥荒自动订阅mod在哪设置 | 手游网游页游攻略大全...