VFS的IO接口里提供了文件的共享缓存机制,在test_vfs里内置了一个Shared memory模块用来模拟测试文件的共享缓存,而不是使用原来的VFS提供的接口。
1.结构定义
pFd结构
每一个数据库文件的连接都对应着一个连接句柄pFile,上层函数调用VFS接口时会传入pFile,在test_vfs里pFile会被强制转换为TestvfsFile*类型,相当于sqlite3_file*的一个继承
typedef struct sqlite3_file sqlite3_file; struct sqlite3_file { const struct sqlite3_io_methods *pMethods; /* Methods for an open file */ }; sqlite3_file *pFile;// pFile作为连接句柄传入 /*强制转换为TestvfsFile*类型*/ typedef struct TestvfsFd TestvfsFd; struct TestvfsFile { sqlite3_file base; /* Base class. Must be first */ TestvfsFd *pFd; /* File data */ }; #define tvfsGetFd(pFile) (((TestvfsFile *)pFile)->pFd) /*从连接句柄中提取pFd*/ typedef struct TestvfsFd TestvfsFd; struct TestvfsFd { sqlite3_vfs *pVfs; /* The VFS */ const char *zFilename; /* Filename as passed to xOpen() */ sqlite3_file *pReal; /* The real, underlying file descriptor */ Tcl_Obj *pShmId; /* Shared memory id for Tcl callbacks */ TestvfsBuffer *pShm; /* Shared memory buffer */ u32 excllock; /* Mask of exclusive locks */ u32 sharedlock; /* Mask of shared locks */ TestvfsFd *pNext; /* Next handle opened on the same file */ }; TestvfsFd *pFd = tvfsGetFd(pFile);//每个连接对应一个pFd
pBuffer结构
pBuffer用来存储共享缓存,每一个文件都有自己的缓存,这些缓存组成一个链表。如果有多个连接打开同一个文件,那么相同文件对应连接的pFd又会组成一个链表,pFile指向pFd的表头。
pBuffer的类型为TestvfsBuffer*,定义如下:
struct TestvfsBuffer { char *zFile; /* Associated file name */ int pgsz; /* Page size */ u8 *aPage[TESTVFS_MAX_PAGES]; /* Array of ckalloc'd pages */ TestvfsFd *pFile; /* List of open handles */ TestvfsBuffer *pNext; /* Next in linked list of all buffers */ };
缓存链表的表头pBuffer存在pFd->pVfs->pAppData里
结构关系
假设用多个连接打开同一个文件,其关系如下
2.具体实现
tvfsShmMap():
获取共享缓存中的具体页面空间
static int tvfsShmMap( sqlite3_file *pFile, /* Handle open on database file */ int iPage, /* Page to retrieve */ int pgsz, /* Size of pages */ int isWrite, /* True to extend file if necessary */ void volatile **pp /* OUT: Mapped memory */ ){ int rc = SQLITE_OK; TestvfsFd *pFd = tvfsGetFd(pFile); Testvfs *p = (Testvfs *)(pFd->pVfs->pAppData); …… /*如果该连接的缓存不存在,为该连接分配缓存*/ if( 0==pFd->pShm ){ rc = tvfsShmOpen(pFile); if( rc!=SQLITE_OK ){ return rc; } } …… /*如果第iPage页的缓存为空,那么分配pgsz 长度的空间*/ if( rc==SQLITE_OK && isWrite && !pFd->pShm->aPage[iPage] ){ tvfsAllocPage(pFd->pShm, iPage, pgsz); } *pp = (void volatile *)pFd->pShm->aPage[iPage]; return rc; }
tvfsShmOpen():
为对应的连接分配缓存地址,如果缓存不存在,那么新建一个节点插入到pFd链表中。
static int tvfsShmOpen(sqlite3_file *pFile){ Testvfs *p; int rc = SQLITE_OK; /* Return code */ TestvfsBuffer *pBuffer; /* Buffer to open connection to */ TestvfsFd *pFd; /* The testvfs file structure */ pFd = tvfsGetFd(pFile); p = (Testvfs *)pFd->pVfs->pAppData; assert( 0==p->isFullshm ); assert( pFd->pShmId && pFd->pShm==0 && pFd->pNext==0 ); …… /* Search for a TestvfsBuffer. Create a new one if required. */ for(pBuffer=p->pBuffer; pBuffer; pBuffer=pBuffer->pNext){ if( 0==strcmp(pFd->zFilename, pBuffer->zFile) ) break; } /*如果缓存链表中没有该连接文件的缓存,新建一个节点插入到pBuffer链表中*/ if( !pBuffer ){ int szName = (int)strlen(pFd->zFilename); int nByte = sizeof(TestvfsBuffer) + szName + 1; pBuffer = (TestvfsBuffer *)ckalloc(nByte); memset(pBuffer, 0, nByte); pBuffer->zFile = (char *)&pBuffer[1]; memcpy(pBuffer->zFile, pFd->zFilename, szName+1); pBuffer->pNext = p->pBuffer; p->pBuffer = pBuffer; } /*把新连接插入到pFd链表的表头,并让pBuffer->pFile指向表头*/ /* Connect the TestvfsBuffer to the new TestvfsShm handle and return. */ pFd->pNext = pBuffer->pFile; pBuffer->pFile = pFd; pFd->pShm = pBuffer; return SQLITE_OK; }
tvfsShmUnmap():
把连接从pFd链表中移除,移除后对应的pBuffer如果没有与之对应的连接,那么释放pBuffer的所有空间
static int tvfsShmUnmap( sqlite3_file *pFile, int deleteFlag ){ int rc = SQLITE_OK; TestvfsFd *pFd = tvfsGetFd(pFile); Testvfs *p = (Testvfs *)(pFd->pVfs->pAppData); TestvfsBuffer *pBuffer = pFd->pShm; TestvfsFd **ppFd; /*把连接从pFd链表中移除, pBuffer->pFile存放头节点地址*/ for(ppFd=&pBuffer->pFile; *ppFd!=pFd; ppFd=&((*ppFd)->pNext)); assert( (*ppFd)==pFd ); *ppFd = pFd->pNext;//注意ppFd是二级指针,假如pPrev是pFd的上一个节点地址,那么*ppFd相当于pPrev->pNext pFd->pNext = 0; /*如果pFd是pBuffer的最后一个连接,那么释放pBuffer->pFile */ if( pBuffer->pFile==0 ){ int i; TestvfsBuffer **pp; for(pp=&p->pBuffer; *pp!=pBuffer; pp=&((*pp)->pNext)); *pp = (*pp)->pNext; for(i=0; pBuffer->aPage[i]; i++){ ckfree((char *)pBuffer->aPage[i]); } ckfree((char *)pBuffer); } pFd->pShm = 0; return rc; }
tvfsShmLock():
如果多个线程的连接共享一个缓存,需要对共享缓存加锁,如果已经有独占锁了那么返回busy。
如果加的是独占锁已经有共享锁了,那么也返回busy,也就是说有共享锁的情况下不能获取独占锁,这是为了防止中途读取了一半的数据的时候被修改。
这里似乎有个漏洞,如果持续不断地加共享锁,那么会导致独占锁一直加不上,也就不能写数据了。
static int tvfsShmLock( sqlite3_file *pFile, int ofst, int n, int flags ){ int rc = SQLITE_OK; TestvfsFd *pFd = tvfsGetFd(pFile); Testvfs *p = (Testvfs *)(pFd->pVfs->pAppData) …… if( rc==SQLITE_OK ){ int isLock = (flags & SQLITE_SHM_LOCK); int isExcl = (flags & SQLITE_SHM_EXCLUSIVE); u32 mask = (((1<pShm->pFile; p2; p2=p2->pNext){ if( p2==pFd ) continue; if( (p2->excllock&mask) || (isExcl && p2->sharedlock&mask) ){ rc = SQLITE_BUSY; break; } } /*获得锁资源后对缓存加锁*/ if( rc==SQLITE_OK ){ if( isExcl ) pFd->excllock |= mask; if( !isExcl ) pFd->sharedlock |= mask; } }else{/*释放锁*/ if( isExcl ) pFd->excllock &= (~mask); if( !isExcl ) pFd->sharedlock &= (~mask); } } return rc; }