本文在前两篇《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找打它指向的索引节点和叶子节点呢?先看下它的数据结构
- struct ext4_extent_idx {
- //起始逻辑块地址
- __le32 ei_block;
- //由ei_leaf_lo和ei_leaf_hi组成起始逻辑块地址对应的物理块地址
- __le32 ei_leaf_lo;
- __le16 ei_leaf_hi;
- __u16 ei_unused;
- };
一个叶子节点或者索引节点都是占一个物理块大小,这里ext4文件系统一个物理块默认是4K。也就是说,一个叶子节点或者索引节点的数据都是4K大小。并且,叶子节点和索引节点的物理块号保存在ext4_extent_idx结构的ei_leaf_lo和ei_leaf_hi成员。因此,正是通过ext4_extent_idx结构的ei_leaf_lo和ei_leaf_hi的成员,找到叶子节点或者索引节点物理块号,然后就可以访问叶子节点的4K数据(ext4_extent_header+N个ext4_extent数据),或者访问索引节点的4K数据(ext4_extent_header+N个ext4_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。则进行如下赋值
- path[0].p_idx = c0//指向根节点起始逻辑块地址最接近map->m_lblk的并且小于等于map->m_lblk的ext4_extent_idx
- path[1].p_idx = d0//指向索引节点中起始逻辑块地址最接近map->m_lblk的并且小于等于map->m_lblk的ext4_extent_idx
- path[2].p_ext = e0//指向叶子节点中起始逻辑块地址最接近map->m_lblk的并且小于等于map->m_lblk的ext4_extent
struct ext4_ext_path *path[]结构体定义如下:
- struct ext4_ext_path {
- /*ext4_ext_find_extent()中赋值,是索引节点时,是由ext4_extent_idx结构的ei_leaf_lo和ei_leaf_hi成员计算出的物理块号,这个物理块保存了下层叶子节点或者索引节点4K数据。是叶子节点时,是由ext4_extent结构的ee_start_hi和ee_start_lo成员计算出的物理块号,这个物理块号是ext4_extent的逻辑块地址映射的的起始物理块号*/
- ext4_fsblk_t p_block;
- //当前索引节点或者叶子节点处于ext4 extent B+树第几层。ext4 extent B+树没有索引节点或者叶子节点时层数是0
- __u16 p_depth;
- //起始逻辑块地址最接近map->m_lblk的ext4_extent
- struct ext4_extent *p_ext;
- //起始逻辑块地址最接近传map->m_lblk的ext4_extent_idx
- struct ext4_extent_idx *p_idx;
- //指向ext4 extent B+索引节点和叶子节点的头结点结构体
- struct ext4_extent_header *p_hdr;
- //保存索引节点或者叶子节点4K数据的物理块映射的bh
- struct buffer_head *p_bh;
- };
- struct ext4_extent_idx {
- //起始逻辑块地址
- __le32 ei_block;
- /*由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成员指向下层的索引节点或者叶子节点。这点非常重要*/
- __le32 ei_leaf_lo;
- __le16 ei_leaf_hi;
- __u16 ei_unused;
- };
- struct ext4_extent {
- //起始逻辑块地址
- __le32 ee_block;
- //逻辑块映射的连续物理块个数
- __le16 ee_len;
- //由ee_start_hi和ee_start_lo一起计算出起始逻辑块地址映射的起始物理块地址
- __le16 ee_start_hi;
- __le32 ee_start_lo;
- };
我们这里只展示了对它的成员p_idx和p_ext赋值,这两个成员最关键,其他成员没展示。
还有一点,ext4 extent内核源码中经常看到ex变量,它的定义是struct ext4_extent *ex,赋值是ex = path[depth].p_ext。depth是ext4 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~10的ext4_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+树的深度depth由0变为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_block的ext4_extent_idr
- path[depth].p_ext = a80//depth=1, path[1].p_idx指向叶子节点中逻辑块地址最接近newext->ee_block的ext4_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_lblk的ext4_extent_idr,即根节点第一个ext4_extent_idr结构,即b0
- path[depth].p_ext = a100//depth=1 ,path[1].p_idx指向叶子节点中逻辑块地址最接近map->m_lblk的ext4_extent,叶子节点第5个ext4_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_lblk的ext4_extent_idr,即根节点第一个ext4_extent_idr结构,即b0
- path[depth].p_ext = a280//depth=1 ,path[1].p_idx指向叶子节点中逻辑块地址最接近map->m_lblk的ext4_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 = b0,curp->p_idx->ei_block是0。则执行ix = curp->p_idx + 1令ix指向根节点第2个ext4_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_block的ext4_extent_idr
- path[depth].p_ext = NULL//depth=1, path[1].p_idx指向叶子节点中逻辑块地址最接近newext->ee_block的ext4_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_lblk的ext4_extent_idr,即根节点最后一个ext4_extent_idr结构,即b900
- path[depth].p_ext = a1200//depth=1 ,path[1].p_idx指向叶子节点中逻辑块地址最接近map->m_lblk的ext4_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+树的深度depth由1变为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_block的ext4_extent_idr
- path[1].p_idx =b900//path[1].p_idx指向索引节点中逻辑块地址最接近newext->ee_block的ext4_extent_idr
- path[depth].p_ext = a1200//depth=2, path[2].p_idx指向叶子节点中逻辑块地址最接近newext->ee_block的ext4_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 + 1令ix指向索引节点第5个ext4_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_block的ext4_extent_idr
- path[1].p_idx =1300//path[1].p_idx指向索引节点中逻辑块地址最接近newext->ee_block的ext4_extent_idr
- path[depth].p_ext = NULL//depth=2, path[2].p_idx指向叶子节点中逻辑块地址最接近newext->ee_block的ext4_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_block的ext4_extent_idr
- path[1].p_idx =1300//path[1].p_idx指向索引节点中逻辑块地址最接近newext->ee_block的ext4_extent_idr
- path[depth].p_ext = a1300//depth=2, path[2].p_idx指向叶子节点中逻辑块地址最接近newext->ee_block的ext4_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_lblk的ext4_extent_idr,即根节点第一个ext4_extent_idr结构,即d0
- path[1].p_idx = c0//path[0].p_idx指向第2层索引节点中逻辑块地址最接近map->m_lblk的ext4_extent_idr,即c0
- path[2].p_idx = b600//path[0].p_idx指向第3层索引节点中逻辑块地址最接近map->m_lblk的ext4_extent_idr,即 b600
- path[depth].p_ext = a630//depth=3 ,path[1].p_idx指向叶子节点中逻辑块地址最接近map->m_lblk的ext4_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=15。ex此时的逻辑块地址只有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 = c0,curp->p_idx->ei_block是0。则执行ix = curp->p_idx + 1令ix指向第2层的索引节点第2个ext4_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_lblk的ext4_extent_idr,即根节点第一个ext4_extent_idr结构,即d0
- path[1].p_idx = c0//path[0].p_idx指向第2层索引节点中逻辑块地址最接近map->m_lblk的ext4_extent_idr,即c0
- path[2].p_idx = b600//path[0].p_idx指向第3层索引节点中逻辑块地址最接近map->m_lblk的ext4_extent_idr,即 b600
- path[depth].p_ext = a630//depth=3 ,path[1].p_idx指向叶子节点中逻辑块地址最接近map->m_lblk的ext4_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 + 1后if (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内核代码逻辑真的复杂,在最后的举例+示意图下才算较为透彻的理解了。