Unix环境高级编程009.pdf
![资源得分’ title=](/images/score_1.gif)
![资源得分’ title=](/images/score_1.gif)
![资源得分’ title=](/images/score_1.gif)
![资源得分’ title=](/images/score_1.gif)
![资源得分’ title=](/images/score_05.gif)
《Unix环境高级编程009.pdf》由会员分享,可在线阅读,更多相关《Unix环境高级编程009.pdf(50页珍藏版)》请在淘文阁 - 分享文档赚钱的网站上搜索。
1、节给出了采用细锁的实现,并详细讨论了锁的实现(粗锁是实现的简化)。在源文件中,直接调用了r e a d,r e a d v,w r i t e和w r i t e v。没有使用标准I/O函数库。虽然使用标准I/O函数库也可以使用记录锁,但是需要非常复杂的缓存管理。我们不希望例如 f g e t s返回的数据是1 0分钟之前读入标准I/O缓存的,而被另一个进程在5分钟之前修改了。我们对并发讨论依据的是数据库函数库的简单的需求。商业系统一般有更多的需要。关于并发更多的细节可以参见D a t a 1 9 8 2 的第3章。16.7 源码我们从程序1 6-2中的头文件d b.h开始。所有函数以及调用此
2、函数库的用户进程都包含这一头文件。程序16-2 db.h头文件3 9 2U N I X环境高级编程下载该程序定义了实现的基本限制。如果要支持更大的数据库的话,这些限制也可以修改。其中一些定义为常数的值也可定义为变量,只是会使实现复杂一些。例如,设定散列表的大小为1 3 7,一个也许更好的方法是让d b _ o p e n的调用者根据数据库的大小通过参数来设定这个值,然后将这个值存在索引文件的最前面。在D B结构中记录一个打开的数据库的所有信息。d b _ o p e n函数返回一个D B结构的指针,这个指针被用于其他所有函数。选择用d b _开头来命名用户可调用的库函数,用_ d b _开头来
3、命名内部函数。程序1 6-3中定义了函数d b _ o p e n。它打开索引文件和数据文件,必要的话初始化索引文件。通过调用_ d b _ a l l o c来为D B结构分配空间,并初始化此结构。程序16-3 db_open函数第1 6章数据库函数库3 9 3下载3 9 4U N I X环境高级编程下载如果数据库正被建立,则必须加锁。考虑两个进程试图同时建立同一个数据库的情况。第一个进程运行到调用f s t a t,并且在f s t a t返回后被内核切换。这时第二个进程调用d b _ o p e n,发现索引文件的长度为0,并初始化空闲链表和散列链表。第二个进程继续运行,向数据库中添加了
4、一条记录。这时第二个进程被阻塞,第一个进程继续运行,并发现索引文件的大小为 0(因为第一个进程是在 f s t a t返回后才被切换),所以第一个进程重新初始化空闲链表和散列链表,第二个进程写入的记录就被抹去了。要避免发生这种情况的方法是进行加锁,可以使用 1 2.3节中的r e a d w _ l o c k,w r i t e w _ l o c k和u n _ l o c k这三个函数。d b _ o p e n调用程序1 6-4中定义的函数_ d b _ a l l o c来为D B结构分配空间,包括一个索引缓存和一个数据缓存。程序16-4 _db_alloc函数索引缓存和数据缓存的大
5、小在d b.h中定义。数据库函数库可以通过让这些缓存按需要动态扩张来得到加强。其方法可以是记录这两个缓存的大小,然后在需要更大的缓存时调用r e a l l o c。在函数_ d b _ f r e e(见程序1 6-5)中,这些缓存将被释放,同时打开的文件被关闭。d b _ o p e n在打开索引文件和数据文件时如果遇到错误,则调用_ d b _ f r e e释放资源。d b _ c l o s e(见程序1 6-6)也调用_ d b _ f r e e。函数d b _ f e t c h(见程序1 6-7)根据给定的关键字来读取一条记录。它调用_ d b _ f i n d在数据库中查
6、找一条索引记录,如果找到,再调用_ d b _ r e a d d a t来读取对应的数据记录。程序16-5 _db_free函数第1 6章数据库函数库3 9 5下载程序16-6 db_close函数程序16-7 db_fetch函数函数_ d b _ f i n d(见程序1 6-8)通过遍历散列链来查找记录。d b _ f e t c h,d b _ d e l e t e和d b _ s t o r e这几个需要根据关键字查找记录的函数都调用它。3 9 6U N I X环境高级编程下载程序16-8 _db_find函数_ d b _ f i n d的最后一个参数指明需要加什么样的锁,0表
7、示读锁,1表示写锁。我们知道,d b _ f e t c h需要加读锁,而d b _ d e l e t e和d b _ s t o r e需要加写锁。_ d b _ f i n d在获得需要的锁之前将等待。_ d b _ f i n d中的w h i l e循环遍历散列链中的索引记录,并比较关键字。函数 _ d b _ r e a d i d x用于读取每条索引记录。请注意阅读_ d b _ f i n d中的最后一条注释。当沿着散列链进行遍历时,必须始终跟踪当前索引记录的前一条索引记录,其中有一个指针指向当前记录。这一点在删除一条记录时很有用,因为必须修改当前索引记录的前一条记录的链指针。
8、第1 6章数据库函数库3 9 7下载先来看看_ d b _ f i n d调用的一些比较简单的函数。_ d b _ h a s h(见程序1 6-9)根据给定的关键字计算散列值。它将关键字中的每一个 A S C I I字符乘以这个字符在字符串中以 1开始的索引号,将这些结果加起来,除以散列表的大小,将余数作为这个关键字的散列值。程序16-9 _db_hash函数_ d b _ f i n d调用的下一个函数是_ d b _ r e a d p t r(见程序1 6-1 0)。这个函数能够读取以下三种不同的链表指针中的任意一种:(1)索引文件最开始的空闲链表指针,(2)散列表中指向散列链的第一条
9、索引记录的指针,(3)每条索引记录头的指向下一条记录的指针(这里的索引记录既可以处于一条散列链表中,也可以处于空闲链表中)。这个函数的调用者应做好必要的加锁,此函数不进行任何加锁。程序16-10 _db_readptr函数_ d b _ f i n d中的w h i l e循环通过调用_ d b _ r e a d i d x来读取各条索引记录。_ d b _ r e a d i d x(见程序1 6-11)是一个较长的函数,这个函数读取索引记录,并将索引记录的信息存储到适当的字段中。程序1 6-11 _db_readidx函数3 9 8U N I X环境高级编程下载第1 6章数据库函数库3
10、9 9下载我们调用r e a d v来读取索引记录开始处的两个固定长度的字段:指向下一条索引记录的链表指针和索引记录剩下的不定长部分的长度。然后,索引记录剩下的部分被读入:关键字、数据记录的位移量和数据记录的长度。我们并不读数据记录,这由调用者自己完成。例如,在d b _ f e t c h中,在_ d b _ f i n d按关键字找到索引记录前是不去读取数据记录的。下面回到d b _ f e t c h。如果_ d b _ f i n d找到了索引记录,则调用 _ d b _ r e a d d a t来读取对应的数据记录。这是一个很简单的函数(见程序1 6-1 2)。程序16-12 _d
11、e_readdat函数我们从 d b _ f e t c h开始,现在已经读取了索引记录和对应的数据记录。注意到,只在_ d b _ f i n d中加了一个读锁。由于对这条散列链加了读锁,所以其他进程在这段时间内不可能对这条散列链进行修改。下面来看函数d b _ d e l e t e(见程序1 6-1 3)。它开始时和d b _ f e t c h一样,调用_ d b _ f i n d来查找记录,只是这里传递给_ d b _ f i n d的最后一个参数为1,表示要对这一条散列链加写锁。d b _ d e l e t e调用_ d b _ d o d e l e t e(见程序1 6-1
12、 4)来完成剩下的工作(在后面将看到 d b _ s t o r e也调用_ d b _ d o d e l e t e)。_ d b _ d o d e l e t e的大部分工作是修正空闲链表以及与关键字对应的散列链。当一条记录被删除后,将其关键字和数据记录设为空。本章后面将提到的函数 d b _ n e x t r e c要用到这一点。_ d b _ d o d e l e t e对空闲链表加写锁,这样能防止两个进程同时删除不同链表上的记录时产生相互影响,因为我们将被删除的记录移到空闲链表上,这将改变空闲链表,而一次只能有一个进程能这样做。程序16-13 db_delete函数4 0 0
13、U N I X环境高级编程下载程序16-14 _db_dodelete函数第1 6章数据库函数库4 0 1下载_ d b _ d o d e l e t e通过调用函数_ d b _ w r i t e d a t(见程序1 6-1 5)清空数据记录。这时_ d b _ w r i t e d a t并不对数据文件加写锁,这是因为d b _ d e l e t e对这条记录索引的散列链加了写锁,这已经保证不会有其他进程能够读写这条记录。本章后面讲述d b _ s t o r e时,将看到在另一种情况下,_ d b _ w r i t e d a t将数据添加到文件的末尾,并加锁。_ d b _
14、 w r i t e d a t调用w r i t e v来写数据记录及回车符。不能够假设调用者传递进来的字符缓存有空间让我们在其后面再添加一个回车符。回忆1 2.7节,在那里曾讨论过一个w r i t e v要比两个w r i t e快。接着_ d b _ d o d e l e t e修改索引记录。让这条记录的链表指针指向空闲链表的第一条记录(如果空闲链表为空,则这个链表指针置为0),空闲链表指针也被修改,指向当前删除的这条记录,这样就将这条删除的记录移到了空闲链表首。空闲链表实际上很象一个先进先出的堆栈。程序16-15 _db_writedat函数4 0 2U N I X环境高级编程下载
15、我们并没有为索引文件和数据文件的空闲记录设置各自的空闲链表。这是因为当一条记录被删除时,对应的索引记录被加入了空闲链表,而这条索引记录中有指向数据文件中数据记录的指针。有其他很多更好的处理记录删除的方法,只是它们会更复杂一些。程序1 6-1 6列出了函数 _ d b _ w r i t e i d x,_ d b _ d o d e l e t e调用此函数来写一条索引记录。和_ d b _ w r i t e d a t一样,这一函数也只有在添加新索引记录时才需要加锁。_ d b _ d o d e l e t e调用此函数是为了重写一条索引记录,在这种情况下调用者已经在散列链上加了写锁,所
16、以不再需要加另外的锁。程序16-16 _db_writeidx函数_ d b _ d o d e l e t e调用的最后一个函数是_ d b _ w r i t e p t r(见程序1 6-1 7)。它被调用两次一次第1 6章数据库函数库4 0 3下载用来写空闲链表指针,一次用来写散列链表指针(指向被删除索引记录的指针)。程序16-17 _db_writeptr函数程序1 6-1 8中列出了最长的数据库函数d b _ s t o r e。它从调用_ d b _ f i n d开始,以查看这个记录是否存在。如果记录存在且标志为D B _ R E P L A C E,或记录不存在且标志为D B
17、 _ I N S E RT,这些都是允许的。替换一条已存在的记录,指的是该记录的关键字一样,而数据很可能不一样。注意到因为d b _ s t o r e可能会改变散列链,所以调用_ d b _ f i n d的最后一条参数说明要对散列链加写锁。如果要加一条新记录到数据库中,则调用函数 _ d b _ f i n d f r e e(见程序1 6-1 9)在空闲链表中搜索一个有同样关键字大小和同样数据大小的已删除的记录。_ d b _ f i n d f r e e中的w h i l e循环遍历空闲链表以搜寻一个有对应关键字大小和数据大小的索引记录项。在这个简单的实现中,只有当一个已删除记录的关
18、键字大小及数据大小与新加入的记录的关键字大小及数据大小一样时才重用已删除的记录的空间。其他更好的算法一般更复杂。_ d b _ f i n d f r e e需要对空闲链表加写锁以避免与其他使用空闲链表的进程互相影响。当一条记录从空闲链表上取下来后,就可以打开这个写锁。_ d b _ d o d e l e t e也要修改空闲链表。程序16-18 db_store函数4 0 4U N I X环境高级编程下载第1 6章数据库函数库4 0 5下载程序16-19 _db_findfree函数4 0 6U N I X环境高级编程下载回到函数d b _ s t o r e,在调用_ d b _ f i
19、n d后,有四种可能:(1)加入一条新的记录,而_ d b _ f i n d f r e e没有找到对应大小的空闲记录。这意味着要将这条新记录添加到索引文件和数据文件的末尾。通过调用 _ d b _ w r i t e p t r将新记录加到对应的散列链的链首。(2)加入一条新记录,且 _ d b _ f i n d f r e e找到对应大小的空闲记录。这条空闲记录被_ d b _ f i n d f r e e从空闲链表上移下来,新的索引记录和数据记录被写入,然后通过调用_ d b _ w r i t e p t r将新记录加到对应的散列链的链首。(3)要替换一条记录,而新数据记录的长度
20、与已存在的记录的长度不一样。调用_ d b _ d o d e l e t e将老记录删除,然后将新记录添加到索引文件和数据文件的末尾(也可以用其他方法,如可以再找一找是否有适当大小的已删除的记录项)。最后调用_ d b _ w r i t e p t r将新记录加到对应的散列链的链首。(4)要替换一条记录,而新数据记录的长度与已存在的记录的长度恰好一样。这是最容易的情况,只需要重写记录即可。在向文件的末尾添加索引记录或数据记录时,需要加锁(回忆在程序 1 2-6中遇到的相对文件尾加锁问题)。在第(1)和第(3)种情况中,d b _ s t o r e调用_ d b _ w r i t e i
21、 d x和_ d b _ w r i t e d a t时,第3个参数为0,第4个参数为S E E K _ E N D。这里,第4个参数作为一个标志用来告诉这两个函数,新的记录将被添加到文件的末尾。_ d b _ w r i t e i d x用到的技术是对索引文件加写锁,加锁的范围从散列链的末尾到文件的末尾。这不会影响其他数据库的读用户和写用户(这些用户将对散列链加锁),但如果其他用户此时调用d b _ s t o r e来添加数据则会被锁住。_ d b _ w r i t e d a t使用的方法是对整个数据文件加写锁。同样这也不会影响其他数据库的读用户和写用户(它们甚至不对数据文件加锁)
22、,但如果其他用户此时调用d b _ s t o r e来向数据文件添加数据则会被锁住(见习题1 6.3)。最后两个函数是d b _ n e x t r e c和d b _ r e w i n d,这两个函数用来读取数据库的所有记录。通常在下列形式的循环中的使用这两个函数:db_rewind(db);while(ptr=db_nextrec(db,key)!=NULL)/*process record*/前面曾警告过,记录的返回没有一定的次序它们并不按关键字的顺序。函数d b _ r e w i n d(见程序1 6-2 0)将索引文件定位在第一条索引记录(紧跟在散列表后面)。程序16-20 d
23、b_rewind函数第1 6章数据库函数库4 0 7下载当d b _ r e w i n d定位好索引文件后,d b _ n e x t r e c一条一条按顺序读取索引记录。在程序 1 6-2 1中可以看到,d b _ n e x t r e c并不使用散列链表。由于d b _ n e x t r e c除了读取散列链表中的记录外,还读取已删除的记录,所以必须检查记录是否是已被删除的(关键字为空),并忽略这些已删除的记录。如果d b _ n e x t r e c在循环中被调用时数据库正被修改,则d b _ n e x t r e c返回的记录只是变化中的数据库在某一时刻的快照(s n a
24、p s h o t)。d b _ n e x t r e c总是返回一条“正确”的记录,也就是说它不会返回一条已删除的记录。但有可能一条记录刚被 d b _ n e x t r e c返回后就被删除。类似的,如果d b _ n e x t r e c刚跳过一条已删除的记录,这条记录的空间就被一条新记录重用,除非用d b _ r e w i n d并重新遍历一遍,否则看不到这条新的记录。如果通过 d b _ n e x t r e c获得一份数据库的准确的“冻结”的快照很重要,则应作到在这段时间内没有添加和删除。下面来看d b _ n e x t r e c使用的加锁。并不使用任何的散列链表,也
25、不判断每条记录属于哪条散列链。所以有可能当d b _ n e x t r e c读取一条记录时,其索引记录正在被删除。为了防止这种情况,d b _ n e x t r e c对空闲链表加读锁,这样就可避免与_ d b _ d o d e l e t e和_ d b _ f i n d f r e e相互影响。程序16-21 db_nextrec函数4 0 8U N I X环境高级编程下载16.8 性能为了测试这个数据库函数库,也为了获得一些时间上的测试数据,我们编写了一个测试程序。这个程序接受两个命令行参数:要创建的子进程的个数和每个子进程向数据库写的数据记录的条数(n re c)。然后创建一
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- Unix 环境 高级 编程 009
![提示](https://www.taowenge.com/images/bang_tan.gif)
限制150内