Tokyo Cabinet TCHDB源码阅读——tchdbnew、tchdbopen及相关函数代码
2012-05-09 10:01
429 查看
转自:/article/2693538.html
在此说明一下:我分析的TC版本是1.4.43,我的阅读流程主要是遵循对TC数据文件的标准操作流程进行分析,即打开数据文件->存放record->获取record->关闭数据文件,期间会涉及到一些相关函数,我会逐个分析。
另外,按我粗浅的理解,我觉得TC不能称为一个完整意义上的数据库,它仅仅是一个存储引擎而已,它的所有操作最终都是针对系统中的一个普通文件进行的,因此以后的分析中,我把TC操作的对象统统称为数据文件对象,而不是数据库对象,它打开的文件称为数据文件。
一、TCHDB *tchdbnew(void)
这个函数实现的功能是:分配一个TCHDB类型的数据文件对象,我们以后的操作都是基于这个对象的,它的功能有点类似于我们打开文件后获得的那个文件描述符,不过TCHDB比文件描述符要复杂的多,下面是代码和注释:
TCHDB *tchdbnew(void){
TCHDB *hdb;
TCMALLOC(hdb, sizeof(*hdb)); // 动态分配一个TCHDB对象
tchdbclear(hdb); // 初始化TCHDB对象
return hdb;
}
下面是tchdbclear对应的代码和注释:
[cpp] view
plaincopy
/* Clear all members.
`hdb' specifies the hash database object. */
static void tchdbclear(TCHDB *hdb){
assert(hdb);
hdb->mmtx = NULL;
hdb->rmtxs = NULL;
hdb->dmtx = NULL;
hdb->wmtx = NULL;
hdb->eckey = NULL;
hdb->rpath = NULL;
hdb->type = TCDBTHASH; // 类型为hash结构的数据文件
hdb->flags = 0;
hdb->bnum = HDBDEFBNUM; // 默认hash桶大小131071
hdb->apow = HDBDEFAPOW;
hdb->fpow = HDBDEFFPOW;
hdb->opts = 0;
hdb->path = NULL;
hdb->fd = -1;
hdb->omode = 0;
hdb->rnum = 0;
hdb->fsiz = 0;
hdb->frec = 0;
hdb->dfcur = 0;
hdb->iter = 0;
hdb->map = NULL;
hdb->msiz = 0;
hdb->xmsiz = HDBDEFXMSIZ; // 默认mmap大小64M
hdb->xfsiz = 0;
hdb->ba32 = NULL;
hdb->ba64 = NULL;
hdb->align = 0;
hdb->runit = 0;
hdb->zmode = false;
hdb->fbpmax = 0;
hdb->fbpool = NULL;
hdb->fbpnum = 0;
hdb->fbpmis = 0;
hdb->async = false;
hdb->drpool = NULL;
hdb->drpdef = NULL;
hdb->drpoff = 0;
hdb->recc = NULL;
hdb->rcnum = 0;
hdb->enc = NULL;
hdb->encop = NULL;
hdb->dec = NULL;
hdb->decop = NULL;
hdb->ecode = TCESUCCESS;
hdb->fatal = false;
hdb->inode = 0;
hdb->mtime = 0;
hdb->dfunit = 0;
hdb->dfcnt = 0;
hdb->tran = false;
hdb->walfd = -1;
hdb->walend = 0;
hdb->dbgfd = -1;
hdb->cnt_writerec = -1;
hdb->cnt_reuserec = -1;
hdb->cnt_moverec = -1;
hdb->cnt_readrec = -1;
hdb->cnt_searchfbp = -1;
hdb->cnt_insertfbp = -1;
hdb->cnt_splicefbp = -1;
hdb->cnt_dividefbp = -1;
hdb->cnt_mergefbp = -1;
hdb->cnt_reducefbp = -1;
hdb->cnt_appenddrp = -1;
hdb->cnt_deferdrp = -1;
hdb->cnt_flushdrp = -1;
hdb->cnt_adjrecc = -1;
hdb->cnt_defrag = -1;
hdb->cnt_shiftrec = -1;
hdb->cnt_trunc = -1;
TCDODEBUG(hdb->cnt_writerec = 0);
TCDODEBUG(hdb->cnt_reuserec = 0);
TCDODEBUG(hdb->cnt_moverec = 0);
TCDODEBUG(hdb->cnt_readrec = 0);
TCDODEBUG(hdb->cnt_searchfbp = 0);
TCDODEBUG(hdb->cnt_insertfbp = 0);
TCDODEBUG(hdb->cnt_splicefbp = 0);
TCDODEBUG(hdb->cnt_dividefbp = 0);
TCDODEBUG(hdb->cnt_mergefbp = 0);
TCDODEBUG(hdb->cnt_reducefbp = 0);
TCDODEBUG(hdb->cnt_appenddrp = 0);
TCDODEBUG(hdb->cnt_deferdrp = 0);
TCDODEBUG(hdb->cnt_flushdrp = 0);
TCDODEBUG(hdb->cnt_adjrecc = 0);
TCDODEBUG(hdb->cnt_defrag = 0);
TCDODEBUG(hdb->cnt_shiftrec = 0);
TCDODEBUG(hdb->cnt_trunc = 0);
}
二、tchdbopen函数
该函数打开指定的数据文件,并将打开的数据文件信息添加到TCHDB结构体中,打开过程中会检查数据文件是否为空等操作,看代码:
[cpp] view
plaincopy
/* Open a database file and connect a hash database object. */
bool tchdbopen(TCHDB *hdb, const char *path, int omode){
assert(hdb && path);
if(!HDBLOCKMETHOD(hdb, true)) return false; // 获得调用函数锁
if(hdb->fd >= 0){ // 若数据文件已打开,则返回出错
tchdbsetecode(hdb, TCEINVALID, __FILE__, __LINE__, __func__); // 设置错误码
HDBUNLOCKMETHOD(hdb);
return false;
}
char *rpath = tcrealpath(path); // 取得数据文件的标准化路径
if(!rpath){
int ecode = TCEOPEN;
switch(errno){
case EACCES: ecode = TCENOPERM; break;
case ENOENT: ecode = TCENOFILE; break;
case ENOTDIR: ecode = TCENOFILE; break;
}
tchdbsetecode(hdb, ecode, __FILE__, __LINE__, __func__);
HDBUNLOCKMETHOD(hdb);
return false;
}
if(!tcpathlock(rpath)){ // 锁定文件路径, 以防止在操作过程中文件消失
tchdbsetecode(hdb, TCETHREAD, __FILE__, __LINE__, __func__);
TCFREE(rpath);
HDBUNLOCKMETHOD(hdb);
return false;
}
bool rv = tchdbopenimpl(hdb, path, omode); // 具体打开操作
if(rv){ // 打开数据文件成功,保存数据文件的标准化路径
hdb->rpath = rpath;
} else {
tcpathunlock(rpath);
TCFREE(rpath);
}
HDBUNLOCKMETHOD(hdb);
return rv; // 返回打开是否成功
}
函数tchdbopen仅仅做简单的一些检查转换操作,具体打开操作在函数tchdbopenimpl中,我们来看看代码:
[cpp] view
plaincopy
/* Open a database file and connect a hash database object.
`hdb' specifies the hash database object.
`path' specifies the path of the database file.
`omode' specifies the connection mode.
If successful, the return value is true, else, it is false. */
static bool tchdbopenimpl(TCHDB *hdb, const char *path, int omode){
assert(hdb && path);
int mode = O_RDONLY;
if(omode & HDBOWRITER){ // 取得数据文件打开模式
mode = O_RDWR;
if(omode & HDBOCREAT) mode |= O_CREAT;
}
int fd = open(path, mode, HDBFILEMODE); // 调用库函数open打开数据文件,这里可以看到,文件仅仅是普通文件而已。
if(fd < 0){
int ecode = TCEOPEN;
switch(errno){
case EACCES: ecode = TCENOPERM; break;
case ENOENT: ecode = TCENOFILE; break;
case ENOTDIR: ecode = TCENOFILE; break;
}
tchdbsetecode(hdb, ecode, __FILE__, __LINE__, __func__);
return false;
}
if(!(omode & HDBONOLCK)){ // 检查是否需要锁定打开的数据文件,这里用的是advisory lock,通过fcntl实现
if(!tclock(fd, omode & HDBOWRITER, omode & HDBOLCKNB)){
tchdbsetecode(hdb, TCELOCK, __FILE__, __LINE__, __func__);
close(fd);
return false;
}
}
if((omode & HDBOWRITER) && (omode & HDBOTRUNC)){ // 写操作,是否需要截短文件?
if(ftruncate(fd, 0) == -1){
tchdbsetecode(hdb, TCETRUNC, __FILE__, __LINE__, __func__);
close(fd);
return false;
}
if(!tchdbwalremove(hdb, path)){
close(fd);
return false;
}
}
struct stat sbuf;
if(fstat(fd, &sbuf) == -1 || !S_ISREG(sbuf.st_mode)){ // 获得文件属性信息,判断文件是否为普通文件
tchdbsetecode(hdb, TCESTAT, __FILE__, __LINE__, __func__);
close(fd);
return false;
}
char hbuf[HDBHEADSIZ];
if((omode & HDBOWRITER) && sbuf.st_size < 1){ // 写模式下,是否为空文件?
// 写模式下为空文件,则这里开始构造默认情况下的数据文件头信息,从这里我们也可以推测出数
// 据文件头在磁盘文件中的存放格式,它的大小为256字节,参见HDBHEADSIZ。
hdb->flags = 0; // 默认flags为0
hdb->rnum = 0; // 当前记录为0
uint32_t fbpmax = 1 << hdb->fpow; // 空闲块池默认大小
uint32_t fbpsiz = HDBFBPBSIZ + fbpmax * HDBFBPESIZ; // 默认空闲块池大小
int besiz = (hdb->opts & HDBTLARGE) ? sizeof(int64_t) : sizeof(int32_t); // hash桶数组中每个元素大小
hdb->align = 1 << hdb->apow; // 默认对齐字节
hdb->fsiz = HDBHEADSIZ + besiz * hdb->bnum + fbpsiz; // 不包含填充区的文件大小
hdb->fsiz += tchdbpadsize(hdb, hdb->fsiz); // 包含填充区的文件大小
hdb->frec = hdb->fsiz; // 数据文件中第一个记录在文件中的偏移
tchdbdumpmeta(hdb, hbuf); // 将数据文件头信息拷贝到hbuf
bool err = false;
if(!tcwrite(fd, hbuf, HDBHEADSIZ)) err = true; // 将数据文件头信息写入数据文件
char pbuf[HDBIOBUFSIZ];
memset(pbuf, 0, HDBIOBUFSIZ);
uint64_t psiz = hdb->fsiz - HDBHEADSIZ;
while(psiz > 0){ // 这个循环将数据文件头部的填充区置位0,有什么用?可能是后面写入新记录时会读到这个区域...
if(psiz > HDBIOBUFSIZ){
if(!tcwrite(fd, pbuf, HDBIOBUFSIZ)) err = true;
psiz -= HDBIOBUFSIZ;
} else {
if(!tcwrite(fd, pbuf, psiz)) err = true;
psiz = 0;
}
}
if(err){
tchdbsetecode(hdb, TCEWRITE, __FILE__, __LINE__, __func__);
close(fd);
return false;
}
sbuf.st_size = hdb->fsiz; // 改写文件属性信息,改写文件大小
}
if(lseek(fd, 0, SEEK_SET) == -1){ // 重置文件指针到数据文件头部
tchdbsetecode(hdb, TCESEEK, __FILE__, __LINE__, __func__);
close(fd);
return false;
}
if(!tcread(fd, hbuf, HDBHEADSIZ)){ // 读出文件头信息,保证hbuf中含有文件头信息
tchdbsetecode(hdb, TCEREAD, __FILE__, __LINE__, __func__);
close(fd);
return false;
}
int type = hdb->type;
tchdbloadmeta(hdb, hbuf); // 将文件头信息copy到TCHDB结构体中,即初始化TCHDB结构体某些成员
// 若文件被标识为已打开,很可能前面针对文件的操作在事务中被中断了,这里将数据文件从wal文件中恢复回来,
// 相当于取消了前面那个事务,目前我们只要知道wal文件用于事务即可,这个函数后面我会补充分析。
if((hdb->flags & HDBFOPEN) && tchdbwalrestore(hdb, path)){
if(lseek(fd, 0, SEEK_SET) == -1){ // 数据文件已恢复,下面需要重新读取头部信息
tchdbsetecode(hdb, TCESEEK, __FILE__, __LINE__, __func__);
close(fd);
return false;
}
if(!tcread(fd, hbuf, HDBHEADSIZ)){ // 重新读出出数据文件头信息
tchdbsetecode(hdb, TCEREAD, __FILE__, __LINE__, __func__);
close(fd);
return false;
}
tchdbloadmeta(hdb, hbuf);// 重新导出出数据文件头信息
if(!tchdbwalremove(hdb, path)){
close(fd);
return false;
}
}
int besiz = (hdb->opts & HDBTLARGE) ? sizeof(int64_t) : sizeof(int32_t); // hash桶大小
size_t msiz = HDBHEADSIZ + hdb->bnum * besiz; // 算出数据文件最小需要mmap的大小,即数据文件头大小加hash同数组大小
if(!(omode & HDBONOLCK)){
if(memcmp(hbuf, HDBMAGICDATA, strlen(HDBMAGICDATA)) || hdb->type != type ||
hdb->frec < msiz + HDBFBPBSIZ || hdb->frec > hdb->fsiz || sbuf.st_size < hdb->fsiz){
tchdbsetecode(hdb, TCEMETA, __FILE__, __LINE__, __func__);
close(fd);
return false;
}
}
if(((hdb->opts & HDBTDEFLATE) && !_tc_deflate) ||
((hdb->opts & HDBTBZIP) && !_tc_bzcompress) || ((hdb->opts & HDBTEXCODEC) && !hdb->enc)){
tchdbsetecode(hdb, TCEINVALID, __FILE__, __LINE__, __func__);
close(fd);
return false;
}
size_t xmsiz = (hdb->xmsiz > msiz) ? hdb->xmsiz : msiz; // 数据文件需要mmap的大小,至少为msize
if(!(omode & HDBOWRITER) && xmsiz > hdb->fsiz) xmsiz = hdb->fsiz; // mmap的大小不超过文件大小
void *map = mmap(0, xmsiz, PROT_READ | ((omode & HDBOWRITER) ? PROT_WRITE : 0),
MAP_SHARED, fd, 0); // 映射指定大小数据文件
if(map == MAP_FAILED){
tchdbsetecode(hdb, TCEMMAP, __FILE__, __LINE__, __func__);
close(fd);
return false;
}
hdb->fbpmax = 1 << hdb->fpow; // 空闲块池区域最大数量
if(omode & HDBOWRITER){ // 写模式,分配空闲块池
TCMALLOC(hdb->fbpool, hdb->fbpmax * HDBFBPALWRAT * sizeof(HDBFB));
} else {
hdb->fbpool = NULL; // 非写模式,不分配空闲块池,节约内存
}
hdb->fbpnum = 0; // 其它值的初始化
hdb->fbpmis = 0;
hdb->async = false;
hdb->drpool = NULL;
hdb->drpdef = NULL;
hdb->drpoff = 0;
hdb->recc = (hdb->rcnum > 0) ? tcmdbnew2(hdb->rcnum * 2 + 1) : NULL; // record的缓冲区大小计算
hdb->path = tcstrdup(path); // 复制一份数据文件raw路径,没有经过标准化处理的路径。
hdb->fd = fd; // 从这里开始复制一些值到TCHDB结构体中
hdb->omode = omode;
hdb->dfcur = hdb->frec;
hdb->iter = 0;
hdb->map = map;
hdb->msiz = msiz;
hdb->xfsiz = 0;
if(hdb->opts & HDBTLARGE){ // 判断使用32位还是64为的hash桶
hdb->ba32 = NULL;
hdb->ba64 = (uint64_t *)((char *)map + HDBHEADSIZ);
} else {
hdb->ba32 = (uint32_t *)((char *)map + HDBHEADSIZ);
hdb->ba64 = NULL;
}
hdb->align = 1 << hdb->apow;
hdb->runit = tclmin(tclmax(hdb->align, HDBMINRUNIT), HDBIOBUFSIZ);
hdb->zmode = (hdb->opts & HDBTDEFLATE) || (hdb->opts & HDBTBZIP) ||
(hdb->opts & HDBTTCBS) || (hdb->opts & HDBTEXCODEC);
hdb->ecode = TCESUCCESS;
hdb->fatal = false;
hdb->inode = (uint64_t)sbuf.st_ino; // 数据文件的inode号
hdb->mtime = sbuf.st_mtime; // 数据文件修改时间
hdb->dfcnt = 0;
hdb->tran = false;
hdb->walfd = -1;
hdb->walend = 0;
if(hdb->omode & HDBOWRITER){ // 若是写模式,并且当前数据文件不处于打开状态,则从数据文件中读取fbp信息,在内存中建立fbp区域
bool err = false;
if(!(hdb->flags & HDBFOPEN) && !tchdbloadfbp(hdb)) err = true;
memset(hbuf, 0, 2);
if(!tchdbseekwrite(hdb, hdb->msiz, hbuf, 2)) err = true;
if(err){
TCFREE(hdb->path);
TCFREE(hdb->fbpool);
munmap(hdb->map, xmsiz);
close(fd);
hdb->fd = -1;
return false;
}
tchdbsetflag(hdb, HDBFOPEN, true); // 设置数据文件已被打开标志
}
return true; // 打开成功
}
这里补充下上面函数中调用的tchdbwalrestore函数,wal文件的格式为:更新前数据文件大小(8字节) + 一条或多条更新记录,其中,每条更新记录格式如下:
[cpp] view
plaincopy
/* Restore the database from the write ahead logging file.
`hdb' specifies the hash database object.
`path' specifies the path of the database file.
If successful, the return value is true, else, it is false. */
static int tchdbwalrestore(TCHDB *hdb, const char *path){
assert(hdb && path);
char *tpath = tcsprintf("%s%c%s", path, MYEXTCHR, HDBWALSUFFIX); // 构造数据文件对应的wal文件名
int walfd = open(tpath, O_RDONLY, HDBFILEMODE); // 打开wal文件
TCFREE(tpath);
if(walfd < 0) return false;
bool err = false;
uint64_t walsiz = 0;
struct stat sbuf;
if(fstat(walfd, &sbuf) == 0){ // 获得wal文件属性信息
walsiz = sbuf.st_size;
} else {
tchdbsetecode(hdb, TCESTAT, __FILE__, __LINE__, __func__);
err = true;
}
if(walsiz >= sizeof(walsiz) + HDBHEADSIZ){ // 粗略判断wal文件中是否有更新操作记录,我的理解是:有更新操作必然会导致更新数据文件头信息,因此这里的判断可以根据这一点来做一点优化。
int dbfd = hdb->fd;
int tfd = -1;
if(!(hdb->omode & HDBOWRITER)){ // 若不是写模式,则需要打开数据文件,以进行后面的操作
tfd = open(path, O_WRONLY, HDBFILEMODE);
if(tfd >= 0){
dbfd = tfd;
} else {
int ecode = TCEOPEN;
switch(errno){
case EACCES: ecode = TCENOPERM; break;
case ENOENT: ecode = TCENOFILE; break;
case ENOTDIR: ecode = TCENOFILE; break;
}
tchdbsetecode(hdb, ecode, __FILE__, __LINE__, __func__);
err = true;
}
}
uint64_t fsiz = 0;
if(tcread(walfd, &fsiz, sizeof(fsiz))){ // wal文件头8字节包含了事务开始前数据文件大小
fsiz = TCITOHLL(fsiz);
} else {
tchdbsetecode(hdb, TCEREAD, __FILE__, __LINE__, __func__);
err = true;
}
TCLIST *list = tclistnew();
uint64_t waloff = sizeof(fsiz);
char stack[HDBIOBUFSIZ];
while(waloff < walsiz){ // 循环读取wal文件中的更新记录,将读到的记录都保存在list中
uint64_t off; // 存放wal文件中记录的更新区域offset
uint32_t size; // 存放wal文件中记录的更新区域大小
if(!tcread(walfd, stack, sizeof(off) + sizeof(size))){ // 从wal文件中读取off和size到stack中
tchdbsetecode(hdb, TCEREAD, __FILE__, __LINE__, __func__);
err = true;
break;
}
// 拷贝读到的值到off和size变量并转换成实际值
memcpy(&off, stack, sizeof(off));
off = TCITOHLL(off);
memcpy(&size, stack + sizeof(off), sizeof(size));
size = TCITOHL(size);
char *buf; // buf用来存放更新区域offset和更新区域数据(更新前的数据),这里检查能否复用stack,否则动态分配空间
if(sizeof(off) + size <= HDBIOBUFSIZ){
buf = stack;
} else {
TCMALLOC(buf, sizeof(off) + size);
}
*(uint64_t *)buf = off; // 存放offset到buf中
if(!tcread(walfd, buf + sizeof(off), size)){ //读取更新前数据到buf中
tchdbsetecode(hdb, TCEREAD, __FILE__, __LINE__, __func__);
err = true;
if(buf != stack) TCFREE(buf);
break;
}
TCLISTPUSH(list, buf, sizeof(off) + size); // 存放buf到链表list中,这个链表会自动增长
if(buf != stack) TCFREE(buf);
waloff += sizeof(off) + sizeof(size) + size; // 移动waloff指针,循环检查是否还有其它的更改,若有,则继续读取
}
size_t xmsiz = 0;
if(hdb->fd >= 0 && hdb->map) xmsiz = (hdb->xmsiz > hdb->msiz) ? hdb->xmsiz : hdb->msiz;
for(int i = TCLISTNUM(list) - 1; i >= 0; i--){
const char *rec;
int size;
TCLISTVAL(rec, list, i, size);
uint64_t off = *(uint64_t *)rec; // rec指向的内存包含信息: 区域大小(8字节) + 区域数据
rec += sizeof(off); // 调整rec指向更新区域更新前的数据
size -= sizeof(off); // size为区域数据大小
if(lseek(dbfd, off, SEEK_SET) == -1){ // 移动数据文件指针,向数据文件写入更新前数据
tchdbsetecode(hdb, TCESEEK, __FILE__, __LINE__, __func__);
err = true;
break;
}
if(!tcwrite(dbfd, rec, size)){
tchdbsetecode(hdb, TCEWRITE, __FILE__, __LINE__, __func__);
err = true;
break;
}
if(!TCUBCACHE && off < xmsiz){
size = (size <= xmsiz - off) ? size : xmsiz - off;
memcpy(hdb->map + off, rec, size);
}
}
tclistdel(list); // 释放链表空间
if(ftruncate(dbfd, fsiz) == -1){ // 调整数据文件大小,去掉尾部多余数据
tchdbsetecode(hdb, TCETRUNC, __FILE__, __LINE__, __func__);
err = true;
}
if((hdb->omode & HDBOTSYNC) && fsync(dbfd) == -1){ // 若有可能,同步事务操作
tchdbsetecode(hdb, TCESYNC, __FILE__, __LINE__, __func__);
err = true;
}
if(tfd >= 0 && close(tfd) == -1){ // 若在这个函数中打开了数据文件,则关闭数据文件文件描述符
tchdbsetecode(hdb, TCECLOSE, __FILE__, __LINE__, __func__);
err = true;
}
} else {
err = true;
}
if(close(walfd) == -1){ // 关闭wal文件
tchdbsetecode(hdb, TCECLOSE, __FILE__, __LINE__, __func__);
err = true;
}
return !err; // 返回操作结果
}
在此说明一下:我分析的TC版本是1.4.43,我的阅读流程主要是遵循对TC数据文件的标准操作流程进行分析,即打开数据文件->存放record->获取record->关闭数据文件,期间会涉及到一些相关函数,我会逐个分析。
另外,按我粗浅的理解,我觉得TC不能称为一个完整意义上的数据库,它仅仅是一个存储引擎而已,它的所有操作最终都是针对系统中的一个普通文件进行的,因此以后的分析中,我把TC操作的对象统统称为数据文件对象,而不是数据库对象,它打开的文件称为数据文件。
一、TCHDB *tchdbnew(void)
这个函数实现的功能是:分配一个TCHDB类型的数据文件对象,我们以后的操作都是基于这个对象的,它的功能有点类似于我们打开文件后获得的那个文件描述符,不过TCHDB比文件描述符要复杂的多,下面是代码和注释:
TCHDB *tchdbnew(void){
TCHDB *hdb;
TCMALLOC(hdb, sizeof(*hdb)); // 动态分配一个TCHDB对象
tchdbclear(hdb); // 初始化TCHDB对象
return hdb;
}
下面是tchdbclear对应的代码和注释:
[cpp] view
plaincopy
/* Clear all members.
`hdb' specifies the hash database object. */
static void tchdbclear(TCHDB *hdb){
assert(hdb);
hdb->mmtx = NULL;
hdb->rmtxs = NULL;
hdb->dmtx = NULL;
hdb->wmtx = NULL;
hdb->eckey = NULL;
hdb->rpath = NULL;
hdb->type = TCDBTHASH; // 类型为hash结构的数据文件
hdb->flags = 0;
hdb->bnum = HDBDEFBNUM; // 默认hash桶大小131071
hdb->apow = HDBDEFAPOW;
hdb->fpow = HDBDEFFPOW;
hdb->opts = 0;
hdb->path = NULL;
hdb->fd = -1;
hdb->omode = 0;
hdb->rnum = 0;
hdb->fsiz = 0;
hdb->frec = 0;
hdb->dfcur = 0;
hdb->iter = 0;
hdb->map = NULL;
hdb->msiz = 0;
hdb->xmsiz = HDBDEFXMSIZ; // 默认mmap大小64M
hdb->xfsiz = 0;
hdb->ba32 = NULL;
hdb->ba64 = NULL;
hdb->align = 0;
hdb->runit = 0;
hdb->zmode = false;
hdb->fbpmax = 0;
hdb->fbpool = NULL;
hdb->fbpnum = 0;
hdb->fbpmis = 0;
hdb->async = false;
hdb->drpool = NULL;
hdb->drpdef = NULL;
hdb->drpoff = 0;
hdb->recc = NULL;
hdb->rcnum = 0;
hdb->enc = NULL;
hdb->encop = NULL;
hdb->dec = NULL;
hdb->decop = NULL;
hdb->ecode = TCESUCCESS;
hdb->fatal = false;
hdb->inode = 0;
hdb->mtime = 0;
hdb->dfunit = 0;
hdb->dfcnt = 0;
hdb->tran = false;
hdb->walfd = -1;
hdb->walend = 0;
hdb->dbgfd = -1;
hdb->cnt_writerec = -1;
hdb->cnt_reuserec = -1;
hdb->cnt_moverec = -1;
hdb->cnt_readrec = -1;
hdb->cnt_searchfbp = -1;
hdb->cnt_insertfbp = -1;
hdb->cnt_splicefbp = -1;
hdb->cnt_dividefbp = -1;
hdb->cnt_mergefbp = -1;
hdb->cnt_reducefbp = -1;
hdb->cnt_appenddrp = -1;
hdb->cnt_deferdrp = -1;
hdb->cnt_flushdrp = -1;
hdb->cnt_adjrecc = -1;
hdb->cnt_defrag = -1;
hdb->cnt_shiftrec = -1;
hdb->cnt_trunc = -1;
TCDODEBUG(hdb->cnt_writerec = 0);
TCDODEBUG(hdb->cnt_reuserec = 0);
TCDODEBUG(hdb->cnt_moverec = 0);
TCDODEBUG(hdb->cnt_readrec = 0);
TCDODEBUG(hdb->cnt_searchfbp = 0);
TCDODEBUG(hdb->cnt_insertfbp = 0);
TCDODEBUG(hdb->cnt_splicefbp = 0);
TCDODEBUG(hdb->cnt_dividefbp = 0);
TCDODEBUG(hdb->cnt_mergefbp = 0);
TCDODEBUG(hdb->cnt_reducefbp = 0);
TCDODEBUG(hdb->cnt_appenddrp = 0);
TCDODEBUG(hdb->cnt_deferdrp = 0);
TCDODEBUG(hdb->cnt_flushdrp = 0);
TCDODEBUG(hdb->cnt_adjrecc = 0);
TCDODEBUG(hdb->cnt_defrag = 0);
TCDODEBUG(hdb->cnt_shiftrec = 0);
TCDODEBUG(hdb->cnt_trunc = 0);
}
二、tchdbopen函数
该函数打开指定的数据文件,并将打开的数据文件信息添加到TCHDB结构体中,打开过程中会检查数据文件是否为空等操作,看代码:
[cpp] view
plaincopy
/* Open a database file and connect a hash database object. */
bool tchdbopen(TCHDB *hdb, const char *path, int omode){
assert(hdb && path);
if(!HDBLOCKMETHOD(hdb, true)) return false; // 获得调用函数锁
if(hdb->fd >= 0){ // 若数据文件已打开,则返回出错
tchdbsetecode(hdb, TCEINVALID, __FILE__, __LINE__, __func__); // 设置错误码
HDBUNLOCKMETHOD(hdb);
return false;
}
char *rpath = tcrealpath(path); // 取得数据文件的标准化路径
if(!rpath){
int ecode = TCEOPEN;
switch(errno){
case EACCES: ecode = TCENOPERM; break;
case ENOENT: ecode = TCENOFILE; break;
case ENOTDIR: ecode = TCENOFILE; break;
}
tchdbsetecode(hdb, ecode, __FILE__, __LINE__, __func__);
HDBUNLOCKMETHOD(hdb);
return false;
}
if(!tcpathlock(rpath)){ // 锁定文件路径, 以防止在操作过程中文件消失
tchdbsetecode(hdb, TCETHREAD, __FILE__, __LINE__, __func__);
TCFREE(rpath);
HDBUNLOCKMETHOD(hdb);
return false;
}
bool rv = tchdbopenimpl(hdb, path, omode); // 具体打开操作
if(rv){ // 打开数据文件成功,保存数据文件的标准化路径
hdb->rpath = rpath;
} else {
tcpathunlock(rpath);
TCFREE(rpath);
}
HDBUNLOCKMETHOD(hdb);
return rv; // 返回打开是否成功
}
函数tchdbopen仅仅做简单的一些检查转换操作,具体打开操作在函数tchdbopenimpl中,我们来看看代码:
[cpp] view
plaincopy
/* Open a database file and connect a hash database object.
`hdb' specifies the hash database object.
`path' specifies the path of the database file.
`omode' specifies the connection mode.
If successful, the return value is true, else, it is false. */
static bool tchdbopenimpl(TCHDB *hdb, const char *path, int omode){
assert(hdb && path);
int mode = O_RDONLY;
if(omode & HDBOWRITER){ // 取得数据文件打开模式
mode = O_RDWR;
if(omode & HDBOCREAT) mode |= O_CREAT;
}
int fd = open(path, mode, HDBFILEMODE); // 调用库函数open打开数据文件,这里可以看到,文件仅仅是普通文件而已。
if(fd < 0){
int ecode = TCEOPEN;
switch(errno){
case EACCES: ecode = TCENOPERM; break;
case ENOENT: ecode = TCENOFILE; break;
case ENOTDIR: ecode = TCENOFILE; break;
}
tchdbsetecode(hdb, ecode, __FILE__, __LINE__, __func__);
return false;
}
if(!(omode & HDBONOLCK)){ // 检查是否需要锁定打开的数据文件,这里用的是advisory lock,通过fcntl实现
if(!tclock(fd, omode & HDBOWRITER, omode & HDBOLCKNB)){
tchdbsetecode(hdb, TCELOCK, __FILE__, __LINE__, __func__);
close(fd);
return false;
}
}
if((omode & HDBOWRITER) && (omode & HDBOTRUNC)){ // 写操作,是否需要截短文件?
if(ftruncate(fd, 0) == -1){
tchdbsetecode(hdb, TCETRUNC, __FILE__, __LINE__, __func__);
close(fd);
return false;
}
if(!tchdbwalremove(hdb, path)){
close(fd);
return false;
}
}
struct stat sbuf;
if(fstat(fd, &sbuf) == -1 || !S_ISREG(sbuf.st_mode)){ // 获得文件属性信息,判断文件是否为普通文件
tchdbsetecode(hdb, TCESTAT, __FILE__, __LINE__, __func__);
close(fd);
return false;
}
char hbuf[HDBHEADSIZ];
if((omode & HDBOWRITER) && sbuf.st_size < 1){ // 写模式下,是否为空文件?
// 写模式下为空文件,则这里开始构造默认情况下的数据文件头信息,从这里我们也可以推测出数
// 据文件头在磁盘文件中的存放格式,它的大小为256字节,参见HDBHEADSIZ。
hdb->flags = 0; // 默认flags为0
hdb->rnum = 0; // 当前记录为0
uint32_t fbpmax = 1 << hdb->fpow; // 空闲块池默认大小
uint32_t fbpsiz = HDBFBPBSIZ + fbpmax * HDBFBPESIZ; // 默认空闲块池大小
int besiz = (hdb->opts & HDBTLARGE) ? sizeof(int64_t) : sizeof(int32_t); // hash桶数组中每个元素大小
hdb->align = 1 << hdb->apow; // 默认对齐字节
hdb->fsiz = HDBHEADSIZ + besiz * hdb->bnum + fbpsiz; // 不包含填充区的文件大小
hdb->fsiz += tchdbpadsize(hdb, hdb->fsiz); // 包含填充区的文件大小
hdb->frec = hdb->fsiz; // 数据文件中第一个记录在文件中的偏移
tchdbdumpmeta(hdb, hbuf); // 将数据文件头信息拷贝到hbuf
bool err = false;
if(!tcwrite(fd, hbuf, HDBHEADSIZ)) err = true; // 将数据文件头信息写入数据文件
char pbuf[HDBIOBUFSIZ];
memset(pbuf, 0, HDBIOBUFSIZ);
uint64_t psiz = hdb->fsiz - HDBHEADSIZ;
while(psiz > 0){ // 这个循环将数据文件头部的填充区置位0,有什么用?可能是后面写入新记录时会读到这个区域...
if(psiz > HDBIOBUFSIZ){
if(!tcwrite(fd, pbuf, HDBIOBUFSIZ)) err = true;
psiz -= HDBIOBUFSIZ;
} else {
if(!tcwrite(fd, pbuf, psiz)) err = true;
psiz = 0;
}
}
if(err){
tchdbsetecode(hdb, TCEWRITE, __FILE__, __LINE__, __func__);
close(fd);
return false;
}
sbuf.st_size = hdb->fsiz; // 改写文件属性信息,改写文件大小
}
if(lseek(fd, 0, SEEK_SET) == -1){ // 重置文件指针到数据文件头部
tchdbsetecode(hdb, TCESEEK, __FILE__, __LINE__, __func__);
close(fd);
return false;
}
if(!tcread(fd, hbuf, HDBHEADSIZ)){ // 读出文件头信息,保证hbuf中含有文件头信息
tchdbsetecode(hdb, TCEREAD, __FILE__, __LINE__, __func__);
close(fd);
return false;
}
int type = hdb->type;
tchdbloadmeta(hdb, hbuf); // 将文件头信息copy到TCHDB结构体中,即初始化TCHDB结构体某些成员
// 若文件被标识为已打开,很可能前面针对文件的操作在事务中被中断了,这里将数据文件从wal文件中恢复回来,
// 相当于取消了前面那个事务,目前我们只要知道wal文件用于事务即可,这个函数后面我会补充分析。
if((hdb->flags & HDBFOPEN) && tchdbwalrestore(hdb, path)){
if(lseek(fd, 0, SEEK_SET) == -1){ // 数据文件已恢复,下面需要重新读取头部信息
tchdbsetecode(hdb, TCESEEK, __FILE__, __LINE__, __func__);
close(fd);
return false;
}
if(!tcread(fd, hbuf, HDBHEADSIZ)){ // 重新读出出数据文件头信息
tchdbsetecode(hdb, TCEREAD, __FILE__, __LINE__, __func__);
close(fd);
return false;
}
tchdbloadmeta(hdb, hbuf);// 重新导出出数据文件头信息
if(!tchdbwalremove(hdb, path)){
close(fd);
return false;
}
}
int besiz = (hdb->opts & HDBTLARGE) ? sizeof(int64_t) : sizeof(int32_t); // hash桶大小
size_t msiz = HDBHEADSIZ + hdb->bnum * besiz; // 算出数据文件最小需要mmap的大小,即数据文件头大小加hash同数组大小
if(!(omode & HDBONOLCK)){
if(memcmp(hbuf, HDBMAGICDATA, strlen(HDBMAGICDATA)) || hdb->type != type ||
hdb->frec < msiz + HDBFBPBSIZ || hdb->frec > hdb->fsiz || sbuf.st_size < hdb->fsiz){
tchdbsetecode(hdb, TCEMETA, __FILE__, __LINE__, __func__);
close(fd);
return false;
}
}
if(((hdb->opts & HDBTDEFLATE) && !_tc_deflate) ||
((hdb->opts & HDBTBZIP) && !_tc_bzcompress) || ((hdb->opts & HDBTEXCODEC) && !hdb->enc)){
tchdbsetecode(hdb, TCEINVALID, __FILE__, __LINE__, __func__);
close(fd);
return false;
}
size_t xmsiz = (hdb->xmsiz > msiz) ? hdb->xmsiz : msiz; // 数据文件需要mmap的大小,至少为msize
if(!(omode & HDBOWRITER) && xmsiz > hdb->fsiz) xmsiz = hdb->fsiz; // mmap的大小不超过文件大小
void *map = mmap(0, xmsiz, PROT_READ | ((omode & HDBOWRITER) ? PROT_WRITE : 0),
MAP_SHARED, fd, 0); // 映射指定大小数据文件
if(map == MAP_FAILED){
tchdbsetecode(hdb, TCEMMAP, __FILE__, __LINE__, __func__);
close(fd);
return false;
}
hdb->fbpmax = 1 << hdb->fpow; // 空闲块池区域最大数量
if(omode & HDBOWRITER){ // 写模式,分配空闲块池
TCMALLOC(hdb->fbpool, hdb->fbpmax * HDBFBPALWRAT * sizeof(HDBFB));
} else {
hdb->fbpool = NULL; // 非写模式,不分配空闲块池,节约内存
}
hdb->fbpnum = 0; // 其它值的初始化
hdb->fbpmis = 0;
hdb->async = false;
hdb->drpool = NULL;
hdb->drpdef = NULL;
hdb->drpoff = 0;
hdb->recc = (hdb->rcnum > 0) ? tcmdbnew2(hdb->rcnum * 2 + 1) : NULL; // record的缓冲区大小计算
hdb->path = tcstrdup(path); // 复制一份数据文件raw路径,没有经过标准化处理的路径。
hdb->fd = fd; // 从这里开始复制一些值到TCHDB结构体中
hdb->omode = omode;
hdb->dfcur = hdb->frec;
hdb->iter = 0;
hdb->map = map;
hdb->msiz = msiz;
hdb->xfsiz = 0;
if(hdb->opts & HDBTLARGE){ // 判断使用32位还是64为的hash桶
hdb->ba32 = NULL;
hdb->ba64 = (uint64_t *)((char *)map + HDBHEADSIZ);
} else {
hdb->ba32 = (uint32_t *)((char *)map + HDBHEADSIZ);
hdb->ba64 = NULL;
}
hdb->align = 1 << hdb->apow;
hdb->runit = tclmin(tclmax(hdb->align, HDBMINRUNIT), HDBIOBUFSIZ);
hdb->zmode = (hdb->opts & HDBTDEFLATE) || (hdb->opts & HDBTBZIP) ||
(hdb->opts & HDBTTCBS) || (hdb->opts & HDBTEXCODEC);
hdb->ecode = TCESUCCESS;
hdb->fatal = false;
hdb->inode = (uint64_t)sbuf.st_ino; // 数据文件的inode号
hdb->mtime = sbuf.st_mtime; // 数据文件修改时间
hdb->dfcnt = 0;
hdb->tran = false;
hdb->walfd = -1;
hdb->walend = 0;
if(hdb->omode & HDBOWRITER){ // 若是写模式,并且当前数据文件不处于打开状态,则从数据文件中读取fbp信息,在内存中建立fbp区域
bool err = false;
if(!(hdb->flags & HDBFOPEN) && !tchdbloadfbp(hdb)) err = true;
memset(hbuf, 0, 2);
if(!tchdbseekwrite(hdb, hdb->msiz, hbuf, 2)) err = true;
if(err){
TCFREE(hdb->path);
TCFREE(hdb->fbpool);
munmap(hdb->map, xmsiz);
close(fd);
hdb->fd = -1;
return false;
}
tchdbsetflag(hdb, HDBFOPEN, true); // 设置数据文件已被打开标志
}
return true; // 打开成功
}
这里补充下上面函数中调用的tchdbwalrestore函数,wal文件的格式为:更新前数据文件大小(8字节) + 一条或多条更新记录,其中,每条更新记录格式如下:
[cpp] view
plaincopy
/* Restore the database from the write ahead logging file.
`hdb' specifies the hash database object.
`path' specifies the path of the database file.
If successful, the return value is true, else, it is false. */
static int tchdbwalrestore(TCHDB *hdb, const char *path){
assert(hdb && path);
char *tpath = tcsprintf("%s%c%s", path, MYEXTCHR, HDBWALSUFFIX); // 构造数据文件对应的wal文件名
int walfd = open(tpath, O_RDONLY, HDBFILEMODE); // 打开wal文件
TCFREE(tpath);
if(walfd < 0) return false;
bool err = false;
uint64_t walsiz = 0;
struct stat sbuf;
if(fstat(walfd, &sbuf) == 0){ // 获得wal文件属性信息
walsiz = sbuf.st_size;
} else {
tchdbsetecode(hdb, TCESTAT, __FILE__, __LINE__, __func__);
err = true;
}
if(walsiz >= sizeof(walsiz) + HDBHEADSIZ){ // 粗略判断wal文件中是否有更新操作记录,我的理解是:有更新操作必然会导致更新数据文件头信息,因此这里的判断可以根据这一点来做一点优化。
int dbfd = hdb->fd;
int tfd = -1;
if(!(hdb->omode & HDBOWRITER)){ // 若不是写模式,则需要打开数据文件,以进行后面的操作
tfd = open(path, O_WRONLY, HDBFILEMODE);
if(tfd >= 0){
dbfd = tfd;
} else {
int ecode = TCEOPEN;
switch(errno){
case EACCES: ecode = TCENOPERM; break;
case ENOENT: ecode = TCENOFILE; break;
case ENOTDIR: ecode = TCENOFILE; break;
}
tchdbsetecode(hdb, ecode, __FILE__, __LINE__, __func__);
err = true;
}
}
uint64_t fsiz = 0;
if(tcread(walfd, &fsiz, sizeof(fsiz))){ // wal文件头8字节包含了事务开始前数据文件大小
fsiz = TCITOHLL(fsiz);
} else {
tchdbsetecode(hdb, TCEREAD, __FILE__, __LINE__, __func__);
err = true;
}
TCLIST *list = tclistnew();
uint64_t waloff = sizeof(fsiz);
char stack[HDBIOBUFSIZ];
while(waloff < walsiz){ // 循环读取wal文件中的更新记录,将读到的记录都保存在list中
uint64_t off; // 存放wal文件中记录的更新区域offset
uint32_t size; // 存放wal文件中记录的更新区域大小
if(!tcread(walfd, stack, sizeof(off) + sizeof(size))){ // 从wal文件中读取off和size到stack中
tchdbsetecode(hdb, TCEREAD, __FILE__, __LINE__, __func__);
err = true;
break;
}
// 拷贝读到的值到off和size变量并转换成实际值
memcpy(&off, stack, sizeof(off));
off = TCITOHLL(off);
memcpy(&size, stack + sizeof(off), sizeof(size));
size = TCITOHL(size);
char *buf; // buf用来存放更新区域offset和更新区域数据(更新前的数据),这里检查能否复用stack,否则动态分配空间
if(sizeof(off) + size <= HDBIOBUFSIZ){
buf = stack;
} else {
TCMALLOC(buf, sizeof(off) + size);
}
*(uint64_t *)buf = off; // 存放offset到buf中
if(!tcread(walfd, buf + sizeof(off), size)){ //读取更新前数据到buf中
tchdbsetecode(hdb, TCEREAD, __FILE__, __LINE__, __func__);
err = true;
if(buf != stack) TCFREE(buf);
break;
}
TCLISTPUSH(list, buf, sizeof(off) + size); // 存放buf到链表list中,这个链表会自动增长
if(buf != stack) TCFREE(buf);
waloff += sizeof(off) + sizeof(size) + size; // 移动waloff指针,循环检查是否还有其它的更改,若有,则继续读取
}
size_t xmsiz = 0;
if(hdb->fd >= 0 && hdb->map) xmsiz = (hdb->xmsiz > hdb->msiz) ? hdb->xmsiz : hdb->msiz;
for(int i = TCLISTNUM(list) - 1; i >= 0; i--){
const char *rec;
int size;
TCLISTVAL(rec, list, i, size);
uint64_t off = *(uint64_t *)rec; // rec指向的内存包含信息: 区域大小(8字节) + 区域数据
rec += sizeof(off); // 调整rec指向更新区域更新前的数据
size -= sizeof(off); // size为区域数据大小
if(lseek(dbfd, off, SEEK_SET) == -1){ // 移动数据文件指针,向数据文件写入更新前数据
tchdbsetecode(hdb, TCESEEK, __FILE__, __LINE__, __func__);
err = true;
break;
}
if(!tcwrite(dbfd, rec, size)){
tchdbsetecode(hdb, TCEWRITE, __FILE__, __LINE__, __func__);
err = true;
break;
}
if(!TCUBCACHE && off < xmsiz){
size = (size <= xmsiz - off) ? size : xmsiz - off;
memcpy(hdb->map + off, rec, size);
}
}
tclistdel(list); // 释放链表空间
if(ftruncate(dbfd, fsiz) == -1){ // 调整数据文件大小,去掉尾部多余数据
tchdbsetecode(hdb, TCETRUNC, __FILE__, __LINE__, __func__);
err = true;
}
if((hdb->omode & HDBOTSYNC) && fsync(dbfd) == -1){ // 若有可能,同步事务操作
tchdbsetecode(hdb, TCESYNC, __FILE__, __LINE__, __func__);
err = true;
}
if(tfd >= 0 && close(tfd) == -1){ // 若在这个函数中打开了数据文件,则关闭数据文件文件描述符
tchdbsetecode(hdb, TCECLOSE, __FILE__, __LINE__, __func__);
err = true;
}
} else {
err = true;
}
if(close(walfd) == -1){ // 关闭wal文件
tchdbsetecode(hdb, TCECLOSE, __FILE__, __LINE__, __func__);
err = true;
}
return !err; // 返回操作结果
}
相关文章推荐
- Tokyo Cabinet TCHDB源码阅读——tchdbnew、tchdbopen及相关函数代码
- Tokyo Cabinet TCHDB源码阅读——tchdbput及相关函数代码注释
- HM编码器代码阅读(7)——整个编码流程以及相关的函数
- Discuz!NT 代码阅读笔记(9)--DNT数据库中唯一的用户函数解析
- C#去除指定字符串中的HTML标签相关代码函数
- FFMPeg代码分析:AVFrame结构体及其相关的函数
- homerHEVC代码阅读(24)——编码器控制函数HOMER_enc_control
- 深度学习 5. MatConvNet 相关函数解释说明,MatConvNet 代码理解(一)cnn_mnist.m 的注释
- 【第五篇】Volley代码修改之图片二级缓存以及相关源码阅读(重写ImageLoader.ImageCache)
- 34 网络相关函数(二)——live555源码阅读(四)网络
- 37 网络相关函数(五)——live555源码阅读(四)网络
- 【前端阅读】——《代码整洁之道》摘记之整洁代码、命名、函数、注释
- HM编码器代码阅读(1)——介绍以及相关知识
- MATLAB句柄相关函数和代码示例(设置美化图表属性)
- 35 网络相关函数(三)——live555源码阅读(四)网络
- WordPress文章作者的相关函数调用代码
- 深度学习 6. MatConvNet 相关函数解释说明,MatConvNet 代码理解(二)cnn_mnist_init.m 的注释
- WordPress开发中短代码的实现及相关函数使用技巧
- C++程序运行时内存布局之----------局部变量,全局变量,静态变量,函数代码,new出来的变量
- larbin中sequencer()及其相关函数阅读