转载:https://www.xuebuyuan.com/1470646.html
概述
在ext4文件系统中,该函数扮演了非常重要的角色,虽然其实现如此短小精悍,其主要作用是根据逻辑块号找到所在的extent,进而可以找到其物理块号,因为每个extent中的所有磁盘块在物理位置上连续的。
看看函数的定义:
struct ext4_ext_path * ext4_ext_find_extent(struct inode *inode, ext4_lblk_t block, struct ext4_ext_path *path)
- inode:代表要映射的文件;
- block:要映射的逻辑块,该函数的目的就是根据该逻辑块号找到所在的extent并返回;
- path:保存查找过程中的路径,一般调用者传入的是NULL,由函数内部分配空间,而且会作为返回值返回给调用者;
- 返回值:查找成功则返回查找路径,根据查找路径的最后一部分即可获得block所在的extent信息。
实现
之前的博客中我们知道,ext4文件系统中文件在磁盘上的存储位置以extent的形式存储在一棵B树上。而我们这里的查找无非就是根据逻辑块号一级级地查找该B树,直到最终定位到逻辑块号所在的extent。好了,废话少说,让我们来看看查找的过程。
struct ext4_ext_path *
ext4_ext_find_extent(struct inode *inode, ext4_lblk_t block,
struct ext4_ext_path *path)
{
struct ext4_extent_header *eh;
struct buffer_head *bh;
short int depth, i, ppos = 0, alloc = 0;
eh = ext_inode_hdr(inode);
depth = ext_depth(inode);
/* account possible depth increase */
if (!path) {
path = kzalloc(sizeof(struct ext4_ext_path) * (depth + 2),
GFP_NOFS);
if (!path)
return ERR_PTR(-ENOMEM);
alloc = 1;
}
path[0].p_hdr = eh;
path[0].p_bh = NULL;
i = depth;
/* walk through the tree */
/* 如果树的深度大于0,我们首先查找索引节点*/
while (i) {
int need_to_validate = 0;
ext_debug("depth %d: num %d, max %d\n",
ppos, le16_to_cpu(eh->eh_entries), le16_to_cpu(eh->eh_max));
/*在索引节点块中利用二分查找定位block的映射路径*/
/*找到的下一级路径记录在path[ppos]的各个成员中*/
ext4_ext_binsearch_idx(inode, path + ppos, block);
path[ppos].p_block = idx_pblock(path[ppos].p_idx);
path[ppos].p_depth = i;
path[ppos].p_ext = NULL;
/*从磁盘上读出下一级路径所在的磁盘块*/
bh = sb_getblk(inode->i_sb, path[ppos].p_block);
if (unlikely(!bh))
goto err;
if (!bh_uptodate_or_lock(bh)) {
if (bh_submit_read(bh) < 0) {
put_bh(bh);
goto err;
}
/* validate the extent entries */
need_to_validate = 1;
}
eh = ext_block_hdr(bh);
ppos++;
if (unlikely(ppos > depth)) {
put_bh(bh);
EXT4_ERROR_INODE(inode,
"ppos %d > depth %d", ppos, depth);
goto err;
}
path[ppos].p_bh = bh;
path[ppos].p_hdr = eh;
i--;
if (need_to_validate && ext4_ext_check(inode, eh, i))
goto err;
}
path[ppos].p_depth = i;
path[ppos].p_ext = NULL;
path[ppos].p_idx = NULL;
/*在映射路径的最后一级(叶子节点) 查找需要的extent*/
ext4_ext_binsearch(inode, path + ppos, block);
/* if not an empty leaf */
if (path[ppos].p_ext)
path[ppos].p_block = ext_pblock(path[ppos].p_ext);
ext4_ext_show_path(inode, path);
return path;
err:
ext4_ext_drop_refs(path);
if (alloc)
kfree(path);
return ERR_PTR(-EIO);
}
上述代码逻辑非常清楚,根据B树的深度,一直从根节点查找到叶子节点,对于只有叶子节点的B树,其深度为0。查找之前需要进行一些准备工作,分配查找路径(如果调用者尚未分配的话),存储查找路径中每个节点的信息,最终如果查找成功,还需要返回给调用者完整的查找路径。查找路径中的每个节点的结构体如下:
struct ext4_ext_path {
ext4_fsblk_t p_block; //所在的物理块号
__u16 p_depth; //所处深度,叶子节点的话,深度为0
struct ext4_extent *p_ext; //如果是叶子节点,其存储最终找到的extent
struct ext4_extent_idx *p_idx; //如果是中间节点。其存储索引extent
struct ext4_extent_header *p_hdr; //该extent所在的block的头部信息
struct buffer_head *p_bh; //该extent所在的block的数据在内存中的缓存信息
};
该数据结构只是对查找过程中经历的路径(extent)作一次封装而已。因为路径中的每个节点要么是索引extent(ext4_extent_idx),要么是叶子extent(ext4_extent),而且记录了extent所在的物理块信息(主要是针对索引extent而言)。
有了记录查找路径的数据结构,接下来我们的工作就是真正滴查找了,查找沿着下图的路径进行,查找的起点是B+树的根节点(存储在inode->i_data[]中)。如果当前B+树的深度大于0,意味着我们需要查找中间的索引extent。这是通往查找的最终叶子节点的必经之路。对于B+树的每个节点来说,其中的数据都是有序的,所以每到一级都需要进行二分查找来决定下一级该往哪里走。
举例来说,我在ext4文件系统中创建文件test,并且写入了一些数据(不多,但刻意安排过写入位置),形成的文件B+树结构如下:
根据上图我们就能够知道,文件test的B+树深度为1(有一级索引),其中根节点的索引项有2个,第一个索引extent索引的逻辑块号从0开始,而第二个索引extent索引的逻辑块号从8820开始。因为只有两个索引extent,因此叶子节点的磁盘块也只有两个,第一个extent的磁盘块如上图左边所示,里面保存了extent_header和extent(包含extent映射的起始逻辑块号),第二个extent磁盘块如右图所示,该块尚未完全利用。
现在,假如我们要查找逻辑块号为8711对应的物理块号,即查找该逻辑块所在的extent。
首先,我们从根节点开始查找,当前树的根节点只有两个索引项,第一个索引项索引逻辑块号位于[0,8820)之间的那些逻辑块,第二个索引项索引逻辑块号大于8820的那些逻辑块。我们要查找的逻辑块号为8711,因此定位到第一个索引项,并获取该索引项指向的物理块号,读出该块内容。
接下来,我们判断出已经进入了叶子节点,因此接下来需要查找该叶子节点块中的内容,定位到8711属于的extent,因为块内的数据有序,因此,利用二分查找即可。我们顺利找到8711所属的extent[8710, 8714]。
附:
上述函数在实现的过程中有利用二分查找来定位索引项的位置,理解这个函数的实现对我们理解ext4_ext_find_extent的实现原理非常重要。这里就来简单描述该函数的实现原理。ext4_ext_find_extent有两个地方进行二分查找,第一是索引块中索引项的查找,第二是叶子块中叶子extent的查找。虽然调用的函数不同,但它们的实现机理相同。我们就以索引块中索引项的二分查找为例。
首先,我们要明白,我们二分查找目的是找到逻辑块号所属的索引项,即第一个大于逻辑块号的索引项之前那一项,如果不存在该索引项,那么返回最后一个索引项。举例来说,如果索引块中索引项的排列如下:
如果我们要利用二分查找逻辑块号为15的索引项,首先找到第一个大于15的索引项(第二项50所在),然后返回其之前的那一项(10),因为10代表的索引项的意思是该索引项指向的下一级路径中包含了所有逻辑块大于等于10而小于50的extent。如果我们要查找逻辑块号为600的索引项,那只能返回最后一个索引项。其实现如下:
static void
ext4_ext_binsearch_idx(struct inode *inode,
struct ext4_ext_path *path, ext4_lblk_t block)
{
struct ext4_extent_header *eh = path->p_hdr;
struct ext4_extent_idx *r, *l, *m;
ext_debug("binsearch for %u(idx): ", block);
l = EXT_FIRST_INDEX(eh) + 1;
r = EXT_LAST_INDEX(eh);
while (l <= r) {
m = l + (r - l) / 2;
if (block < le32_to_cpu(m->ei_block))
r = m - 1;
else
l = m + 1;
ext_debug("%p(%u):%p(%u):%p(%u) ", l, le32_to_cpu(l->ei_block),
m, le32_to_cpu(m->ei_block),
r, le32_to_cpu(r->ei_block));
}
path->p_idx = l - 1;
ext_debug(" -> %d->%lld ", le32_to_cpu(path->p_idx->ei_block),
idx_pblock(path->p_idx));
}