SQLite自身实现了一个动态内存分配的子系统,源代码在malloc.c里面,在这个系统里,包含一个可选的底层内存分配器,默认的实现在mem1.c里面,源码里还提供了其他多种实现,甚至应用程序自己也可以实现一个来替换掉默认的内存分配器。
对于SQLite的动态内存分配,官方的一篇详细的文档来介绍:
http://blog.csdn.net/zhoudaxia/article/details/8257784
这里主要再结合源码分析一下。
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的大小,那么使用通用接口来分配,这个在上一篇已经说过。
对于同一个数据库连接,如果一次内存分配失败,那么以后都直接返回失败,直到db->mallocFailed清0,或者释放本次连接。
3临时内存分配
SQLite偶尔需要一大块的“临时”内存来执行一些临时的计算。例如,当重新平衡一棵B-Tree时,需要使用临时内存。这些临时内存通常在10KB左右,用于一个单一的、短暂的函数调用。
如果内存较大,可以直接从栈中获取。对于小容量栈的处理器,如大部分嵌入式系统中,在栈中申请一个大的缓存区会出现问题,这就需要在堆中申请。
临时内存分配器的设置方法如下:
sqlite3_config(SQLITE_CONFIG_SCRATCH, pBuf, sz, N);
其中pBuf指向一段连续的内存,SQLite用它来进行临时内存分配。这段连续内存至少要有sz*N字节的大小,"sz"参数是每次临时内存分配的最大字节数,N是同时进行临时内存分配的最大次数。"sz"参数值应该为最大数据库页面的6倍左右,N应该为系统中运行线程数量的2倍左右。
使用时类似lookaside,首先初始化,把pBuf分割成N个sz大小的slot然后用链表连接起来,让链表的第一个slot作为mem0.pScratchFree。
int i, n, sz;
ScratchFreeslot *pSlot;
sz = ROUNDDOWN8(sqlite3GlobalConfig.szScratch);
sqlite3GlobalConfig.szScratch = sz;
pSlot = (ScratchFreeslot*)sqlite3GlobalConfig.pScratch;
n = sqlite3GlobalConfig.nScratch;
mem0.pScratchFree = pSlot;
mem0.nScratchFree = n;
for(i=0; i<n-1; i++){
pSlot->pNext = (ScratchFreeslot*)(sz+(char*)pSlot);
pSlot = pSlot->pNext;
}
pSlot->pNext = 0;
mem0.pScratchEnd = (void*)&pSlot[1];
分配时摘下链表的第1个slot即mem0.pScratchFree,然后让mem0.pScratchFree指向下slot。
p = mem0.pScratchFree;
mem0.pScratchFree = mem0.pScratchFree->pNext;
mem0.nScratchFree--;
释放的slot指向原来第一个slot,然后更新mem0.pScratchFree为当前释放的slot:
pSlot->pNext = mem0.pScratchFree;
mem0.pScratchFree = pSlot;
mem0.nScratchFree++;
4初始化
动态内存分配的子系统初始化主要再sqlite3MallocInit()里完成,做了以下几件事:
1)设置默认底层内存分配器
sqlite3MemSetDefault();
2)初始化临时内存分配器Scratch
3)初始化底层内存分配器
sqlite3GlobalConfig.m.xInit()