ext4 extent详解3之内核源码流程讲解

news2024/11/15 13:34:52

本文在前两篇《ext4 extent详解1之示意图演示》和《ext4 extent详解2之内核源码详解》讲解ext4 extent 文章的基础上,本文从内核源码、实例演示等角度详细介绍ext4 extent B+树的形成过程,希望看过本文的读者能理解ext4 extent B+树的工作原理。

1 :ext4 extent B+树索引节点和叶子节点怎么建立联系

首先提一个问题,ext4 extent B+树的根节点、索引节点、叶子节点是怎么保存的呢?根节点共16*4=64字节大小,保存在struct ext4_inode_info的__le32    i_data[15]。索引节点和叶子节点的都是一个物理块大小,这里ext4文件系统一个物理块默认4K大小。保存索引节点和叶子节点4K数据的物理块都是调用ext4_ext_new_meta_block()函数在ext4文件系统分配的。先看下ext4 extent B+树的简单示意图:

第1层是根节点,第2层是索引节点,第3层是叶子节点。如图所示,根节点的ext4_extent_idx指向一个索引节点,索引节点的ext4_extent_idx指向一个叶子节点。可以看到ext4_extent_idx在ext4 extent B+树中起着非常重要的索引作用,那怎么通过ext4_extent_idx找打它指向的索引节点和叶子节点呢?先看下它的数据结构

  1. struct ext4_extent_idx {
  2.     //起始逻辑块地址
  3.     __le32  ei_block;  
  4.     //ei_leaf_loei_leaf_hi组成起始逻辑块地址对应的物理块地址
  5.     __le32  ei_leaf_lo;
  6.     __le16  ei_leaf_hi;
  7.     __u16   ei_unused;
  8. };

一个叶子节点或者索引节点都是占一个物理块大小,这里ext4文件系统一个物理块默认是4K。也就是说,一个叶子节点或者索引节点的数据都是4K大小。并且,叶子节点和索引节点的物理块号保存在ext4_extent_idx结构的ei_leaf_lo和ei_leaf_hi成员。因此,正是通过ext4_extent_idx结构的ei_leaf_loei_leaf_hi的成员,找到叶子节点或者索引节点物理块号,然后就可以访问叶子节点的4K数据(ext4_extent_header+Next4_extent数据),或者访问索引节点的4K数据(ext4_extent_header+Next4_extent_idx数据)。这就是我们前边说的通过一个ext4_extent_idx找到它指向的索引节点或者叶子节点的原理。

好的,我们对上图做个简答改造,如下图所示。表上ext4_extent_idx和ext4_extent代表的逻辑块地址,我们这里演示一下怎么在ext4 extent b+树查找逻辑块地址5映射的物理块地址。

 首先struct ext4_inode_info的__le32   i_data[15]保存的是ext4 extent b+树根节点的数据:1个ext4_extent_header结构+4个ext4_extent_idx结构(也有可能是1个ext4_extent_header结构+4个ext4_extent结构,以上边的示意图为准)。找到根节点中起始逻辑块地址最接近5并且小于等于5的ext4_extent_idx结构,显然肯定是根节点的第一个ext4_extent_idx位置处的那个ext4_extent_idx结构,我们给它编号c0。

继续,通过该ext4_extent_idx的成员ei_leaf_lo和ei_leaf_hi计算出它指向的索引节点的物理块块号。读取这个物理块号中的索引节点节点的4K数据,是1个ext4_extent_header结构+N个ext4_extent_idx结构。好的,现在就有了示意图中,ext4 extent b+树第2层最左边的索引节点的数据。找到该索引节点中起始逻辑块地址最接近5并且小于等于5的ext4_extent_idx结构,显然是该索引节点中的第一个ext4_extent_idx位置处的那个ext4_extent_idx结构,我们给它编号d0。

接着,知道了索引节点中的编号d0的ext4_extent_idx结构,通过其成员ei_leaf_lo和ei_leaf_hi计算出它指向的叶子节点的物理块号,最后读取这个物理块号中的叶子节点的4K数据,是1个ext4_extent_header结构+N个ext4_extent结构。好的,有了叶子节点的数据,找到该叶子节点中起始逻辑块地址最接近5并且小于等于5的ext4_extent结构,显然就是编号的e0的ext4_extent。该ext4_extent包含了逻辑块地址0~10映射的物理块号,那肯定就可以计算出逻辑块地址5映射的物理块号。

2 :ext4_extent插入ext4 extent B+树函数流程总结

Ok,下边聊另外一个问题,什么时候会用到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_ext_insert_extent()把新的ext4_extent插入到ext4 extent b+树,ext4_ext_insert_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 extent内核源码中经常看到ex变量,它的定义是struct ext4_extent *ex,赋值是ex = path[depth].p_extdepthext4 extent b+树深度,上边的示意图中ext4 extent b+树深度是2。如果ext4 extent b+树只有根节点,没有叶子节点或者索引节点,ext4 extent b+树深度是0.

ok,准备的比较充分了,下边我们针对几个典型场景,说明下ext4  extent b+树的形成过程涉及的核心函数流程。

2.1: ext4  extent B+树插入第一个ext4_extent函数流程

首先,最初ext4  extent B+树是空的

 现在要先找到逻辑块地址0~10映射的物理块,起始逻辑块地址map->m_lblk=0,要映射的逻辑块个数(或者说要分配的连续物理块个数)map->m_len=10。首先执行到ext4_ext_map_blocks():

  • 1:先定义并定义struct ext4_extent newex变量。接着执行ext4_ext_find_extent(),试图查找逻辑块地址最接近map->m_lblk的索引节点ext4_extent_idr结构和叶子节点ext4_extent结构保存到path[],但是一个都没找到,即ex = path[depth].p_ext=NULL。
  • 2:故ext4_ext_find_extent()函数最开头的ex = path[depth].p_ext后的if (ex)不成立。则执行到newblock = ext4_mb_new_blocks(handle, &ar, &err)针对本次需映射的起始逻辑块地址map->m_lblk(0)和需映射的逻辑块个数map->m_len(10),分配10个连续的物理块并返回这10个物理块的第一个物理块号给newblock。
  • 3:接着先执行newex.ee_block = cpu_to_le32(map->m_lblk)赋值本次要映射的起始逻辑块地址,然后执行ext4_ext_store_pblock(&newex, newblock + offset)把本次逻辑块地址0~10映射的起始物理块号newblock保存到newex的ee_start_lo和ee_start_hi成。并执行newex.ee_len = cpu_to_le16(ar.len)把成功映射的物理块数保存到newex.ee_len。ar.len是10,表示本次逻辑块地址0~10成功映射了10个物理块。最后,执行err = ext4_ext_insert_extent(handle, inode, path,&newex, flags)把代表本次逻辑块地址0~10映射关系的newex,插入到ext4  extent b+树。

接着执行ext4_ext_insert_extent()把newex插入到b+树:

  • 1:if (ex && !(flag & EXT4_GET_BLOCKS_PRE_IO)) 不成立,if (le16_to_cpu(eh->eh_entries) < le16_to_cpu(eh->eh_max))成立,则执行goto has_space跳到has_space分支。
  • 2:因为nearex = path[depth].p_ext是NULL,if (!nearex)成立。执行nearex = EXT_FIRST_EXTENT(eh)令nearex指向根节点第一个ext4_extent位置处(nearex 定义是struct ext4_extent *nearex)。
  • 3:然后执行nearex->ee_block = newext->ee_block=0(本次映射的起始逻辑块地址0);执行ext4_ext_store_pblock(nearex, ext4_ext_pblock(newext))向nearex赋值本次起始逻辑块地址newext->ee_block (0)映射的起始物理块地址;执行nearex->ee_len = newext->ee_len=10,这是本次成功映射的物理块个数。Ok,这3个赋值后,就相当于把newex成功插入到了ext4  extent B+树的第一个ext4_extent位置处。

插入后的效果如图所示,逻辑块地址0~10的ext4_extent保存到了根节点第一个ext4_extent位置处,给这个ext4_extent编号a0。

 好的,简单总结一下:先找到本次逻辑块地址0~10映射的10个连续物理块(起始物理块号是newblock)。然后把本次映射的起始逻辑块地址0、本次映射的起始物理块号newblock、本次成功映射的物理块个数等信息赋值给struct ext4_extent newex,然后把newex中的这3个参数保存到ext4  extent B+树第一个ext4_extent位置处的ext4_extent结构。为了描述方便,我们这里把这个过程称为逻辑块地址是0~10ext4_extent插入到ext4  extent B+,后续会经常遇到类似描述。

2.2 :ext4  extent B+树插入第2~4个ext4_extent函数流程

现在向ext4  extent B+树插入逻辑块地址是20~30的ext4_extent,函数流程是什么呢?

现在要先找到逻辑块地址20~30映射的物理块,起始逻辑块地址map->m_lblk=20,要映射的逻辑块个数(或者说要分配的连续物理块个数)map->m_len=10。首先执行到ext4_ext_map_blocks():

  • 1:先定义并定义struct ext4_extent newex变量。接着执行ext4_ext_find_extent(),试图查找逻辑块地址最接近map->m_lblk的索引节点ext4_extent_idr结构和叶子节点ext4_extent结构保存到path[],即ex = path[depth].p_ext=a0(根节点插入的第一个ext4_extent结构)。
  • 2:故ext4_ext_find_extent()函数开头的ex = path[depth].p_ext后的if (ex)成立,但是if (in_range(map->m_lblk, ee_block, ee_len))不成立,因为map->m_lblk不在a0的逻辑块的地址范围内。于是执行到newblock = ext4_mb_new_blocks(handle, &ar, &err)针对本次需映射的起始逻辑块地址map->m_lblk(20)和需映射的逻辑块个数map->m_len(10),分配10个连续的物理块并返回这10个物理块的第一个物理块号给newblock。
  • 3:接着执行ext4_ext_store_pblock(&newex, newblock + offset)把本次逻辑块地址20~30映射的起始物理块号newblock保存到newex的ee_start_lo和ee_start_hi成。并执行newex.ee_len = cpu_to_le16(ar.len)把成功映射的物理块数保存到newex.ee_len。ar.len是10,表示本次逻辑块地址20~30成功映射了10个物理块。最后,执行err = ext4_ext_insert_extent(handle, inode, path,&newex, flags)把代表本次逻辑块地址20~30映射关系的newex,插入到ext4  extent b+树。

继续,执行ext4_ext_insert_extent()把newex插入到b+树。

  • 1:if (ex && !(flag & EXT4_GET_BLOCKS_PRE_IO))不成立,该if判断里边的代码作用是:判断newex跟ex、ex前边的ext4_extent结构、ex后边的ext4_extent结构逻辑块地址范围是否紧挨着,是的话才能将二者合并,可惜本次执行不到这些代码。
  • 2:继续,if (le16_to_cpu(eh->eh_entries) < le16_to_cpu(eh->eh_max))成立,则执行goto has_space跳到has_space分支。
  • 3:因为nearex = path[depth].p_ext是a0(nearex 定义是struct ext4_extent *nearex),if (!nearex)不成立。则跳到else分支,并且if (le32_to_cpu(newext->ee_block)> le32_to_cpu(nearex->ee_block))成立,因为newext->ee_block是本次要插入的ext4_extent的起始逻辑块地址20,nearex->ee_block是根节点第一个ext4_extent结构的起始逻辑块地址0。4:于是执行if (le32_to_cpu(newext->ee_block)> le32_to_cpu(nearex->ee_block))里的nearex++,令nearex指向a0后边的ext4_extent,也就是根节点第2个ext4_extent。
  • 5:最后,执行nearex->ee_block = newext->ee_block=20(本次映射的起始逻辑块地址20);执行ext4_ext_store_pblock(nearex, ext4_ext_pblock(newext))向nearex赋值本次起始逻辑块地址newext->ee_block (20)映射的起始物理块地址;执行nearex->ee_len = newext->ee_len=10,本次成功映射的起始物理块个数。这3个赋值后,就相当于把newex成功插入到了ext4  extent B+树的第2个ext4_extent位置处,如下图所示。这里对这个新插入的ext4_extent编号a20。

 好的,现在向ext4  extent B+树根节点又插入了逻辑块地址是50~60、80~90的2个ext4_extent,过程与逻辑块地址是20~10的ext4_extent插入到根节点类似,不再赘述。插入后效果如下:

2.3: ext4  extent B+树根节点下创建叶子节点

在上一节的基础上, ext4  extent B+树根节点ext4_extent已经全占满了,如果此时把逻辑块地址100~130插入到ext4  extent B+树,过程是什么呢?

现在要先找到逻辑块地址100~130映射的物理块,起始逻辑块地址map->m_lblk=100,要映射的逻辑块个数(或者说要分配的连续物理块个数)map->m_len=30。首先执行到ext4_ext_map_blocks(),

  • 1:先定义并定义struct ext4_extent newex变量,接着执行ext4_ext_find_extent(),试图查找逻辑块地址最接近map->m_lblk的索引节点ext4_extent_idr结构和叶子节点ext4_extent结构保存到path[],即ex = path[depth].p_ext=a80(根节点插入的第4个ext4_extent结构)。
  • 2:故ext4_ext_find_extent()函数开头的ex = path[depth].p_ext后的if (ex)成立,但是if (in_range(map->m_lblk, ee_block, ee_len))不成立,因为map->m_lblk不在a80的逻辑块的地址范围内。于是执行到newblock = ext4_mb_new_blocks(handle, &ar, &err)针对本次需映射的起始逻辑块地址map->m_lblk(100)和需映射的逻辑块个数map->m_len(30),分配30个连续的物理块并返回这30个物理块的第一个物理块号给newblock。
  • 3:接着先执行newex.ee_block = cpu_to_le32(map->m_lblk)赋值本次要映射的起始逻辑块地址,然后执行ext4_ext_store_pblock(&newex, newblock + offset)把本次逻辑块地址100~130映射的起始物理块号newblock保存到newex的ee_start_lo和ee_start_hi成。并执行newex.ee_len = cpu_to_le16(ar.len)把成功映射的物理块数保存到newex.ee_len。ar.len是30,表示本次逻辑块地址100~130成功映射了30个物理块。最后,执行err = ext4_ext_insert_extent(handle, inode, path,&newex, flags)把代表本次逻辑块地址100~130映射关系的newex,插入到ext4  extent b+树。

继续,执行ext4_ext_insert_extent()把newex插入到b+树:

  • 1:if (ex && !(flag & EXT4_GET_BLOCKS_PRE_IO))不成立,该if判断里边的代码作用是:判断newex跟ex、ex前边的ext4_extent结构、ex后边的ext4_extent结构逻辑块地址范围是否紧挨着,是的话才能将二者合并,可惜本次执行不到这些代码。
  • 2:继续,if (le16_to_cpu(eh->eh_entries) < le16_to_cpu(eh->eh_max))不成立,eh->eh_entries是根节点现在已经使用的ext4_extent个数,eh->eh_max是根节点最多能保存的ext4_extent个数,二者都是4,显然不成立。
  • 3:继续,if (le32_to_cpu(newext->ee_block) > le32_to_cpu(fex->ee_block))成立,newext->ee_block是本次插入的ext4_extent的起始逻辑块地址100,fex->ee_block是叶子节点最后一个ext4_extent的起始逻辑块地址80,显然成立。但执行里边的next = ext4_ext_next_leaf_block(path)因if (depth == 0)返回EXT_MAX_BLOCKS(b+树深度是0),则返回ext4_ext_insert_extent()函数后if (next != EXT_MAX_BLOCKS)不成立。
  • 4:继续,执行到ext4_ext_create_new_leaf(handle, inode, flags, path, newext)函数。先执行在该函数里的while (i > 0 && !EXT_HAS_FREE_INDEX(curp))循环,退出循环时curp指向根节点,i是0,if (EXT_HAS_FREE_INDEX(curp))不成立,因为此时根节点4个ext4_extent全用完了,于是执行ext4_ext_grow_indepth(handle, inode, flags, newext)函数。
  • 5:继续,执行ext4_ext_create_new_leaf(handle, inode, flags, path, newext)-> ext4_ext_grow_indepth(handle, inode, flags, newext)。在ext4_ext_grow_indepth()函数里,在根节点下创建一层叶子节点,具体过程是执行newblock = ext4_ext_new_meta_block(handle, inode, NULL,newext, &err, flags)分配一个物理块,将来保存叶子节点的4K数据。并且把根节点原有的a0、a20、a50、a80这4个ext4_extent复制到叶子节点前个ext4_extent位置处。此时根节点的原有的4个ext4_extent变为ext4_extent_idr。并且在根节点第一个ext4_extent_idr位置处创建新的ext4_extent_idr结构,令它的起始逻辑块地址是0(原叶子节点第一个ext4_extent结构的起始逻辑块地址),还把这个新创建的叶子节点的物理块号newblock保存到该ext4_extent_idr的ei_leaf_lo和ei_leaf_hi成员。这样通过根节点第一个ext4_extent_idr位置处的ext4_extent_idr结构的ei_leaf_lo和ei_leaf_hi成员,就能找到它指向叶子节点。此时的ext4 extent B+树如下所示,注意,此时b+树的深度depth0变为1。根节点此时只有第一个ext4_extent_idr(编号b0)是有效的,剩余3个ext4_extent_idr并没有使用。通过根节点的b0这个ext4_extent_idr指向新创建的叶子节点。

5:从ext4_ext_grow_indepth()返回到ext4_ext_create_new_leaf(),执行path = ext4_ext_find_extent(inode, (ext4_lblk_t)le32_to_cpu(newext->ee_block),path)重新在ext4_extent b+树查找逻辑块地址最接近newext->ee_block的根节点的ext4_extent_idr和叶子节点ext4_extent。newext->ee_block=100,查找后path[]如下所示:

  • path[0].p_idx = b0//path[0].p_idx指向根节点中逻辑块地址最接近newext->ee_blockext4_extent_idr
  • path[depth].p_ext = a80//depth=1 path[1].p_idx指向叶子节点中逻辑块地址最接近newext->ee_blockext4_extent

6:从ext4_ext_create_new_leaf()回到ext4_ext_insert_extent()函数,因为nearex = path[depth].p_ext是a80(nearex 定义是struct ext4_extent *nearex),if (!nearex)不成立。继续,if (le32_to_cpu(newext->ee_block)> le32_to_cpu(nearex->ee_block))成立,newext->ee_block是本次要插入的ext4_extent的起始逻辑块地址100,nearex->ee_block是叶子节点第4个ext4_extent结构的起始逻辑块地址90。于是执行该if判断里的nearex++,令nearex指向a80后边的ext4_extent,也就是叶子节点第5个ext4_extent。最后,执行nearex->ee_block = newext->ee_block=100(本次映射的起始逻辑块地址100);执行ext4_ext_store_pblock(nearex, ext4_ext_pblock(newext))向nearex赋值本次起始逻辑块地址newext->ee_block (90)映射的起始物理块地址;执行nearex->ee_len = newext->ee_len=30,这是本次成功映射的起始物理块个数。这3个赋值后,就相当于把newex成功插入到了ext4  extent B+树的叶子节点第5个ext4_extent位置处,如下图所示。这里对这个新插入的ext4_extent编号a100。

2.4 :ext4  extent B+树向叶子节点插入ext4_extent

在上一节基础上,向ext4  extent B+树插入逻辑块地址是150~180的ext4_extent。

现在要先找到逻辑块地址150~180映射的物理块,起始逻辑块地址map->m_lblk=150,要映射的逻辑块个数(或者说要分配的连续物理块个数)map->m_len=30。首先执行到ext4_ext_map_blocks():

1:先定义并定义struct ext4_extent newex变量。接着执行ext4_ext_find_extent(),试图查找逻辑块地址最接近map->m_lblk的索引节点ext4_extent_idr结构和叶子节点ext4_extent结构保存到path[]。此时ext4  extent B+树深度depth是1,则ext4_ext_find_extent()执行后的情况是:

  • path[0].p_idx = b0//path[0].p_idx指向根节点中逻辑块地址最接近map->m_lblkext4_extent_idr,即根节点第一个ext4_extent_idr结构,即b0
  • path[depth].p_ext = a100//depth=1 path[1].p_idx指向叶子节点中逻辑块地址最接近map->m_lblkext4_extent,叶子节点第5ext4_extent结构,即a100

则ex = path[depth].p_ext=a100

  • 2:继续, ext4_ext_find_extent()后边的if (ex)成立,但是if (in_range(map->m_lblk, ee_block, ee_len))不成立,因为map->m_lblk不在a100的逻辑块的地址范围内。于是执行到newblock = ext4_mb_new_blocks(handle, &ar, &err)针对本次需映射的起始逻辑块地址map->m_lblk(150)和需映射的逻辑块个数map->m_len(30),分配30个连续的物理块并返回这30个物理块的第一个物理块号给newblock。
  • 3:接着先执行newex.ee_block = cpu_to_le32(map->m_lblk)赋值本次要映射的起始逻辑块地址,然后执行ext4_ext_store_pblock(&newex, newblock + offset)把本次逻辑块地址150~180映射的起始物理块号newblock保存到newex的ee_start_lo和ee_start_hi成。并执行newex.ee_len = cpu_to_le16(ar.len)把成功映射的物理块数保存到newex.ee_len。ar.len是30,表示本次逻辑块地址150~180成功映射了30个物理块。最后,执行err = ext4_ext_insert_extent(handle, inode, path,&newex, flags)把代表本次逻辑块地址150~180映射关系的newex,插入到ext4  extent b+树。

继续,执行ext4_ext_insert_extent()把newex插入到b+树:

  • 1:if (ex && !(flag & EXT4_GET_BLOCKS_PRE_IO))不成立,该if判断里边的代码作用是:判断newex跟ex、ex前边的ext4_extent结构、ex后边的ext4_extent结构逻辑块地址范围是否紧挨着,是的话才能将二者合并,可惜本次执行不到这些代码。
  • 2:继续,if (le16_to_cpu(eh->eh_entries) < le16_to_cpu(eh->eh_max))成立,则执行goto has_space跳到has_space分支。
  • 3:因为nearex = path[depth].p_ext是a100(nearex 定义是struct ext4_extent *nearex),if (!nearex)不成立。则跳到else分支,并且if (le32_to_cpu(newext->ee_block)> le32_to_cpu(nearex->ee_block))成立,因为newext->ee_block是本次要插入的ext4_extent的起始逻辑块地址150,nearex->ee_block是叶子节点第5个ext4_extent结构的起始逻辑块地址100。
  • 4:于是执行if (le32_to_cpu(newext->ee_block)> le32_to_cpu(nearex->ee_block))里的nearex++,令nearex指向a100后边的ext4_extent,也就是叶子节点第6个ext4_extent。
  • 5:最后,执行nearex->ee_block = newext->ee_block=150(本次映射的起始逻辑块地址150);执行ext4_ext_store_pblock(nearex, ext4_ext_pblock(newext))向nearex赋值本次起始逻辑块地址newext->ee_block (150)映射的起始物理块地址;执行nearex->ee_len = newext->ee_len=30,这是本次成功映射的起始物理块个数。这3个赋值后,就相当于把newex成功插入到了ext4  extent B+树的叶子节点第6个ext4_extent位置处,如下图所示。这里对这个新插入的ext4_extent编号a150。

2.5: ext4  extent B+树创建叶子节点

在上一小节基础上,继续向叶子节点插入ext4_extent,把叶子节点的ext4_extent全用完,如下图所示:

此时向ext4  extent B+树插入逻辑块地址 300~320的ext4_extent时,函数流程是什么呢?

现在要先找到逻辑块地址300~320映射的物理块,起始逻辑块地址map->m_lblk=300,要映射的逻辑块个数(或者说要分配的连续物理块个数)map->m_len=20。首先执行到ext4_ext_map_blocks(),

1:先定义并定义struct ext4_extent newex变量。接着执行ext4_ext_find_extent(),试图查找逻辑块地址最接近map->m_lblk的索引节点ext4_extent_idr结构和叶子节点ext4_extent结构保存到path[]。此时ext4  extent B+树深度depth是1,则ext4_ext_find_extent()执行后的情况是:

  • path[0].p_idx = b0//path[0].p_idx指向根节点中逻辑块地址最接近map->m_lblkext4_extent_idr,即根节点第一个ext4_extent_idr结构,即b0
  • path[depth].p_ext = a280//depth=1 path[1].p_idx指向叶子节点中逻辑块地址最接近map->m_lblkext4_extent,叶子节点最后一个ext4_extent结构,即a280

则ex = path[depth].p_ext=a280    

  • 2:故ext4_ext_find_extent()函数开头的ex = path[depth].p_ext后的if (ex)成立,但是if (in_range(map->m_lblk, ee_block, ee_len))不成立,因为map->m_lblk不在a280的逻辑块的地址范围内。于是执行到newblock = ext4_mb_new_blocks(handle, &ar, &err)针对本次需映射的起始逻辑块地址map->m_lblk(300)和需映射的逻辑块个数map->m_len(20),分配20个连续的物理块并返回这20个物理块的第一个物理块号给newblock。
  • 3:接着先执行newex.ee_block = cpu_to_le32(map->m_lblk)赋值本次要映射的起始逻辑块地址,然后执行ext4_ext_store_pblock(&newex, newblock + offset)把本次逻辑块地址300~320映射的起始物理块号newblock保存到newex的ee_start_lo和ee_start_hi成。并执行newex.ee_len = cpu_to_le16(ar.len)把成功映射的物理块数保存到newex.ee_len。ar.len是20,表示本次逻辑块地址300~320成功映射了30个物理块。最后,执行err = ext4_ext_insert_extent(handle, inode, path,&newex, flags)把代表本次逻辑块地址300~320映射关系的newex插入到ext4  extent b+树。

继续,执行ext4_ext_insert_extent()把newex插入到b+树:

  • 1:if (ex && !(flag & EXT4_GET_BLOCKS_PRE_IO))不成立,该if判断里边的代码作用是:判断newex跟ex、ex前边的ext4_extent结构、ex后边的ext4_extent结构逻辑块地址范围是否紧挨着,是的话才能将二者合并,可惜本次执行不到这些代码。
  • 2:继续,if (le16_to_cpu(eh->eh_entries) < le16_to_cpu(eh->eh_max))不成立,eh->eh_entries是叶子节点现在已经使用的ext4_extent个数,eh->eh_max是叶子节点最多能保存的ext4_extent个数,现在叶子节点的ext4_extent全用完了,eh->eh_entries等于eh->eh_max,if (le16_to_cpu(eh->eh_entries) < le16_to_cpu(eh->eh_max))显然不成立。
  • 3:继续,if (le32_to_cpu(newext->ee_block) > le32_to_cpu(fex->ee_block))成立,newext->ee_block是本次插入的ext4_extent的起始逻辑块地址300,fex->ee_block是叶子节点最后一个ext4_extent的起始逻辑块地址280,显然成立。执行到里边的next = ext4_ext_next_leaf_block(path)函数,if (depth == 0)不成立,因为此时b+树深度depth是1。然后,执行depth--变为0,继续执行到while (depth >= 0)循环。第一次循环,if (path[depth].p_idx != EXT_LAST_INDEX(path[depth].p_hdr))不成立,因为此时path[depth].p_idx是根节点第1个ext4_extent_idx位置处的b0,而ext4 extent b+树根节点此时只有ext4_extent_idx即b0,因此b0就是根节点最后一个ext4_extent_idx。故if (path[depth].p_idx != EXT_LAST_INDEX(path[depth].p_hdr))不成立,于是执行depth—后,depth是负数,退出while (depth >= 0)循环 ,ext4_ext_next_leaf_block()函数返回EXT_MAX_BLOCKS。回到ext4_ext_insert_extent()函数,if (next != EXT_MAX_BLOCKS)不成立。
  • 4:继续,执行到ext4_ext_create_new_leaf(handle, inode, flags, path, newext)函数。先执行在该函数里的while (i > 0 && !EXT_HAS_FREE_INDEX(curp))循环,退出循环时curp指向根节点,i是0,if (EXT_HAS_FREE_INDEX(curp))成立,因此此时根节点只用了一个ext4_extent_idx即b0,还剩下3个空闲的ext4_extent_idx 。于是执行ext4_ext_split(handle, inode, flags, path, newext, i)函数。
  • 5:继续,来到ext4_ext_split()函数,形参at是0,if (path[depth].p_ext != EXT_MAX_EXTENT(path[depth].p_hdr))不成立,因为path[depth].p_ext正是叶子节点最后一个ext4_extent即a280。于是执行else分支的border = newext->ee_block,赋值本次要插入的ext4_extent的起始逻辑块地址300。然后执行for (a = 0; a < depth - at; a++)里的newblock = ext4_ext_new_meta_block(handle, inode, path,newext, &err, flags)分配一个物理块,物理块号是newblock,循环只执行一次。这个物理块将来新创建的叶子节点使用,保存叶子节点的4K数据!接着执行bh = sb_getblk(inode->i_sb, newblock)令bh映射newblock的物理块。接着执行neh = ext_block_hdr(bh)及以下的几行代码,这是对新分配的叶子节点头结构ext4_extent_header的赋值。继续执行,m = EXT_MAX_EXTENT(path[depth].p_hdr) - path[depth].p_ext++,计算结果是m=0,因为path[depth].p_ext就是老的叶子节点最后一个ext4_extent。后边的代码,if (m)不成立, k = depth - at – 1=0,while (k--)不成立。最后执行err = ext4_ext_insert_index(handle, inode, path + at,le32_to_cpu(border), newblock),border = newext->ee_block即本次要插入的ext4_extent的起始逻辑块地址300,newblock是保存新创建的叶子节点数据的物理块号,path + at就是path+0.。
  • 6:执行到ext4_ext_insert_index()函数,if (logical > le32_to_cpu(curp->p_idx->ei_block))成立,logical是border(本次要插入的ext4_extent的起始逻辑块地址300),curp->p_idx在第1步已经指向根节点的第一个索引节点,即curp->p_idx=path[0].p_idx = b0curp->p_idx->ei_block是0。则执行ix = curp->p_idx + 1ix指向根节点第2ext4_extent_idx位置处的ext4_extent_idx。继续执行len = EXT_LAST_INDEX(curp->p_hdr) - ix + 1=0,因为此时根节点只有第一个ext4_extent_idx位置处的b0是有效的,EXT_LAST_INDEX(curp->p_hdr)实际正是指向b0。因此if (len > 0)不成立。然后ix->ei_block = cpu_to_le32(logical)=300,即border(本次要插入的ext4_extent的起始逻辑块地址300),执行ext4_idx_store_pblock(ix, ptr),这是把刚新创建的叶子节点的物理块号保存到根节点第2个ext4_extent_idx位置的ext4_extent_idx结构的成员ei_leaf_lo和ei_leaf_hi。最后执行le16_add_cpu(&curp->p_hdr->eh_entries, 1)令根节点有效ext4_extent_idx数由1加到2。此时ext4 extent b+树如下所示。b300就是刚才提的根节点第2个ext4_extent_idx,空的叶子节点是刚新创建的叶子节点。b300这个ext4_extent_idx结构的成员ei_leaf_lo和ei_leaf_hi保存了该叶子节点的4K数据的物理块号,b300这个ext4_extent_idx指向该叶子节点。

7:从ext4_ext_insert_index()返回ext4_ext_split(),再返回到ext4_ext_create_new_leaf(),执行path = ext4_ext_find_extent(inode, (ext4_lblk_t)le32_to_cpu(newext->ee_block),path)重新在ext4_extent查找逻辑块地址最接近newext->ee_block的根节点的ext4_extent_idr和叶子节点ext4_extent。newext->ee_block=300,查找后path[]如下所示:

  • path[0].p_idx = b300//path[0].p_idx指向根节点中逻辑块地址最接近newext->ee_blockext4_extent_idr
  • path[depth].p_ext = NULL//depth=1 path[1].p_idx指向叶子节点中逻辑块地址最接近newext->ee_blockext4_extent,但是这个新创建的叶子节点是空的

8:从ext4_ext_create_new_leaf()回到ext4_ext_insert_extent()函数,因为nearex = path[depth].p_ext是NULL,if (!nearex)成立。执行nearex = EXT_FIRST_EXTENT(eh)令nearex指向新创建的叶子节点第一个ext4_extent位置处(nearex 定义是struct ext4_extent *nearex)。

9:然后执行nearex->ee_block = newext->ee_block=300(本次映射的起始逻辑块地址300);执行ext4_ext_store_pblock(nearex, ext4_ext_pblock(newext))向nearex赋值本次起始逻辑块地址newext->ee_block (300)映射的起始物理块地址;执行nearex->ee_len = newext->ee_len=20,这是本次成功映射的起始物理块个数。ok,这3个赋值后,就相当于把newex成功插入到了ext4  extent B+树的第一个ext4_extent位置处。如下是插入后的示意图,newex就是a300。

2.6: ext4  extent B+树创建索引节点

在上一节的基础上,继续向该ext4  extent B+树添加ext4_extent,把根节点的b300这个ext4_extent_idx指向的叶子节点的ext4_extent全占满(过程与2.4类似)。然后在根节点第3和第4个ext4_extent_idx位置处,创建新的ext4_extent_idx结构,接着再创建他们指向的叶子节点,继续向这些添加插入ext4_extent(过程与2.5类似),直到把ext4  extent B+树的ext4_extent全占满,如下如所示:

 为了演示方便,省略了叶子节点部分ext4_extent演示,此时向ext4  extent B+树插入逻辑块地址是1300~1320的ext4_extent时,函数流程是什么呢?

现在要先找到逻辑块地址1300~1320映射的物理块,起始逻辑块地址map->m_lblk=1300,要映射的逻辑块个数(或者说要分配的连续物理块个数)map->m_len=20。首先执行到ext4_ext_map_blocks(),

1:先定义并定义struct ext4_extent newex变量。接着执行ext4_ext_find_extent(),试图查找逻辑块地址最接近map->m_lblk的索引节点ext4_extent_idr结构和叶子节点ext4_extent结构保存到path[]。此时ext4  extent B+树深度depth是1,则ext4_ext_find_extent()执行后的情况是:

  • path[0].p_idx = b900//path[0].p_idx指向根节点中逻辑块地址最接近map->m_lblkext4_extent_idr,即根节点最后一个ext4_extent_idr结构,即b900
  • path[depth].p_ext = a1200//depth=1 path[1].p_idx指向叶子节点中逻辑块地址最接近map->m_lblkext4_extent,是第4个叶子节点最后一个ext4_extent结构,即a1200

则ex = path[depth].p_ext=a1200

  • 2:故ext4_ext_find_extent()函数开头的ex = path[depth].p_ext后的if (ex)成立,但是if (in_range(map->m_lblk, ee_block, ee_len))不成立,因为map->m_lblk不在a1200的逻辑块的地址范围内。于是执行到newblock = ext4_mb_new_blocks(handle, &ar, &err)针对本次需映射的起始逻辑块地址map->m_lblk(1300)和需映射的逻辑块个数map->m_len(20),分配20个连续的物理块并返回这20个物理块的第一个物理块号给newblock。
  • 3:接着先执行newex.ee_block = cpu_to_le32(map->m_lblk)赋值本次要映射的起始逻辑块地址1300,然后执行ext4_ext_store_pblock(&newex, newblock + offset)把本次逻辑块地址1300~1320映射的起始物理块号newblock保存到newex的ee_start_lo和ee_start_hi成。并执行newex.ee_len = cpu_to_le16(ar.len)把成功映射的物理块数保存到newex.ee_len。ar.len是20,表示本次逻辑块地址1300~1320成功映射了20个物理块。最后,执行err = ext4_ext_insert_extent(handle, inode, path,&newex, flags)把代表本次逻辑块地址1300~1320映射关系的newex插入到ext4  extent b+树。

继续,执行ext4_ext_insert_extent()把newex插入到b+树:

  • 1:if (ex && !(flag & EXT4_GET_BLOCKS_PRE_IO))不成立,该if判断里边的代码作用是:判断newex跟ex、ex前边的ext4_extent结构、ex后边的ext4_extent结构逻辑块地址范围是否紧挨着,是的话才能将二者合并,可惜本次执行不到这些代码。
  • 2:继续,if (le16_to_cpu(eh->eh_entries) < le16_to_cpu(eh->eh_max))不成立,eh->eh_entries是叶子节点现在已经使用的ext4_extent个数,eh->eh_max是根节点最多能保存的ext4_extent个数,现在叶子节点的ext4_extent全用完了,eh->eh_entries等于eh->eh_max,if (le16_to_cpu(eh->eh_entries) < le16_to_cpu(eh->eh_max))显然不成立。
  • 3:继续,if (le32_to_cpu(newext->ee_block) > le32_to_cpu(fex->ee_block))成立,newext->ee_block是本次插入的ext4_extent的起始逻辑块地址1300,fex->ee_block是叶子节点最后一个ext4_extent的起始逻辑块地址1200,显然成立。执行到里边的next = ext4_ext_next_leaf_block(path)函数,if (depth == 0)不成立,因为此时b+树深度depth是1。然后,执行depth--变为0,继续执行到while (depth >= 0)循环。第一次循环,if (path[depth].p_idx != EXT_LAST_INDEX(path[depth].p_hdr))不成立,因为此时path[depth].p_idx是根节点第4个ext4_extent_idx位置处的b900,而ext4 extent b+树根节点最后一个ext4_extent_idx正是b900。故if (path[depth].p_idx != EXT_LAST_INDEX(path[depth].p_hdr))不成立,于是执行depth--后,depth是负数,退出while (depth >= 0)循环 ,ext4_ext_next_leaf_block()函数返回EXT_MAX_BLOCKS。回到ext4_ext_insert_extent()函数,if (next != EXT_MAX_BLOCKS)不成立。
  • 4:继续,执行到ext4_ext_create_new_leaf(handle, inode, flags, path, newext)函数。先执行在该函数里的while (i > 0 && !EXT_HAS_FREE_INDEX(curp))循环,退出循环时curp指向根节点,i是0,if (EXT_HAS_FREE_INDEX(curp))不成立,因为此时根节点4个ext4_extent_idx全用完了,于是执行ext4_ext_grow_indepth(handle, inode, flags, newext)函数。
  • 5:继续,执行ext4_ext_create_new_leaf(handle, inode, flags, path, newext)-> ext4_ext_grow_indepth(handle, inode, flags, newext)。在ext4_ext_grow_indepth()函数里,在根节点下创建一层索引节点,具体过程是执行newblock = ext4_ext_new_meta_block(handle, inode, NULL,newext, &err, flags)分配一个物理块,将来保存索引节点的4K数据。并且把根节点原有的b0、b300、b600、b900这4个ext4_extent_idr复制到新创建的索引节点前个ext4_extent_idr位置处。并且,在根节点第一个ext4_extent_idr位置处创建新的ext4_extent_idr结构,令它的起始逻辑库地址是0(原来在这里的索引节点b0的的起始逻辑块地址),然后把这个新创建的索引节点的物理块号newblock保存到该ext4_extent_idr的ei_leaf_lo和ei_leaf_hi成员。这样通过根节点第一个ext4_extent_idr位置处的ext4_extent_idr的ei_leaf_lo和ei_leaf_hi成员,就能找到它指向索引节点。此时的ext4 extent B+树如下所示,注意,此时b+树的深度depth1变为2。根节点此时只有第一个ext4_extent_idr(编号c0)是有效的,剩余3个ext4_extent_idr并没有使用。通过根节点的c0这个ext4_extent_idr指向新创建的索引节点。

6:从ext4_ext_grow_indepth()返回到ext4_ext_create_new_leaf (),执行path = ext4_ext_find_extent(inode, (ext4_lblk_t)le32_to_cpu(newext->ee_block),path)重新在ext4_extent b+树查找逻辑块地址最接近newext->ee_block的根节点的ext4_extent_idr和叶子节点ext4_extent。newext->ee_block=1300,查找后path[]如下所示:

  • path[0].p_idx = c0//path[0].p_idx指向根节点中逻辑块地址最接近newext->ee_blockext4_extent_idr
  • path[1].p_idx =b900//path[1].p_idx指向索引节点中逻辑块地址最接近newext->ee_blockext4_extent_idr
  • path[depth].p_ext = a1200//depth=2 path[2].p_idx指向叶子节点中逻辑块地址最接近newext->ee_blockext4_extent

7:继续执行,ext4_ext_create_new_leaf ()函数中,if (path[depth].p_hdr->eh_entries == path[depth].p_hdr->eh_max)却成立了。因为path[depth].p_ext指向的根节点ext4_extent全用满了,path[depth].p_hdr->eh_entries表示该叶子节点已经使用的ext4_extent数,path[depth].p_hdr->eh_max表示叶子节点最多能容纳多少个ext4_extent。显然该if成立,于是执行goto repeat,跳转到ext4_ext_create_new_leaf ()函数的repeat分支,执行while (i > 0 && !EXT_HAS_FREE_INDEX(curp))循环。等该循环退出后,i=1,curp指向索引节点,if (EXT_HAS_FREE_INDEX(curp))成立,因为索引节点只用了ext4_extent_idr,还有大把空闲的ext4_extent_idr。接着就执行ext4_ext_split(handle, inode, flags, path, newext, i)函数了。

  • 8:来到ext4_ext_split()函数,形参at是1,if (path[depth].p_ext != EXT_MAX_EXTENT(path[depth].p_hdr))不成立,因为path[depth].p_ext正是叶子节点最后一个ext4_extent即a1200。于是执行else分支的border = newext->ee_block,赋值本次要插入的ext4_extent的起始逻辑块地址1300。然后执行for (a = 0; a < depth - at; a++)里的newblock = ext4_ext_new_meta_block(handle, inode, path,newext, &err, flags)分配一个物理块,物理块号是newblock,循环只执行一次。这个物理块就是新分配的叶子节点!接着执行bh = sb_getblk(inode->i_sb, newblock)令bh映射newblock的物理块。接着执行neh = ext_block_hdr(bh)及以下的几行代码,这是对新分配的叶子节点简单赋值。继续执行,m = EXT_MAX_EXTENT(path[depth].p_hdr) - path[depth].p_ext++,计算结果是m=0,因为path[depth].p_ext就是老的a1200所在的叶子节点最后一个ext4_extent。后边的代码,if (m)不成立, k = depth - at – 1=0,while (k--)不成立。最后执行err = ext4_ext_insert_index(handle, inode, path + at,le32_to_cpu(border), newblock),border = newext->ee_block是本次要插入的ext4_extent的起始逻辑块地址1300,newblock是保存新创建的叶子节点数据的物理块号,path + at就是path+1。
  • 9:执行到ext4_ext_insert_index()函数,if (logical > le32_to_cpu(curp->p_idx->ei_block))成立,logical是border(本次要插入的ext4_extent的起始逻辑块地址1300),curp->p_idx在第5步已经指向索引节点的第4个ext4_extent_idx,即curp->p_idx=path[1].p_idx = b900。则执行ix = curp->p_idx + 1ix指向索引节点第5ext4_extent_idx位置处的ext4_extent_idx。继续执行len = EXT_LAST_INDEX(curp->p_hdr) - ix + 1=0,因为此时索引节点只有前4个ext4_extent_idx是有效的,EXT_LAST_INDEX(curp->p_hdr)实际正是指向b900。因此if (len > 0)不成立。然后ix->ei_block = cpu_to_le32(logical)=1300,即border(本次要插入的ext4_extent的起始逻辑块地址1300)。执行ext4_idx_store_pblock(ix, ptr),这是把刚新创建的叶子节点的物理块号保存到索引节点第5个ext4_extent_idx位置的ext4_extent_idx结构的成员ei_leaf_lo和ei_leaf_hi。最后执行le16_add_cpu(&curp->p_hdr->eh_entries, 1)令索引节点有效ext4_extent_idx数由4加1。此时ext4 extent b+树如下所示,b1300就是刚才提的索引节点第5个ext4_extent_idx,空的叶子节点是刚新创建的叶子节点。b1300这个ext4_extent_idx结构的成员ei_leaf_lo和ei_leaf_hi保存了该叶子节点的4K数据的物理块号,b1300这个ext4_extent_idx指向该叶子节点。

10:从ext4_ext_insert_index()返回ext4_ext_split(),再返回到ext4_ext_create_new_leaf(),执行path = ext4_ext_find_extent(inode, (ext4_lblk_t)le32_to_cpu(newext->ee_block),path)重新在ext4_extent b+树查找逻辑块地址最接近newext->ee_block的根节点的ext4_extent_idr和叶子节点ext4_extent。newext->ee_block=1300,查找后path[]如下所示:

  • path[0].p_idx = c0//path[0].p_idx指向根节点中逻辑块地址最接近newext->ee_blockext4_extent_idr
  • path[1].p_idx =1300//path[1].p_idx指向索引节点中逻辑块地址最接近newext->ee_blockext4_extent_idr
  • path[depth].p_ext = NULL//depth=2 path[2].p_idx指向叶子节点中逻辑块地址最接近newext->ee_blockext4_extent,但这个叶子节点是空的。

12:从ext4_ext_create_new_leaf()回到ext4_ext_insert_extent()函数,因为nearex = path[depth].p_ext是NULL,if (!nearex)成立。执行nearex = EXT_FIRST_EXTENT(eh)令nearex指向新创建的叶子节点第一个ext4_extent位置处(nearex 定义是struct ext4_extent *nearex)。

13:然后执行nearex->ee_block = newext->ee_block=1300(本次映射的起始逻辑块地址1300);执行ext4_ext_store_pblock(nearex, ext4_ext_pblock(newext))向nearex赋值本次起始逻辑块地址newext->ee_block (1300)映射的起始物理块地址;执行nearex->ee_len = newext->ee_len=20,这是本次成功映射的起始物理块个数。Ok,这3个赋值后,就相当于把newex成功插入到了ext4  extent B+树的b1300指向的叶子节点的第一个ext4_extent位置处。如下是插入后的示意图,newex就是a1300。

2.7: ext4  extent B+树继续插入ext4_extent

在上一节的基础上,继续向b1300指向的叶子节点插入逻辑块地址是1330~1350的ext4_extent,函数流程是什么呢?这个函数流程2.4节类似,在ext4_ext_map_blocks() 函数中执行ext4_ext_find_extent()后path如下所示:

  • path[0].p_idx = c0//path[0].p_idx指向根节点中逻辑块地址最接近newext->ee_blockext4_extent_idr
  • path[1].p_idx =1300//path[1].p_idx指向索引节点中逻辑块地址最接近newext->ee_blockext4_extent_idr
  • path[depth].p_ext = a1300//depth=2 path[2].p_idx指向叶子节点中逻辑块地址最接近newext->ee_blockext4_extent,但这个叶子节点是空的。

后续的流程2.4节就很相似了,按部就班把逻辑块地址是1330~1350的ext4_extent插入ext4 extent b+树,这里不再详细介绍了。

继续,一直向ext4 extent b+树b1300指向的叶子节点插入ext4_extent,直到把该叶子节点的ext4_extent全占满,如下图所示:

继续,ext4 extent B+树第2层的索引节点前5个ext4_extent_idx(b0、b300、b600、b900、b1300)指向的叶子节点的ext4_extent全占满了,此时如果向ext4 extent B+树插入逻辑块地址是1600~1620的ext4_extent该怎么办?下边的示意图演示了:

显然,就是在索引节点的第6个ext4_extent_idx(该ext4_extent_idx此时空闲,并未使用)位置处,创建新的ext4_extent_idx,它的起始逻辑块地址1600,我们给它编号b1600。然后创建b1600指向的叶子节点,把逻辑块地址是1600~1620的ext4_extent插入到该叶子节点第一个ext4_extent位置处。这个过程的函数流程与2.5类似,无非ext4 extent B+树此时深度由1变为2。

加下来,继续向b1600这个ext4_extent_idx指向的叶子节点的插入ext4_extent,最后把该叶子节点所有的ext4_extent全占满了。再插入新的ext4_extent时,则在索引节点第7个ext4_extent_idx位置处(b1600后边的那个ext4_extent_idx, 该ext4_extent_idx此时空闲,并未使用)创建新的ext4_extent_idx,然后为这个新的ext4_extent_idx创建叶子节点,把新的ext4_extent插入到该叶子节点第一个ext4_extent位置处。这个过程跟前边b1300那个ext4_extent_idx指向的叶子节点的ext4_extent全占满时,向ext4 extent B+树插入逻辑块地址是1600~1620的ext4_extent的过程是类似的。

加大力度,随着不断向向ext4 extent B+树新的ext4_extent,第2层的索引节点的所有ext4_extent_idx全部被使用,这些ext4_extent_idx指向的叶子节点的ext4_extent也全占满。如下图所示:

在第一篇示意图讲解ext4_extent b+树形成的文章,最后的ext4_extent b+树形态如下。它的函数流程是什么呢?虽然很复杂,但是2.1~2.6节的介绍的知识点也是可以完全解释的,这里就不再啰嗦了。

3 :ext4_exent B+树ext4_extent的分割

第2节向ext4_exent b+树插入的ext4_extent的逻辑块地址都是不连续的,如果现在要插入的逻辑块地址与ext4_exent b+树已有的ext4_extent的逻辑块地址有重叠,会发生什么?函数流程是什么?流程是相当复杂,这节主要介绍这个。

首先看下如下示意图,ext4_exent b+树深度是3,此时向ext4_exent b+树插入逻辑块地址是635~685的ext4_extent,函数流程是什么呢?

现在要先找到逻辑块地址635~685映射的物理块,起始逻辑块地址map->m_lblk=635,要映射的逻辑块个数(或者说要分配的连续物理块个数)map->m_len=50。首先执行到ext4_ext_map_blocks(),

1:先定义并定义struct ext4_extent newex变量。接着执行ext4_ext_find_extent(),试图查找逻辑块地址最接近map->m_lblk的索引节点ext4_extent_idr结构和叶子节点ext4_extent结构保存到path[]。此时ext4  extent B+树深度depth是3,则ext4_ext_find_extent()执行后的情况是:

  • path[0].p_idx = d0//path[0].p_idx指向根节点中逻辑块地址最接近map->m_lblkext4_extent_idr,即根节点第一个ext4_extent_idr结构,即d0
  • path[1].p_idx = c0//path[0].p_idx指向第2层索引节点中逻辑块地址最接近map->m_lblkext4_extent_idr,即c0
  • path[2].p_idx = b600//path[0].p_idx指向第3层索引节点中逻辑块地址最接近map->m_lblkext4_extent_idr,即 b600
  • path[depth].p_ext = a630//depth=3 path[1].p_idx指向叶子节点中逻辑块地址最接近map->m_lblkext4_extent,即a630

则ex = path[depth].p_ext=a630

  • 2:故ext4_ext_find_extent()函数开头的ex = path[depth].p_ext后的if (ex)成立,但是if (in_range(map->m_lblk, ee_block, ee_len))成立,因为map->m_lblk在a630的逻辑块的地址范围内。于是执行里边的newblock = map->m_lblk - ee_block + ee_start,map->m_lblk是本次映射的起始逻辑块地址,ee_block是ex(a630)的起始逻辑块地址,ee_start是ex(a630)起始逻辑块地址映射的物理块地址。这是根据ex(a630)的逻辑块地址与物理块地址映射的线性关系,计算map->m_lblk映射的起始物理块地址,肯定在ex(a630)的逻辑块地址映射的物理块地址范围内。接着执行allocated = ee_len - (map->m_lblk - ee_block)计算成功映射的的物理块个数,ee_len是ex(a630)逻辑块地址630~650映射的物理块个数,即20。map->m_lblk是635, ee_block是630。因此本次映射的逻辑块范围635~685内,只有前半段635~650的逻辑块地址完成了映射,映射了15个物理块。后半段逻辑块地址651~685映射的物理块该怎么办?
  • 3:继续,假设ex是未初始化状态,if (!ext4_ext_is_uninitialized(ex))成立,然后执行ext4_ext_handle_uninitialized_extents(handle, inode, map, path, flags,allocated, newblock),在高版本内核该函数名字改为了ext4_ext_handle_unwritten_extents()。这个函数里验证主要执行了ext4_ext_convert_to_initialized(handle, inode, map, path, flags)函数。于是转到ext4_ext_convert_to_initialized()。
  • 4:进入ext4_ext_convert_to_initialized()函数,if ((map->m_lblk == ee_block) &&(map_len < ee_len) &&(ex > EXT_FIRST_EXTENT(eh)))不成立,因为map_len(本次逻辑块地址635~685映射的物理块数,50)大于ee_len(ex逻辑块地址映射的物理块数20)。继续执行if (allocated) 不成立则执行else分支的allocated = ee_len - (map->m_lblk - ee_block)=20-(635-630)=15,这是计算本次要映射的逻辑块地址635~685在ex的已经完成映射的逻辑块地址630~(630+20)内,捞到已经映射的20个物理块。继续,下边的if (max_zeroout && (ee_len <= max_zeroout))和if (max_zeroout && (allocated > map->m_len))测试都不成立,于是执行allocated = ext4_split_extent(handle, inode, path,&split_map, split_flag, flags)将ex的逻辑块地址进行分割。
  • 5:继续,进入ext4_split_extent()函数。if (map->m_lblk + map->m_len < ee_block + ee_len) 不成立,因为map->m_lblk + map->m_len(本次要映射的结束逻辑块地址,685)大于ex的逻辑块结束地址650。继续,执行path = ext4_ext_find_extent(inode, map->m_lblk, path)没啥用。if (map->m_lblk >= ee_block)成立,map->m_lblk本次要映射的起始逻辑块地址635,ee_block是ex的起始逻辑块地址630。接着执行ext4_split_extent_at(handle, inode, path,map->m_lblk, split_flag1, flags)把ex的逻辑块地址进行分割,630~650分割成630~635和635~650两段。
  • 6:进入ext4_split_extent_at()函数。if (split == ee_block)不成立。执行下边的代码ex->ee_len = cpu_to_le16(split - ee_block);ex2 = &newex;ex2->ee_block = cpu_to_le32(split);ex2->ee_len   = cpu_to_le16(ee_len - (split - ee_block));ext4_ext_store_pblock(ex2, newblock)。这就是把ex的逻辑块地址630~650和物理块地址进行了跟个,ex只保存了逻辑块地址前半段630~635的映射关系,ex2或者newex保持了后半段逻辑块地址635~650的映射关系。然后执行ext4_ext_insert_extent(handle, inode, path, &newex, flags)把后半段的ex2插入ext4_extent b+树。

终于进入了久违的ext4_ext_insert_extent函数,newext此时是ex原来逻辑块地址630~650分割后的后半段635~650,即newext->ee_block=635,newext-> ee_len=650-635=15ex此时的逻辑块地址只有630~635,这点需记住,下文经常用到ex

  • 1:if (ex && !(flag & EXT4_GET_BLOCKS_PRE_IO))不成立,该if判断里边的代码作用是:判断newex跟ex、ex前边的ext4_extent结构、ex后边的ext4_extent结构逻辑块地址范围是否紧挨着,是的话才能将二者合并。不成立,最起码因为newex是未初始化状态,因此ext4_can_extents_be_merged(inode, ex, newext)肯定不成立。
  • 2:继续,if (le16_to_cpu(eh->eh_entries) < le16_to_cpu(eh->eh_max))不成立,eh->eh_entries是ex所在叶子节点现在已经使用的ext4_extent个数,eh->eh_max是该叶子节点最多能保存的ext4_extent个数,现在叶子节点的ext4_extent全用完了,eh->eh_entries等于eh->eh_max,if (le16_to_cpu(eh->eh_entries) < le16_to_cpu(eh->eh_max))显然不成立。
  • 3:继续,if (le32_to_cpu(newext->ee_block) > le32_to_cpu(fex->ee_block))成立,newext->ee_block是本次插入的ext4_extent的起始逻辑块地址635,fex->ee_block是叶子节点最后一个ext4_extent的起始逻辑块地址880,显然不成立。
  • 4:继续,执行到ext4_ext_create_new_leaf(handle, inode, flags, path, newext)函数。先执行在该函数里的while (i > 0 && !EXT_HAS_FREE_INDEX(curp))循环,退出循环时curp指向第2层的索引节点,i是1,if (EXT_HAS_FREE_INDEX(curp))成立,因此第2层的索引节点只用了一个ext4_extent_idx即c0,还剩下很多个空闲的ext4_extent_idx 。于是执行ext4_ext_split(handle, inode, flags, path, newext, i)函数。
  • 5:继续,来到ext4_ext_split()函数,形参at是1,if (path[depth].p_ext != EXT_MAX_EXTENT(path[depth].p_hdr))成立,因为path[depth].p_ext是ex所在叶子节点的第2个ext4_extent即a630,不是叶子节点最后一个ext4_extent。于是border = path[depth].p_ext[1].ee_block,path[depth].p_ext[1]是ex后边的ext4_extent,也就是该叶子节点第3个ext4_extent,故path[depth].p_ext[1].ee_block=700。然后执行for (a = 0; a < depth - at; a++)里的newblock = ext4_ext_new_meta_block(handle, inode, path,newext, &err, flags)分配两个物理块,循环执行两次。这两个物理块,一个是将来新创建的叶子节点使用,保存叶子节点的4K数据,物理块号我们这里称为newblock1;一个是将来新创建的索引节点使用,保存索引节点的4K数据,物理块号我们这里称为newblock2。
  • 6: ext4_ext_split()函数中继续,执行bh = sb_getblk(inode->i_sb, newblock)令bh映射newblock1的物理块。接着执行neh = ext_block_hdr(bh)及以下的几行代码,这是对新创建的叶子节点头头结构ext4_extent_header的赋值。继续,m = EXT_MAX_EXTENT(path[depth].p_hdr) - path[depth].p_ext++,计算结果是m大于0。path[depth].p_ext是叶子节点第2个ext4_extent,EXT_MAX_EXTENT(path[depth].p_hdr)是叶子节点最后一个ext4_extent,这是阶段叶子节点第2个ext4_extent到最后一个ext4_extent之间有多少个ext4_extent结构。后边的代码,if (m)里的memmove(ex, path[depth].p_ext, sizeof(struct ext4_extent) * m),是把叶子节点path[depth].p_ext指向的ext4_extent(即ex,也是第2个ext4_extent a630)后的m个ext4_extent结构移动到前边新分配的叶子节点开头,该叶子节点就增加了m个ext4_extent结构。if (m) 里的le16_add_cpu(&path[depth].p_hdr->eh_entries, -m),是path[depth].p_ext指向的叶子节点减少了m个ext4_extent结构。
  • 7:ext4_ext_split()函数中继续,k = depth - at – 1=1,while (k--)成立,该循环执行一次。这个循环里,先bh = sb_getblk(inode->i_sb, newblock)令bh指向物理块号是newblock2的物理块,这个物理块是新创建的索引节点使用。neh = ext_block_hdr(bh)即下边的几行代码,是对该索引节点的ext4_extent_header头结构赋值,fidx = EXT_FIRST_INDEX(neh)是令fidx指向该索引节点第一个ext4_extent_idx位置处的ext4_extent_idx,然后令fidx->ei_block = border,border前文提过是path[depth].p_ext指向的ext4_extent(即ex,也是第2个ext4_extent  a630)后边的ext4_extent的起始逻辑块地址,即700。接着执行ext4_idx_store_pblock(fidx, oldblock),这是把上边新创建的叶子节点的物理块号newblock1保存到新创建的索引节点的第一个ext4_extent_idx结构(即fidx)的成员ei_leaf_lo和ei_leaf_hi。这样通过fidx就可以找到新创建的叶子节点。
  • 8:ext4_ext_split()函数中继续,执行while (k--)循环里的m = EXT_MAX_INDEX(path[i].p_hdr) - path[i].p_idx++。path[i].p_hdr是b600,这是计算b600所在索引节点中,b600到最后一个索引节点b2000之间的ext4_extent_idx个数。显然m大于0,于是执行memmove(++fidx, path[i].p_idx,sizeof(struct ext4_extent_idx) * m)把b600到最后一个索引节点b2000之间的m个ext4_extent_idx复制到新创建的索引节点第2个ext4_extent_idx位置处及后边。于是,创建的索引节点多了m个ext4_extent_idx结构(le16_add_cpu(&neh->eh_entries, m)),b600所在索引节点少了m个ext4_extent_idx结构(le16_add_cpu(&path[i].p_hdr->eh_entries, -m))。
  • 9:ext4_ext_split()函数中继续,while (k--)循环退出。最后执行err = ext4_ext_insert_index(handle, inode, path + at,le32_to_cpu(border), newblock),border是path[depth].p_ext指向的ext4_extent(即ex,也是第2个ext4_extent  a630)后边的ext4_extent的起始逻辑块地址,即700。newblock(newblock2)是新创建的索引节点的物理块号,path + at是path+1。
  • 10:来到ext4_ext_insert_index()函数,if (logical > le32_to_cpu(curp->p_idx->ei_block))成立,logical是border(700),curp->p_idx在第1步已经指向第2层节点的第一个索引节点c0,即curp->p_idx=path[1].p_idx = c0curp->p_idx->ei_block是0。则执行ix = curp->p_idx + 1ix指向第2层的索引节点第2ext4_extent_idx位置处的ext4_extent_idx。继续执行len = EXT_LAST_INDEX(curp->p_hdr) - ix + 1=0,因为此时第2层节点只有第一个ext4_extent_idx位置处的c0是有效的,EXT_LAST_INDEX(curp->p_hdr)实际正是指向c0。因此if (len > 0)不成立。然后ix->ei_block = cpu_to_le32(logical)=700,即border。执行ext4_idx_store_pblock(ix, ptr),这是把刚新创建的索引节点的物理块号newblock2保存到第2层节点第2个ext4_extent_idx位置的ext4_extent_idx结构的成员ei_leaf_lo和ei_leaf_hi。最后执行le16_add_cpu(&curp->p_hdr->eh_entries, 1)令第2层节点有效ext4_extent_idx数由1加到2。此时ext4 extent b+树如下所示。加红的索引节点和叶子节点是新创建的,第2层的索引节点的c700就是前边说的ix指向的ext4_extent_idx。c700指向新创建的索引节点,这个索引节点的第一个ext4_extent_idx结构又指向新创建的叶子节点。标青色的叶子节点及其ext4_extent 和 标青色的索引节点的ext4_extent_idr都是从ext4 extent b+树原来的索引节点和叶子节点复制过来的,比如b900~b2000这些ext4_extent_idx原来在第3层索引节点b600后边,现在被复制到了新创建的索引节点上。a700~a880这些ext4_extent原来在b600指向的叶子节点 a630这个ext4_extent结构后边,现在被移动到了新创建的叶子节点。

11:从ext4_ext_insert_index()返回ext4_ext_split(),再返回到ext4_ext_create_new_leaf(),执行path = ext4_ext_find_extent(inode, (ext4_lblk_t)le32_to_cpu(newext->ee_block),path)重新在ext4_extent查找逻辑块地址最接近newext->ee_block的索引节点的ext4_extent_idr和叶子节点ext4_extent。newext->ee_block可能都要忘了,它是要插入ext4 extent b+树的ext4_extent的起始逻辑块地址635,查找后path[]如下所示:

  • path[0].p_idx = d0//path[0].p_idx指向根节点中逻辑块地址最接近map->m_lblkext4_extent_idr,即根节点第一个ext4_extent_idr结构,即d0
  • path[1].p_idx = c0//path[0].p_idx指向第2层索引节点中逻辑块地址最接近map->m_lblkext4_extent_idr,即c0
  • path[2].p_idx = b600//path[0].p_idx指向第3层索引节点中逻辑块地址最接近map->m_lblkext4_extent_idr,即 b600
  • path[depth].p_ext = a630//depth=3 path[1].p_idx指向叶子节点中逻辑块地址最接近map->m_lblkext4_extent,即a630

可以发现,ext4_ext_find_extent()执行后,path竟然与最开始一模一样。但是呢a630此时的逻辑块范围已经缩减为630~(635-1)。并且,a630后边的ext4_extent都是空的,有很多坑位了,这得感谢前边ext4_ext_split()函数。

  • 12:从ext4_ext_create_new_leaf()回到ext4_ext_insert_extent()函数,因为nearex = path[depth].p_ext=a630,不是NULL,if (!nearex)不成立。则执行else分支,又因为if (le32_to_cpu(newext->ee_block)> le32_to_cpu(nearex->ee_block))成立,newext->ee_block是本次要插入ext4 extent b+树的ext4_extent的起始逻辑块地址635,nearex->ee_block是a650的起始逻辑块地址630。则执行nearex++,令nearex++指向后边的一个ext4_extent结构,即a630后边的那个位置处的ext4_extent。len = EXT_LAST_EXTENT(eh) - nearex + 1if (len > 0)不成立,因为EXT_LAST_EXTENT(eh)是a630所在叶子节点的最后一个有效的ext4_extent,就是a630,因为该叶子节点此时只有两个有效的ext4_extent,故len = EXT_LAST_EXTENT(eh) - nearex + 1=0。
  • 13:然后执行nearex->ee_block = newext->ee_block=635(本次映射的起始逻辑块地址635,逻辑块范围是635~650);执行ext4_ext_store_pblock(nearex, ext4_ext_pblock(newext))向nearex赋值本次起始逻辑块地址newext->ee_block映射的起始物理块地址;执行nearex->ee_len = newext->ee_len=650-635=15,这是本次成功映射的物理块个数。ok,这3个赋值后,就相当于把newex成功插入到了ext4  extent B+树a630所在叶子节点中第3个ext4_extent位置处,如下图所示,a635就是newex。

继续,我推测会执行ext4_ext_try_to_merge(handle, inode, path, nearex),把a630和a635进行合并,合并后a630吞并了a635的逻辑块范围,a635消失,如下图所示。

折腾了一大圈,a630的逻辑块范围由630~650到630~650。但是总有效果的,a630所在的叶子节点有了很多空闲的ext4_extent,还分割产生了新的索引节点和叶子节点。

我们最初要向ext4_exent b+树插入逻辑块地址是635~685的ext4_extent,要先找到逻辑块地址635~685映射的物理块。现在从ext4_ext_map_blocks()函数返回,只是通过a630找打了逻辑块地址635~650映射的物理块,650~685映射的物理块怎么办?凉拌,再执行一次ext4_ext_map_blocks()找到逻辑块地址650~685映射的物理块即可,这个过程跟2.4节类似。

不得不服,ext4_extent 算法设计的真巧妙,几个函数不是太多的代码就完成了ext4_exent b+树针对ext4_extent结构的各种操作。内核开发者写的每一行代码真的是经过反复推敲的呀!不过ext4_extent内核代码逻辑真的复杂,在最后的举例+示意图下才算较为透彻的理解了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/131646.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

6.3 返回类型和return语句

文章目录无返回值函数有返回值函数值是如何被返回的不要返回局部对象的引用或指针引用返回左值列表初始化返回值主函数main的返回值递归返回数组指针声明一个返回数组指针的函数使用尾置返回类型使用decltypereturn语句终止当前正在执行的函数并将控制权返回到调用该函数的地方…

2022年终总结:点滴积累让我不再迷茫

今年是开始写作的第二年&#xff0c;如果说第一年是起步的话&#xff0c;今年就是开始有了一些小收获了&#xff0c;通过点滴积累让我知道积累的充实感&#xff0c;通过一点一点粉丝或阅读量的积累&#xff0c;增加写作的自信。 今年的收获 首先看一下今年的阅读量和粉丝量: …

cheunghonghui的【22年度总结】

cheunghonghui的【22年度总结】 好久好久没写博客了&#xff0c;看了下后台&#xff0c;上一次发表博客已经是一年半之前&#xff0c;趁着年底&#xff0c;抓紧时间写&#xff08;水&#xff09;一篇不然就要断更了。 【年度工作总结】 1、迭代了未知bug 2、修复了已知bug …

迎接2023,他真的想说“新年快乐”

&#x1f60a;你好&#xff0c;我是小航&#xff0c;一个正在变秃、变强的文艺倾年。 &#x1f514;2023年快要到来啦&#xff0c;再此祝大家诸事顺遂&#xff0c;所见所盼皆如愿。 &#x1f514;本文讲解如何使用Java演奏一首歌曲&#xff0c;一起卷起来叭&#xff01; 众所周…

Faster RCNN网络源码解读(Ⅶ) --- RPN网络代码解析(中)RegionProposalNetwork类解析

目录 一、代码作用&#xff08;rpn_function.py&#xff09; 二、代码解析 2.1 RegionProposalNetwork类 2.1.1 初始化函数__init__ 2.1.2 正向传播过程forward 2.1.3 concat_box_prediction_layers函数 2.1.4 permute_and_flatten 2.1.5 filter_proposals 2.1.6 _…

2022 许些遗憾 年终总结

目录回首过去展望未来验收 2022年任务清单 ---------------------------》 2023年 flag2023 展望回首过去 此刻&#xff0c;想想这一年&#xff0c;口罩&#xff0c;38.5℃&#xff0c;艰难时刻&#xff0c;终究在2022最后十天被确诊了“阳”&#xff0c;没有备任何药&#xff…

Linux系列——Linux操作系统安装及服务控制(1)

作者简介&#xff1a;一名在校云计算网络运维学生、每天分享网络运维的学习经验、和学习笔记。 座右铭&#xff1a;低头赶路&#xff0c;敬事如仪 个人主页&#xff1a;网络豆的主页​​​​​​ 目录 前言 一.Linux介绍 1.Linux是什么&#xff1f; 2.Linux系统的优点 …

ArcGIS基础实验操作100例--实验31纠正栅格坐标

本实验专栏参考自汤国安教授《地理信息系统基础实验操作100例》一书 实验平台&#xff1a;ArcGIS 10.6 实验数据&#xff1a;请访问实验1&#xff08;传送门&#xff09; 高级编辑篇--实验31 纠正栅格坐标 目录 一、实验背景 二、实验数据 三、实验步骤 &#xff08;1&…

2023新年祝福代码[css动画特效]

目录 前言 一、jQuery之家 二、2023新年祝福页面 2.1 我的博客和祝福语 2.2 我的博客头像和动态烟花 ​编辑 2.3 背景为动图 三、完整效果图 总结 前言 心态还需努力呀在这里祝大家新的一年里愿望都能实现。2022年已经过去&#xff0c;2022年的遗憾、不开心&#xff…

Spring学习笔记(1)

Spring系统架构 Spring Framework是Spring生态圈中最基础的项目&#xff0c;是其他项目的根基。 Spring Framework系统架构 学习线路 核心概念 IoC( Inversion of Control )控制反转 使用对象时&#xff0c;由主动new产生对象转换为由外部提供对象&#xff0c;此过程中对象…

DoIP协议从入门到精通—Alive check

惯例,为避免自己成为高知识低文化的汉子,分享一段喜欢的文字: 一、Socket 概念 在DoIP(Diagnostic on IP)概念中,通信的核心是Socket(套接字,实际通信的载体),是车载以太网在诊断范畴进行通信的句柄,Socket是支持TCP/IP协议的网络通信的基本操作单元。对于Socket: …

python多进程的理解 multiprocessing Process join run

最近看了下多进程。 一种接近底层的实现方法是使用 os.fork()方法&#xff0c;fork出子进程。但是这样做事有局限性的。比如windows的os模块里面没有 fork() 方法。 windows&#xff1a; 。linux&#xff1a; 另外还有一个模块&#xff1a;subprocess。这个没整过&#xff0c…

Canvas学习笔记 | 图片操作

图片素材 本篇文章的示例采用下图进行图片操作演示。 图片原始尺寸为&#xff1a;640px * 640px。 绘制图片 在Canvas中&#xff0c;我们使用drawImage()方法绘制图片。drawImage()方法有如下3种调用方式&#xff1a; 1.drawImage(image, dx, dy) 2.drawImage(image, dx, d…

【Android】带你细看Android input系统中ANR的机制

“本文基于Android13源码&#xff0c;分析Input系统的Anr实现原理“ 在文章之前&#xff0c;先提几个问题&#xff1a; 如果在activity任意周期&#xff08;onCreate,onResume等&#xff09;&#xff0c;同步执行耗时超过5s&#xff08;ANR时间&#xff09;的任务&#xff0c;…

JavaWeb06 AJAX 黑马用Axios实现用户名已存在问题? JSON Vue Element

01-AJAX-概述-替换JSP 02-AJAX-快速入门 03-案例-验证用户是否存在 04-Axios-基本使用&请求方式别名(可读性不强) 练习:用Axios替换AJAX实现用户名是否存在功能 就把原来的.html 2.2步换成下面的响应代码即可 为啥就是不行呢????? 05-JSON-概述和基础语法 06-JSON-…

并发编程——4.共享模型之内存

目录4.共享模型之内存4.1.Java 内存模型4.2.可见性4.2.1.退不出的循环4.2.2.解决办法4.2.3.可见性 vs 原子性4.3.终止模式之两阶段终止模式4.3.1.错误思路4.3.2.两阶段终止模式4.4.同步模式之 Balking4.4.1.定义4.4.2.实现4.5.有序性4.5.1.指令级并行原理4.5.2.案例4.6.原理之 …

LVS详解

一、负载均衡&#xff1a;必不可少的基础手段 1.1 找更多的牛来拉车吧 当前大多数的互联网系统都使用了服务器集群技术&#xff0c;集群即将相同服务部署在多台服务器上构成一个集群整体对外提供服务&#xff0c;这些集群可以是Web应用服务器集群&#xff0c;也可以是数据库服务…

新冠“照妖镜”,体质弱点现原形。你是啥症状?2023年,请好好善待你的身体!

新冠“照妖镜”&#xff0c;体质弱点现原形。你是啥症状&#xff1f; 阳性之后的不同症状&#xff0c;是我们身体发出的【预警信号】。 病毒进入时&#xff0c;最先攻击我们自身最薄弱的地方。 2023年&#xff0c;请好好【善待】你的身体&#xff01; 症状1 、头疼 出现头痛…

Kurganov-Tadmor二阶中心格式:理论介绍

简介 CFD的核心问题是求解双曲偏微分方程 ∂∂tu(x,t)∂∂xf(u(x,t))0\frac{\partial}{\partial t} u(x, t)\frac{\partial}{\partial x} f(u(x, t))0 ∂t∂​u(x,t)∂x∂​f(u(x,t))0在CFD中&#xff0c;双曲偏微分方程一般使用Godunov型迎风格式求解。但是这种迎风格式往往实…

2022年度学习总结

2022年有焦虑也有成长&#xff0c;记录和总结也是成长的一部分。这一年&#xff0c;我也努力在不确定性中做一些确定的事情&#xff0c;感恩被保护的三年&#xff0c;三年清零抗疫结束&#xff0c;做好自己健康的第一责任人。研一半个学期在网课或者封校中度过&#xff0c;我们…