ext2_get_branch解析

ext2文件系统采用了直接和间接映射的方式来保存逻辑块(逻辑号从1开始(一般是相对于数据区的首地址而言),1一般给了/目录文件)至物理块(文件系统的第1个block给了MBR,数据区的块号并不是1)的映射关系。因此,在每次读或者写某个逻辑块之前,需要查找这种映射关系,将逻辑块号转化为物理块号。这就是ext2_get_block的主要作用,另外,如果要访问的逻辑块尚未分配物理磁盘块,也会在该函数中为该逻辑块分配数据块以及可能所需的间接块,然后建立好映射关系。这一般出现在写情况中。

        关于ext2文件系统如何通过直接块和间接块来建立逻辑块至物理块之间的映射可参考之前的博客,今天我们所要描述的函数是ext2_get_branch,从字面上来看,该函数是完成从逻辑块号到物理块号的主战场,如果一切正常,从该函数返回时,这种映射关系就已经建立了,但是需要注意的是:如果该逻辑块尚未分配物理块,该函数是不负责分配的,它只负责查找到已经建立好映射的地方,至于未映射的关系会在后面处理。

        我们举个例子来描述该代码的具体实现逻辑。假设我们需要读一个起始块号为281的逻辑块,读的块数目为2,根据ext2文件系统映射方式,直接映射可映射的数据块数量为12,假设文件系统块大小为1k,那么0~12K大小的文件可通过直接映射方式来索引;一级间接块映射的方式最多可索引的块大小为1K * (1K/4)= 256K,因此直接映射+一级间接映射的方式最多可索引文件大小为268K,而现在我们需要读取的逻辑块号为281K,必须采用二级间接映射的方式才可索引该逻辑块。如下图所示:

从上图可以看到,逻辑块号为281的物理块号存储在上图所示的一级间接块的第13项(第一项存储的逻辑块号是268对应的物理块号),而该一级间接块所在的物理块号又被存储在二级间接块的偏移为0 的地方(间接块的每一项存储的是下一级索引或者数据块的物理块号,每项占用4个字节)。而该二级间接块的物理块号则是存储在EXT2_I(inode)->i_data[13]中,这也是我们查找的起点,因此,整的来看,我们的主要目的就是从起点开始,一步一步定位到最终找到逻辑块号对应的物理块号

        以本例来说,我们自己可以想象的查找过程:

(1)确定281该逻辑块号采用的是二级映射,使用i_data[13]

(2)从i_data[13]中获取到二级间接块的物理块号,假如为199,如果尚未读出到内存,则读出该块内容到内存,如果内存已经有,则找到它

(3)计算该块在二级间接块中的偏移(本例中偏移为0)

(4)读出该偏移处(偏移量为0的4个字节)的物理块号,假如为200,这就是一级间接块的物理块号

(5)跳转到(3),重复上述过程,读出一级间接块内容,再来计算该逻辑块号在一级间接块中的偏移(该间接块映射的逻辑块号范围是268 ~ 523,因此逻辑块281的偏移为13,所以一级间接块的第13项存储的即是逻辑块号281的物理块号)

(6)至此,我们便一步步地查找了逻辑块对应的物理块号。

        其实内核在实现的时候并不是按照我上述的流程来的,文件系统的做法:

首先会计算好该逻辑块号所需的映射深度(直接映射or一级间接映射or二级间接映射or三级间接映射),并计算好每级映射块的物理块号在上级映射块中的存储位置,将这些位置存储在一个偏移量数组offsets[]中。如上例中,281该逻辑块的物理块号在一级间接块的偏移量为13,而一级间接块其物理块号存储在二级间接块的偏移量为0处,二级间接块的物理块号存储在i_data[13]中。因此,整个offsets[]的便是{13, 0, 13}。有了这个offsets数组,就可以唯一确定要找的block的位置,根据13-->0-->13就可以找到我们所需需要的block的存储位置了,从这个位置读取4个字节,就是对应的逻辑块号。

       好了,言归正传,我们来看看ext2_get_branch()的实现:

static Indirect *ext2_get_branch(struct inode *inode,
				 int depth,
				 int *offsets,
				 Indirect chain[4],
				 int *err)
{
	struct super_block *sb = inode->i_sb;
	Indirect *p = chain;
	struct buffer_head *bh;
 
	*err = 0;
	/* i_data is not going away, no lock needed */
	add_chain (chain, NULL, EXT2_I(inode)->i_data + *offsets);
	//如果p->key为0,说明尚未映射
	if (!p->key)
		goto no_block;
	while (--depth) {
		bh = sb_bread(sb, le32_to_cpu(p->key));
		if (!bh)
			goto failure;
		read_lock(&EXT2_I(inode)->i_meta_lock);
		if (!verify_chain(chain, p))
			goto changed;
		add_chain(++p, bh, (__le32*)bh->b_data + *++offsets);
		read_unlock(&EXT2_I(inode)->i_meta_lock);
		if (!p->key)
			goto no_block;
	}
	return NULL;
 
changed:
	read_unlock(&EXT2_I(inode)->i_meta_lock);
	brelse(bh);
	*err = -EAGAIN;
	goto no_block;
failure:
	*err = -EIO;
no_block:
	return p;
}

该函数的实现,基本上就是按照我前面所说的那样,从梦开始的地方一级级地查找,直到最终找到我们想要的或者是到某个地方映射关系被打断(为什么会断呢?在写文件的过程中,找不到某个块,此时需要新建一个块)。这里需要仔细看看的可能就是内核实现的时候使用了一个数据结构Indirect。

typedef struct {
	__le32	*p;
	__le32	key;
	struct buffer_head *bh;
} Indirect;

该数据结构记录了映射链中每一级的索引信息。p是索引指针,指向记录下一级索引物理块号(或者数据块)的存储位置,key记录p指针里面的内容,key=0意味着映射链的断裂,bh则指向该当前间接块被从磁盘读出保存在内存中的数据结构(即与磁盘块一一对应的buffer_head结构)。还是以上面的例子来说,如下图:

上图中的左下角的数据结构中完整地记录了二级间接块时当前的索引全景,branch->p是指向存储一级间接块物理块号的指针(指向了二级间接块中偏移量为0的那个地址),branch->key记录该指针处的内容,即物理块号(对应的一级逻辑块号),branch->bh则指向的是该二级间接块被从磁盘中读出并存储在内存中的数据结构(buffer_head),branch->bh是二级间接块本身对应在缓存中的缓冲头。

        让我们在回头看看ext2_get_branch()的代码,首先看看该函数的参数:

  •     inode:自然是表征读写文件;
  •     depth:调用者计算的逻辑块号需要使用的映射的深度,直接映射该值为1,一级映射该值为2......最多三级映射,值为4;
  •     offsets:就是前面我们说到的,在每一级映射的哪个偏移处我们可以寻找到下一级的物理块号,因为最多有三级映射,所以数组最多使用4项即可;
  •     chain[4]:包含每一级映射的详细信息,因为最多使用3级间接映射,因此同offsets[],有4项就够用了;
  •     err:记录该函数的出错情况,返回给调用者。

        弄懂了上面这些内容后,我们再来看看该函数的实现就显得较为直白了,我们会根据映射的深度来决定循环的次数。首先,我们会将最初查找的索引信息添加到chain[0]中,作为映射链的最开始,add_chain (chain, NULL, EXT2_I(inode)->i_data + *offsets);其实现也非常简单,如下:

static inline void add_chain(Indirect *p, struct buffer_head *bh, __le32 *v)
{
	p->key = *(p->p = v);
	p->bh = bh;//初始的时候,bh=NULL,因为此时不是通过间接块来存储索引,而是i_data[];
}

接下来,我们从映射链的起始地方开始一级一级地往下查找,代码如下:

while (--depth) {
		bh = sb_bread(sb, le32_to_cpu(p->key));
		if (!bh)
			goto failure;
		read_lock(&EXT2_I(inode)->i_meta_lock);
		if (!verify_chain(chain, p))
			goto changed;
		add_chain(++p, bh, (__le32*)bh->b_data + *++offsets);
		read_unlock(&EXT2_I(inode)->i_meta_lock);
		if (!p->key)
			goto no_block;
	}
	return NULL;

查找结束条件是已经到了最后一级间接映射或者映射链断裂(p->key = 0,这对应着写情况),到每一级的时候将从其到下一级的映射关系记录在映射链中,以便可继续往下查找(add_chain(++p, bh, (__le32*)bh->b_data + *++offsets))。

        接下来让我们看看程序结束时的返回值状况,通过代码我们知道,如果一直找到了最后一级映射(可以通过逻辑块找到其物理块号),并且其中没有断链,而且该逻辑块业已分配物理块,那么此时大功告成,返回值为NULL,如果在其中某个地方断链了,即此时逻辑块对应的物理块和建立映射需要的间接块可能未分配,那么返回值就为断链处的详细信息p(Indirect结构)。
 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值