之前分析的ext2_new_blocks()看似复杂,但其基本思想却比较简单,而且,它把最复杂的功能留给了本博客中需要讨论的ext2_try_to_allocate_with_rsv()函数中完成了。
看函数的名字便知道,如果上层调用者决定使用预留窗口机制,则该函数优先从文件的预留窗口中分配磁盘块,并且根据需要可能分配一个全新的预留窗口,或者有必要将原来
的预留窗口进行扩充。废话不多说,让我们先来看看代码吧:
static ext2_grpblk_t
ext2_try_to_allocate_with_rsv(struct super_block *sb, unsigned int group,
struct buffer_head *bitmap_bh, ext2_grpblk_t grp_goal,
struct ext2_reserve_window_node * my_rsv,
unsigned long *count)
{
ext2_fsblk_t group_first_block, group_last_block;
ext2_grpblk_t ret = 0;
unsigned long num = *count;
/*
* we don't deal with reservation when
* filesystem is mounted without reservation
* or the file is not a regular file
* or last attempt to allocate a block with reservation turned on failed
*/
/* 如果不使用预留窗口机制,那么就使用
** 传统的分配方式了
*/
if (my_rsv == NULL) {
return ext2_try_to_allocate(sb, group, bitmap_bh,
grp_goal, count, NULL);
}
/*
* grp_goal is a group relative block number (if there is a goal)
* 0 <= grp_goal < EXT2_BLOCKS_PER_GROUP(sb)
* first block is a filesystem wide block number
* first block is the block number of the first block in this group
*/
group_first_block = ext2_group_first_block_no(sb, group);
group_last_block = group_first_block + (EXT2_BLOCKS_PER_GROUP(sb) - 1);
/*
* Basically we will allocate a new block from inode's reservation
* window.
*
* We need to allocate a new reservation window, if:
* a) inode does not have a reservation window; or
* b) last attempt to allocate a block from existing reservation
* failed; or
* c) we come here with a goal and with a reservation window
*
* We do not need to allocate a new reservation window if we come here
* at the beginning with a goal and the goal is inside the window, or
* we don't have a goal but already have a reservation window.
* then we could go to allocate from the reservation window directly.
*/
/*
* 1. allocate a new reservation window or expand old reservation window if necessary;
* 2. allocate free block(s) from reservation window by calling ext2_try_to_allocate().
*/
/* 如果有以下情况发生,那么就分配一个全新的预留窗口
** 1. 当前预留窗口为空,即还未分配物理磁盘块
** 2. 如果建议块号不在预留窗口之内
*/
while (1) {
if (rsv_is_empty(&my_rsv->rsv_window) || (ret < 0) ||
!goal_in_my_reservation(&my_rsv->rsv_window,
grp_goal, group, sb)) {
if (my_rsv->rsv_goal_size < *count)
my_rsv->rsv_goal_size = *count;
ret = alloc_new_reservation(my_rsv, grp_goal, sb,
group, bitmap_bh);
if (ret < 0)
break; /* failed */
//we allocate new reservation window but the goal block is still out of this window
// if so, we set the target block to -1
if (!goal_in_my_reservation(&my_rsv->rsv_window,
grp_goal, group, sb))
grp_goal = -1;
/* 这里的else if 意味着:
** 1. 预留窗口非空;
** 2. ret = 0;
** 3. goal 在预留窗口之中
** 那么磁盘需要判断是否需要扩张预留窗口
** 判断的条件: 从当前要分配的位置开始直至
** 预留窗口结束,中间这么多的数据块是否
** 少于我们需要分配的数量(*count)
** -----------------------------------------
**| | | |
**------------------------------------------
**start goal end
** goal:我要从这儿开始分配
** end: 预留窗口结束的位置
** curr = end - goal + 1: 从想要分配的地方到预留窗口
** 结束的位置,我可以分配多少个空闲块
** 如果curr 小于想要的数量,那么需要扩充该预留窗口了
*/
} else if (grp_goal >= 0) {
/*expand old reservation window
**what is @current? current =? my_rsv->rsv_end - my_rsv->start + 1
*/
int curr = my_rsv->rsv_end -
(grp_goal + group_first_block) + 1;
if (curr < *count)
try_to_extend_reservation(my_rsv, sb,
*count - curr);
}
//if the reservation window is out of range
if ((my_rsv->rsv_start > group_last_block) ||
(my_rsv->rsv_end < group_first_block)) {
rsv_window_dump(&EXT2_SB(sb)->s_rsv_window_root, 1);
BUG();
}
/*allocate free block from reservation window
*we get @num free blocks this time
*/
ret = ext2_try_to_allocate(sb, group, bitmap_bh, grp_goal,
&num, &my_rsv->rsv_window);
if (ret >= 0) {
my_rsv->rsv_alloc_hit += num;
*count = num;
break; /* succeed */
}
num = *count;
}
return ret;
}
首先,我们再次确认下该函数的功能:从预留窗口或者块组中分配若干数量的磁盘块,这里不保证能分配到想要数量的磁盘块(尽力而为而已),但能够保证分配的磁盘块一定连续。
其次,我们来看看该函数的参数情况:
- sb:ext2文件系统内存超级块结构;
- group:从所在的块组分配磁盘块;
- bitmap_bh:块组的位图信息;
- grp_goal:调用者建议的最佳分配块号,快组内编号;
- my_rsv:文件的预留窗口,如果为NULL,则意味着不使用预留窗口;
- count:调用者传入的欲分配磁盘块数量,同时该参数也作为返回值,告知调用者时机分配的磁盘块数量。
该函数的返回值情况:返回值为分配到的空闲磁盘块的起始块号,同时*count中记录了分配到的磁盘块数量。
接下来,我们看看该函数的具体实现如何。
if (my_rsv == NULL) {
return ext2_try_to_allocate(sb, group, bitmap_bh,
grp_goal, count, NULL);
}
首先,我们判断调用者是否决定使用预留窗口,如果不使用预留窗口机制,那我们还是老老实实采用最传统的方法,调用ext2_try_to_allocate()来分配。这个函数我们会在接下来的博客中详细解析。
如果,调用者决定采用预留窗口机制来分配的话,那么好,事情好像就不是这么简单了。下面的这个while循环就很能说明情况:
while (1) {
if (rsv_is_empty(&my_rsv->rsv_window) || (ret < 0) ||
!goal_in_my_reservation(&my_rsv->rsv_window,
grp_goal, group, sb)) {
if (my_rsv->rsv_goal_size < *count)
my_rsv->rsv_goal_size = *count;
ret = alloc_new_reservation(my_rsv, grp_goal, sb,
group, bitmap_bh);
if (ret < 0)
break; /* failed */
//we allocate new reservation window but the goal block is still out of this window
// if so, we set the target block to -1
if (!goal_in_my_reservation(&my_rsv->rsv_window,
grp_goal, group, sb))
grp_goal = -1;
/* 这里的else if 意味着:
** 1. 预留窗口非空;
** 2. ret = 0;
** 3. goal 在预留窗口之中
** 那么磁盘需要判断是否需要扩张预留窗口
** 判断的条件: 从当前要分配的位置开始直至
** 预留窗口结束,中间这么多的数据块是否
** 少于我们需要分配的数量(*count)
** -----------------------------------------
**| | | |
**------------------------------------------
**start goal end
** goal:我要从这儿开始分配
** end: 预留窗口结束的位置
** curr = end - goal + 1: 从想要分配的地方到预留窗口
** 结束的位置,我可以分配多少个空闲块
** 如果curr 小于想要的数量,那么需要扩充该预留窗口了
*/
} else if (grp_goal >= 0) {
/*expand old reservation window
**what is @current? current =? my_rsv->rsv_end - my_rsv->start + 1
*/
int curr = my_rsv->rsv_end -
(grp_goal + group_first_block) + 1;
if (curr < *count)
try_to_extend_reservation(my_rsv, sb,
*count - curr);
}
//if the reservation window is out of range
if ((my_rsv->rsv_start > group_last_block) ||
(my_rsv->rsv_end < group_first_block)) {
rsv_window_dump(&EXT2_SB(sb)->s_rsv_window_root, 1);
BUG();
}
/*allocate free block from reservation window
*we get @num free blocks this time
*/
ret = ext2_try_to_allocate(sb, group, bitmap_bh, grp_goal,
&num, &my_rsv->rsv_window);
if (ret >= 0) {
my_rsv->rsv_alloc_hit += num;
*count = num;
break; /* succeed */
}
num = *count;
}
在while()循环的一开始,就来了个比较恶心的判断,当前是否需要分配一个新的预留窗口,我们仔细考虑下,需要分配新的预留窗口的条件是:
- 文件当前预留窗口为空,即尚未分配物理磁盘块 或者;
- 当前建议最佳分配块号不在预留窗口之内,我们分配的准则是尽量从建议块那个地方或者附近开始分配,如果出现建议块不在预留窗口范围之内,我们毫不犹豫地丢弃老的预留窗口,并为之分配一个新的预留窗口(建议分配块号落在其中)。
等上面的准备工作都已经完成后,我们再来调用ext2_try_to_allocate()从我们已经分配或者扩展后的预留窗口中分配物理磁盘块,这也是我们下一篇博客需要重点分析的内容。