dbm数据库源代码分析(5):gdbmopen.c和gdbmclose.c

来源:互联网 发布:移民行业前景知乎 编辑:程序博客网 时间:2024/05/11 23:40

   现在解剖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)最后释放文件头和文件信息结构。

原创粉丝点击