您的位置:首页 > 数据库

dbm数据库源代码分析(7):falloc.c

2016-07-29 00:00 375 查看
现在解剖fallo.c的源代码。文件中包含了管理dbm文件空间的2个函数_gdbm_alloc、_gdbm_free。后面6个函数pop_avail_block、push_avail_block、get_elem、_gdbm_put_av_elem、get_block、adjust_bucket_avail都是前面这个函数需要使用的工具函数,是static函数(也叫内部函数),局部于文件的,只能在本文件中使用。

(1)off_t _gdbm_alloc(dbf,num_bytes)函数。在dbf表示的文件中分配num_bytes个字节的空间,返回这个空间的起始地址。每个散列桶有一个固定大小的可用块列表。我们首先检查这个可用块列表,以满足空间申请的要求。在大多数情况下我们可以分配空间并且这种改变只会在当前的散列桶中进行。num_bytes个字节空间的分配能在这个可用块列表中得到满足。如果分配请求在当前的散列桶不能得到满足,则它会在文件头的可用块中得到满足。如果那里也不能分配,则我们使用文件尾部的一个可用块,在这个可用块分配num_bytes个字节的空间,剩余的不再使用的部分放回到可用块中。本例程保证一个空间分配不会越过块的边界,除非给出的大小参数大于一个单一的块。分配完空间后avail_elem结构将会改变。如果一个错误发生,则返回0值。

(2)void _gdbm_free (dbf, file_adr, num_bytes)函数。在文件dbf的地址file_adr地址处释放num_bytes个字节,使得它可以通过_gdbm_alloc进行再分配。如果释放的空间字节数不小于blocksize,则把要释放的空间(用avail_elem标识)插入到文件头的可用块列表中,如果事先这个可用块列表已满,则分裂,并把其中一半压入到文件的可用块栈中。如果释放的空间字节数小于blocksize,则把要释放的空间插入到当前桶的可用块列表中,如果这个可用块列表已满,则也要进行分裂。最后文件头改变时,要调整当前桶的可用块个数,使之为最大值BUCKET_AVAIL的1/3。

(3)void pop_avail_block(dbf)函数。在文件的可用块栈顶上获得一个可用块并把它载入到文件头的活动可用块列表中。如果文件头的可用块个数达到最大,则先用push_avail_block进行分裂。程序先为表示栈顶的可用块元素设置一些域并分配内存空间、从文件上读取这个可用块、把这个可用块元素(avail_elem)插入到文件头的可用块列表中去(如果事先已满,则先分裂),最后释放内存空间。

(4)void push_avail_block(dbf)函数。分裂文件头中的可用块,并把其中一半压入到文件的可用块栈中。先计算分裂块的大小(为所有可用块元素大小总和的一半再加上avail_block的大小)的偏移地址。在分裂时,把可用块元素列表中索引号为奇数的表项分裂出去,放在一个temp(avail_block结构)的可用块元素列表中。这个temp的大小可能没有一个块的大小,释放多余的空间,然后把这个temp写入文件。

(5)avail_elem get_elem(size,av_table[],av_count)函数。在可用块列表av_table[]中获取一个大于size字节的可用块。av_count是列表中的元素个数。如果找到一个块,则提取出它来,并且把其余的可用块前移以填满空间。如果没有大于size的块,则返回大小为0的块。本例程并不做I/O操作。
(6)int _gdbm_put_av_elem(new_el,av_table[],av_count,can_merger)函数。把一个可用块元素new_el插入到可用块列表av_table[]中。本例程不做I/O操作,av_count为表中元素的个数,can_merge表示是否允许合并可用块。程序先在表中找到与new_el相邻的块(在其后面或前面),然后进行合并。合并完后,在表中寻找要插入可用块元素new_el的位置,并插入new_el这个标识可用块的元素,列表长度增1。

(7)avail_elem get_block(size,dbf)函数。在整数字节的块大小内分配一个新的文件空间,这能确保小于这个大小的数据能存放在一个单一的块中。足够的块会被分配以确保被分配在块中的字节数大于size。dbf中包含需要更新的文件头。程序根据size参数在文件头中获取我们需要的足够大小的块,并返回标识这个块的avail_elem元素。

(8)void adjust_bucket_avail(dbf)函数。调整当前桶对应的可用块数,确保它为最大值BUCKET_AVAIL的1/3。如果桶中可用块元素个数还不够,则从文件头中取出并插入到当前桶中去。如果元素个数有多,则弹出多余的可用块并插入到文件头的可用块列表中去。

/* falloc.c - dbm的文件空间管理的例程集 */
#include "autoconf.h"   /* 包含平台相关头文件和函数的常量标志 */
#include "gdbmdefs.h"   /* 包含桶、缓存项、数据库文件结构的定义
所有平台相关的头文件和函数声明、gdbm的所有函数声明和要用到的常量也在这里 */
/* 本文件中的前向定义,可在下面参看相应函数的具体功能 */
static avail_elem get_elem __P((int, avail_elem [], int *)); /* __P这样的宏在proto.h中有定义,proto.h被gdbmdefs.h包含 */
static avail_elem get_block __P((int, gdbm_file_info *));
static void push_avail_block __P((gdbm_file_info *));
static void pop_avail_block __P((gdbm_file_info *));
static void adjust_bucket_avail __P((gdbm_file_info *));
/* 在dbf表示的文件中分配num_bytes个字节的空间,返回这个空间的起始地址
每个散列桶有一个固定大小的可用块列表。我们首先检查这个可用块列表,以满足空间申请的要求。在大多数情况下
我们可以分配空间并且这种改变只会在当前的散列桶中进行。num_bytes个字节空间的分配能在这个可用块列表中得到满足。如果
分配请求在当前的散列桶不能得到满足,则它会在文件头的可用块中得到满足。如果那里也不能分配,则我们使用
文件尾部的一个可用块,在这个可用块分配num_bytes个字节的空间,剩余的不再使用的部分放回到可用块中。本
例程保证一个空间分配不会越过块的边界,除非给出的大小参数大于一个单一的块。分配完空间后avail_elem结构
将会改变。如果一个错误发生,则返回0值 */
off_t
_gdbm_alloc (dbf, num_bytes)
gdbm_file_info *dbf;
int num_bytes;
{
off_t file_adr;		/* 分配的空间的地址 */
avail_elem av_el;		/* 作为临时变量用 */
/* 首先在当前桶中查找可用空间 */
av_el = get_elem (num_bytes, dbf->bucket->bucket_avail,
&dbf->bucket->av_count);
/* 如果没有找到,则需要做更多的工作 */
if (av_el.av_size == 0)
{
/* 如果文件头中可用块列表不满一半,且在栈中还有可用块元素 */
if ((dbf->header->avail.count <= (dbf->header->avail.size >> 1))
&& (dbf->header->avail.next_block != 0))
pop_avail_block (dbf); /* 弹出栈顶的可用块元素并压入到文件头的活动可用块列表中 */
/* 从文件头的可用块列表中查找可用空间 */
av_el = get_elem (num_bytes, dbf->header->avail.av_table,
&dbf->header->avail.count);
if (av_el.av_size == 0) /* 若没有找到 */
/* 从文件的尾部获取一个可用块 */
av_el = get_block (num_bytes, dbf);
dbf->header_changed = TRUE;
}
/* 现在我们得到了一个可用空间,保存它的地址,并在这个可用空间处分配我们需要的空间 */
file_adr = av_el.av_adr;
/* 把剩余的不用空间放回可用块中 */
av_el.av_adr += num_bytes;
av_el.av_size -= num_bytes;
_gdbm_free (dbf, av_el.av_adr, av_el.av_size);
/* 返回新空间的地址 */
return file_adr;

}

/* 在文件dbf的地址file_adr地址处释放num_bytes个字节,使得它可以通过_gdbm_alloc来分配以达到重用,
本例程会改变avail_elem结构 */
void
_gdbm_free (dbf, file_adr, num_bytes)
gdbm_file_info *dbf;
off_t file_adr;
int num_bytes;
{
avail_elem temp;

if (num_bytes <= IGNORE_SIZE) /* 要释放的字节数太少了,返回。这个常量见gdbmconst.h,值为4 */
return;
/* 初始化标识要释放空间的avail_elem */
temp.av_size = num_bytes;
temp.av_adr = file_adr;
/* 如果释放的空间字节数不小于blocksize */
if ((num_bytes >= dbf->header->block_size) || dbf->central_free)
{
if (dbf->header->avail.count == dbf->header->avail.size) /* 头文件中的可用块个数达到最大 */
{
push_avail_block (dbf); /* /* 分裂文件头中的可用块,并把其中一半压入到可用块栈中 */
}
_gdbm_put_av_elem (temp, dbf->header->avail.av_table, /* 把释放空间表示的可用块插入到文件头的可用块列表中 */
&dbf->header->avail.count, dbf->coalesce_blocks);
dbf->header_changed = TRUE;
}
else /* 否则释放的空间字节数小于blocksize */
{
/* 把这段新的可用空间插入到当前桶的可用块列表中 */
if (dbf->bucket->av_count < BUCKET_AVAIL) /* 当前的桶可用块列表还未满 */
_gdbm_put_av_elem (temp, dbf->bucket->bucket_avail, /* 插入操作 */
&dbf->bucket->av_count, dbf->coalesce_blocks);
else
{
if (dbf->header->avail.count == dbf->header->avail.size) /* 已满,则进行分裂 */
{
push_avail_block (dbf);
}
_gdbm_put_av_elem (temp, dbf->header->avail.av_table, /* 插入操作 */
&dbf->header->avail.count, dbf->coalesce_blocks);
dbf->header_changed = TRUE;
}
}
if (dbf->header_changed) /* 文件头改变 */
adjust_bucket_avail (dbf); /* 则调整当前桶中的可用块个数,使之为最大值的1/3 */
/* 所有工作完成 */
return;
}

/* 下面是前面两个函数需要的所有工具函数 */
/* 在文件的可用块栈顶上获得一个可用块并把它载入到文件头的活动可用块列表中。 */
static void
pop_avail_block (dbf)
gdbm_file_info *dbf;
{
int  num_bytes;		/* 用于read系统调用 */
off_t file_pos;		/* 用于lseek系统调用 */
avail_elem new_el;
avail_block *new_blk;
int index;
if (dbf->header->avail.count == dbf->header->avail.size) /* 文件头的可用块个数达到最大,要进行分裂 */
{
push_avail_block(dbf); /* 分裂文件头中的可用块,并把其中一半压入到可用块栈中 */
}
/* 设置avail_elem的相关域,以表示这个要出栈的可用块 */
new_el.av_adr = dbf->header->avail.next_block;
new_el.av_size = ( ( (dbf->header->avail.size * sizeof (avail_elem)) >> 1)
+ sizeof (avail_block)); /* 所有可用块元素大小总和的一半再加上avail_block的大小 */
/* 为这个可用块分配内存空间 */
new_blk = (avail_block *) malloc (new_el.av_size);
if (new_blk == NULL) _gdbm_fatal(dbf, "malloc failed");
/* 从文件上读取这个可用块 */
file_pos = lseek (dbf->desc, new_el.av_adr, L_SET);
if (file_pos != new_el.av_adr)  _gdbm_fatal (dbf, "lseek error");
num_bytes = read (dbf->desc, new_blk, new_el.av_size);
if (num_bytes != new_el.av_size) _gdbm_fatal (dbf, "read error");
/* 把这个新可用块的可用块元素(avail_elem)加入到文件头中去(会进行可用块的合并) */
index = 0;
while (index < new_blk->count)
{
while(index < new_blk->count
&& dbf->header->avail.count < dbf->header->avail.size)
{
/* 幸运,这将合并很多可用块 */
_gdbm_put_av_elem(new_blk->av_table[index],
dbf->header->avail.av_table,
&dbf->header->avail.count, TRUE);
index++;
}
if (dbf->header->avail.count == dbf->header->avail.size)
{
push_avail_block(dbf); /* 分裂文件头中的可用块,并把其中一半压入到可用块栈中 */
}
}
/* 设置好可用块中的next_block域 */
dbf->header->avail.next_block = new_blk->next_block;
/* We changed the header. */
dbf->header_changed = TRUE;
/* 释放先前的可用块。这时如果头部的可用块列表可能已满,则要进行分裂 */
if (dbf->header->avail.count == dbf->header->avail.size)
{
push_avail_block(dbf); /* 分裂文件头中的可用块,并把其中一半压入到可用块栈中 */
}
_gdbm_put_av_elem (new_el, dbf->header->avail.av_table,
&dbf->header->avail.count, TRUE);
free (new_blk); /* 释放先前可用块的内存 */
}

/* 分裂文件头中的可用块,并把其中一半压入到可用块栈中 */
static void
push_avail_block (dbf)
gdbm_file_info *dbf;
{
int  num_bytes;
int  av_size;
off_t av_adr;
int  index;
off_t file_pos;
avail_block *temp;
avail_elem  new_loc;

/* 计算分裂块的大小 */
av_size = ( (dbf->header->avail.size * sizeof (avail_elem)) >> 1)
+ sizeof (avail_block); /* 所有可用块元素大小总和的一半再加上avail_block的大小 */
/* 获得这新的av_size个字节(即分裂块)的偏移地址 */
new_loc = get_elem (av_size, dbf->header->avail.av_table,
&dbf->header->avail.count);
if (new_loc.av_size == 0)
new_loc = get_block (av_size, dbf);
av_adr = new_loc.av_adr;

/* 分裂头部中的块 */
temp = (avail_block *) malloc (av_size);
if (temp == NULL) _gdbm_fatal (dbf, "malloc error");
/* 在pop_avail_block之后,设置正确的大小值. */
temp->size = dbf->header->avail.size;
temp->count = 0;
temp->next_block = dbf->header->avail.next_block;
dbf->header->avail.next_block = av_adr;
for (index = 1; index < dbf->header->avail.count; index++)
if ( (index & 0x1) == 1)	/* Index是奇数 */
temp->av_table[temp->count++] = dbf->header->avail.av_table[index]; /* 复制可用块元素 */
else
dbf->header->avail.av_table[index>>1]
= dbf->header->avail.av_table[index];
/* 把文件头的当前可用块个数更新为原来个数的一半 */
dbf->header->avail.count >>= 1;
/* 释放不再需要的空间 */
new_loc.av_adr += av_size;
new_loc.av_size -= av_size;
_gdbm_free (dbf, new_loc.av_adr, new_loc.av_size);
/* 把分裂出来的块的写入磁盘 */
file_pos = lseek (dbf->desc, av_adr, L_SET);
if (file_pos != av_adr) _gdbm_fatal (dbf, "lseek error");
num_bytes = write (dbf->desc, temp, av_size);
if (num_bytes != av_size) _gdbm_fatal (dbf, "write error");
free (temp);
}

/* 在可用块列表av_table[]中获取一个大于size字节的可用块。av_count是列表
中的元素个数。如果找到一个块,则提取出它来,并且把其余的可用块前移以填满空间。如果没
有大于size的块,则返回大小为0的块。本例程并不做I/O操作 */
static avail_elem
get_elem (size, av_table, av_count)
int size;
avail_elem av_table[];
int *av_count;
{
int index;			/* 用来查找可用块 */
avail_elem val;		/* 默认的返回值 */
/* 初始化默认的返回值 */
val.av_adr = 0;
val.av_size = 0;
/* 查找元素,列表项按size排序 */
index = 0;
while (index < *av_count && av_table[index].av_size < size)
{
index++;
}
/* 如果没找到,则返回大小为0的块 */
if (index >= *av_count)
return val;
/* 找到了,则保存这个可用块,后面所有可用块前移一个位置 */
val = av_table[index];
*av_count -= 1;
while (index < *av_count)
{
av_table[index] = av_table[index+1];
index++;
}
return val;
}
/* 把一个可用块元素new_el插入到可用块列表av_table[]中。本例程不做I/O操作,av_count为
表中元素的个数,can_merge表示是否允许合并可用块 */
int
_gdbm_put_av_elem (new_el, av_table, av_count, can_merge)
avail_elem new_el;
avail_elem av_table[];
int *av_count;
int can_merge;		/* 我们应该允许可用块合并 */
{
int index;			/* 用来搜索可用块 */
int index1;
/* 若要插入的可用块太小了,则直接返回FALSE */
if (new_el.av_size <= IGNORE_SIZE)
return FALSE;
if (can_merge == TRUE)
{
/* 搜索要与new_el可用块合并的可用块 */
index = 0;
while (index < *av_count)
{
/* 表中可用块恰好位于new_el可用块的前面 */
if ((av_table[index].av_adr
+ av_table[index].av_size) == new_el.av_adr)
{
/* 合并,简单地扩展它的终止点即可 */
av_table[index].av_size += new_el.av_size;
}
/* 表中可用块恰好位于new_el可用块的后面 */
else if ((new_el.av_adr
+ new_el.av_size) == av_table[index].av_adr)
{
/* 合并,并更新地址和大小 */
av_table[index].av_adr = new_el.av_adr;
av_table[index].av_size += new_el.av_size;
}
/* 还没找到合并的可用块,继续 */
else
{
index++;
continue;
}

/* 如果到达这里,则表示合并成功 */
return TRUE;
}
}
/* 寻找要插入可用块元素的位置,列表按size排序 */
index = 0;
while (index < *av_count && av_table[index].av_size < new_el.av_size)
{
index++;
}
/* 把后面所有元素后移一个位置 */
index1 = *av_count-1;
while (index1 >= index)
{
av_table[index1+1] = av_table[index1];
index1--;
}
/* 插入标识这个新的可用块的可用块元素 */
av_table[index] = new_el;
/* 元素个数增1 */
*av_count += 1;
return TRUE;
}
/* 在整数字节的块大小内分配一个新的文件空间,这能确保小于这个大小的数据能存放在一个单一的块中。足够的块会被分配
以确保被分配在块中的字节数大于size。dbf中包含需要更新的文件头。本例程不做I/O操作 */
static avail_elem
get_block (size, dbf)
int size;
gdbm_file_info *dbf;
{
avail_elem val;
/* 至少需要一个块 */
val.av_adr  = dbf->header->next_block;
val.av_size = dbf->header->block_size;
/* 获取足够的多的块以满足我们需要 */
while (val.av_size < size)
val.av_size += dbf->header->block_size;
/* 更新文件头,然后返回 */
dbf->header->next_block += val.av_size;
/* 我们修改了文件头 */
dbf->header_changed = TRUE;
return val;

}
/* 调整当前桶对应的可用块数。当文件头需要写回时,我们能确保当前桶对应的可用块数至少能达到最大值的1/3 */
static void
adjust_bucket_avail (dbf)
gdbm_file_info *dbf;
{
int third = BUCKET_AVAIL / 3; /* 可用块数为最大值的1/3 */
avail_elem av_el;
/* 如果还能往桶中添加更多的可用块元素 */
if (dbf->bucket->av_count < third)
{
if (dbf->header->avail.count > 0)
{
dbf->header->avail.count -= 1;
av_el = dbf->header->avail.av_table[dbf->header->avail.count];
_gdbm_put_av_elem (av_el, dbf->bucket->bucket_avail, /* 把文件头中的可用块列表每个元素插入到当前桶的可用块列表中 */
&dbf->bucket->av_count, dbf->coalesce_blocks);
dbf->bucket_changed = TRUE;
}
return;
}
/* 如果当前桶中可用块元素个数太多了,则多出来的可用块弹出并插入到文件头的可用块列表中 */
while (dbf->bucket->av_count > BUCKET_AVAIL-third
&& dbf->header->avail.count < dbf->header->avail.size)
{
av_el = get_elem (0, dbf->bucket->bucket_avail, &dbf->bucket->av_count); /* 在当前桶获取大于0字节的可用块 */
_gdbm_put_av_elem (av_el, dbf->header->avail.av_table,  /* 把它插入到文件头的可用块列表中 */
&dbf->header->avail.count, dbf->coalesce_blocks);
dbf->bucket_changed = TRUE;
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: