testpcache是page cache的一个简单插件,用tcl调试时输入命令sqlite3_shutdown和sqlite3_config_alt_pcache 1就会把pcache1替换成testpcache。
testpcache由下列函数构成:
static const sqlite3_pcache_methods2 testPcache = {
1,
(void*)&testpcacheGlobal,
testpcacheInit,
testpcacheShutdown,
testpcacheCreate,
testpcacheCachesize,
testpcachePagecount,
testpcacheFetch,
testpcacheUnpin,
testpcacheRekey,
testpcacheTruncate,
testpcacheDestroy,
};
testpcacheInit():
这个函数就是测试系统分配内存是否正常
testpcacheShutdown():
释放testpcacheInit()中给testpcacheGlobal.pDummy分配的内存。
testpcacheRandom():
获得一个伪随机数,种子是sqlite3_config_alt_pcache命令的第4个个输入参数。
testpcacheCreate():
给sqlite3_pcache *pCache分配空间,即返回指针testpcache *p。先给p申请内存,内存包括testpcache大小和所有cache page所需的内存:
nMem =sizeof(testpcache) + TESTPCACHE_NPAGE*(szPage+szExtra);
p = sqlite3_malloc( nMem );
testpcacheCachesize():
设置cache中最大的页面数量,目前该函数为空。
testpcachePagecount():
获取已经使用的页面数量。
testpcacheFetch():
取出一个页面,在cache中寻找该页面,如果存在,那么标记该页面为已使用p->a[i].isPinned = 1;
/* See if the page is already in cache. Return immediately if it is */
for(i=0; i<TESTPCACHE_NPAGE; i++){
if( p->a[i].key==key ){
if( !p->a[i].isPinned ){
p->nPinned++;
assert( p->nPinned <= TESTPCACHE_NPAGE - p->nFree );
p->a[i].isPinned = 1;
}
return &p->a[i].page;
}
}
如果没有该页面,那么从缓存中取出一个空闲的页面,记录该页面的序号即key值,并标记为已使用
/* Find a free page to allocate if there are any free pages.
** Withhold TESTPCACHE_RESERVE free pages until createFlag is 2.
*/
if( p->nFree>TESTPCACHE_RESERVE || (createFlag==2 && p->nFree>0) ){
j = testpcacheRandom(p) % TESTPCACHE_NPAGE;
for(i=0; i<TESTPCACHE_NPAGE; i++, j = (j+1)%TESTPCACHE_NPAGE){
if( p->a[j].key==0 ){
p->a[j].key = key;
p->a[j].isPinned = 1;
memset(p->a[j].page.pBuf, 0, p->szPage);
memset(p->a[j].page.pExtra, 0, p->szExtra);
p->nPinned++;
p->nFree--;
assert( p->nPinned <= TESTPCACHE_NPAGE - p->nFree );
return &p->a[j].page;
}
}
如果没有空闲页面,那么从没有使用的页面中回收一个页面再分配
/* If there are no free pages, recycle a page. The page to
** recycle is selected at random from all unpinned pages.
*/
j = testpcacheRandom(p) % TESTPCACHE_NPAGE;
for(i=0; i<TESTPCACHE_NPAGE; i++, j = (j+1)%TESTPCACHE_NPAGE){
if( p->a[j].key>0 && p->a[j].isPinned==0 ){
p->a[j].key = key;
p->a[j].isPinned = 1;
memset(p->a[j].page.pBuf, 0, p->szPage);
memset(p->a[j].page.pExtra, 0, p->szExtra);
p->nPinned++;
assert( p->nPinned <= TESTPCACHE_NPAGE - p->nFree );
return &p->a[j].page;
}
}
testpcacheUnpin():
把传进来的pOldPage标记为未使用,并根据discard标志位决定是否释放该页面
for(i=0; i<TESTPCACHE_NPAGE; i++){
if( &p->a[i].page==pOldPage ){
/* The pOldPage pointer always points to a pinned page */
assert( p->a[i].isPinned );
p->a[i].isPinned = 0;
p->nPinned--;
assert( p->nPinned>=0 );
if( discard ){
p->a[i].key = 0;
p->nFree++;
assert( p->nFree<=TESTPCACHE_NPAGE );
}
return;
}
}
testpcacheRekey():
重新标记缓存页面的序号,传入两个参数oldKey和newKey,把oldKey的页面序号替换成newKey的值:
/* Find the page to be rekeyed and rekey it.
*/
for(i=0; i<TESTPCACHE_NPAGE; i++){
if( p->a[i].key==oldKey ){
/* The oldKey and pOldPage parameters match */
assert( &p->a[i].page==pOldPage );
/* Page to be rekeyed must be pinned */
assert( p->a[i].isPinned );
p->a[i].key = newKey;
return;
}
}
在替换之前如果cache中已经存在页面序号为newKey的页面,那么释放该页面。
testpcacheTruncate():
释放key值大于iLimit的缓存页面:
for(i=0; i<TESTPCACHE_NPAGE; i++){
if( p->a[i].key>=iLimit ){
p->a[i].key = 0;
if( p->a[i].isPinned ){
p->nPinned--;
assert( p->nPinned>=0 );
}
p->nFree++;
assert( p->nFree<=TESTPCACHE_NPAGE );
}
}
testpcacheDestroy():销毁pCache
testpcache只是实现了page cache插件最简单的功能,效率更高更完整的实现是pcache1插件,通过对testpcache的学习可以很快知道page cache分配的基本原理。