继续走,233行,设置map_bh的b_page字段为当前page。随后进入循环,对于页中的每一块,调用ext2文件系统的get_block函数,作为参数传递page的inode、相对于文件起始位置的块索引block_in_file、map_bh进去,最后返回相对于磁盘分区开始位置的逻辑块号,即相对于磁盘或分区开始位置的块索引,存放在结果参数map_bh的b_blocknr字段中。所以这里我们重点关注get_block的原型,ext2_get_block函数,来自fs/ext2/inode.c:
547int ext2_get_block(struct inode *inode, sector_t iblock, struct buffer_head *bh_result, int create)
548{
549 int err = -EIO;
550 int offsets[4];
551 Indirect chain[4];
552 Indirect *partial;
553 unsigned long goal;
554 int left;
555 int boundary = 0;
556 int depth = ext2_block_to_path(inode, iblock, offsets, &boundary);
557
558 if (depth == 0)
559 goto out;
560
561reread:
562 partial = ext2_get_branch(inode, depth, offsets, chain, &err);
563
564 /* Simplest case - block found, no allocation needed */
565 if (!partial) {
566got_it:
567 map_bh(bh_result, inode->i_sb, le32_to_cpu(chain[depth-1].key));
568 if (boundary)
569 set_buffer_boundary(bh_result);
570 /* Clean up and exit */
571 partial = chain+depth-1; /* the whole chain */
572 goto cleanup;
573 }
574
575 /* Next simple case - plain lookup or failed read of indirect block */
576 if (!create || err == -EIO) {
577cleanup:
578 while (partial > chain) {
579 brelse(partial->bh);
580 partial--;
581 }
582out:
583 return err;
584 }
585
586 /*
587 * Indirect block might be removed by truncate while we were
588 * reading it. Handling of that case (forget what we've got and
589 * reread) is taken out of the main path.
590 */
591 if (err == -EAGAIN)
592 goto changed;
593
594 goal = 0;
595 if (ext2_find_goal(inode, iblock, chain, partial, &goal) < 0)
596 goto changed;
597
598 left = (chain + depth) - partial;
599 err = ext2_alloc_branch(inode, left, goal,
600 offsets+(partial-chain), partial);
601 if (err)
602 goto cleanup;
603
604 if (ext2_use_xip(inode->i_sb)) {
605 /*
606 * we need to clear the block
607 */
608 err = ext2_clear_xip_target (inode,
609 le32_to_cpu(chain[depth-1].key));
610 if (err)
611 goto cleanup;
612 }
613
614 if (ext2_splice_branch(inode, iblock, chain, partial, left) < 0)
615 goto changed;
616
617 set_buffer_new(bh_result);
618 goto got_it;
619
620changed:
621 while (partial > chain) {
622 brelse(partial->bh);
623 partial--;
624 }
625 goto reread;
626}
ext2_get_block函数代码比较复杂,怎么吃透它呢?首先,请大家访问博客“Ext2数据块分配” http://blog.csdn.net/yunsongice/archive/2010/08/18/5822495.aspx补充一下数据块寻址的预备知识;然后我在网上找了一个ext2_get_block函数调用层次图,如下:
ext2_get_block()将对文件系统的逻辑块号转换为对块设备的逻辑块号,这种转换关系是由ext2_inode结构中i_block[]数组描述的。i_block[]的前12项为直接块索引表,第13项为间接索引块指针,第14项为二重索引块指针,第15项为三重索引块指针。当文件长度不超过12个块时(一个块是1024字节,12个块就是12k),可通过直接块索引表直接定位目标块,当文件长度超过12块并且剩余的部分不超过间接索引块索引数量时,就在间接索引块中定位目标块,依次类推。
ext2_get_block函数功能是从相对于文件开始位置的块索引转换为相对于磁盘分区开始位置的逻辑块号,若对应逻辑块被删除,则重新分配得到它间接块路径。系统是以块索引查找逻辑块的。例如,要找到第100个逻辑块对应的逻辑块,因为256+12>100>12,所以要用到一次间接块,在一次间接块中查找第88项,此项内容就是对应的逻辑块的地址。
首先556行,ext2_block_to_path得到block_in_file位于直接块还是n次间接块。该函数只返回四个可能的值:如果是直接块中,则返回1;如果是间接块则返回2;如果是二次间接则返回3;如果是三次间接则返回4。
static int ext2_block_to_path(struct inode *inode, long i_block, int offsets[4], int *boundary)
{
//每块地址数=块大小 / 每个指针大小即32位,一般为1kbyte/32bit=256
int ptrs = EXT2_ADDR_PER_BLOCK(inode->i_sb);
int ptrs_bits = EXT2_ADDR_PER_BLOCK_BITS(inode->i_sb);
//直接块数direct_blocks =12
const long direct_blocks = EXT2_NDIR_BLOCKS,
//一次间接块数indirect_blocks =256 (256k字节)
indirect_blocks = ptrs,
//二次间接块数double_blocks =256*256 (65536k字节,64MB字节)
double_blocks = (1 << (ptrs_bits * 2));
int n = 0;
if (i_block < 0) {
ext2_warning (inode->i_sb, "ext2_block_to_path", "block < 0");
} else if (i_block < direct_blocks) {
offsets[n++] = i_block;//直接块
} else if ( (i_block -= direct_blocks) < indirect_blocks) {
offsets[n++] = EXT2_IND_BLOCK;//一次间接块
offsets[n++] = i_block;
} else if ((i_block -= indirect_blocks) < double_blocks) {
offsets[n++] = EXT2_DIND_BLOCK;//二次间接块
offsets[n++] = i_block >> ptrs_bits;//一次间接块
offsets[n++] = i_block & (ptrs - 1);//直接块
} else if (((i_block -= double_blocks) >> (ptrs_bits * 2)) < ptrs) {
offsets[n++] = EXT2_TIND_BLOCK;//三次间接块
offsets[n++] = i_block >> (ptrs_bits * 2);//二次间接块
offsets[n++] = (i_block >> ptrs_bits) & (ptrs - 1); //一次间接块
offsets[n++] = i_block & (ptrs - 1); //直接块
} else {
ext2_warning (inode->i_sb, "ext2_block_to_path", "block > big");
}
if (boundary)
*boundary = (i_block & (ptrs - 1)) == (final - 1);
return n;
}
ext2_block_to_path的返回值保存在ext2_get_block函数的内部变量depth中,然后ext2_get_block执行562行的ext2_get_branch函数,从逻辑块中读取数据到chain的buffer中。
函数的参数说明如下:
inode: 文件VFS的索引节点
depth: 间接块深度(如1 —— 一次间接块指针)
offsets: 间接逻辑块的指针数组
chain: 存储读取逻辑块的数据
err: 存储错误标识
函数ext2_get_branch的功能是填充Indirect结构的数组,如果运行正常,则返回NULL。
typedef struct {
u32 *p; 索引块中索引项的地址
u32 key; 索引块中索引项的值
struct buffer_head *bh; 索引块所在的缓冲区
} Indirect;
static inline void add_chain(Indirect *p, struct buffer_head *bh, __le32 *v)
{
p->key = *(p->p = v);
p->bh = bh;
}
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;
//初始化chain
add_chain (chain, NULL, EXT2_I(inode)->i_data + *offsets);
//depth为间接块深度,如三次间接块深度为4
while (--depth) {
bh = sb_bread(sb, le32_to_cpu(p->key));
……
//读出逻辑块数据到chain中,p为chain数组的指针
add_chain(++p, bh, (__le32*)bh->b_data + *++offsets);
if (!p->key)
goto no_block;
}
return NULL;
……
}
这个函数比较绕脑子,我们还是举个例子吧。假如block_in_file的磁盘索引节点逻辑块号较大,比方说16741216,其大于256*256,小于256*256*256,那么肯定是3次间接块。所以在ext2_block_to_path函数中,offsets[0]=EXT2_TIND_BLOCK,也就是15,作为三次间接块存放二次间接块的逻辑块号;offsets[1]= 16741216 >> (8*2),等于255,也就是作为二次间接块存放一次间接块的逻辑块号;offsets[2]=16741216 >> 8 & (256 -1)等于65395,作为一次间接块存放直接寻址块的逻辑块号;offsets[3]=16741216 & (256 -1)等于16741216,就是直接块,也就是我们的block_in_file的逻辑块号。
那么进入ext2_get_branch函数以后,首先调用add_chain函数初始化作为参数传递进来的Indirect变量空数组chain,有4个元素,仅初始化第一个Indirect元素的p字段为对应ext2_inode_info结构的i_data + *offsets,就是i_data[15]的值。随后进入while (--depth)循环,depth这里等于4,首先通过sb_bread读取i_data[15]对应的块到块设备页高速缓存中,然后调用add_chain函数将i_data[15]对应的块存放的第255项的内容,也就是第二次间接寻址的逻辑块号加入到chain[1]的p中。这样第二次、第三次循环后,chain[2]和chain[3]就分别对应一次间接块地址和直接地址的逻辑地址,就存放在Indirect结构的p字段中,而对应具体的逻辑块号就存在他们的key字段中,同时保留对应缓冲块的buffer_head结构。眼睛看晕了就看看我画的关系图:
如图,经过ext2_block_to_path和ext2_get_branch这么一折腾,ext2_get_block中的内部变量chain[4]就被赋上值了,其中chain[3]的key字段就存放了相对于文件开始位置的block_in_file(也就是我们例子中的16741216)对应的逻辑块号。而partial内部变量是NULL。
随后跳到ext2_get_block的567行,调用map_bh函数将bh_result这个buffer_head的b_bdev、b_blocknr和b_size字段分别设置为该文件超级块的设备,chain[3]的key字段(block_in_file对应的逻辑块号)和块大小1024。然后释放brelse刚才chain数组中用了临时存放块的高速缓存并返回。