2021SC@SDUSC
上次讲了Sqlite分配内存的malloc()方法,free(),remalloc();Win32特定堆的最大初始大小,计算初始大小时所用的缓存大小,根据初始堆和默认页大小计算最大合法缓存大小;数组aSyscall[]用来保存所有可重写系统调用的名称和指针;以及用语所有Win32 VFSes的很重要的xSetSystemCall()方法。
接下来介绍VFS的内存分配和共享缓存机制
VFS的IO接口里提供了文件的共享缓存机制。该机制通过在test_vfs里内置一个Shared memory模块来模拟测试文件的共享缓存,以代替原来VFS提供的接口。
1.通用接口既可以给内核用,也可有由应用程序调用,主要是由以下几个函数组成:
void *sqlite3_malloc(int n);
void *sqlite3_realloc(void *pOld, int n);
void sqlite3_free(void *p);
sqlite3_uint64 sqlite3_msize(void *p);
void *sqlite3MallocZero(u64 n);
前面3个是对库函数malloc、realloc、free的封装,而sqlite3_msize用来查看分配的内存大小。
封装有以下几个作用:
1)统计内存的使用情况,由以下函数完成:
sqlite3StatusHighwater(SQLITE_STATUS_MALLOC_SIZE, n); /*记录分配的最大值*/
nFull = sqlite3MallocSize(p);
sqlite3StatusUp(SQLITE_STATUS_MEMORY_USED, nFull); /*记录内存分配的总量*/
sqlite3StatusUp(SQLITE_STATUS_MALLOC_COUNT, 1); /*记录分配的次数*/
2)内存分配请求失败或超过限制时,释放空闲的缓存页并重新分配:
sqlite3MallocAlarm(nFull); /* nFull是请求分配的内存大小,按8字节对齐*/
3)为了保证线程安全,分配和释放时要加互斥锁:
sqlite3_mutex_enter(mem0.mutex);
sqlite3_mutex_leave(mem0.mutex);
2. 内部接口主要针对sqlite本身使用,使用时需要传入特定的数据库连接,接口由以下函数组成:
void *sqlite3DbMallocRaw(sqlite3 *db, u64 n);’
void *sqlite3DbRealloc(sqlite3 *db, void *p, u64 n);
void sqlite3DbFree(sqlite3 *db, void *p);
void *sqlite3DbReallocOrFree(sqlite3 *db, void *p, u64 n);
void *sqlite3DbMallocZero(sqlite3 *db, u64 n);
int sqlite3DbMallocSize(sqlite3 *db, void *p);
相较于通用接口,内部接口先判断分配内存的大小,如果内存较小会使用lookaside来分配,如果分配的内存超过每个slot的大小,那么使用通用接口来分配
共享缓存:
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;
};
sqlite3_file *pFile; /* pFile作为连接句柄传入*/
typedef struct TestvfsFd TestvfsFd;
struct TestvfsFile {
sqlite3_file base;
TestvfsFd *pFd;
};
#define tvfsGetFd(pFile) (((TestvfsFile *)pFile)->pFd)
/*从pFile中提取pFd*/
typedef struct TestvfsFd TestvfsFd;
struct TestvfsFd {
sqlite3_vfs *pVfs;
const char *zFilename; /*传递给xOpen()的文件名*/
sqlite3_file *pReal; /* 真实的底层文件描述符 */
Tcl_Obj *pShmId; /* Tcl回调的共享内存id */
TestvfsBuffer *pShm; /* 共享内存缓冲区*/
u32 excllock; /* 排它锁的掩码 */
u32 sharedlock; /* 共享锁的掩码 */
TestvfsFd *pNext; /* 在该文件上打开的下一个句柄 */
};
TestvfsFd *pFd = tvfsGetFd(pFile); /*每个连接都对应一个pFd*/
附:数据库两种基本的锁:排它锁和共享锁。
如果数据对象加上排它锁,则其它事务不能对它读取和修改。
如果加上共享锁,则该数据库对象可以被其他事务读取,但不能修改。
pBuffer结构
pBuffer用来存储共享缓存,每一个文件都有自己的缓存,这些缓存组成一个链表。如果有多个连接打开同一个文件,那么相同文件对应连接的pFd又会组成一个链表,pFile指向pFd的表头。
pBuffer的类型为TestvfsBuffer,如下:
struct TestvfsBuffer {
char *zFile;
int pgsz; /* 页面大小*/
u8 *aPage[TESTVFS_MAX_PAGES];
TestvfsFd *pFile; /* 打开的句柄列表 */
TestvfsBuffer *pNext; /* 所有缓冲区的链接列表中的下一个 */
};
Connection->pFd->pVfs->pAppData->pBuffer
2.具体实现
tvfsShmMap(): /*获取共享缓存中的具体页面空间*/
static int tvfsShmMap(
sqlite3_file *pFile,
int iPage,
int pgsz,
int isWrite,
void volatile **pp
){
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;
}
tvfsShmLock():
如果多个线程的连接共享一个缓存,则需对共享缓存加锁,如果已经有排它锁了那么返回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;
}