跟索引节点一样,Ext2也对磁盘数据块进行分配与释放。在详细分析相关代码之前,先引出两个重要的预备,一个是数据块寻址,一个是文件的洞
1 数据块寻址
每个非空的普通文件都由一组数据块组成。这些块或者由文件内的相对位置(它们的文件块号)来标识,或者由磁盘分区内的位置(它们的逻辑块号)来标识。
从文件内的偏移量 f 导出相应数据块的逻辑块号需要两个步骤:
1. 从偏移量f导出文件的块号,即在偏移量f处的字符所在的块索引。
2. 把文件的块号转化为相应的逻辑块号。
因为Unix文件不包含任何控制字符,因此,导出文件的第f个字符所在的文件块号当容易的,只是用f除以文件系统块的大小,并取整即可。
例如,让我们假定块的大小为4KB。如果f小于4096,那么这个字符就在文件的第一数据块中,其文件的块号为O。如果f等于或大于4096而小于8192,则这个字符就在文件块号为1的数据块中,以此类推。
得到了文件的块号是第一步。但是,由于Ext2文件的数据块在磁盘上不必是相邻的,因此把文件的块号转化为相应的逻辑块号可不是这么直截了当的了。
因此,Ext2文件系统必须提供一种方法,用这种方法可以在磁盘上建立每个文件块号与相应逻辑块号之间的关系。在索引节点内部部分实现了这种映射(回到了
AT&T Unix的早期版本)。这种映射也涉及一些包含额外指针的专用块,这些块用来处理大型文件的索引节点的扩展。
ext2磁盘索引节点ext2_inode的i_block字段是一个有EXT2_N_BLOCKS个元素且包含逻辑块号的数组。在下面的讨论中,
我们假定EXT2_N_BLOCKS的默认值为15(实际上到2.6.18这个值都一直是15)。如图所示,这个数组表示一个大型数据结构的初始化部分。
正如从图中所看到的,数组的15个元素有4种不同的类型:
- 最初的12个元素产生的逻辑块号与文件最初的12个块对应,即对应的文件块号从0 - 11。
- 下标12中的元素包含一个块的逻辑块号(叫做间接块),这个块中存放着一个表示逻辑块号的二级数组。这个数组的元素对应的文件块号从12 到
b/4+11,这里b是文件系统的块大小(每个逻辑块号占4个字节,因此我们在式子中用4作除数,如果块大小是4096,则该数组对应文件块号从12到
1035)。因此,内核为了查找指向一个块的指针必须先访问这个元素,然后,在这个块中找到另一个指向最终块(包含文件内容)的指针。
-
下标13中的元素包含一个间接块的逻辑块号,而这个块包含逻辑块号的一个二级数组,这个二级数组的数组项依次指向三级数组,这个三级数组存放的才是文件块
号对应的逻辑块号,范围从b/4+12到(b/4)^2+(b/4)+11。如果块大小是4096,则范围是从1036到1049611。
- 最后,下标14中的元素使用三级间接索引,第四级数组中存放的才是文件块号对应的逻辑块号,范围从(b/4)^2+(b/4)+12到(b/4)^3+(b/4)^2+(b/4)+11。
在图中,块内的数字表示相应的文件块号。箭头(表示存放在数组元素中的逻辑块号)指示了内核如何通过间接块找到包含文件实际内容的块。
注意这种机制是如何支持小文件的。如果文件需要的数据块小于12,那么两次磁盘访问就可以检索到任何数据:一次是读磁盘索引节点i_block数组
的一个元素,另一次是读所需要的数据块。对于大文件来说,可能需要三四次的磁盘访问才能找到需要的块。实际上,这是一种最坏的估计,因为目录项、索引节
点、页高速缓存都有助于极大地减少实际访问磁盘的次数。
还要注意文件系统的块大小是如何影响寻址机制的,因为大的块允许Ext2把更多的逻辑块号存放在一个单独的块中。例如,如果块的大小是1024字
节,并且文件包含的数据最多为268KB,那么,通过直接映射可以访问文件最初的12KB数据,通过简单的间接映射可以访问剩的13KB到268KB的数
据。大于2GB的大型文件通过指定O_LARGEFILE打开标志必须在32位体系结构上进行打开。
2 文件的洞
文件的洞(file hole)是普通文件的一部分,它是一些空字符但没有存放在磁盘的任何数据块中。洞是Unix文件一直存在的一个特点。例如,下列的Unix命令创建了第一个字节是洞的文件:
[root@localhost]# echo -n "X" | dd of=/tmp/hole bs=1024 seek=6
现在,/tmp/hole有6145个字符(6144个空字符加一个X字符),然而,这个文件在磁盘上只占一个数据块。
引人文件的洞是为了避免磁盘空间的浪费。它们因此被广泛地用在数据库应用中,更一般地说,用于在文件上进行散列的所有应用。
文件洞在Ext2中的实现是基于动态数据块的分配的:只有当进程需要向一个块写数据时,才真正把这个块分配给文件。每个索引节点的i_size字段定义程序所看到的文件大小,包括洞,而i_blocks字段存放分配给文件有效的数据块数(以512字节为单位)。
在前面dd命令的例子中,假定/tmp/hole文件创建在块大小为4096的Ext2分区上。其相应磁盘索引节点的i_size字段存放的数为
6145,而i_blocks字段存放的数为8(因为每4096字节的逻辑块包含8个512字节的物理块)。i_block数组的第二个元素(对应块的文
件块号为1)存放已分配块的逻辑块号,而数组中的其他元素都为空(参看下图)。
3 分配数据块
当内核要分配一个数据块来保存Ext2普通文件的数据时,就调用ext2_get_block()函数。如果块不存在,该函数就自动为文件分配块。
请记住,每当内核在Ext2普通文件上执行读或写操作时就调用这个函数;显然,这个函数只在页高速缓存内没有相应的块时才被调用。
ext2_get_block()函数处理在刚才“数据块寻址”描述的数据结构,并在必要时调用ext2_alloc_block()函数在Ext2分区真正搜索一个空闲块。如果需要,该函数还为间接寻址分配相应的块(参见本篇博文第一个图)。
为了减少文件的碎片,Ext2文件系统尽力在已分配给文件的最后一个块附近找一个新块分配给该文件。如果失败,Ext2文件系统又在包含这个文件索引节点的块组中搜寻一个新的块。如果还是失败,作为最后一个办法,可以从其他一个块组中获得空闲块。
Ext2文件系统使用数据块的预分配策略。文件并不仅仅获得所需要的块,而是获得一组多达8个邻接的块。ext2_inode_info结构的
i_prealloc_count字段存放预分配给某一文件但还没有使用的数据块的数量,而i_prealloc_block字段存放下一次要使用的预分
配块的逻辑块号。当下列情况发生时,释放预分配而一直没有使用的块:当文件被关闭时,当文件被缩短时,或者当一个写操作相对于引发块预分配的写操作不是顺
序的时。
ext2_alloc_block()函数接收的参数为指向索引节点对象的指针、目标(goal)和存放错误码的变量地址。目标是一个逻辑块号,表示新块的首选位置:
static unsigned long ext2_alloc_block (struct inode * inode, unsigned long goal, int *err)
{
#ifdef EXT2FS_DEBUG
static unsigned long alloc_hits, alloc_attempts;
#endif
unsigned long result;
#ifdef EXT2_PREALLOCATE
struct ext2_inode_info *ei = EXT2_I(inode);
write_lock(&ei->i_meta_lock);
if (ei->i_prealloc_count &&
(goal == ei->i_prealloc_block || goal + 1 == ei->i_prealloc_block))
{
result = ei->i_prealloc_block++;
ei->i_prealloc_count--;
write_unlock(&ei->i_meta_lock);
ext2_debug ("preallocation hit (%lu/%lu)./n",
++alloc_hits, ++alloc_attempts);
} else {
write_unlock(&ei->i_meta_lock);
ext2_discard_prealloc (inode);
ext2_debug ("preallocation miss (%lu/%lu)./n",
alloc_hits, ++alloc_attempts);
if (S_ISREG(inode->i_mode)) /* 如果是普通文件 */
result = ext2_new_block (inode, goal,
&ei->i_prealloc_count,
&ei->i_prealloc_block, err);
else /* 如果是目录或符号链接 */
result = ext2_new_block(inode, goal, NULL, NULL, err);
}
#else
result = ext2_new_block (inode, goal, 0, 0, err);
#endif
return result;
}
代码很容易看懂,如果先前有预分配,则直接返回ei->i_prealloc_block++,没有,则丢弃所有剩余的预分配块ext2_discard_prealloc(inode),并调用ext2_new_block函数分配一个块:
unsigned long ext2_new_block(struct inode *inode, unsigned long goal,
u32 *prealloc_count, u32 *prealloc_block, int *err)
{
struct buffer_head *bitmap_bh = NULL;
struct buffer_head *gdp_bh; /* bh2 */
struct ext2_group_desc *desc;
int group_no; /* i */
int ret_block; /* j */
int group_idx; /* k */
unsigned long target_block; /* tmp */
unsigned long block = 0;
struct super_block *sb = inode->i_sb;
struct ext2_sb_info *sbi = EXT2_SB(sb);
struct ext2_super_block *es = sbi->s_es;
unsigned group_size = EXT2_BLOCKS_PER_GROUP(sb);
unsigned prealloc_goal = es->s_prealloc_blocks;
unsigned group_alloc = 0, es_alloc, dq_alloc;
int nr_scanned_groups;
if (!prealloc_goal--)
prealloc_goal = EXT2_DEFAULT_PREALLOC_BLOCKS - 1;
if (!prealloc_count || *prealloc_count)
prealloc_goal = 0;
if (DQUOT_ALLOC_BLOCK(inode, 1)) {
*err = -EDQUOT;
goto out;
}
while (prealloc_goal && DQUOT_PREALLOC_BLOCK(inode, prealloc_goal))
prealloc_goal--;
dq_alloc = prealloc_goal + 1;
es_alloc = reserve_blocks(sb, dq_alloc);
if (!es_alloc) {
*err = -ENOSPC;
goto out_dquot;
}
ext2_debug ("goal=%lu./n", goal);
if (goal < le32_to_cpu(es->s_first_data_block) ||
goal >= le32_to_cpu(es->s_blocks_count))
goal = le32_to_cpu(es->s_first_data_block);
group_no = (goal - le32_to_cpu(es->s_first_data_block)) / group_size;
desc = ext2_get_group_desc (sb, group_no, &gdp_bh);
if (!desc) {
/*
* gdp_bh may still be uninitialised. But group_release_blocks
* will not touch it because group_alloc is zero.
*/
goto io_error;
}
group_alloc = group_reserve_blocks(sbi, group_no, desc,
gdp_bh, es_alloc);
if (group_alloc) {
ret_block = ((goal - le32_to_cpu(es->s_first_data_block)) %
group_size);
brelse(bitmap_bh);
bitmap_bh = read_block_bitmap(sb, group_no);
if (!bitmap_bh)
goto io_error;
ext2_debug("goal is at %d:%d./n", group_no, ret_block);
ret_block = grab_block(sb_bgl_lock(sbi, group_no),
bitmap_bh->b_data, group_size, ret_block);
if (ret_block >= 0)
goto got_block;
group_release_blocks(sb, group_no, desc, gdp_bh, group_alloc);
group_alloc = 0;
}
ext2_debug ("Bit not found in block group %d./n", group_no);
/*
* Now search the rest of the groups. We assume that
* i and desc correctly point to the last group visited.
*/
nr_scanned_groups = 0;
retry:
for (group_idx = 0; !group_alloc &&
group_idx < sbi->s_groups_count; group_idx++) {
group_no++;
if (group_no >= sbi->s_groups_count)
group_no = 0;
desc = ext2_get_group_desc(sb, group_no, &gdp_bh);
if (!desc)
goto io_error;
group_alloc = group_reserve_blocks(sbi, group_no, desc,
gdp_bh, es_alloc);
}
if (!group_alloc) {
*err = -ENOSPC;
goto out_release;
}
brelse(bitmap_bh);
bitmap_bh = read_block_bitmap(sb, group_no);
if (!bitmap_bh)
goto io_error;
ret_block = grab_block(sb_bgl_lock(sbi, group_no), bitmap_bh->b_data,
group_size, 0);
if (ret_block < 0) {
/*
* If a free block counter is corrupted we can loop inifintely.
* Detect that here.
*/
nr_scanned_groups++;
if (nr_scanned_groups > 2 * sbi->s_groups_count) {
ext2_error(sb, "ext2_new_block",
"corrupted free blocks counters");
goto io_error;
}
/*
* Someone else grabbed the last free block in this blockgroup
* before us. Retry the scan.
*/
group_release_blocks(sb, group_no, desc, gdp_bh, group_alloc);
group_alloc = 0;
goto retry;
}
got_block:
ext2_debug("using block group %d(%d)/n",
group_no, desc->bg_free_blocks_count);
target_block = ret_block + group_no * group_size +
le32_to_cpu(es->s_first_data_block);
if (target_block == le32_to_cpu(desc->bg_block_bitmap) ||
target_block == le32_to_cpu(desc->bg_inode_bitmap) ||
in_range(target_block, le32_to_cpu(desc->bg_inode_table),
sbi->s_itb_per_group))
ext2_error (sb, "ext2_new_block",
"Allocating block in system zone - "
"block = %lu", target_block);
if (target_block >= le32_to_cpu(es->s_blocks_count)) {
ext2_error (sb, "ext2_new_block",
"block(%d) >= blocks count(%d) - "
"block_group = %d, es == %p ", ret_block,
le32_to_cpu(es->s_blocks_count), group_no, es);
goto io_error;
}
block = target_block;
/* OK, we _had_ allocated something */
ext2_debug("found bit %d/n", ret_block);
dq_alloc--;
es_alloc--;
group_alloc--;
/*
* Do block preallocation now if required.
*/
write_lock(&EXT2_I(inode)->i_meta_lock);
if (group_alloc && !*prealloc_count) {
unsigned n;
for (n = 0; n < group_alloc && ++ret_block < group_size; n++) {
if (ext2_set_bit_atomic(sb_bgl_lock(sbi, group_no),
ret_block,
(void*) bitmap_bh->b_data))
break;
}
*prealloc_block = block + 1;
*prealloc_count = n;
es_alloc -= n;
dq_alloc -= n;
group_alloc -= n;
}
write_unlock(&EXT2_I(inode)->i_meta_lock);
mark_buffer_dirty(bitmap_bh);
if (sb->s_flags & MS_SYNCHRONOUS)
sync_dirty_buffer(bitmap_bh);
ext2_debug ("allocating block %d. ", block);
*err = 0;
out_release:
group_release_blocks(sb, group_no, desc, gdp_bh, group_alloc);
release_blocks(sb, es_alloc);
out_dquot:
DQUOT_FREE_BLOCK(inode, dq_alloc);
out:
brelse(bitmap_bh);
return block;
io_error:
*err = -EIO;
goto out_release;
}
ext2_new_block()函数用下列策略在Ext2分区内搜寻一个空闲块:
1. 如果传递给ext2_alloc_block()的首选块(目标块)是空闲的,就分配它。
2. 如果目标为忙,就检查首选块后的其余块之中是否有空闲的块。
3. 如果在首选块附近没有找到空闲块,就从包含目标的块组开始,查找所有的块组,对每个块组有:
a. 寻找至少有8个相邻空闲块的一个组块。
b. 如果没有找到这样的一组块,就寻找一个单独的空闲块。
下面我们就来详细分析这个函数,其接收的参数为:
- inode:指向被分配块的文件的索引节点
- goal:由ext2_alloc_block传递过来的目标块号
- prealloc_count:指向打算预分配块的计数器的指针
- prealloc_block:指向预分配的第一个块的位置
- err:存放错误码的变量地址
只要找到一个空闲块,搜索就结束,返回该块的块号。在结束前,ext2_new_block()函数还尽力在找到的空闲块附近的块中找8个空闲块进
行预分配,并把磁盘索引节点的i_prealloc_block和i_prealloc_count字段置为适当的块位置及块数。函数执行以下步骤:
1. 首先初始化一些内部变量:
struct buffer_head *bitmap_bh = NULL;
struct buffer_head *gdp_bh; /* bh2 */
struct ext2_group_desc *desc;
……
unsigned long block = 0;
struct super_block *sb = inode->i_sb;
struct ext2_sb_info *sbi = EXT2_SB(sb);
struct ext2_super_block *es = sbi->s_es;
unsigned group_size = EXT2_BLOCKS_PER_GROUP(sb);
unsigned prealloc_goal = es->s_prealloc_blocks;
unsigned group_alloc = 0, es_alloc, dq_alloc;
这些内部变量各有各的含义,其中,bitmap_bh是位图的缓存;gdp_bh是块组缓存;block是当前搜索到的块号;sb是VFS
超级快结构,由inode的i_sb字段得出;sbi是磁盘超级块的内存对象描述符,由VFS超级快的s_fs_info字段得到;es是磁盘超级快对
象,由超级快内存对象描述符的s_es字段得到;group_size是磁盘块组的以块为单位的大小,由超级快的s_blocks_per_group字
段得到;
prealloc_goal是已预分配的块数,由磁盘超级快对象的s_prealloc_blocks字段得到;group_alloc表示同一个组分配块的数量。
2. 如果prealloc_goal减1为0了,则说明预分配的块已经用完,则:
if (!prealloc_goal--)
prealloc_goal = EXT2_DEFAULT_PREALLOC_BLOCKS - 1;
if (!prealloc_count || *prealloc_count)
prealloc_goal = 0;
其中EXT2_DEFAULT_PREALLOC_BLOCKS为8,也就是需要重新预分配8个块。当然,如果传递进来的参数prealloc_count为空或者是0,则说明不是普通文件,或者没有启动预分配机制,则prealloc_goal设置为0 。
3.
对配额进行检查,分配的目标块超过了用户配额,则将出错对象err设置为-EDQUOT;如果预分配超过了用户配额,则将预分配数量减至配额以内;检查完
毕后,将预分配数量赋给dq_alloc内部变量,再执行reserve_blocks函数检查预分配的块dq_alloc是否到达了保留块,如果是则减
去所处保留块的数量。如果得到的结果es_alloc为0了,则将出错对象err设置为-ENOSPC,表示no space:
if (DQUOT_ALLOC_BLOCK(inode, 1)) {
*err = -EDQUOT;
goto out;
}
while (prealloc_goal && DQUOT_PREALLOC_BLOCK(inode, prealloc_goal))
prealloc_goal--;
dq_alloc = prealloc_goal + 1;
es_alloc = reserve_blocks(sb, dq_alloc);
if (!es_alloc) {
*err = -ENOSPC;
goto out_dquot;
}
4. 如果目标块小于1(es->s_first_data_block总为1,查看“Ext2磁盘数据结构”博文),或者大于整个文件系统所有块的大小,则将goal设置为1。
if (goal < le32_to_cpu(es->s_first_data_block) ||
goal >= le32_to_cpu(es->s_blocks_count))
goal = le32_to_cpu(es->s_first_data_block);
5. 得到goal所对应的块组号,并根据块组号获得对应的组描述符:
group_no = (goal - le32_to_cpu(es->s_first_data_block)) / group_size;
desc = ext2_get_group_desc (sb, group_no, &gdp_bh);
这里面的ext2_get_group_desc接收三个参数,sb是VFS超级快,group_no是块组号,&gdp_bh是该块组对应的页高速缓存:
struct ext2_group_desc * ext2_get_group_desc(struct super_block * sb,
unsigned int block_group,
struct buffer_head ** bh)
{
unsigned long group_desc;
unsigned long offset;
struct ext2_group_desc * desc;
struct ext2_sb_info *sbi = EXT2_SB(sb);
……
group_desc = block_group >> EXT2_DESC_PER_BLOCK_BITS(sb);
offset = block_group & (EXT2_DESC_PER_BLOCK(sb) - 1);
if (!sbi->s_group_desc[group_desc]) {
ext2_error (sb, "ext2_get_group_desc",
"Group descriptor not loaded - "
"block_group = %d, group_desc = %lu, desc = %lu",
block_group, group_desc, offset);
return NULL;
}
desc = (struct ext2_group_desc *) sbi->s_group_desc[group_desc]->b_data;
if (bh)
*bh = sbi->s_group_desc[group_desc];
return desc + offset;
}
函数里面的group_desc内部变量通过block_group参数获得组描述符数组的位置下标,ext2磁盘组描述符
ext2_group_desc缓存于sbi->s_group_desc[group_desc]->b_data的某个位置,因为总是缓
存的,见博文“Ext2的索引节点对象”最后那个表。
6. desc指向了组描述符以后,事情就好办了。执行 group_reserve_blocks(sbi, group_no, desc,
gdp_bh,
es_alloc)查看goal对应的那个组内是否有连续es_alloc个空闲的块,当然,组内如果一个空闲的块都没有,就返回0。如果空闲的块小于
es_alloc,就返回可以分配的块数,并修改组描述符的空闲块数,然后把组描述符对应的缓存标记为脏:
static int group_reserve_blocks(struct ext2_sb_info *sbi, int group_no,
struct ext2_group_desc *desc, struct buffer_head *bh, int count)
{
unsigned free_blocks;
if (!desc->bg_free_blocks_count)
return 0;
spin_lock(sb_bgl_lock(sbi, group_no));
free_blocks = le16_to_cpu(desc->bg_free_blocks_count);
if (free_blocks < count)
count = free_blocks;
desc->bg_free_blocks_count = cpu_to_le16(free_blocks - count);
spin_unlock(sb_bgl_lock(sbi, group_no));
mark_buffer_dirty(bh);
return count;
}
7.
好了,group_alloc局部变量就等于group_reserve_blocks的返回值。此时此刻,group_alloc为goal期望的那个
块可分配的数量。那么接下来就要做一个判断,如果group_alloc大于0,则说明首选块是空闲的,就分配它:
if (group_alloc) {
ret_block = ((goal - le32_to_cpu(es->s_first_data_block)) %
group_size);
bitmap_bh = read_block_bitmap(sb, group_no);
if (!bitmap_bh)
goto io_error;
ret_block = grab_block(sb_bgl_lock(sbi, group_no),
bitmap_bh->b_data, group_size, ret_block);
if (ret_block >= 0)
goto got_block;
group_release_blocks(sb, group_no, desc, gdp_bh, group_alloc);
group_alloc = 0;
}
read_block_bitmap读取超级块sb对应块组group_no的那个位图,并把这个位图动态缓存到bitmap_bh页高速缓存中:
static struct buffer_head * read_block_bitmap(struct super_block *sb, unsigned int block_group)
{
struct ext2_group_desc * desc;
struct buffer_head * bh = NULL;
desc = ext2_get_group_desc (sb, block_group, NULL);
if (!desc)
goto error_out;
bh = sb_bread(sb, le32_to_cpu(desc->bg_block_bitmap));
if (!bh)
ext2_error (sb, "read_block_bitmap",
"Cannot read block bitmap - "
"block_group = %d, block_bitmap = %u",
block_group, le32_to_cpu(desc->bg_block_bitmap));
error_out:
return bh;
}
随后调用grab_block(sb_bgl_lock(sbi, group_no), bitmap_bh->b_data,
group_size,
ret_block)检查一下goal所对应的那个数据块位图对应的位是否为0,如果是,则把它的块号赋给相对位置ret_block;如果不是,则
ext2_find_next_zero_bit在组内分配一个空闲块的块号到ret_block。当然,如果ret_block大于等于0,则说明这个
组内有空闲块,则跳到got_block;如果小于0,就说明这个组的块已经分配完了,那么就把刚才给组描述符增加的那些值减回来,并把
group_alloc重新设置为0。
8. 如果group_alloc为0,则说明组内没有goal期望的那个块可分配的数量那么多的块,就到retry程序段,到其他组去找es_alloc个空闲块,具体代码跟前边一样。
9. 如果得到这个块了,那么ret_block就是这个连续块的第一个块的相对组头的位置,到got_block程序段,此时此刻,group_no是该快所在的块组号,随后:
target_block = ret_block + group_no * group_size + le32_to_cpu(es->s_first_data_block);
target_block就是要分配的实际逻辑块号(相对于目标块号)。got_block程序段随后对这个逻辑块号进行一系列检查,包括这个块是否已经存放了索引节点、位图、组描述符等内容了。当然,肯定不会出现这些问题的,因为这些都是系统bug。
10. 设置位图(注意,是一个组内的预分配连续块都设置),并把bitmap_bh标记为脏。
write_lock(&EXT2_I(inode)->i_meta_lock);
if (group_alloc && !*prealloc_count) {
unsigned n;
for (n = 0; n < group_alloc && ++ret_block < group_size; n++) {
if (ext2_set_bit_atomic(sb_bgl_lock(sbi, group_no),
ret_block,
(void*) bitmap_bh->b_data))
break;
}
*prealloc_block = block + 1;
*prealloc_count = n;
es_alloc -= n;
dq_alloc -= n;
group_alloc -= n;
}
write_unlock(&EXT2_I(inode)->i_meta_lock);
mark_buffer_dirty(bitmap_bh);
11. 最后返回这个物理块:
brelse(bitmap_bh);
block = target_block;
return block