Page cache是进程分配的内存空间,用来缓存数据页面。page cache的管理独立于操作系统,当一个线程打开一个数据库连接时就会建立一个page cache,对于一个进程中的多线程,它们可以有独立的cache也可以共享一个cahce,下图描述了page cache的结构:
在Pager初始化时为pPager->pPCache分配了空间,pPCache是PCache类型的结构体,其成员变量定义如下:
struct PCache {
PgHdr *pDirty, *pDirtyTail; /* List of dirty pages in LRU order */
PgHdr *pSynced; /* Last synced page in dirty page list */
int nRefSum; /* Sum of ref counts over all pages */
int szCache; /* Configured cache size */
int szSpill; /* Size before spilling occurs */
int szPage; /* Size of every page in this cache */
int szExtra; /* Size of extra space for each page */
u8 bPurgeable; /* True if pages are on backing store */
u8 eCreate; /* eCreate value for for xFetch() */
int (*xStress)(void*,PgHdr*); /* Call to try make a page clean */
void *pStress; /* Argument to xStress */
sqlite3_pcache *pCache; /* Pluggable cache module */
};
在page cache的一系列管理操作中,pPCache会作为句柄传入。cache的空间是以一个个slot组织在一起的,这些slot组成一个hash表来存放page。每个在cache里的page用PgHdr结构体来表示:
struct PgHdr {
sqlite3_pcache_page *pPage; /* Pcache object page handle */
void *pData; /* Page data */
void *pExtra; /* Extra content */
PCache *pCache; /* PRIVATE: Cache that owns this page */
PgHdr *pDirty; /* Transient list of dirty sorted by pgno */
Pager *pPager; /* The pager this page is part of */
Pgno pgno; /* Page number for this page */
#ifdef SQLITE_CHECK_PAGES
u32 pageHash; /* Hash of page content */
#endif
u16 flags; /* PGHDR flags defined below */
/**********************************************************************
** Elements above, except pCache, are public. All that follow are
** private to pcache.c and should not be accessed by other modules.
** pCache is grouped with the public elements for efficiency.
*/
i16 nRef; /* Number of users of this page */
PgHdr *pDirtyNext; /* Next element in list of dirty pages */
PgHdr *pDirtyPrev; /* Previous element in list of dirty pages */
};
page cache使用的是一种可插入式的cache管理方式,这也就是说page cache先搭建了一个cache的框架,这部分代码在pcache.c里,而对于slot的具体管理如创建hash表, page的添加删除和查找、页面置换算法LRU等操作通过接口来实现,接口定义了一系列相关的函数,sqlite3GlobalConfig.pcache2就是实现这个接口的结构体,其定义如下:
struct sqlite3_pcache_methods2 {
int iVersion;
void *pArg;
int (*xInit)(void*);
void (*xShutdown)(void*);
sqlite3_pcache *(*xCreate)(int szPage, int szExtra, int bPurgeable);
void (*xCachesize)(sqlite3_pcache*, int nCachesize);
int (*xPagecount)(sqlite3_pcache*);
sqlite3_pcache_page *(*xFetch)(sqlite3_pcache*, unsigned key, int createFlag);
void (*xUnpin)(sqlite3_pcache*, sqlite3_pcache_page*, int discard);
void (*xRekey)(sqlite3_pcache*, sqlite3_pcache_page*,
unsigned oldKey, unsigned newKey);
void (*xTruncate)(sqlite3_pcache*, unsigned iLimit);
void (*xDestroy)(sqlite3_pcache*);
void (*xShrink)(sqlite3_pcache*);
};
可以看到sqlite3GlobalConfig.pcache2包含了一系列函数指针,通过diaosqlite3_config(SQLITE_CONFIG_PCACHE2, &defaultMethods);来初始化。defaultMethods是一个默认的方法,其函数的实现在PCache 1.c中
static const sqlite3_pcache_methods2 defaultMethods = {
1, /* iVersion */
0, /* pArg */
pcache1Init, /* xInit */
pcache1Shutdown, /* xShutdown */
pcache1Create, /* xCreate */
pcache1Cachesize, /* xCachesize */
pcache1Pagecount, /* xPagecount */
pcache1Fetch, /* xFetch */
pcache1Unpin, /* xUnpin */
pcache1Rekey, /* xRekey */
pcache1Truncate, /* xTruncate */
pcache1Destroy, /* xDestroy */
pcache1Shrink /* xShrink */
};
pcache1就相当于page cache的一个插件,在PCache结构体里有如下成员变量sqlite3_pcache *pCache,sqlite3_pcache是一个没有具体定义的结构体,在pcache1插件中,PCache1结构体是一个对sqlite3_pcache重新定义的实体,同时PgHdr也会被重新定义成PgHdr1,下图可以大致描述pcache1的轮廓
如果page的数量超过hash表的长度,相同hash值的page会通过链表形成一个桶。pinned page表示正在使用的page,不能被回收,unpinned page表示未使用的page,通过一个双向链表连接在一起,当cache满了后,会通过LRU算法替换掉最久未使用的page。
除了pcache1外,test_pcache.c中还实现了一个简单的page cache插件testPcache,该插件可以替换来替换pcache1,调用sqlite3_config(SQLITE_CONFIG_PCACHE2, &testPcache)即可替换。
以上图片都取自《SQLite Database System Design and Implementation》