概述
实现了页号和内存的映射管理,页缓存的回收与分配
基础概念
Cache Line :缓存行,除了页缓存来存储页数据之外,还需要额外的内存(PgHdr1、PgHdr等)来管理这些页缓存。页缓存 加上这些额外的内存就叫做缓存行。
Sqlite 有一个全局页缓存管理结构体PCacheGlobal,用于管理页缓存。PCacheGlobal 在数据库启动时初始化。
每个链接(操作 db的句柄)有一个局部的页缓存管理结构PCache1,每一个PCache1都会申请一个Bulk(一些缓存行)。缓存行个数取决于Min(PCacheGlobal.nInitPage, PCache1.nMax)。PCache1有两种工作模式:
(1)每个PCache1独自管理自己的LRU链表,会有单独的PGroup。
(2)所有PCache1都从属于同一个PGroup,由这个PGroup统一管理LRU链表。
独立的PGroup意味着没有竞争资源,不需要加锁,同时也意味着,不能使用其他PCache1的未使用的缓存页。因此模式1性能更好,但更耗内存。
PIN页是短期内不能被换入换出的页。
当PCacheGlobal.nFreeSlot < PCacheGlobal.nReserve时,称PCache处于内存压力下。只有在某些条件下才能申请新页,
基础原理
内存布局
PCache1首先会申请一块大内存用于存放页缓存、PgHdr1和额外的内存(本模块不需要关注额外的内存,知道有这么个东西就行),布局如下:
Page | PgHdr1 | extra | Page | PgHdr1 | extra …
PCacheGlobal 使用PCacheGlobal->pFree 指向空闲页链表的首页。
内存布局:
PgFreeslot | reserve mem | PgFreeSlot | reserve mem | …
两者管理空闲页链表的方式不一样,是因为PCacheGlobal管理着多个PCache1,每个PCache1的页大小可能不一样
哈希查找机制
PCache1提供了哈希查找机制,可以快速通过页号找到对应的页缓存。
PCache1申请了长度为nHash的PgHdr1 指针数组apHash。使用index = pgNo % nHash 找到数组下标, apHash[index]就是所需要找的PgHdr1指针(不存在哈希冲突时)。
缓存页哈希表采用链式扩展的方式来解决哈希冲突,如果两个页号哈希后有冲突,使用PgHdr1->pNext来找到下一个冲突的缓存页描述符
哈希表长度的动态扩展:初始化时,nHash = 256, 当缓存页个数超过256时,就重新申请一块长度为nHash * 2 的内存,并将原哈希表的页重新哈希并插入到新的哈希表
缓存页分配与回收机制
缓存页的分配顺序:先从PCache1中分配,当PCache1的缓存页个数不足时,再从PCacheGlobal 中分配,PCacheGlobal 不足的话就malloc一个新页
缓存页的回收用PGroup机制来回收,所有PCache1的可回收页都在PGroup的LRU链表上,也就是说有可能回收到其他PCache1的页缓存。
结构体
PgHdr1
缓存页描述符
struct PgHdr1 {
sqlite3_pcache_page page; /* Base class. Must be first. pBuf & pExtra */
unsigned int iKey; // page 序号
u16 isBulkLocal; // 缓存页的归属,是上下文的还是管理结构的
u16 isAnchor; // 是不是LRU双向循环链表的带头结点
PgHdr1 *pNext; // 如果当前页是空闲页,是下一个空闲页,否则是哈希表上映射到同一个entry的链表下一跳
PCache1 *pCache; // 缓存页所属的上下文
PgHdr1 *pLruNext; // LRU 链表下一跳
PgHdr1 *pLruPrev; // LRU 链表上一跳
};
PGroup
缓存页管理组
struct PGroup {
sqlite3_mutex *mutex; /* MUTEX_STATIC_LRU or NULL */
unsigned int nMaxPage; // PGroup最多可拥有缓存页的个数,当PCache1的最大缓存页个数发生变化时,需要将LRU链表中多余的页释放掉。
unsigned int nMinPage; // 最少需要保留的缓存页个数
unsigned int mxPinned; /* nMaxpage + 10 - nMinPage */
unsigned int nPurgeable; // LRU链表缓存页的个数
PgHdr1 lru; // LRU 双向循环链表的带头节点
};
PCache1
局部的缓存页管理结构体
struct PCache1 {
PGroup *pGroup; /* PGroup this cache belongs to */
unsigned int *pnPurgeable; // 当前PCache1管理的页是可换入换出时为&pGroup->nPurgeable,否则为&PCache1->nPurgeableDummy
int szPage; // 数据页大小
int szExtra; // 管理单个缓存页需要额外的内存大小, sizeof(MemPage)+sizeof(PgHdr)
int szAlloc; // 申请一个缓存页需要的内存大小,szPage + szExtra + sizeof(PgHdr1)
int bPurgeable; // 当前PCache1是不是可换入换出
unsigned int nMin; // 需要保留的缓存页个数
unsigned int nMax; // PCache1最多可拥有的缓存页个数
unsigned int n90pct; // nMax * 0.9, pin页个数不能超过n90pct,否则失败
unsigned int iMaxKey; // 缓存页的最大页号
unsigned int nPurgeableDummy; // UNUSED
unsigned int nRecyclable; // LRU链表缓存页的个数
unsigned int nPage; // 当前缓存页的个数
unsigned int nHash; // hash slot个数
PgHdr1 **apHash; // hash表地址
PgHdr1 *pFree; // 空闲页链表
void *pBulk; // PCache1管理的大块内存,内存布局:Page | PageHdr1 | extra | Page | PageHdr1 | extra ...
};
PgFreeslot
缓存页管理结构的空闲页链表
struct PgFreeslot {
PgFreeslot *pNext; // 下一跳
};
PCacheGlobal
全局页缓存管理结构体
static SQLITE_WSD struct PCacheGlobal {
PGroup grp; /* The global PGroup for mode (2) */
int isInit; /* True if initialized */
int separateCache; //
int nInitPage; /* Initial bulk allocation size */
int szSlot; // 缓存行大小
int nSlot; // 缓存行个数
int nReserve; // 保留有空闲页的个数
void *pStart, *pEnd; // 全局缓存行的地址边界
sqlite3_mutex *mutex; // 保护下面三个字段的锁
PgFreeslot *pFree; // 全局空闲页链表的首页
int nFreeSlot; // 全局空闲页空闲slot的个数
int bUnderPressure; // 当nFreeSlot < nReserve 时 为 1,改变bUnderPressure需要加锁,读不需要加锁,因为读错并不会产生实际的损害
} pcache1_g;
PCacheGlobal 维护了一个空闲页链表,pFree字段就是页链表的首页,PgFreeslot 并没有申请单独的空间,而是复用PgHdr1的空间,当页归还到PCacheGlobal页链表时,将会对PgHdr1的空间重新初始化为PgFreeslot 并赋值。
接口
pcache1Init
int pcache1Init(void *NotUsed);
作用:初始化PCacheGlobal ,在Sqlite启动时初始化。
参数 | 含义 |
---|---|
NotUsed | 未使用参数 |
sqlite3PCacheBufferSetup
void sqlite3PCacheBufferSetup(void *pBuf, int sz, int n);
作用:设置PCacheGlobal的缓存页,在Sqlite启动时调用。
参数 | 含义 |
---|---|
pBuf | PCacheGlobal 缓存行地址 |
sz | 缓存行大小 |
n | 缓存行个数 |
pcache1Create
sqlite3_pcache *pcache1Create(int szPage, int szExtra, int bPurgeable)
作用:创建一个PCache1
参数 | 含义 |
---|---|
szPage | PCache1的页内容缓存大小 |
szExtra | 管理页缓存需要的额外内存大小(不包括PCache1需要使用的) |
bPurgeable | 是否支持可回收机制 |
pcache1Fetch
sqlite3_pcache_page *pcache1Fetch(sqlite3_pcache *p, unsigned int iKey, int createFlag);
作用:从pCache1中获取缓存页
参数 | 含义 |
---|---|
p | pCache1指针 |
iKey | 页号 |
createFlag | createFlag = 0时,不申请新页,PCache1有这个缓存页就返回,没有就返回NULL createFlag = 1,PCache1没有这个缓存页且缓存页充足时,申请一个 createFlag = 1,PCache1没有这个缓存页时,即使缓存页不足时,也要申请一个 |
pcache1Unpin
void pcache1Unpin(sqlite3_pcache *p, sqlite3_pcache_page *pPg, int reuseUnlikely)
作用:解除PIN页,页缓存加入到LRU链表
参数 | 含义 |
---|---|
p | pCache1指针 |
sqlite3_pcache_page | 页缓存句柄 |
reuseUnlikely | true 直接归还到空闲页链表 false 加入到LRU链表 |
pcache1Destroy
void pcache1Destroy(sqlite3_pcache *p);
作用:销毁PCache1
参数 | 含义 |
---|---|
p | pCache1指针 |