Berkeley DB 1.8.6源代码学习(九)

hash_buf.c 文件是记录调试用函数;

hash_func.c 文件是存放多个哈希函数,其中就有缺省的 hash4

 

 

hash_log2.c 文件

__log2 函数:用于计算指定数据的 log2 的值,如果需要可能会采用进 1 法解决不是 2 的幂的问题;

函数参数:

num :整型,待计算的值;

返回值: log2 的值;

伪代码:

limit 1

limit 每次左移一位,然后与 num 比较,直到 limit 大于等于 num ,返回移动的次数;

 

hash_page.c 文件:关于页面操作的函数;

__get_item 函数:获取游标指定的记录;

hashp HTAB 类型指针;

cursorp :游标类型指针;

key DBT 类型指针,返回记录的键值;

val DBT 类型指针,返回记录的数据值;

item_info ITEM_INFO 类型指针,传入和传出查找项信息;

返回值:成功或失败;

伪代码:

首先,获取游标所在的页面;如果游标的页面编号无效,则使用桶号获取页面,同时将游标指向桶的第一页的第 1 条记录,即 ndx 0 pgndx 0 ;如果游标页面标号有效,则获取页面指针保存到 cursorp pagep 成员;

判断本页是否有足够的空间存储记录,这一功能主要是为插入操作服务,具体的记录信息由 item_info 参数传入,即 seek_size 成员大于 0 ,且小于本页的空闲空间(可参考 hash.c hash_access 函数中对 seek_size 的赋值);如果本页有足够的空间可以存储记录,则将本页页面编号保存到 seek_found_page 成员返回;

如果游标的记录是页面最后一项,则需要到下一页取第 1 条记录;注意, pgndx 0 开始计数,如果其等于页面内记录个数,则表示当前页已经在上一轮获取中取出最后一条记录;如果下一页为空,即本页是桶链表最后一页,则返回失败;否则将游标移到下一页, pgndx 0 ,同时重新获取页面;

找到需要取的记录后,在取之前先计算记录键值和数据值的长度;计算方法是:

对于键值,按照其存储方式,应该是前一条普通记录数据值存储的首地址减去键值的首地址,如果是页面第一条记录,则直接使用页面最高地址减即可;获取前一条普通记录的索引编号使用 prev_realkey 函数;

对于数据值的长度,则使用键值的起始地址减去数据值的起始地址;

key val 的数据部分指向各自的值;

将返回记录的基本信息保存到 item_info 中返回,如页面编号、桶号、桶内序号、页面内序号、键值索引、数据值索引,同时将 status ITEM_OK 即可;

返回;

 

__get_item_reset 函数:重置游标

函数参数:

hashp HTAB 指针;

cursorp :游标指针;

返回值:成功

伪代码:

将游标指向的页面释放会缓冲区;

游标页面置空;

桶号为- 1

桶内编号为 0

页面内序号为 0

页面编号无效;

返回;

 

__get_item_done 函数:将游标所指的页面释放;

函数参数:

hashp HTAB 指针;

cursorp :游标指针;

返回值:成功;

伪代码:

释放游标所指页面;

将游标的页面指针置空;

返回;

本函数主要用于在遍历桶页链表查找指定项的时候,找到或到达最后一条记录后,作结束操作,释放所占的缓冲区,但是依然保留桶号和页面编号等,主要是方便后续如果有需要不用重新赋值;

 

__get_item_first 函数:获取哈希表第 0 桶第一条记录;

函数参数:

hashp HTAB 指针;

cursorp :游标指针;

key DBT 指针,返回记录的键值;

val DBT 指针,返回记录的数据值;

item_info ITEM_INFO 指针,传入和传出查找项信息;

返回值:成功或失败;

伪代码:

使用 __get_item_reset 函数重置游标;

将游标的桶号置为 0

使用 __get_item_next 函数获取指定记录即可;

返回获取的结果;

 

__get_item_next 函数:获取游标指向的当前记录,同时将游标移动到下一项;

函数参数:

hashp HTAB 指针;

cursorp :游标指针;

key DBT 指针,返回记录的键值;

val DBT 指针,返回记录的数据值;

item_info ITEM_INFO 指针,传入和传出查找项信息;

返回值:成功或失败;

伪代码:

使用 __get_item 函数获取游标指向的记录;

调整游标的 ndx pgndx ,分别递增 1

返回获取结果;

注意:此处只是简单的将 ndx pgndx 递增,并没有对 pgndx 作是否到达页内最后一条记录作判断,则就会出现 __get_item 函数中的 pgndx ==页面内记录数量的情况;

 

putpair 函数:向页面中存储普通记录;

函数参数:

p PAGE8 类型指针;

key DBT 类型指针,传入记录的键值;

val DBT 类型指针,传入记录数据值;

返回值:无;

伪代码:

获取当前页面内记录的数量 n ;由于记录序号从 0 开始,所以 n 就是新插入记录的序号;

为键值分配存储空间,首地址为 off

将键值写入,保存索引;

为数据值分配存储空间,写入数据值,保存索引;

将页面的记录数加 1

调整页面空闲空间的偏移;

 

next_realkey 函数:查找页面内指定记录之后的第一条普通记录的序号;

函数参数:

pagep PAGE16 类型指针;

n :整型,记录页面内序号;

返回值:记录序号;

伪代码:

从第 n 1 条记录开始,依次判断是否是大数据记录,即索引的键值部分为 BIGPAIR 大数据标记,找到第一个不是大数据记录的则返回其编号,直至页面内最后一条记录,仍未找到则返回- 1

 

prev_realkey 函数:查找页面内指定记录之前的第一条普通记录的序号;

函数参数:

pagep PAGE16 类型指针;

n :整型,记录页面内序号;

返回值:记录序号;

伪代码:

从第 n 1 条记录开始,向页面中记录序号较小的方向,依次判断是否是大数据记录,即索引的键值部分为 BIGPAIR 大数据标记,找到第一个不是大数据记录的则返回其编号,直至页面内第一条记录,仍未找到则返回 n

 

__delpair 函数:删除游标指定的记录;

函数参数:

hashp HTAB 指针;

cursorp :游标指针;

item_info ITEM_INFO 类型指针;

返回值:成功或失败;

伪代码:

将游标的内面内序号赋给 ndx

获取游标的页面 pagep

如果 pagep 的第 ndx 条记录为大数据记录,则使用 __big_delete 函数删除第 ndx 条记录; delta 0

对于普通记录,记录的删除需要引起页面内其他记录的移动,从而调整空闲空间,而记录的移动导致对应的索引的调整;根据记录的存储方式,调整记录及索引都是 ndx 后续的记录,其前面的记录无语移动;

首先,找 ndx 之前的第一条普通记录 check_ndx ,其数据部分的起始地址就是待删除记录存储的终止地址,从而计算出记录的尺寸(包括键值和数据值);如果待删除记录是第一条普通记录,则记录的尺寸 delta =页面最高地址- ndx 记录的数据值起始存储地址;如果不是第一条普通记录,则 delta check_ndx 的数据值存储起始地址- ndx 数据值存储起始地址;

然后将 ndx 后续记录的存储值向高地址移动 delta ,从而将删除的空间移动到空闲区域;首先,计算需要移动部分的起始地址 src ,即空闲空间的高地址+ 1 ,即最后一条普通记录数据值存储首地址;计算需要移动数据的长度 len ndx 的数据值存储首地址减去 src 相对页面地址的偏移量;最后计算移动的目的地址,注意移动后的终止地址就是 ndx 键值存储的终止地址,所以起始地址就是 ndx 键值存储终止地址减去 len ;最后使用 memmove 函数完成内存移动,注意, memmove 函数保证重叠部分的安全;

经过上述删除或移动后, ndx 的索引需要删除,同时调整其他索引的值;

从第 ndx 条索引开始,如果当前记录的下一条记录不是大数据记录,则第 ndx 条记录索引等于下一条记录索引的对应值加上 delta (即将记录前移一条);如果当前记录下一条记录是大数据记录,则当前记录索引等于下一条记录的索引(因为大数据记录并不存储在页面内,页面中记录删除与否并不影响其存储页面链表表头页的编号);

调整页面空闲空间的指针,增加 delta

页面内记录数量减 1

hashp 内记录数量减 1 ,即头部信息的 nkeys 成员减 1

判断 pagep 是否是空的溢出页,即页面中记录数量为 0 的桶链表页(非表头页);如果是,则需要将页面释放,标记为空闲页面;

由于桶链表采用单链表,所以页面的删除需要找到起左兄弟页面;

pagep 的页面编号保存为 to_find ;页面指针保存为 empty_page ;获取 empty_page 的右兄弟页面编号 link_page

获取 pagep 所在桶的首页 pagep

pagep 开始顺着链表找到右兄弟为 to_find 的页面 pagep

pagep 的右兄弟赋值为 link_page ;从而完成将空页面从链表中删除;

如果 item_info 的页面标号为 to_find ,则将其调整为 pagep 的页面编号, pgndx 调整为 pagep 的最后一条记录, seek_found_page 调整为 pagep 的页面编号;即在原有记录删除后,对应信息指向其前一条记录;

使用 __delete_page 函数将空页面标记为空闲页面;

pagep 页面标记为需要写回(修改了右兄弟页面);

使用 adjust_cursor 函数调整游标的位置;

返回;

 

__split_page 函数:将旧桶中的记录在哈希表扩展时重新分配;

函数参数:

hashp HTAB 指针;

obucket :整型,旧桶桶号;

nbucket :整型,新桶桶号;

返回值:成功或失败;

伪代码:

在本函数调用前哈希表必定已经作了扩展操作,即函数 __expand_table 被调用,此时由于最大桶号的调整(掩码也可能调整),哈希函数对于旧桶中记录键值的返回值将不再等于 obucket ,其中一部分高位掩码下最高位为 1 的记录将移动到新桶中,所以本函数的主要功能就是将 obucket 桶链表中的记录作重新插入;由于需要从 obucket 中将数据分裂,采取的策略就是将 obucket 的页面先由 hashp 的分裂缓冲区缓存,然后将其中的记录按照调整后的哈希函数重新插入到哈希表中;

首先,获取 obucket 桶链表的表头页 old_pagep

old_pagep 备份到 hashp split_buf 中;

使用 page_init 函数格式化 old_pagep ,为即将的插入作准备;

old_pagep 标记为已经更新;

初始化两个记录项信息 old_ii 指向 obucket 的桶首页, new_ii 指向 nbucket 的桶首页;二者 seek_found_page 成员均为 0

开始记录的重插入操作;

temp_pagep 为存储记录的页,由于 obucket 桶首页已经复制到 hashp 分裂缓冲区,所以, temp_pagep 初始化为 hashp 的分裂缓冲区;

1 、如果 temp_pagep 不等于空,则转 2 ,否则转 10

2 、设当前需要转移的第一条普通记录的存储结束偏移为 off off 初始化为页面大小,即当前页最高地址,页面中存储的第一条普通记录就是从高地址存放的, off 的作用主要的方便计算记录的尺寸;将当前记录序号 n 初始化为 0

3 、如果 n 小于本页记录数,则表示本页还有记录没有分配完;转 4 ,否则转 7

4 、如果记录 n 为大数据记录,则转 5 ;否则转 6

5 、使用 __get_bigkey 函数获取记录 n 的键值 key ,使用 __call_hash 函数计算桶号,如果为 obucket ,则使用 add_bigptr 函数将记录重新插入到 obucket 中,否则使用 add_bigptr 函数将记录插入到 nbucket 中;需要注意的是大数据记录的插入其实就是将索引部分迁移,数据部分由于是额外存储,所以不用移动;

6 、对于普通记录,则需要移动索引和数据,首先将记录取出赋值到 key val 中,此时 off 用来计算键值的长度,从而方便取出记录,然后使用 __call_hash 函数重新计算 key 的桶号,按照结果插入到 obucket nbucket 中,注意,由于此时是哈希表的扩展,所以插入操作需要传入 NO_EXPAND, 标记,禁止引起哈希表的扩展;最后需要调整 off 的值到数据值存储的起始地址,从而方便下一条记录计算键值尺寸;

7 、获取 temp_pagep 的右兄弟页面编号 next_pgno

8 、如果 temp_pagep 不是 obucket 的首页,则在其页面中记录重新分配完之后,需要将其释放,标记为空闲空间,即使用 __delete_page 函数释放 temp_pagep 页;至于 obucket 的桶首页,由于其与桶号一一对应,已经重新利用,所以不能释放;

9 、如果 next_pgno 不为空,则表示旧桶链表中还有页面没有完成分配,获取 next_pgno 的页面赋给 temp_pagep ,转 1 ;如果为空,则表示分裂完成;转 10

10 、返回成功;

 

 

__addel 函数:向页面中添加普通记录,注意函数名为 add_elment ,不是 adjust_delete

函数参数:

hashp HTAB 指针;

item_info ITEM_INFO 类型指针,传入插入点信息;

key DBT 指针,传入键值;

val DBT 指针,传入数据值;

num_items :整型,用于标志当前插入记录在桶中的序号,用于判断此次插入是否要引发哈希表的扩展,也可以明确使用是否引发扩展的标记传入来决定;

expanding :整型,标记插入时机,是记录增加( 0 ),还是页面分裂( 1 );前者引起哈希表记录数的递增,后者则不会;

返回值:成功或失败;

伪代码:

首先获取插入记录的页面,如果 item_info seek_found_page 成员不等于 0 ,则表示 seek_found_page 成语保存的页面编号中可以存放当前记录(参看 hash_access 函数),取出页面 pagep ;否则,使用 pgno 成员保存的页面编号,取出页面 pagep

由上可以知道, pagep 中不一定就能够有足够的容量存储记录,索引沿着页面链表判断查找可以存放记录的页面 pagep ;判断的方式就是,如果是大数据记录,只用检查是否有存放索引的空间;如果是普通记录,则需要检查是否有存放索引和数据的空间;

找到具有足够空间的 pagep 后,对于大数据使用 __big_insert 函数插入大数据记录,对于普通记录则使用 putpair 函数插入;(程序中让我困括的是并没有判断大数据记录的索引是否有空间存储?)

如果没有足够的空间,则此时 pagep 处于桶页面链表的表尾,在 pagep 之后使用 __add_ovflpage 函数添加一个溢出页作为桶链表的表尾,将 pagep 指向这一页;

使用 putpair 将记录插入到 pagep 中;

调整 item_info 的页面标号信息为 pagep 的页面编号,这一操作主要为页面分裂服务,因为 __split_page 函数会在一个桶中连续插入记录,保存当前页面的编号,可以有效的减少再次插入时需要从桶首页重新找具有足够空闲空间页面的循环操作,即本函数开始的部分;

如果 expanding 等于 0 ,则表示这是新记录插入,将 hashp 头部信息的 nkey 成员递增 1

对于插入的是普通的记录,将 pagep 页面标记为已更新;(对于大数据记录在插入函数中已经完成这一标记)

如果 expanding 大于 0 ;则表示是分裂插入操作, item_info caused_expand 0 ,传回不会引发表扩展的信息;否则需要根据 num_items 来判断是否引发表的分裂;

如果 num_items NO_EXPAND ,则表示调用这明确指定不应引发表扩展,如桶分裂的操作,将 caused_expand 赋值为 0

如果 num_items UNKNOWN :表示是否会引发扩展由哈希表每桶平均记录数或当前页面内的记录数来决定;若平均每桶中的记录数 (hashp->hdr.nkeys / hashp->hdr.max_bucket) 大于填充因子,则表示需要引起表的扩展,如果当前页面内页面的数量已经超过填充因子,则表示需要引起表的扩展;

如果 num_items 为其他数据:根据 num_items 来判断是否需要扩展, num_items 其实是当前插入记录的桶内序号,如果大于填充因子,则表明桶内记录在数量超标,需要引发表的扩展;如果 num_items 小于填充因子,则根据桶首页是否不足以存储桶中记录,而在本函数中新增了溢出页面构成链表来存储记录,如果是,则引发表的扩展,否则不引发;

返回;

注意:将 item_info 的页面编号标记为本页编号的操作似乎会引发页面碎片,因为普通记录的大小不等,当前记录不能存储到本页面,后续尺寸较小的记录可能可以存储;

 

add_bigptr 函数:插入大数据记录索引;

函数参数:

hashp HTAB 指针;

item_info ITEM_INFO 类型指针,传入插入点信息;

big_pgno :整型,大数据记录存储链表的表头页页面标号;

返回值:成功或失败;

伪代码:

首先,获取桶首页 pagep

顺着 pagep 的链表开始查找,找到可以存储大数据记录索引的页面 pagep ,如果桶链表中没有空间,则使用 __add_ovflpage 在表尾附加页面, pagep 指向表尾页;

pagep 中追加一条索引,键值部分为大数据记录标志,数据值部分存储存储链表首页编号,即 big_pgno

pagep 中记录数量加 1

pagep 标记为需写回;

返回;

 

__add_ovflpage 函数:添加分配溢出页面;

函数参数:

hashp HTAB 类型指针;

pagep :页面指针,传入分配页面的左兄弟页面;

返回值:分配页的指针;

伪代码:

首先,调整填充因子;如果使用的是缺省的填充因子,则填充因子为 pagep 中记录数量的一半,如果修订后填充因子小于填充因子的下限,则填充因子取其下限;

使用 overflow_page 函数分配一个溢出页面编号 ovfl_num

使用 __new_page 函数分配 ovfl_num 对应的页面;

使用 __get_page 函数获得 ovfl_num 对应的页面 new_pagep

pagep 的右兄弟指示为 new_pagep 页面编号;

new_pagep 设置为溢出类型页面;

设置 pagep 页面需写回;

返回 new_pagep

 

__add_bigpage 函数:为大数据存储链表添加溢出页;

函数参数:

hashp HTAB 指针;

pagep :分配页左兄弟页面指针;

ndx :大数据记录在页面内序号;

is_basepage :是否是表头页;

返回值:添加页面的指针;

伪代码:

关于参数需要注意的是当 is_basepage 1 ,标志着开始分配大数据链表的表头页,此时 pagep 为桶页面, ndx 为大数据记录在桶页中的序号;如果 is_basepage 0 ,则表示添加的是链表的非表头页,此时 pagep 就是表尾页,分配的页面将附加在 pagep 之后, ndx 此时无效;

首先使用 overflow_page 分配溢出页面编号 ovfl_num

使用 __new_page 函数分配 ovfl_num 对应的页面;

使用 __get_pagep 函数获取 ovfl_num 的页面 new_pagep

如果 is_basepage 等于 1 ,则表示 ovfl_num 页将作为表头页,将 pagep 的第 ndx 索引的键值标记为大数据记录标记,数据值存储为 ovfl_num

如果 is_basepage 等于 0 ,则将 pagep 的右兄弟页面编号赋值为 new_pagep 的右兄弟;

pagep 标记为需写回;

new_pagep 标记为大数据存储页面;

返回 new_pagep

 

page_init 函数:页面格式化;

函数参数:

hashp HTAB 指针;

pagep :页面指针,待格式化的页面;

pgno :页面编号;

type :页面类型;

返回值:无;

伪代码:

将页面内记录数赋值为 0

将页面的左右兄弟页面编号初始化为无效;

页面类型标记为 type

页面空闲空间首地址赋值为页面最高地址;

页面编号赋值为 pgno

返回;

 

__new_page 函数:根据指定信息获取分配新的页面;

函数参数:

hashp HTAB 指针;

addr :整型,页面标记:桶号、溢出页面编号、页面编号等;

addr_type :整型, addr 的类型,即页面编号的转换方式;

返回值:成功或失败;

伪代码:

使用 paddr 保存页面编号;

如果 addr_type A_BUCKET addr 表示桶号,则使用 BUCKET_TO_PAGE 宏获取 paddr ,根据桶号页面编号的映射关系,就是 addr +头部信息占用的页面+对应分裂点之前一共分配的溢出页面数量(即 spares 数组保存的项); berkely Db 哈希表桶号和分裂点的映射策略是 2 的幂, addr 对应的分裂点为 __log2 的关系;

如果 addr_type A_OVFL A_BITMAP addr 表示溢出页面编号;使用 OADDR_TO_PAGE 宏获取 paddr

如果是其它类型,则 addr 就是页面编号;

使用 mpool_new 函数获取指定页面编号的页面 pagep

如果 addr_type 不是 A_BITMAP ,即不用作位图页,则使用 page_init 函数格式化 pagep 页面;

pagep 标记为需写回;

返回;

 

__delete_page 函数:释放页面,将其置为空闲页面;

函数参数:

hashp HTAB 指针;

pagep :页面指针,待释放的页面;

page_type :页面类型;

返回值:成功或失败;

伪代码:

如果 page_type 是溢出页的,则使用 __free_ovflpage 将起在位图页中对应的位复位;

使用 mpool_delete 函数将 pagep 置为空闲页面;

返回;

 

is_bitmap_pgno 函数:判断指定页面是否是位图页;

hashp HTAB 指针;

pgno :指定的页面编号;

返回值:是( 1 )或不是( 0 );

伪代码:

遍历头部信息的 bitmaps 数组,如果其中有成员的溢出页面编号转换成页面编号后等于 pgno ,则返回 1

没有找到,则返回 0

 

__pgin_routine 函数:过滤转换函数,同 B 树;

__pgout_routine 函数:过滤转换函数,同 B 树;

 

__put_page 函数:将页面标记为是否写回状态,便于调出缓冲区使用;

函数参数:

hashp HTAB 指针;

pagep :页面指针;

addr_type :无效;

is_dirty :整型,是否置为需写回;

返回值:成功或失败;

伪代码:

使用 mpool_put 函数根据 is_dirty 作页面 pagep 对应的标记;

 

__get_page 函数:获取指定的页面;

函数参数:

hashp HTAB 指针;

addr :整型,页面标记:桶号、溢出页面编号、页面编号等;

addr_type :整型, addr 的类型,即页面编号的转换方式;

返回值:获取的页面的指针;

伪代码:

使用 paddr 保存页面编号;

如果 addr_type A_BUCKET addr 表示桶号,则使用 BUCKET_TO_PAGE 宏获取 paddr ,根据桶号页面编号的映射关系,就是 addr +头部信息占用的页面+对应分裂点之前一共分配的溢出页面数量(即 spares 数组保存的项); berkely Db 哈希表桶号和分裂点的映射策略是 2 的幂, addr 对应的分裂点为 __log2 的关系;

如果 addr_type A_OVFL A_BITMAP addr 表示溢出页面编号;使用 OADDR_TO_PAGE 宏获取 paddr

如果是其它类型,则 addr 就是页面编号;

使用 mpool_get 函数获取指定页面编号的页面 pagep

返回 pagep

 

swap_page_header_in 函数:应付大小端的转换函数;

swap_page_header_out 函数:应付大小端的转换函数;

 

__ibitmap 函数:分配并初始化位图页;

函数参数:

hashp HTAB 指针;

pnum :位图页溢出页面标号;

nbits :初始化时,预分配溢出页面的数量;

ndx :位图页的序号;

返回值:成功或失败;

伪代码:

使用 __new_page 函数根据 pnum 分配位图页;

使用 __get_page 获取刚分配的位图页 ip

hashp nmaps 成员递增,表示位图页增加;

前文已述位图页的结构:由 32 位的 map 组成,每位置位表示对应页面不可用或已经被使用,复位表示对应页面空闲;

计算需要全部置位的 map clearints ((nbits - 1) >> INT32_T_BYTE_SHIFT) + 1

计算需要置为的字节数 clearbytes = clearints << INT32_T_TO_BYTE ;即 map 32 4 字节,所以字节数为 map 数乘以 4

ip 的前 clearbytes 个字节值为 0 (对应的位复位,表示对应的页面可用),将其它字节置 1

由于 nbits 可能不是 map 位数的整数倍,所以需要将最后一个 map 作调整,只将前面的若干位复位;

最后,由于本位图页也是溢出页,同时已经分配,对应于位图页的第 0 位,所以将位图页第 0 位置 1 ;最终返回的可用溢出页数实际为 nbits-1 个;

将位图页注册到头部信息的 bitmaps 数组;

由于位图页一旦读入内存或初始化,将持续到哈希表关闭,所以使用 hashp mapp 数组保存 ip ,以方便后续访问;

返回;

 

first_free 函数:返回位图中第一个为 0 的位序号(从 0 计数);

函数参数:

map 32 位数;位图;

返回值:第一个为 0 的位序号;

伪代码:

设掩码为 mask 1

依次移动 mask 1 的位置,与 map 作与操作,取出对应的位,如果为 0 ,则返回对应的位;

如果没有找到,则返回 map 中位的数量;

 

overflow_page 函数:分配溢出页面编号;

函数参数:

hashp HTAB 指针;

返回值:溢出页面编号;

伪代码:

分配溢出页面编号就是找到位图页中第一个为 0 的位,将其对应的溢出页面编号返回;如果位图页中没有为 0 的位,则需要新分配一个溢出页面编号,并作相关调整;

获取溢出页面所在的分裂点 splitnum ,即头部信息的 ovfl_point 的值;

取出当前溢出页面的总数 max_free ,即 spares 数组第 splitnum 项的值;

找到最后一个溢出页面所在的位图页 free_page 和对应的位 free_bit ;即如果没有空闲的溢出页,则新分配的溢出页面将从此处开始;

获取距离第一个为 0 的位距离最近的位的序号所在的页 first_page ;即 last_freed 的高 17 位的值;

first_page 中开始查找为 0 的位; i 初始化为 first_page

1 、使用 fetch_bitmap 函数获取第 i 个位图页 freep

2 、计算 freep 中已经使用的位的数量;如果 freep 等于 free_page ,即最后一个位图页,则可能只使用了一部分位作映射, in_use_bits free_bit ;如果不是 free_page ,则所有的位都投入映射, in_use_bits 等于一页含有位的数量;

3 、确定在 i 位图页中查找的起始位的地址,如果 i first_page ,则起始为必然是 last_freed 指向的位,即取 last_freed 的低 15 bit ,由于后续程序中为了方便将按位图( 32 位)比较,所以需要将 bit 转换为位图的序号和在位图中的位序号, bit 除以 32 得到所在位图的序号 j bit 取低 5 位得到在位图 j 中的序号 bit ;如果 i 不是 first_page ,则从位图页的第 0 位开始查找,即 j 0 bit 0

4 、开始在位图页 i 中查找,如果第 j map 中没有为 0 的位,则将 j 递增, bit 增加一个位图的位数,直至 bit 达到 in_use_bits ;如果仍然没有找到,则表示当前位图页中没有为 0 的位,转 5 ;如果找到,转 9

5 i 递增,如果 i 大于 free_page, 则表示所有位图页面都已经搜索,转 6 ;否则,转 1

6 、所有位图页中都没有为 0 的位,则表明当前没有空闲的溢出页面,需要重新分配一个溢出页面编号;首先调整 last_freed 指向最后一个溢出页面,即赋值为 spares[splitnum] ;将 spares[splitnum] 增加 1 ,表示准备分配溢出页面;根据溢出页面编号俄组成原则,前 5 位记录分裂点,后 11 为记录分裂点内页面序号(偏移),所以,一个分裂点最多只能分配 2 11 次幂个溢出页面,此时新增加一个溢出页面,需要检测是否超出本分裂点的分配能力,由于 spares 数组各项分别记录到达对应分裂点是溢出页面分配的总数,所以 spares[splitnum] 减去前一项的值就是 splitnum 分裂点分配页面的数量 offset

如果 offset 大于 SPLITMASK ,表示当前分裂点已经分配了超过自身能力的页面数量,需要使用下一个分裂点(由此看出,桶号使用的分裂点并分和溢出页面使用的分裂点相差 1 ,可能更多);即 splitnum 1 ,如果此时导致 spares 数组溢出,则表示不能在分配新的溢出页面,返回 0 ;如果 splitnum 此时处于安全状态,将 ovfl_point 移向 splitnum ,将 spares[splitnum] 赋值为前一项的值,前一项的值减 1 (前一项即因为溢出而不能分配溢出页的项,由于已经加 1 ,此时需要减 1 ,从而恢复原值); offset 1 ,即本分裂点分配了 1 个溢出页面;

7 、第 6 步中讨论了分裂点容量的情况,本步骤将要讨论位图页的容量情况;

如果 free_bit 等于位图页最后一位,则新分配的溢出页对应的位不能在本页找到,需要添加新的位图页; free_page 原是最后一个位图页的序号, free_page 1 得到新位图页的序号 free_page ;如果 free_page 大于 NCACHED ,则表示位图页分配过量,从而不能在分配新的溢出页,返回 0

使用 __ibitmap 函数在 splitnum 分裂点的 offset 偏移处分配新的位图页,位图页的序号为 free_page ;此处需要注意的是函数的第三个传入参数为 1 ,即只分配一位用作映射,而位图页本身就需要一个溢出页的编号,实际的情况是用于映射的位由 spares[spilitnum] 决定,由于为本函数返回值分配的溢出页马上就要 分配,对应的位就要置为 1 ,所以这个参数的传递是合理的;

由于为位图页分配了溢出页面,所以 spares[splitnum] 1 ,同时 offset 1 ,表示本分裂点分配了两个溢出页面;

如果上述位图页的分配导致了分裂点溢出,则同 6 一样,分裂点下移,同时调整相关的值,此处 offset 赋值为 0 显得比较诡异,因为这样会导致溢出页面将于桶号冲突;

如果位图页不会溢出,则直接将 free_bit 1 ,将位图页中对应的位置位;

8

8 、将分裂点 splitnum 和偏移 offset 构成溢出页面编号 addr ;如果 addr 对应的页面编号超出哈希表允许的最大页面编号,则不能分配溢出页面,返回失败;否则返回 addr

9 、找到为 0 的位之后计算位在位图页中的序号,等于 bit +位图 j 中第一个为 0 的位的序号;将这一位置位;

10 、根据 bit 的值计算溢出页面编号及更新 last_freed 的值;

首先,将 bit 映射到哈希表所有溢出页的编号中,即在所有位图中的序号(从 1 计数);由于 bit 所在的位图页前面有 i 个位图也,所以 bit 的总序号为 bit +前 i 页所有位的和+ 1 ,将其赋值给 bit ;如果 bit 不小于 last_freed ,则将 last_freed 赋值为 bit 1 (这里可以看出 last_freed 实际是已知的最接近最小空闲页面的位的总序号,从而本函数前面对 last_freed 的使用就比较清楚了);

判断 bit 所在的分裂点,由于 bit 其实就是溢出页面的序号,判断 bit 处于哪两个 spares 项之间,就可以得到 bit 的分裂点 i ;然后 bit 减去 i 的前一项分裂点统计的溢出页面总数,就得到在分裂点 i 中的偏移 offset ;如果 offset 大于 SPLITMASK ,则表明出现异常,返回 0 ;否则,计算分裂点 i 偏移 offset 的溢出页面编号 addr ;判断 addr 是否有效,是则返回 addr

 

page_to_oaddr 函数:页面编号转溢出页页面编号;

函数参数:

hashp HTAB 指针;

pgno :页面编号;

返回值:溢出页面编号;

伪代码:

分析溢出页页面编号与溢出页面编号的映射关系,页面编号由 4 部分构成;分裂点基准号+头部信息占用页面数+上一分裂点统计溢出页面数+分裂点内偏移;

pgno 减去头部信息占用的页数;

sp 0 分裂点开始,依次检查页面编号处于分裂点的位置 sp

得到 sp 后,按照页面编号的构成得到偏移,从而构成溢出页面编号 ret_val

返回 ret_val

 

__free_ovflpage 函数:将指定溢出页面标记为空闲;

函数参数:

hashp HTAB 指针;

pagep :页面指针;

返回值:无;

伪代码:

将溢出页面置为空闲,即将位图页中对应的位复位;

使用 __free_ovflpage 函数获取 pagep 的溢出页面编号 addr

根据 addr 得到分裂点 ndx

计算页面的位编号 bit_address ,即 ndx 前一个分裂点统计的溢出页面数量加上 pagep ndx 中的偏移;

如果 bit_address 小于 last_freed ,则表示在本页置为空闲页后, last_freed 将不再是距离空闲位最近的最大值,所以需要调整 last_freed 等于 bit_address

计算 bit_address 所在的位置,即位图页序号 free_page bit_address 17 位)和位图页内位序号 free_bit bit_address 15 位);

获取第 free_page 个位图页 freep

freep free_bit 位复位;

 

 

fetch_bitmap 函数:获取指定位图页;

函数参数:

hashp HTAB 指针;

ndx :位图页序号;

返回值:指定位图页指针;

伪代码:

如果 ndx 大于 nmaps ,则返回失败;

如果 mapp[ndx] 为空,则表示对应位图页尚未读入内存,使用 __get_page 函数将第 ndx 个位图页读入内存,并存储在 mapp[ndx] 中;

返回 mapp[ndx]

 

adjust_cursor 函数:调整游标

函数参数:

hashp HTAB 指针;

ndx :游标在桶中的序号;

chg_pgno :游标所在页的页面编号;

nxt_pgno :游标所在页的下一页页面编号;

page_deleted :用于指示删除记录后,是否删除了页面(最后一条删除,页面为空);是为 1 ,否为 0

返回值:无;

伪代码:

对于 hashp 中游标队列的所有游标 cp ,如果其页面编号为 chg_pgno 并且 ndx 小于 cp pgndx (即处于已删除的记录之后),则

如果 page_deleted 1 ,则将游标移动到右兄弟页面的第一条记录,如果右兄弟页面编号为空,则移动到下一桶的第一条记录;同时将游标的页面指针置空;

如果 page_deleted 0 ,则将游标前移一位,即 cp pgndx 1 cp ndx 1

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值