SQLite3源码学习(17) test_vfs的共享内存机制
程序员文章站
2022-05-09 23:48:16
...
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<<n)-1) << ofst);//n为掩码数量,ofst为掩码偏移
if( isLock ){
TestvfsFd *p2;
for(p2=pFd->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;
}
上一篇: PHP设计模式--概述