dbm数据库源代码分析(5):gdbmopen.c和gdbmclose.c
2016-07-29 00:00
447 查看
现在解剖gdbmopen.c和gdbmclose.c的源码。
gdbmopen.c包含打开数据库的函数gdbm_oepen和初始化桶缓存的函数_gdbm_init_cache。注意其中的函数签名格式,函数参数列表中没给出类型,类型信息在圆括号和语句开始的左花括号之间。这种风格的函数签名是为了兼容老式的非标准的C编译器,gcc也支持这种风格的函数签名。
(1)gdbm_file_info* gdbm_oepen(name,block_size,flags,mode,fatal_func)函数。name是指向文件名指针。注意这个函数返回一个指向gdbm_file_info结构的指针变量(称为文件句柄),而在导出的头文件中(参看gdbm.proto),此函数声明返回的是GDBM_FILE型变量,也是一个指针变量。由于都是指针型变量,即都是int型,存放的是一个地址值,因此是一致的。至于这个指针具体指向什么类型的结构,对用户来说无关紧要,他只需使用这个句柄来操作文件,并不需要知道结构中的具体内容。因此为了隐藏实现,在导出的用户接口中我们通常让这个返回的指针变量指向一个“虚设”的无实际用途的结构,这样用户就不会知道gdbm_file_info结构的具体内容,但同时又不要影响他对接口的使用。这就是为什么gdbm.proto中要定义一个GDBM_FILE型的指针变量指向一个“虚设”的结构。在其他导出的用户接口中,这个打开的文件句柄dbf也被声明为GDBM_FILE类型的指针变量,在具体的函数实现时,dbf实际上是指向打开的gdbm_file_info结构的。
函数执行时,如果文件大小为0字节,则执行一个文件初始化过程,设置文件中的一些初始结构。block_size用来在初始化过程中确定各个结构的大小,如果它的值小于512,则使用文件系统的默认值,否则就使用这个传进来的block_size值。如果文件事先已经初始化过了,则忽略block_size值。如果flags设置为GDBM_READER,用户以只读方式访问数据库,则任何对dbm_store或dbm_delete的调用都将会失败。多个用户可同时访问一个数据库(使用文件锁)。如果flag设置为GDBM_WRITER,说明用户以可读写方式访问数据库,这需要一个排它锁。如果flags是GDBM_WRCREAT,则用户以可读写的的方式访问数据库,且当数据库不存在时,创建一个新的数据库(也是可读写的)。整个过程中任何的操作出错都将导致返回NULL,并且把错误码设置到全局变量gdbm_errno中。如果没有错误发生,将返回一个指向gdbm文件结构的指针。
gdbm_open的执行流程:
1)创建文件结构dbf并分配空间,然后一些指针域被初始化为空;
2)为文件名的字符串存储分配空间,然后保存文件名;
3)初始化错误处理函数及一些访问标志;
4)根据flags确定是否设置快速写模式;
5)根据flags用open函数打开文件,获得其文件描述符;
6)获取文件的状态信息,然后以适当的方式(共享锁或排它锁)锁定文件;
7)根据状态信息确定打开的是新文件还是已经存在的数据库文件;
8)若是新文件(即文件大小为0字节),则
a)设置传递块的大小;
b)为文件头分配空间,设置其魔数和传递块大小;
c)创建要跟踪的散列桶目录表,并分配空间;
d)创建文件头要跟踪的散列桶,先计算桶中元素个数,然后分配空间;
f)调用_gdbm_new_bucket初始化这个新桶并设置其中的一些标志,然后初始化每个目录项;
g)将所有这些初始化信息写入文件,开始是文件头和活动的可用块,接着是目录表,最后是桶。
9)若是一个已经存在的数据库文件,则
a)读取文件中的文件头,并用魔数对其进行校验;
b)为我们的文件头分配空间,并设置为所读取的文件头信息;
c)为我们的散列表目录分配空间,并设置为所读取的散列表目录。
10)完成文件的操作后,将dbf中其他的指针域(如桶缓存)初始化为空,并完成记帐信息的初始化;
11)返回最终的文件信息结构指针dbf。
注意我们要获取的文件的各项信息,如文件头、散列桶目录表、第一个散列桶等,被读入到了内存(若是新文件则直接在内存中创建),并且在dbf中都有指针指向它。
我们知道,数据库文件中只包含gdbm_file_header(其中包含avail_block及avail_elem)、dir[dir_size/4]、hash_bucket(其中包含bucket_element)。在初始化新文件时,会先截断文件,表示文件将从位置0处开始初始化。gdbm_file_header本身只有52个字节,但初始化时它要占blocksize个字节(此参数由用户指定,但不能小于512,小于512时会重设为文件系统的默认值,目前Linux上一般为1024),空出来的部分用来存放avail_elem元素列表(指明可用存储块的实际位置和大小)。文件头中的block_size域、dir_size域都会被初始化为blocksize,dir_bits为其所占的二进制位数。可见散列桶目录表中有blocksize/4个目录项(每个目录项为off_t类型,即long,占4个字节),且整个散列桶目录表共占blocksize个字节。目录表跟在文件头之后,因此文件头中的指向目录表起始地址的dir域恰好初始化为blocksize。
一个hash_bucket本身只有80个字节,但初始化时它也要占blocksize个字节,因此文件头的中bucket_size域也被初始化为blocksize。后面空出的部分用来存放以后分裂出来的bucket_element[]列表(即可扩展散列表),能存放的桶元素个数为(blocksize-sizeof(hash_bucket))/sizeof(bucket_element)+1 = (blocksize-80)/20+1,加1是因为hash_bucket中那个活动的bucket_element也要算进去。这个值会设置到文件头的bucket_elems域中去。这样bucket_elems记录了一个blocksize个字节的hash_bucket总共能存放的bucket_element个数。在用_gdbm_new_bucket初始化这个hash_bucket时,bucket_bits标识域和count域设为0,每个桶元素的哈希值hash_val初始化为-1。然后可用块个数av_count设为1,表示桶对应了1个分配的可用存储块(目前一个hash_bucket最多只能对应6个可用存储块),用bucket_avail[0](avail_elem结构)指明这个可用块。这个可用块紧跟在hash_bucket之后,因此它的地址av_adr为3*blocksize,可用块的大小av_size也设置为blocksize。
之后,对目录表中的每个目录项以及文件头中的活动的可用块进行初始化。由于初始时只有一个hash_bucket,因此dir[0...dir_size/4-1]中的每个目录项初始时都指向这个hash_bucket。文件头中的avail(avail_block类型)的size域表示文件头空间中能存储的avail_elem元素个数,它的值为(blocksize-sizeof(gdbm_file_header))/sizeof(avail_elem)+1 = (blocksize-52)/8+1,加1是因为avail中的那个活动的avail_elem也要算进去。这样avail.size记录了一个blocksize个字节的文件头总共能存放的avail_elem个数。avail的count和next_block(下一个可用块的偏移地址)均初始化为0。文件头的next_block指针表示下一个未分配可用块地址,因此要初始化为4*blocksize。所有工作做完后,把这些数据按顺序写入到文件中。
可见,数据库文件上的排布为:文件头、目录表、散列桶,每一部分都占blocksize个字节。后面接着的就是存放关键字/数据的可用块,每个可用块也占blocksize个字节。散列桶可以对可用块及块中的关键字/数据(这是我们需要的)进行定位、查找、存取等。
(2)int _gdbm_init_cache(dbf,size)函数。用于初始化桶缓存数组。dbf为文件信息结构,size为桶缓存中缓存项的个数,默认为DEFAULT_CACHESIZE,在gdbmconst.h中定义,为100。桶缓存为一个数组,每个元素为一个缓存项(cache_elem),缓存项不仅包含了实际的桶指针,还包含了数据缓存块及一些标志。数据缓存块包含包含哈希值、数据长度、关键字长度、指向数据起点的指针、偏移位置值等字段。
函数执行流程如下:
1)根据size参数为桶楥存数组分配空间;
2)初始化每个缓存项持有的桶,并分配空间;
3)初始化每个缓存项的的数据缓存块;
4)设置文件信息结构中的当前桶和当前缓存项指针;
5)成功,返回0。
dbf中的cache_size域是缓存项的个数,初始化为size;bucket域初始化为指向第一个缓存项持有的桶;cache_entry域初始化为指向第一个缓存项。每个缓存项的桶偏移地址ca_adr初始化为0,其数据缓存元素的hash_val和elem_loc初始化为-1,数据起点指针dptr初始化NULL。
(3)gdbm_close.c中的唯一一个函数void gdbm_close(dbf)。dbf为事先打开的数据库文件句柄。
执行流程如下:
1)确保未写入的数据全部写入到磁盘;
2)若加了锁,则对文件解锁;
3)关闭文件,释放文件名和散列目录表;
4)对每个缓存项,释放其持有的桶、其数据缓存元素中的数据;
5)释放整个桶缓存(即所有的缓存项);
6)最后释放文件头和文件信息结构。
gdbmopen.c包含打开数据库的函数gdbm_oepen和初始化桶缓存的函数_gdbm_init_cache。注意其中的函数签名格式,函数参数列表中没给出类型,类型信息在圆括号和语句开始的左花括号之间。这种风格的函数签名是为了兼容老式的非标准的C编译器,gcc也支持这种风格的函数签名。
(1)gdbm_file_info* gdbm_oepen(name,block_size,flags,mode,fatal_func)函数。name是指向文件名指针。注意这个函数返回一个指向gdbm_file_info结构的指针变量(称为文件句柄),而在导出的头文件中(参看gdbm.proto),此函数声明返回的是GDBM_FILE型变量,也是一个指针变量。由于都是指针型变量,即都是int型,存放的是一个地址值,因此是一致的。至于这个指针具体指向什么类型的结构,对用户来说无关紧要,他只需使用这个句柄来操作文件,并不需要知道结构中的具体内容。因此为了隐藏实现,在导出的用户接口中我们通常让这个返回的指针变量指向一个“虚设”的无实际用途的结构,这样用户就不会知道gdbm_file_info结构的具体内容,但同时又不要影响他对接口的使用。这就是为什么gdbm.proto中要定义一个GDBM_FILE型的指针变量指向一个“虚设”的结构。在其他导出的用户接口中,这个打开的文件句柄dbf也被声明为GDBM_FILE类型的指针变量,在具体的函数实现时,dbf实际上是指向打开的gdbm_file_info结构的。
函数执行时,如果文件大小为0字节,则执行一个文件初始化过程,设置文件中的一些初始结构。block_size用来在初始化过程中确定各个结构的大小,如果它的值小于512,则使用文件系统的默认值,否则就使用这个传进来的block_size值。如果文件事先已经初始化过了,则忽略block_size值。如果flags设置为GDBM_READER,用户以只读方式访问数据库,则任何对dbm_store或dbm_delete的调用都将会失败。多个用户可同时访问一个数据库(使用文件锁)。如果flag设置为GDBM_WRITER,说明用户以可读写方式访问数据库,这需要一个排它锁。如果flags是GDBM_WRCREAT,则用户以可读写的的方式访问数据库,且当数据库不存在时,创建一个新的数据库(也是可读写的)。整个过程中任何的操作出错都将导致返回NULL,并且把错误码设置到全局变量gdbm_errno中。如果没有错误发生,将返回一个指向gdbm文件结构的指针。
gdbm_open的执行流程:
1)创建文件结构dbf并分配空间,然后一些指针域被初始化为空;
2)为文件名的字符串存储分配空间,然后保存文件名;
3)初始化错误处理函数及一些访问标志;
4)根据flags确定是否设置快速写模式;
5)根据flags用open函数打开文件,获得其文件描述符;
6)获取文件的状态信息,然后以适当的方式(共享锁或排它锁)锁定文件;
7)根据状态信息确定打开的是新文件还是已经存在的数据库文件;
8)若是新文件(即文件大小为0字节),则
a)设置传递块的大小;
b)为文件头分配空间,设置其魔数和传递块大小;
c)创建要跟踪的散列桶目录表,并分配空间;
d)创建文件头要跟踪的散列桶,先计算桶中元素个数,然后分配空间;
f)调用_gdbm_new_bucket初始化这个新桶并设置其中的一些标志,然后初始化每个目录项;
g)将所有这些初始化信息写入文件,开始是文件头和活动的可用块,接着是目录表,最后是桶。
9)若是一个已经存在的数据库文件,则
a)读取文件中的文件头,并用魔数对其进行校验;
b)为我们的文件头分配空间,并设置为所读取的文件头信息;
c)为我们的散列表目录分配空间,并设置为所读取的散列表目录。
10)完成文件的操作后,将dbf中其他的指针域(如桶缓存)初始化为空,并完成记帐信息的初始化;
11)返回最终的文件信息结构指针dbf。
注意我们要获取的文件的各项信息,如文件头、散列桶目录表、第一个散列桶等,被读入到了内存(若是新文件则直接在内存中创建),并且在dbf中都有指针指向它。
我们知道,数据库文件中只包含gdbm_file_header(其中包含avail_block及avail_elem)、dir[dir_size/4]、hash_bucket(其中包含bucket_element)。在初始化新文件时,会先截断文件,表示文件将从位置0处开始初始化。gdbm_file_header本身只有52个字节,但初始化时它要占blocksize个字节(此参数由用户指定,但不能小于512,小于512时会重设为文件系统的默认值,目前Linux上一般为1024),空出来的部分用来存放avail_elem元素列表(指明可用存储块的实际位置和大小)。文件头中的block_size域、dir_size域都会被初始化为blocksize,dir_bits为其所占的二进制位数。可见散列桶目录表中有blocksize/4个目录项(每个目录项为off_t类型,即long,占4个字节),且整个散列桶目录表共占blocksize个字节。目录表跟在文件头之后,因此文件头中的指向目录表起始地址的dir域恰好初始化为blocksize。
一个hash_bucket本身只有80个字节,但初始化时它也要占blocksize个字节,因此文件头的中bucket_size域也被初始化为blocksize。后面空出的部分用来存放以后分裂出来的bucket_element[]列表(即可扩展散列表),能存放的桶元素个数为(blocksize-sizeof(hash_bucket))/sizeof(bucket_element)+1 = (blocksize-80)/20+1,加1是因为hash_bucket中那个活动的bucket_element也要算进去。这个值会设置到文件头的bucket_elems域中去。这样bucket_elems记录了一个blocksize个字节的hash_bucket总共能存放的bucket_element个数。在用_gdbm_new_bucket初始化这个hash_bucket时,bucket_bits标识域和count域设为0,每个桶元素的哈希值hash_val初始化为-1。然后可用块个数av_count设为1,表示桶对应了1个分配的可用存储块(目前一个hash_bucket最多只能对应6个可用存储块),用bucket_avail[0](avail_elem结构)指明这个可用块。这个可用块紧跟在hash_bucket之后,因此它的地址av_adr为3*blocksize,可用块的大小av_size也设置为blocksize。
之后,对目录表中的每个目录项以及文件头中的活动的可用块进行初始化。由于初始时只有一个hash_bucket,因此dir[0...dir_size/4-1]中的每个目录项初始时都指向这个hash_bucket。文件头中的avail(avail_block类型)的size域表示文件头空间中能存储的avail_elem元素个数,它的值为(blocksize-sizeof(gdbm_file_header))/sizeof(avail_elem)+1 = (blocksize-52)/8+1,加1是因为avail中的那个活动的avail_elem也要算进去。这样avail.size记录了一个blocksize个字节的文件头总共能存放的avail_elem个数。avail的count和next_block(下一个可用块的偏移地址)均初始化为0。文件头的next_block指针表示下一个未分配可用块地址,因此要初始化为4*blocksize。所有工作做完后,把这些数据按顺序写入到文件中。
可见,数据库文件上的排布为:文件头、目录表、散列桶,每一部分都占blocksize个字节。后面接着的就是存放关键字/数据的可用块,每个可用块也占blocksize个字节。散列桶可以对可用块及块中的关键字/数据(这是我们需要的)进行定位、查找、存取等。
(2)int _gdbm_init_cache(dbf,size)函数。用于初始化桶缓存数组。dbf为文件信息结构,size为桶缓存中缓存项的个数,默认为DEFAULT_CACHESIZE,在gdbmconst.h中定义,为100。桶缓存为一个数组,每个元素为一个缓存项(cache_elem),缓存项不仅包含了实际的桶指针,还包含了数据缓存块及一些标志。数据缓存块包含包含哈希值、数据长度、关键字长度、指向数据起点的指针、偏移位置值等字段。
函数执行流程如下:
1)根据size参数为桶楥存数组分配空间;
2)初始化每个缓存项持有的桶,并分配空间;
3)初始化每个缓存项的的数据缓存块;
4)设置文件信息结构中的当前桶和当前缓存项指针;
5)成功,返回0。
dbf中的cache_size域是缓存项的个数,初始化为size;bucket域初始化为指向第一个缓存项持有的桶;cache_entry域初始化为指向第一个缓存项。每个缓存项的桶偏移地址ca_adr初始化为0,其数据缓存元素的hash_val和elem_loc初始化为-1,数据起点指针dptr初始化NULL。
/* gdbmopen.c - 打开dbm文件,并且初始化要使用的数据结构 */ /* 先包含系统相关配置 */ #include "autoconf.h" /* 包含平台相关头文件和函数的常量标志 */ #include "gdbmdefs.h" /* 包含桶、缓存项、数据库文件结构的定义 所有平台相关的头文件和函数声明、gdbm的所有函数声明和要用到的常量也在这里 */ #include "gdbmerrno.h" /* 包含所有的错误码列表 */ /* 打开数据库文件,不存在时则创建新的数据库文件,并执行一个文件初始化过程 */ gdbm_file_info * gdbm_open (file, block_size, flags, mode, fatal_func) char *file; /* 要打开的文件名 */ int block_size; /* 设置内存与磁盘IO传递数据的单位,最小为512字节 */ int flags; /* 文件打开标志,可以是gdbmconst.h中定义的标志GDMB_READER、 GDMB_WRITER */ int mode; /* 文件创建模式,与open(2)函数相同 */ void (*fatal_func) (); /* 错误处理函数 */ { gdbm_file_info *dbf; /* 要返回的文件结构记录 */ struct stat file_stat; /* 封装文件信息stat结构 */ int len; /* 文件名的长度 */ int num_bytes; /* 在读和写的过程中使用 */ off_t file_pos; /* 文件读写位置 */ int lock_val; /* Returned by the flock call. */ int file_block_size; /* 传递的数据块大小(用于新文件) */ int index; /* 作为循环索引 */ char need_trunc; /* 是否需要截断处理,在使用GDBM_NEWDB和锁时需要截断,以避免在只读 的方式下截断文件 */ gdbm_errno = GDBM_NO_ERROR; /* 初始化变量gdbm_errno */ /* 为一个新的文件结构分配空间 */ dbf = (gdbm_file_info *) malloc (sizeof (gdbm_file_info)); if (dbf == NULL) { gdbm_errno = GDBM_MALLOC_ERROR; /* 设置内存分配错误码 */ return NULL; } /* 先初始化一些域为空,这样如果在分配这些结构前gdbm_close被调用,则也能正常工作 */ dbf->dir = NULL; dbf->bucket = NULL; dbf->header = NULL; dbf->bucket_cache = NULL; dbf->cache_size = 0; /* 保存文件名 */ len = strlen (file); dbf->name = (char *) malloc (len + 1); /* 为文件名分配内存空间 */ if (dbf->name == NULL) /* 若分配未成功,则释放已分配的文件结构,直接返回 */ { free (dbf); gdbm_errno = GDBM_MALLOC_ERROR; return NULL; } strcpy (dbf->name, file); /* 保存好文件名 */ /* 初始化错误处理函数 */ dbf->fatal_err = fatal_func; dbf->fast_write = TRUE; /* 默认为快速写模式 */ dbf->file_locking = TRUE; /* 默认需要文件锁 */ dbf->central_free = FALSE; /* 默认不使用central_free */ dbf->coalesce_blocks = FALSE; /* 默认不合并空闲块 */ /* GDBM_FAST用来确定是否设置快速写模式 */ if (flags & GDBM_SYNC) { /* 如果GDBM_SYNC被设置,则不做快速写 */ dbf->fast_write = FALSE; } if (flags & GDBM_NOLOCK) /* 如果设置了无需文件锁标志 */ { dbf->file_locking = FALSE; } /* 打开文件 */ need_trunc = FALSE; switch (flags & GDBM_OPENMASK) /* 掩码可以掩掉除尾三位的所有其他位 */ { case GDBM_READER: /* 只读方式打开 */ dbf->desc = open (dbf->name, O_RDONLY, 0); break; case GDBM_WRITER: /* 可写方式打开 */ dbf->desc = open (dbf->name, O_RDWR, 0); break; case GDBM_NEWDB: /* 要创建新文件(O_CREATE标志),则要使用mode参数 */ dbf->desc = open (dbf->name, O_RDWR|O_CREAT, mode); need_trunc = TRUE; break; default: dbf->desc = open (dbf->name, O_RDWR|O_CREAT, mode); break; bucket_cache } if (dbf->desc < 0) { free (dbf->name); free (dbf); gdbm_errno = GDBM_FILE_OPEN_ERROR; /* 设置打开文件错误 */ return NULL; } /* 获取文件的状态信息 */ fstat (dbf->desc, &file_stat); /* 以适当的方式锁定文件 */ if ((flags & GDBM_OPENMASK) == GDBM_READER) /* 只读时的锁写 */ { if (file_stat.st_size == 0) /* 文件大小为0,即读的是一个空文件 */ { close (dbf->desc); free (dbf->name); free (dbf); gdbm_errno = GDBM_EMPTY_DATABASE; /* 设置读取空数据库错误 */ return NULL; } if (dbf->file_locking) { /* 只读方式锁定文件(共享锁),成功则会设置lock_val为0,参看system.h */ READLOCK_FILE(dbf); } } else if (dbf->file_locking) /* 否则加可写锁 */ { /* 可写方式锁定文件(排它锁),成功则会设置lock_val为0,参看system.h */ WRITELOCK_FILE(dbf); } if (dbf->file_locking && (lock_val != 0)) /* 锁定未成功 */ { close (dbf->desc); free (dbf->name); free (dbf); if ((flags & GDBM_OPENMASK) == GDBM_READER) gdbm_errno = GDBM_CANT_BE_READER; /* 设置为不能加共享锁 */ else gdbm_errno = GDBM_CANT_BE_WRITER; /* 设置为不能加排它锁 */ return NULL; } /* 设置读写标志 */ dbf->read_write = (flags & GDBM_OPENMASK); /* 若我们有一个排它锁,且打开标志为GDBM_NEWDB,则要截断文件,即把文件长度设为0,丢弃已有内容 */ if (need_trunc && file_stat.st_size != 0) { TRUNCATE (dbf); /* 截断文件,参看system.h */ fstat (dbf->desc, &file_stat); /* 重新获得文件的状态信息 */ } /* 确定它是新文件还是旧文件 */ if (file_stat.st_size == 0) /* 是一个新文件,创建一个新数据库 */ { /* 先设置内存和磁盘间传递块的大小,不能小于512字节 */ if (block_size < 512) file_block_size = STATBLKSIZE; /* 参看system.h */ else file_block_size = block_size; /* 为文件头分配空间 */ dbf->header = (gdbm_file_header *) malloc (file_block_size); if (dbf->header == NULL) { gdbm_close (dbf); gdbm_errno = GDBM_MALLOC_ERROR; return NULL; } /* 设置文件头的魔数和传递块的大小 */ dbf->header->header_magic = 0x13579ace; dbf->header->block_size = file_block_size; /* 创建初始的散列桶目录表 */ dbf->header->dir_size = 8 * sizeof (off_t); /* 初始的目录表大小 */ dbf->header->dir_bits = 3; /* 初始时目录地址占用的比特数为3位 */ while (dbf->header->dir_size < dbf->header->block_size) /* 使得目录表大小与传递块的大小相等 */ { dbf->header->dir_size <<= 1; dbf->header->dir_bits += 1; } /* 最后dir_size=block_size=2^dir_bits */ /* 检查目录项的正确性 */ if (dbf->header->dir_size != dbf->header->block_size) { gdbm_close (dbf); gdbm_errno = GDBM_BLOCK_SIZE_ERROR; /* 传递块大小错误 */ return NULL; } /* 为散列桶目录表分配空间 */ dbf->dir = (off_t *) malloc (dbf->header->dir_size); if (dbf->dir == NULL) { gdbm_close (dbf); gdbm_errno = GDBM_MALLOC_ERROR; return NULL; } dbf->header->dir = dbf->header->block_size; /* 指向目录表起始地址 */ /* 创建首个也是唯一的散列桶 */ dbf->header->bucket_elems = (dbf->header->block_size - sizeof (hash_bucket)) / sizeof (bucket_element) + 1; /* 计算桶中元素的个数 */ dbf->header->bucket_size = dbf->header->block_size; /* 桶的大小为传递块的大小 */ dbf->bucket = (hash_bucket *) malloc (dbf->header->bucket_size); /* 为桶分配内存空间 */ if (dbf->bucket == NULL) { gdbm_close (dbf); gdbm_errno = GDBM_MALLOC_ERROR; return NULL; } _gdbm_new_bucket (dbf, dbf->bucket, 0); /* 初始化这个新桶 */ dbf->bucket->av_count = 1; /* 设置桶对应可用块的个数 */ dbf->bucket->bucket_avail[0].av_adr = 3*dbf->header->block_size; dbf->bucket->bucket_avail[0].av_size = dbf->header->block_size; /* 目录表中的每个目录项指向一个散列桶 */ for (index = 0; index < dbf->header->dir_size / sizeof (off_t); index++) dbf->dir[index] = 2*dbf->header->block_size; /* 初始化活动的可用块 */ dbf->header->avail.size = ( (dbf->header->block_size - sizeof (gdbm_file_header)) / sizeof (avail_elem)) + 1; /* 可用块中可用块元素的个数 */ dbf->header->avail.count = 0; dbf->header->avail.next_block = 0; /* 下一个可用块的偏移地址 */ dbf->header->next_block = 4*dbf->header->block_size; /* 将初始化配置信息写入文件 */ /* 块0是文件头和活动的可用块 */ num_bytes = write (dbf->desc, dbf->header, dbf->header->block_size); /* block_size一般为1024 */ if (num_bytes != dbf->header->block_size) /* 实际必须要写入block_size个字节,否则出错 */ { gdbm_close (dbf); gdbm_errno = GDBM_FILE_WRITE_ERROR; /* 文件写入错误 */ return NULL; } /* 块1是初始的散列桶目录表 */ num_bytes = write (dbf->desc, dbf->dir, dbf->header->dir_size); if (num_bytes != dbf->header->dir_size) { gdbm_close (dbf); gdbm_errno = GDBM_FILE_WRITE_ERROR; return NULL; } /* 块2是这个唯一的桶 */ num_bytes = write (dbf->desc, dbf->bucket, dbf->header->bucket_size); if (num_bytes != dbf->header->bucket_size) { gdbm_close (dbf); gdbm_errno = GDBM_FILE_WRITE_ERROR; return NULL; } /* 等待初始化配置信息被写入磁盘 */ fsync (dbf->desc); free (dbf->bucket); } else /* 否则是一个已经存在的数据,从其文件头中读取信息,然后初始化散列目录 */ { gdbm_file_header partial_header; /* 第1部分 */ /* 读取文件头部分 */ num_bytes = read (dbf->desc, &partial_header, sizeof (gdbm_file_header)); if (num_bytes != sizeof (gdbm_file_header)) { gdbm_close (dbf); gdbm_errno = GDBM_FILE_READ_ERROR; /* 读取文件错误 */ return NULL; } /* 用魔数来校验文件的头是否完好 */ if (partial_header.header_magic != 0x13579ace) { gdbm_close (dbf); gdbm_errno = GDBM_BAD_MAGIC_NUMBER; /* 文件头校验出错 */ return NULL; } /* 是一个完好的数据,则读取整个文件头 */ dbf->header = (gdbm_file_header *) malloc (partial_header.block_size); if (dbf->header == NULL) { gdbm_close (dbf); gdbm_errno = GDBM_MALLOC_ERROR; return NULL; } bcopy (&partial_header, dbf->header, sizeof (gdbm_file_header)); num_bytes = read (dbf->desc, &dbf->header->avail.av_table[1], dbf->header->block_size-sizeof (gdbm_file_header)); if (num_bytes != dbf->header->block_size-sizeof (gdbm_file_header)) { gdbm_close (dbf); gdbm_errno = GDBM_FILE_READ_ERROR; return NULL; } /* 为散列表目录分配空间 */ dbf->dir = (off_t *) malloc (dbf->header->dir_size); if (dbf->dir == NULL) { gdbm_close (dbf); gdbm_errno = GDBM_MALLOC_ERROR; return NULL; } /* 读取散列表目录 */ file_pos = lseek (dbf->desc, dbf->header->dir, L_SET); /* L_SET表示一个绝对位置 */ if (file_pos != dbf->header->dir) { gdbm_close (dbf); gdbm_errno = GDBM_FILE_SEEK_ERROR; return NULL; } num_bytes = read (dbf->desc, dbf->dir, dbf->header->dir_size); if (num_bytes != dbf->header->dir_size) { gdbm_close (dbf); gdbm_errno = GDBM_FILE_READ_ERROR; return NULL; } } /* 完成dbf的初始化 */ dbf->last_read = -1; dbf->bucket = NULL; dbf->bucket_dir = 0; dbf->cache_entry = NULL; dbf->header_changed = FALSE; dbf->directory_changed = FALSE; dbf->bucket_changed = FALSE; dbf->second_changed = FALSE; /* 大功告成,返回文件信息结构的指针 */ return dbf; } /* 桶缓存的初始化工作:为缓存分配空间,初始化每个缓存项,初始化缓存项持有的桶和数据缓存元素, 设置文件的桶缓存指针、缓存大小、当前桶及当前缓存项等 */ int _gdbm_init_cache(dbf, size) gdbm_file_info *dbf; /* 数据库文件结构的指针 */ int size; /* 缓存的大小(即缓存项的个数) */ { register int index; if (dbf->bucket_cache == NULL) { /* 为缓存分配内存空间 */ dbf->bucket_cache = (cache_elem *) malloc(sizeof(cache_elem) * size); if(dbf->bucket_cache == NULL) { gdbm_errno = GDBM_MALLOC_ERROR; /* 设置内存分配错误标志 */ return(-1); } dbf->cache_size = size; /* 设置缓存项的个数 */ for(index = 0; index < size; index++) /* 初始化每个缓存项及其中的数据缓存元素 */ { (dbf->bucket_cache[index]).ca_bucket = (hash_bucket *) malloc (dbf->header->bucket_size); /* 为桶分配内存空间 */ if ((dbf->bucket_cache[index]).ca_bucket == NULL) { gdbm_errno = GDBM_MALLOC_ERROR; /* 设置内存分配错误标志 */ return(-1); } (dbf->bucket_cache[index]).ca_adr = 0; /* 缓存项的偏移地址初始化为0 */ (dbf->bucket_cache[index]).ca_changed = FALSE; /* 更改标志初始为FALSE */ (dbf->bucket_cache[index]).ca_data.hash_val = -1; /* 数据缓存元素的哈希值初始化为-1 */ (dbf->bucket_cache[index]).ca_data.elem_loc = -1; /* 数据缓存元素的位置偏移初始化为-1 */ (dbf->bucket_cache[index]).ca_data.dptr = NULL; /* 数据缓存元素的数据起点初始化NULL */ } dbf->bucket = dbf->bucket_cache[0].ca_bucket; /* 文件的当前桶设为缓存中的第一个实际桶 */ dbf->cache_entry = &dbf->bucket_cache[0]; /* 文件的当前缓存项设为第一个缓存项 */ } return(0); }
(3)gdbm_close.c中的唯一一个函数void gdbm_close(dbf)。dbf为事先打开的数据库文件句柄。
执行流程如下:
1)确保未写入的数据全部写入到磁盘;
2)若加了锁,则对文件解锁;
3)关闭文件,释放文件名和散列目录表;
4)对每个缓存项,释放其持有的桶、其数据缓存元素中的数据;
5)释放整个桶缓存(即所有的缓存项);
6)最后释放文件头和文件信息结构。
/* gdbmclose.c - 关闭一个事先打开的dbm文件 */ #include "autoconf.h" /* 包含平台相关头文件和函数的常量标志 */ #include "gdbmdefs.h" /* 包含桶、缓存项、数据库文件结构的定义 所有平台相关的头文件和函数声明、gdbm的所有函数声明和要用到的常量也在这里 */ /* 关闭dbm文件,并释放与dbf关联的所有内存。在释放dbf的成员之前,要检查并确保它们被分配了空间 */ void gdbm_close (dbf) gdbm_file_info *dbf; { register int index; /* 用来释放桶缓存的索引 */ /* 确保数据库已经全部写入了磁盘 */ if (dbf->read_write != GDBM_READER) fsync (dbf->desc); /* 将未写入的数据全部写入磁盘 */ /* 关闭文件并且释放所有的被分配的内存 */ if (dbf->file_locking) { UNLOCK_FILE(dbf); /* 对文件解锁 */ } close (dbf->desc); /* 关闭文件 */ free (dbf->name); /* 释放文件名 */ if (dbf->dir != NULL) free (dbf->dir); /* 释放散列目录表 */ if (dbf->bucket_cache != NULL) { for (index = 0; index < dbf->cache_size; index++) { /* 释放每个缓存项中的相应域 */ if (dbf->bucket_cache[index].ca_bucket != NULL) free (dbf->bucket_cache[index].ca_bucket); /* 释放缓存项所持有的桶 */ if (dbf->bucket_cache[index].ca_data.dptr != NULL) free (dbf->bucket_cache[index].ca_data.dptr); /* 释放缓存项中数据缓存元素的数据 */ } free (dbf->bucket_cache); /* 释放整个桶缓存 */ } if ( dbf->header != NULL ) free (dbf->header); /* 释放文件头 */ free (dbf); /* 最后释放文件信息结构 */ }
相关文章推荐
- dbm数据库源代码分析(8):hash.c和findkey.c
- dbm数据库源代码分析(4):头文件部分(续)
- SQLite剖析(1):功能特性
- dbm数据库源代码分析(11):gdbmerrno.c、gdbmexists.c、gdbmfdesc.c和gdbmsync.c
- SQLite剖析(8):原子提交原理
- dbm数据库源代码分析(11):gdbmerrno.c、gdbmexists.c、gdbmfdesc.c和gdbmsync.c
- SQLite剖析(8):原子提交原理
- MySQL在大型网站的应用架构演变
- NoSQL数据库面面观
- Oracle官方并发教程(1)
- MySQL在大型网站的应用架构演变
- NoSQL数据库面面观
- Oracle官方并发教程(1)
- dbm数据库源代码分析(16):测试程序和转换程序
- 开源软件架构:NoSQL生态系统
- dbm数据库源代码分析(16):测试程序和转换程序
- 开源软件架构:NoSQL生态系统
- ibatis映射文件中 sqlMap 节点 namespace 属性含义
- WTF, MS JDBC Driver for SQL Server 2008
- WTF, MS JDBC Driver for SQL Server 2008