作者:empty 出版社:empty |
SQLite3源程序分析
作者:空转
本文分析的SQLite版本为3.6.18。现在已经变成3.6.20了,但本文中所涉及的内容变化不大。读者最好能下载一个源程序,然后将本文与源程序对照阅读。这样也有利于发现本文的错误,说实话吧,我写的时候是连分析带猜的,错误肯定很多。
参考文献:
1-The Definitive Guide to SQLite . Michael Owens:比较经典的SQLite著作。我边看边翻译了其中的部分内容,但翻得不好,大家还是看原文吧。
2-SQLite文件格式分析_v102 . 空转:我写的,写得特好。现在是v102版,跟前面的版本相比增加了不少背景知识,对文件格式的介绍算是很全面了。看本文之前,应该先浏览一下此参考文献。
1.SQLite3程序分析
1.1.主程序流程
所谓“主程序”是指SQLite所提供的命令行处理程序(CLP)。通过对它的分析可以对SQLite源程序建立整体概念,比一上来就直接分析单独API的处理过程要容易。CLP的主要程序都在shell.c中。
CLP的执行流程很简单:循环接受用户输入的SQL命令,处理SQL命令。命令的执行都是调用sqlite3_exec()函数完成,也就是采用的是“执行封装的Query”的形式[1]。
程序定义了一个功能比较强大的回叫函数来处理SQL命令执行的返回结果:
static int callback(void *pArg, int nArg, char **azArg, char **azCol);
程序定义了9种回显的形式,通过一个callback_data结构来对回显参数进行配置。
1.1.1.程序主函数
程序的main()函数在shell.c的尾部,简化后的main()函数的执行过程主要分为5步:
1.设置回显参数
2.取数据库文件名
3.打开数据库
4.循环处理SQL命令
5.关闭数据库
如下:
int main(int argc, char **argv){
struct callback_data data;//回显参数
int rc = 0;
Argv0 = argv[0];
main_init(&data);//设置默认的回显形式
//取数据库文件名,如没有,默认为内存数据库
data.zDbFilename = argv[1];
data.out = stdout;
/* 如果数据库文件存在,则打开它。
** 如果不存在,先不打开(现在什么都不做),
** 可以防止用户因错误的输入而创建空文件。
*/
if( access(data.zDbFilename, 0)==0 ){
open_db(&data);
}
printf(
SQLite version %s n
Enter .help for instructions n
Enter SQL statements terminated with a ; n ,
sqlite3_libversion()
);
rc = process_input(&data, 0);
if( db ){//关闭数据库
if( sqlite3_close(db)!=SQLITE_OK ){
fprintf(stderr, error closing database: %s n , sqlite3_errmsg(db));
}
}
return rc;
}
说明:上述函数与源程序相比做了很大的简化,去掉的部分不是不重要的,而是“可以不解释”的。实用程序的流程一般都是复杂的,SQLite也不例外。本文按照自己的主线进行介绍,只求能说明问题(自圆其说),主线之外的东西,不管重不重要,都尽量忽略。后面的函数也存在这样情况,就不再说明了。
回显参数的设置就不再介绍了,参考源程序的callback()函数和callback_data结构,有关回叫函数的使用见参考文献一。下面介绍数据库的打开过程。
1.1.2.打开数据库
数据库文件的打开过程在SQLite的权威文档中有介绍,过程如下图:
图1-1 数据库文件的打开过程
在CLP中打开数据库,比上图又多了两层,其调用层次如下:
1-main():
位于shell.c。
从命令行参数中得到数据库名,如果数据库文件存在,则打开它。
2-open_db():
位于shell.c。
功能:确认数据库是否已经打开。如果已打开,则什么都不做。如果没有,则打开它。如果打开失败,输出一个错误信息。
3-sqlite3_open():
位于main.c。
功能:打开一个数据库。
该函数中只包含对opendatabase()的调用,但调用的参数与sqlite3_open_v2()所使用的参数不同。
4-opendatabase():
位于main.c。
功能:这个函数为sqlite3_open()和sqlite3_open16()工作,打开一个数据库。数据库文件名 zFilename 采用UTF-8编码。
先生成各类标志什么的,然后生成默认的排序法。当需要生成数据库后台驱动时,调用sqlite3BtreeFactory()。
在此函数中真正分配sqlite结构的空间:db = sqlite3MallocZero( sizeof(sqlite3) )。
在调用sqlite3BtreeFactory()之前,需要对db的一些域进行设置。
5-sqlite3BtreeFactory()
位于main.c。
功能:本函数创建到数据库BTree驱动的连接。如果zFilename是文件名,则打开并使用它。
如果zFilename是 :memory: ,则使用内存数据库(在连接断开时释放)。
如果zFilename为空且数据库是虚拟(virtual)的,则只是暂时使用,在连接断开时被删除。
虚拟数据库可以是磁盘文件或就在内存中,由sqlite3TempInMemory()函数来决定是哪一种情况。
6-sqlite3BtreeOpen():
位于btree.c。
功能:打开一个数据库文件。
由于在sqlite3BtreeFactory()中已经调用过sqlite3TempInMemory()函数,所以此处逻辑稍简单了一些。
zFilename是数据库文件名。如果zFilename为空,创建一个具有随机文件名的数据库,这个数据库会在调用sqlite3BtreeClose()时被删除。
如果zFilename是 :memory: ,创建内存数据库,并在关闭时被释放。
如果此Btree是共享缓冲区的候选者,则尝试寻找一个已存在的BtShared来共享。(参本文后面关于内存数据结构的介绍)
如果不是共享缓冲区的候选者或未找到已存在的BtShared,则调用sqlite3PagerOpen()函数打开文件。
文件打开之后,调用sqlite3PagerReadFileheader()来读文件头中的配置信息。
7-sqlite3PagerOpen():
位于pager.c。
功能:分配并初始化一个新Pager对象,将其指针放到*ppPager。
该pager会在调用sqlite3PagerClose()时被释放。
zFilename参数是要打开的数据库文件的路径。
如果zFilename为空,创建随机文件名的文件。
如果zFilename为 :memory: ,所有信息都放到缓冲区中,不会被写入磁盘。这用来实现内存数据库。
如果pager对象已分配且指定文件打开成功,返回SQLITE_OK并将*ppPager指向新pager对象。
如果有错误发生,*ppPager置空并返回错误代码。
执行过程是:先申请空间,再调用sqlite3OsOpen()打开文件(如果需要),再根据打开的文件设置内存。
8-sqlite3OsOpen():
位于os.c。
功能:打开一个文件,与具体的操作系统无关。
是一种VFS封装。VFS的意思是 virtual file system ,虚拟文件系统。
本函数只有几条语句,只有一条关键语句:
rc = pVfs->xOpen(pVfs, zPath, pFile, flags & 0x7f1f, pFlagsOut);
对于Win32操作系统,该语句实际调用的是winOpen()函数。
9-winOpen():
位于os_win.c。
功能:打开一个Windows操作系统文件。
先将文件名转换为操作系统所使用的编码。再设置一系列参数。
最终调用操作系统函数CreateFileA()打开文件。
10-CreateFileA():
位于WINBASE.H。
功能:
打开文件名所指定的文件。如果文件不存在,则创建。
1.1.3.循环处理SQL命令
SQL命令的处理是由process_input()函数完成的。该函数还完成”.”命令的处理,这我们就不管了。简化后的process_input()函数如下:
static int process_input(struct callback_data *p, FILE *in){
while( 1 ){
zLine = one_input_line(zSql, in);
if( zLine && zLine[0]=='.' && nSql==0 ){
rc = do_meta_command(zLine, p);
continue;
}
rc = sqlite3_exec(p->db, zSql, callback, p, &zErrMsg);
if( rc || zErrMsg ){
处理出错信息;
}
}
return errCnt;
}
这么简化应该就不用解释了。
1.2.SQL命令编译与执行的过程
1.2.1.sqlite3_exec()函数
函数sqlite3_exec()位于文件legacy.c的尾部,其函数头为:
int sqlite3_exec(
sqlite3 *db, /* 一个打开的数据库连接 */
const char *zSql, /* 要执行的SQL语句 */
sqlite3_callback xCallback, /* 回叫函数 */
void *pArg, /* 传递给xCallback()的第一个参数 */
char **pzErrMsg /* 将错误信息写到*pzErrMsg中 */
)
sqlite3_exec()函数一次可以执行多条SQL命令。执行完成后返回一个SQLITE_ success/failure代码,还会将错误信息写到*pzErrMsg中。如果SQL是查询,查询结果中的每一行都会调用xCallback()函数。pArg为传递给xCallback()的第一个参数。如果xCallback==NULL,即使对查询命令也没有回叫调用。
sqlite3_exec()函数的实现体现了一个典型的、实用的SQL语句处理过程,我认为对应用程序的开发很有借鉴意义,所以就不过多简化了,去掉一些测试代码,增加一些注释,源程序基本如下:
int sqlite3_exec(
sqlite3 *db, /* 一个打开的数据库连接 */
const char *zSql, /* 要执行的SQL语句 */
sqlite3_callback xCallback, /* 回叫函数 */
void *pArg, /* 传递给xCallback()的第一个参数 */
char **pzErrMsg /* 将错误信息写到*pzErrMsg中 */
){
int rc = SQLITE_OK; /* 返回码 */
const char *zLeftover; /* 未处理的SQL串尾部。zSql中可能包含多个SQL
语句,一次处理一个,此变量为剩下的还未处理的
语句。 */
sqlite3_stmt *pStmt = 0; /* 当前SQL语句(对象) */
char **azCols = 0; /* 结果字段(s)的名称 */
int nRetry = 0; /* 重试的次数 */
int callbackIsInit; /* 如果初始化了回叫函数,为true */
if( zSql==0 ) zSql = ;
sqlite3Error(db, SQLITE_OK, 0);/* 清除db中的错误信息 */
while( (rc==SQLITE_OK || (rc==SQLITE_SCHEMA && (++nRetry) 2)) && zSql[0] ){
int nCol;
char **azVals = 0;
pStmt = 0;
rc = sqlite3_prepare(db, zSql, -1, &pStmt, &zLeftover);/* 编译一条语句 */
if( rc!=SQLITE_OK ){
continue;
}
if( !pStmt ){
/* 遇到注释时会执行此分支 */
zSql = zLeftover;
continue;
}
callbackIsInit = 0;
nCol = sqlite3_column_count(pStmt);/* 取字段数 */
while( 1 ){
int i;
rc = sqlite3_step(pStmt);/* 执行语句 */
/* 如果有回叫函数并且需要,则调用回叫函数 */
if( xCallback && (SQLITE_ROW==rc ||
(SQLITE_DONE==rc && !callbackIsInit
&& db->flags&SQLITE_NullCallback)) ){
/* 1-如果回叫函数未初始化,则初始化之 */
if( !callbackIsInit ){
/* 此分支只执行一次 */
azCols = sqlite3DbMallocZero(db, 2*nCol*sizeof(const char*) + 1);
if( azCols==0 ){
goto exec_out;
}
for(i=0; i nCol; i++){
/* 取各字段的名称 */
azCols[i] = (char *)sqlite3_column_name(pStmt, i);
}
callbackIsInit = 1;
}
/* 2-如果返回的是记录 */
if( rc==SQLITE_ROW ){
azVals = &azCols[nCol];
for(i=0; i nCol; i++){
/* 取各字段的值 */
azVals[i] = (char *)sqlite3_column_text(pStmt, i);
if( !azVals[i] && sqlite3_column_type(pStmt, i)!=SQLITE_NULL ){
db->mallocFailed = 1;
goto exec_out;
}
}
}
/* 3-调用回叫函数对返回的记录进行处理 */
if( xCallback(pArg, nCol, azVals, azCols) ){
rc = SQLITE_ABORT;
sqlite3VdbeFinalize((Vdbe *)pStmt);
pStmt = 0;
sqlite3Error(db, SQLITE_ABORT, 0);
goto exec_out;
}
}
/*
如果返回的不是记录,有两种情况:一种是到达结果记录集的结尾,
第二种是执行create table一类的不返回记录集的命令。
无论哪种情况,此处都需要“定案”。
*/
if( rc!=SQLITE_ROW ){
rc = sqlite3VdbeFinalize((Vdbe *)pStmt);
pStmt = 0;
if( rc!=SQLITE_SCHEMA ){
nRetry = 0;
zSql = zLeftover;
while( sqlite3Isspace(zSql[0]) ) zSql++;
}
break;
}
}
sqlite3DbFree(db, azCols);
azCols = 0;
}
exec_out:
if( pStmt ) sqlite3VdbeFinalize((Vdbe *)pStmt);
sqlite3DbFree(db, azCols);
rc = sqlite3ApiExit(db, rc);
/* 对出错信息进行处理 */
if( rc!=SQLITE_OK && ALWAYS(rc==sqlite3_errcode(db)) && pzErrMsg ){
int nErrMsg = 1 + sqlite3Strlen30(sqlite3_errmsg(db));
*pzErrMsg = sqlite3Malloc(nErrMsg);
if( *pzErrMsg ){
memcpy(*pzErrMsg, sqlite3_errmsg(db), nErrMsg);
}else{
rc = SQLITE_NOMEM;
sqlite3Error(db, SQLITE_NOMEM, 0);
}
}else if( pzErrMsg ){
*pzErrMsg = 0;
}
return rc;
}
1.2.2.SQL语句编译的调用层次
当调用sqlite3_prepare()函数时,编译一条SQL语句。编译过程的调用层次如下:
1- sqlite3_prepare()
在prepare.c中。
SQLite现在提供两个版本的编译API函数:遗留的和现在使用的。
在遗留版本中,原始SQL文本没有保存在编译后的语句(sqlite3_stmt结构)中,因此,如果schema发生改变,sqlite3_step()会返回SQLITE_SCHEMA。在新版本中,编译后的语句中保存原始SQL文本,当遇到schema改变时自动重新编译。
sqlite3_prepare()函数中其实只包含一条对sqlite3LockAndPrepare()的调用语句:
rc = sqlite3LockAndPrepare(db,zSql,nBytes,0,ppStmt,pzTail);
其中第4个参数为0,表示不将SQL文本复制到ppStmt中。
空注:源程序中紧跟此函数的sqlite3_prepare_v2()函数中在调用sqlite3LockAndPrepare()时第4个参数为1,不知与上述解释是否矛盾。
2- sqlite3LockAndPrepare()
在prepare.c中。结合注释,很简单,也很清晰。
static int sqlite3LockAndPrepare(
sqlite3 *db, /* 数据库句柄 */
const char *zSql, /* UTF-8编码的SQL语句 */
int nBytes, /* zSql的字节数 */
int saveSqlFlag, /* 如果为True,将SQL文本复制到sqlite3_stmt中。 */
sqlite3_stmt **ppStmt, /* OUT: 指向语句句柄 */
const char **pzTail /* OUT: 未处理的SQL串 */
){
int rc;
*ppStmt = 0;
if( !sqlite3SafetyCheckOk(db) ){/* 确定db指针的合法性。 */
return SQLITE_MISUSE;
}
/* 将UTF-8编码的SQL语句zSql编译成。 */
rc = sqlite3Prepare(db, zSql, nBytes, saveSqlFlag, ppStmt, pzTail);
if( rc==SQLITE_SCHEMA ){/* 如果遇到SCHEMA改变,定案,再编译 */
sqlite3_finalize(*ppStmt);
rc = sqlite3Prepare(db, zSql, nBytes, saveSqlFlag, ppStmt, pzTail);
}
return rc;
}
3- sqlite3Prepare()
在prepare.c中。
很长的函数,在其中调用sqlite3RunParser()函数,在给定的SQL字符串上执行分析器。
函数中,先创建Parse结构、加锁什么的,到调用sqlite3RunParser()函数时参数反而很简单了:
sqlite3RunParser(pParse, zSql, &zErrMsg);
此处zSql是一个完整的SQL语句串。
调用返回后还要做一系列处理,略。
4- sqlite3RunParser()
在tokenize.c中。
功能:在给定的SQL字符串上执行分析器。传入一个parser结构。返回一个SQLITE_状态码。如果有错误发生,将错误信息写入*pzErrMsg。
本函数内部是一个循环语句,每次循环处理一个词,根据词的类型做出不同的处理。如果是正经的词(不是空格什么的),都会调用sqlite3Parser()函数对其进行分析。
5- sqlite3Parser()
在parse.c中。
本函数为分析器主程序。
parse.c中的程序好象都是自动生成的,我反正是看不懂,也就不想看了。摘一段与兄弟们共享:
if( yypParser->yyidx 0 || yymajor==0 ){
yy_destructor(yypParser,(YYCODETYPE)yymajor,&yyminorunion);
yy_parse_failed(yypParser);
yymajor = YYNOCODE;
}else if( yymx!=YYERRORSYMBOL ){
YYMINORTYPE u2;
u2.YYERRSYMDT = 0;
yy_shift(yypParser,yyact,YYERRORSYMBOL,&u2);
}
1.2.3.查询的执行过程
前一小节介绍的编译调用层次看起来还是很清晰的,但实际执行时情况要复杂得多。
比如Oracle一类的数据库,以服务器的形式供客户端访问。在服务器启动的过程中可以完成所有必要的初始化工作,在解析SQL语句时逻辑可能反而简单一些。(Oracle好像也有一些东西在第一次调用时才加载,比如Java虚拟机什么的)。
SQLite这样的数据库主要是提供API供应用程序调用,这就要求在一次单独的调用中要完成所有相关工作。另外,SQLite好像更倾向于将工作留到不得不做时再做(即使不一定非得这样),所以在SQLite中经常会看到“如果还没创建,则创建”或“如果还没打开,则打开”一类的代码。这样,程序的旁枝就会比较多,有时读起来会有一定困难。比如,在SQLite启动后第1次执行select语句时,在编译该语句的过程中需要完成schema信息内存初始化的全部工作。
下面我们就跟踪一条最简单的select语句的执行过程,从中可以了解SQLite的运行机制。首先要准备数据库。
创建一个新的数据库,创建一个表:
create table d (
id integer primary key,
name text,
loca text );
向表中插入4条记录:
insert into d (name,loca) values ('accounting','Beijing');
insert into d (name,loca) values ('research','Nanjing');
insert into d (name,loca) values ('marketing','Xining');
insert into d (name,loca) values ('operation','Baoding');
执行下列命令:
.m col
.h on
.w 4 15 3 3 3 20 3
explain select * from d;
返回结果如下:
addr opcode p1 p2 p3 p4 p5 comment
---- --------------- --- --- --- -------------------- --- -------
0 Trace 0 0 0 00
1 Goto 0 11 0 00
2 OpenRead 0 2 0 3 00
3 Rewind 0 9 0 00
4 Rowid 0 1 0 00
5 Column 0 1 2 00
6 Column 0 2 3 00
7 ResultRow 1 3 0 00
8 Next 0 4 0 01
9 Close 0 0 0 00
10 Halt 0 0 0 00
11 Transaction 0 0 0 00
12 VerifyCookie 0 2 0 00
13 TableLock 0 2 0 d 00
14 Goto 0 2 0 00
上面显示了一条select语句经编译后所生成的VDBE程序。有关VDBE程序的介绍请参考《SQLite权威指南》[1]。其中相关介绍好像有些过时,主要是由于这部分程序变化比较快,但还是很有参考价值的,反正我看了那部分内容之后,上面的程序就能看懂个大概意思了。
下面,我们就在此数据库基础上跟踪查询语句select * from d的处理过程。主要是罗列在处理过程所执行过的函数。每次调用的相关说明并不多,有的只说明关键变量的值,有的简单说明执行过程。主要是调用的太多了,实在没法对每次调用都详细说明。读者最好按上面的方法创建示例数据库,然后边看边跟踪执行。
注意:函数前面的数字表示调用的层次,而不是序号。
1-sqlite3_exec:
zSql= select * from d;
2-sqlite3Prepare:
zSql= select * from d;
调用sqlite3RunParser。
3-sqlite3RunParser:
每处理一个单词,调用一次sqlite3Parser。
当语句处理完毕,语句串变为 ,最后一次调用sqlite3Parser。
在sqlite3Parser中,后部有一个do while循环。循环了好多遍,下面一句也执行了好多遍:
yy_reduce(yypParser,yyact-YYNSTATE);
yy_reduce中有一个大的switch语句,每次调用执行的分支不同。终于有一遍中调用了sqlite3Select。
sqlite3Select是select语句的处理主程序,在其中又经过如下调用层次(太多,这些层次就没编号了):
sqlite3Select(在select.c中)
↓
sqlite3SelectPrep(在select.c中)
↓
sqlite3SelectExpand(在select.c中)
↓
sqlite3WalkSelect(在walker.c中)
↓
selectExpander(在select.c中)
↓
sqlite3LocateTable(在build.c中)
↓
sqlite3ReadSchema(在prepare.c中)
↓
sqlite3Init(在prepare.c中)。
4-sqlite3Init:
功能:
初始化所有数据库文件——主数据库、临时数据库和所有附加的数据库。返回成功码。如果有错误发生,将错误信息写入*pzErrMsg。
执行:
进入第一个循环语句。在循环语句中调用sqlite3InitOne。
需要注意的是程序中有一小句很重要:
db->init.busy = 1;
当db->init.busy被设为1时,就不会再有VDBE代码生成或执行。后面就可以在回叫函数中通过执行系统表中的create语句的方式为对象创建内部数据结构而又不会实际地执行这些创建语句。
5-sqlite3InitOne:
(在prepare.c中)
功能:
读入一个单独数据库文件的schema,并初始化内部的数据结构。
执行:
调用回叫函数sqlite3InitCallback,执行系统表的创建语句,为系统表创建内部数据结构。
6-sqlite3InitCallback:
(在prepare.c中)
功能:
本函数是初始化数据库时的回叫程序。
执行:
调用sqlite3_exec。
7-sqlite3_exec:
zSql=CREATE TABLE sqlite_master(
type text,
name text,
tbl_name text,
rootpage integer,
sql text
)
8-sqlite3Prepare:
zSql=CREATE TABLE sqlite_master(
type text,
name text,
tbl_name text,
rootpage integer,
sql text
)
7-sqlite3_exec:
从sqlite3Prepare返回后,执行到sqlite3_step一句。
8-sqlite3_step:
sqlite3_step是顶层函数,它调用sqlite3Step完成主要工作。
9-sqlite3Step:
此函数中调用了sqlite3VdbeExec。
10-sqlite3VdbeExec:
p->zSql=
p->nOp=2
p->aOp:
021OP_Trace
140OP_Halt
可见,不执行实际的创建功能,直接返回。
9-sqlite3Step:
回到sqlite3Step后,发现sqlite3VdbeExec调用的返回结果为101。
rc又与另一个数“与”操作后,值为21,返回。
8-sqlite3_step:
rc值在其后的运算中变为101,返回。
7-sqlite3_exec:
rc!=SQLITE_ROW,不回显,退出循环。返回。
6-sqlite3InitCallback:
返回。
5-sqlite3InitOne:
继续执行,很多语句之后,遇到调用sqlite3_exec,查询系统表的内容。
6-sqlite3_exec:
zSql=SELECT name, rootpage, sql FROM 'main'.sqlite_master
7-sqlite3Prepare:
zSql=SELECT name, rootpage, sql FROM 'main'.sqlite_master
正常返回。
6-sqlite3_exec:
从sqlite3Prepare返回后,在sqlite3_step中经过几层(略)调用到sqlite3VdbeExec。
7-sqlite3VdbeExec:
p->zSql=
p->nOp=14
p->aOp[]的内容为:
021OP_Trace
196OP_Goto
213OP_OpenRead
3120OP_Rewind
43OP_Column
53OP_Column
63OP_Column
790OP_ResultRow
8104OP_Next
934OP_Close
1040OP_Halt
11101OP_Transaction
1297OP_TableLock
1396OP_Goto
可见,这次要真正地执行查询功能。
6-sqlite3_exec:
注:仍然在SELECT name, rootpage, sql FROM 'main'.sqlite_master的STEP循环中。
从sqlite3_step返回后,rc==SQLITE_ROW,继续处理返回的记录。
在“if( xCallback(pArg, nCol, azVals, azCols) ){”一句中调用sqlite3InitCallback,然后,又调用sqlite3_exec。
这里调用sqlite3_exec是要执行系统表中每个对象的创建语句,目的是为这些对象创建内存数据结构。
7-sqlite3_exec:
zSql=
create table d (
id integer primary key,
name text,
loca text )
8-sqlite3Prepare:
zSql=
create table d (
id integer primary key,
name text,
loca text )
7-sqlite3_exec:
从sqlite3Prepare返回后,在sqlite3_step中经过几层(略)调用sqlite3VdbeExec。
8-sqlite3VdbeExec:
p->zSql=
p->nOp=2
7-sqlite3_exec:
rc!=SQLITE_ROW,不回显,退出循环。返回。
6-sqlite3_exec:
注:仍然在SELECT name, rootpage, sql FROM 'main'.sqlite_master的STEP循环中。
循环处理下一条记录。
从sqlite3Prepare返回后,在sqlite3_step中经过几层(略)调用sqlite3VdbeExec。
7-sqlite3VdbeExec:
p->zSql=
p->nOp=14
p->aOp[]
6-sqlite3_exec:
继续,sqlite3_step的返回值为101
rc!=SQLITE_ROW,不回显,退出循环。返回。
注:此时sqlite_master表中只有一条记录,至此,已经处理完了。
5-sqlite3InitOne:
返回。
4-sqlite3Init:
退出第一个循环。
又调用sqlite3InitOne。
5-sqlite3InitOne:
调用sqlite3InitCallback,创建临时数据库的系统表。
6-sqlite3InitCallback:
调用sqlite3_exec。
7-sqlite3_exec:
zSql=CREATE TEMP TABLE sqlite_temp_master ...
8-sqlite3Prepare:
zSql=CREATE TEMP TABLE sqlite_temp_master ...
7-sqlite3_exec:
从sqlite3Prepare返回后,在sqlite3_step中经过几层(略)调用sqlite3VdbeExec。
8-sqlite3VdbeExec:
p->zSql=
p->nOp=2
7-sqlite3_exec:
rc!=SQLITE_ROW,不回显,退出循环。返回。
6-sqlite3InitCallback:
返回。
5-sqlite3InitOne:
继续执行,调用sqlite3_exec。
4-sqlite3Init:
从sqlite3InitOne返回。
3-sqlite3RunParser:
从sqlite3Init经多级返回。
2-sqlite3Prepare:
zSql= select * from d;
从sqlite3RunParser返回。
1-sqlite3_exec:
zSql= select * from d;
从sqlite3Prepare返回。至此,在第一次sqlite3Prepare调用中所做的一系列准备工作终于做完了,又回到了对 select * from d; 语句的处理过程中。
顺便说明一下,本次 select * from d; 语句的处理过程完成后,如果马上再次执行 select * from d; 语句,上述准备工作就不需要再做一次了。流程会简单很多。
从sqlite3Prepare返回后,在sqlite3_step中调用sqlite3VdbeExec。
2-sqlite3VdbeExec:
p->zSql= select * from d
p->nOp=15
p->aOp[]的内容为:
021OP_Trace
196OP_Goto
213OP_OpenRead
3120OP_Rewind
43OP_Column
53OP_Column
63OP_Column
790OP_ResultRow
8104OP_Next
934OP_Close
1040OP_Halt
11101OP_Transaction
1299OP_VerifyCookie
1397OP_TableLock
1496OP_Goto
可见,这次要真正地执行查询功能。
1-sqlite3_exec:
从sqlite3VdbeExec返回,rc==100==SQLITE_ROW。
在“if( xCallback(pArg, nCol, azVals, azCols) ){”一句中显示一条记录。
2-sqlite3VdbeExec:
p->zSql= select * from d
p->nOp=15
1-sqlite3_exec:
从sqlite3VdbeExec返回,rc==100==SQLITE_ROW。
显示第2条记录。
2-sqlite3VdbeExec:
p->zSql= select * from d
p->nOp=15
1-sqlite3_exec:
从sqlite3VdbeExec返回,rc==100==SQLITE_ROW。
显示第3条记录。
2-sqlite3VdbeExec:
p->zSql= select * from d
p->nOp=15
1-sqlite3_exec:
从sqlite3VdbeExec返回,rc==100==SQLITE_ROW。
显示第4条记录。
2-sqlite3VdbeExec:
p->zSql= select * from d
p->nOp=15
1-sqlite3_exec:
从sqlite3VdbeExec返回,rc!=SQLITE_ROW。定案并退出循环。
处理过程结束,累死了。
2.SQLite3的内存数据结构
2.1.SQLite体系结构介绍
我一向认为,水平越高的程序员编的程序越简单,越容易看懂,就好比水平越高的领导越能将问题简单化,而不是先把单位搞得鸡犬不宁然后自己也累得吐血。(这篇东西写得这么晦涩,作者水平也就可见一斑了)
SQLite的原作者们水平都很高,程序技巧性很强,但我看源程序时还是经常遇到困难,除了我真笨外,还有以下原因:
1-SQLite是实用程序,不是教学程序。一般实用程序在其功能主线之外都会附加一些代码,以保证程序的健壮性或完成测试功能等,SQLite在这方面尤其过分。
2-缺少SQLite实现的背景知识,缺少数据库实现的背景知识,总之,我没文化。
3-缺少对SQLite体系结构的深入了解。有这方面的介绍,都很笼统,本章就是对这类介绍的一个补充。
其实,我费了这么大劲读程序,也就是搞清了本章的内容,搞清本章的内容之后再读程序,就变得比较简单了。“数据结构”很重要。
先来看下面代码,相信兄弟们都看得懂。该代码例示了在SQLite上执行一个查询的一般过程。
#include stdio.h>
#include stdlib.h>
#include sqlite3.h
#include string.h>
#pragma comment(lib, sqlite3.lib )
int main(int argc,char **argv)
{
int rc,i,ncols;
sqlite3 *db;
sqlite3_stmt *stmt;
char *sql;
const char*tail;
//打开数据
rc=sqlite3_open( foods.db ,&db);
if(rc){
fprintf(stderr, Can'topendatabase:%sn ,sqlite3_errmsg(db));
sqlite3_close(db);
exit(1);
}
sql= select * from episodes ;
//预处理
rc=sqlite3_prepare(db,sql,(int)strlen(sql),&stmt,&tail);
if(rc!=SQLITE_OK){
fprintf(stderr, SQLerror:%sn ,sqlite3_errmsg(db));
}
rc=sqlite3_step(stmt);
ncols=sqlite3_column_count(stmt);
while(rc==SQLITE_ROW){
for(i=0;i ncols;i++){
fprintf(stderr, '%s' ,sqlite3_column_text(stmt,i));
}
fprintf(stderr, n );
rc=sqlite3_step(stmt);
}
//释放statement
sqlite3_finalize(stmt);
//关闭数据库
sqlite3_close(db);
printf( n );
return(0);
}
程序就不解释了。在上述程序中,使用了两个结构的指针,分别是sqlite3和sqlite3_stmt。
SQLite中有数不清的结构,而这两个结构正是SQLite数据处理的两大主线。学《数据结构》时讲究先研究存储结构再研究算法,对SQLite来说,搞清这两个结构之后,再读程序就会变得很容易了。
下图是SQLite官方网站对SQLite体系结构的描述,详细说明见参考文献1。
图2-1 SQLite体系结构
在SQLite的实现中,无论是代码的设计还是数据结构的设计都是按上述体系结构完成的,层次非常清晰。SQLite中有数不清的结构,我们只按照两大主线介绍其中的不到20种,如下图所示。图中的箭头代表包含关系,可以看出,这些结构也是按照上述体系结构组织的。
图2-2 SQLite的内存数据结构
关于“结构”与“对象”:
C中只有结构,没有对象,但现在谁又能在编程时完全不受面向对象概念的影响呢?SQLite的好多结构中都有函数指针,注册后我看跟对象的成员函数差不多吧。本文中有时把结构变量称为“结构的一个实例”或“对象”,不影响理解。
2.2.sqlite3主线
2.2.1.sqlite3结构
该结构有70多个域,我们只关心下面几个域。
struct sqlite3 {
int nDb; /* 当前后台(数据库)数量,初始后为2,一个main,一个临时 */
Db *aDb; /* 所有的后台,初始后为db->aDb[0]和db->aDb[1] */
…
struct Vdbe *pVdbe; /* 活动的虚拟机列表 */
int activeVdbeCnt; /* 活动的虚拟机数量 */
int writeVdbeCnt; /* 活动的正在写数据库的虚拟机数量 */
…
};
在本主线中只讨论前两个域,后面三个域将在sqlite3_stmt主线一节讨论。
每个sqlite3结构在应用程序中代表一个“数据库连接”。每个数据库连接中可以连接多个数据库(文件),初始时有两个。编号为0的为主数据库,名称为main。编号为1的为临时数据库。临时数据库可能是内存数据库,也可能是临时文件,在本文1.1.2节有介绍。临时数据库在连接关闭时被删除。从编号2开始为附加的数据库。
在结构中将aDb定义为Db结构的指针,实际使用时是Db结构指针的数组,使用下标存取。nDb为aDb中元素的数量。
再次说明:上述结构与源程序相比做了很大的简化,去掉的部分不是不重要的,而是“可以不解释”的。后面的结构也存在这样情况,就不再说明了。
2.2.2.Db结构
/*
数据库文件
为每个打开的数据库文件建立一个此结构的实例。
在sqlite.aDb[]数组中通常包含两个本结构的实例。
aDb[0]是主数据库文件。aDb[1]是存放临时表的数据库文件。
另外,还可能附加其他数据库。
*/
struct Db {
char *zName; /* 此数据库名:main or temp */
Btree *pBt; /* 此数据库文件的B*Tree结构 */
Schema *pSchema; /* Pointer to database schema (possibly shared) */
};
关于上述两个结构的命名,我想猜测一下:SQLite以前肯定是不支持在一个连接中打开多个数据库文件的,因此上述两个结构原来肯定是一回事,所以直到现在编写应用程序时还是用:
sqlite3 *db;
按现在的定义,是否用下面语句更合适呢?
sqlite3 *conn;
历史问题,不深究了,反正也没多少人附加数据库文件吧。
Db结构的域倒是很少,关键就是每个Db结构中包含一个Btree结构(结构指针,下面在不影响理解的情况下,就不再对结构和其指针加以区分了)的域pBt。也就是说,应用程序为连接中的每个数据库文件建立一个Btree对象。
2.2.3.Schema结构
/*
本结构的实例用于存放数据库schema。
*/
struct Schema {
int schema_cookie; /* Schema版本:每次schema改变时,此值+1。 */
Hash tblHash; /* 所有表名,按名称排序 */
Hash idxHash; /* 所有索引名,按名称排序 */
Hash trigHash; /* 所有触发器名,按名称排序 */
Table *pSeqTab; /* AUTOINCREMENT所使用的sqlite_sequence表 */
u8 file_format; /* 文件格式版本 */
u8 enc; /* 此数据库使用的编码类型 */
u16 flags; /* 与此schema相关的标志 */
int cache_size; /* 缓冲区中的页数 */
};
应用程序还为连接中的每个数据库文件建立一个Schema对象。从上述注释中就可以看出该结构的作用,不再详细说明了。沿此结构研究下去又是一条主线,本文从略。
2.2.4.Btree结构
在进一步说明之前请先注意以下事实:
对数据库文件的读写都是通过Btree来完成的。
可能同时有多个连接访问相同的数据库文件。
数据库并发控制的责任不可能交给OS,也不可能交给更高的一级,只能在Btree一级完成。
因此,Btree中必然有多个连接所共享的部分。
所以,SQLite在设计数据结构时将Btree分为两个层次:
Btree结构,为每个连接所独有,也就是说一个数据库文件在每个连接中都有一个Btree结构的实例。
BtShared结构,为所有连接所共享,也就是说无论有多少个连接同时访问一个数据库,该数据库只有一个BtShared的实例。
图2-3 Btree结构与BtShared结构的关系
/*
在数据库连接中,为每一个打开的数据库文件保持一个指向本对象实例的指针。
这个结构对数据库连接是透明的。数据库连接不能看到此结构的内部,只能通过指针来操作此结构。
有些数据库文件,其缓冲区可能被多个连接所共享。在这种情况下,每个连接都单独保持到此对象的指针。
但此对象的每个实例指向相同的BtShared对象。数据库缓冲区和schema都包含在BtShared对象中。
*/
struct Btree {
sqlite3 *db; /* 拥有此btree的数据库连接 */
BtShared *pBt; /* 此btree的可共享内容 */
Btree *pNext; /* List of other sharable Btrees from the same db */
Btree *pPrev; /* Back pointer of the same list */
};
关于db域:指向拥有此btree的数据库连接。这是一个由子孙结构指向祖先结构的指针。SQLite的各类结构中有大量此类指针,可以实现子孙结构与祖先结构之间方便的相互引用。下面所介绍的结构中此类域就忽略不再介绍了。
2.2.5.BtShared结构
/*
此对象的一个实例描述一个单独的数据库文件。
一个数据库文件同时可以被多个数据库连接使用。
当多个数据库连接共享相同的数据库文件时,每个连接有它自己的文件Btree,这些Btree指向同一个本BtShared对象。
BtShared.nRef是共享此数据库文件的连接的个数。
*/
struct BtShared {
Pager *pPager; /* 页缓冲区 */
BtCursor *pCursor; /* 包含当前打开的所有游标的链表 */
MemPage *pPage1; /* 数据库的page 1 */
u16 pageSize; /* 每页的字节数 */
u16 usableSize; /* 每页可用的字节数。pageSize-每页尾部保留空间的大小,
在文件头偏移为20处设定。 */
void *pSchema; /* 指向由sqlite3BtreeSchema()所申请空间的指针 */
void (*xFreeSchema)(void*); /* BtShared.pSchema的析构函数 */
int nRef; /* 共享此数据库文件的连接的个数 */
};
BtShared主要是管理页缓冲区,包括一个Pager结构和多个描述数据库文件全局参数的域。
关于BtCursor结构和MemPage结构的介绍见2.4节。
2.2.6.Pager结构
/*
一个打开的页缓冲区是本结构的一个实例。
*/
struct Pager {
sqlite3_file *fd; /* 数据库文件描述符 */
sqlite3_file *jfd; /* 主日志文件描述符 */
sqlite3_file *sjfd; /* 子日志文件描述符 */
char *zFilename; /* 数据库文件名 */
char *zJournal; /* 日志文件名 */
PCache *pPCache; /* 页缓冲对象的指针。 */
};
包含相关文件的指针和一个页缓冲区。
关于SQLite所使用的各种临时文件的介绍见参考文献二。
2.2.7.PCache结构
在SQLite权威文档(原文:http://www.sqlite.org/custombuild.html)中有如下描述:
绝大多数应用程序与使用默认编译选项编译的SQLite配合工作得很好。
尽管如此,还是有些特殊的应用程序可能想从SQLite中去掉一些内置的系统接口,代替以更适合应用程序的实现。SQLite很容易在编译时重新配置,以适应特殊项目的需求。SQLite在编译时可以:
用一个不同的实现来代替内置的互斥子系统。
在单线程应用程序中完全去掉所有的互斥机制。
重新配置内存分配子系统,使用一个非malloc()的内存分配器。
重新调整内存分配子系统,使它根本就不再调用malloc()。所有的内存请求都在SQLite启动时分配的固定大小的内存缓冲区中获得。
用另一个设计来替换文件系统接口。换句话说,用一组完全不同的系统调用来替换SQLite现有的操作磁盘的系统调用。
覆盖其它的操作系统接口。
一般来说,在SQLite中有3个子系统可以被修改或覆盖。互斥子系统用来在多线程间将对SQLite资源的存取序列化。内存分配子系统。虚拟文件系统(Virtual File System)子系统在SQLite和底层操作系统(特别是文件系统)间提供一个统一的(portable)接口。我们称这3个子系统为SQLite的接口子系统。
关于上面的描述我有两点说明:
咱不是“绝大多数开发者”,咱是要分析SQLite源程序,所以要了解SQLite的详细实现。
关于操作系统接口的说明在“打开数据库”一节有涉及,互斥子系统本文不涉及,本节涉及内存分配子系统的内容。
SQLite将内存分配子系统实现为两层:
PCache结构为内存分配子系统的接口结构,与具体的实现无关。
PCache1为内存分配子系统的默认实现。
/*
** 一个完整的页缓冲区是本结构的一个实例。
*/
struct PCache {
PgHdr *pDirty, *pDirtyTail; /* 按LRU次序排列的脏页链表 */
PgHdr *pSynced; /* 脏页链表中最近同步过的页 */
int nRef; /* 引用的页数 */
int nMax; /* 配置的缓冲区大小 */
int szPage; /* 此缓冲区中每页的大小 */
int szExtra; /* 每页扩展空间的大小 */
sqlite3_pcache *pCache; /* Pluggable cache module */
PgHdr *pPage1; /* 指向page 1 */
};
SQLite允许加载用户自定义的缓冲区模块,默认的缓冲区模块是pcache1.c。
在pcache1.c中,sqlite3_pcache *被映射为指向PCache1结构的指针。
2.2.8.PgHdr结构
/*
缓冲区中的每个页由此结构的一个实例来控制。
此结构在pcache.c中定义。
*/
struct PgHdr {
void *pData; /* 此页的内容 */
void *pExtra; /* 扩充内容 */
Pgno pgno; /* 本页的页号 */
/**********************************************************************
** 上面的域是公共的。下面的域是pcache.c私有的,其它模块不能存取。
*/
i16 nRef; /* 此页的用户数 */
PCache *pCache; /* 拥有此页的缓冲区 */
PgHdr *pDirtyNext; /* 脏页链表中的Next指针 */
PgHdr *pDirtyPrev; /* 脏页链表中的Previous指针 */
};
2.2.9.PCache1结构
/* 指向此结构的指针作为不透明的sqlite3_pcache*句柄返回。
*/
struct PCache1 {
/* 缓冲区配置参数。页大小(szPage)和可净化标志(bPurgeable)在本结构创建时设置。
nMax有可能在任何时候被pcache1CacheSize()方法的调用所修改。
*/
int szPage; /* 分配页的大小(字节数) */
int bPurgeable; /* 如果缓冲区可净化,为True */
unsigned int nMin; /* Minimum number of pages reserved */
unsigned int nMax; /*配置的缓冲区大小*/
/* 所有页的Hash表。
*/
unsigned int nRecyclable; /* LRU链表中总的页数 */
unsigned int nPage; /* HASH表apHash中总的页数 */
unsigned int nHash; /* apHash[]的槽位数 */
PgHdr1 **apHash; /* Hash表,用于按照键值快速查找 */
unsigned int iMaxKey; /* 从上一次xTruncate()之后的最大键值 */
};
本结构代表一个缓冲区,这个缓冲区中有多个页,这些页存储在Hash表apHash中。从定义来看,apHash是PgHdr1结构指针的指针,实际编程时,apHash是PgHdr1结构的指针数组。可见,apHash是一个采用链接法解决地址冲突的Hash表。apHash有nHash个槽位,每个槽位上是一个链表。
Hash函数很简单:PgHdr1->iKey%nHash。
注:关于SQLite内存分配系统的设计可参考SQLite的官方文档(原文:http://www.sqlite.org/malloc.html)。本文的附录一中有我翻译的该文档的部分内容。
2.2.10.PgHdr1结构
/*
每个缓冲区入口由此结构的一个实例来表示。
一个PgHdr1.pCache->szPage字节大小的缓冲区在内存中创建此结构之前被分配。(参后面的PGHDR1_TO_PAGE()宏)
*/
struct PgHdr1 {
unsigned int iKey; /* Key值(页号) */
PgHdr1 *pNext; /* hash表链中的下一个元素 */
PgHdr1 *pLruNext; /* 如果是未钉住的页,指向LRU链表中的后一个 */
PgHdr1 *pLruPrev; /* 如果是未钉住的页,指向LRU链表中的前一个 */
};
我理解所谓“缓冲区入口”就是内存中对一个数据库页的存取入口。
上面所列出的几个域中,iKey的含义是好理解的,其它几个域和注释中所提到宏的含义将在2.3.4节中结合其它内容一起介绍。
2.3.内在分配子系统的实现方法
无法全面分析,只介绍我注意到的几个问题吧。
上节仅按照结构包含的主线介绍了多个结构的定义,虽然对每个结构都做了相当的简化,但要一下子看明白还是很困难的。看了下面这几个小节之后,有些问题可能就明白了。
2.3.1.数据库页的原始数据到底在哪儿
SQLite数据库文件由固定大小的“页(page)”组成。页的默认大小为1024个字节(1KB)。页是数据库读写和在内存中进行管理的基本单位。(有关SQLite数据库文件格式的内容见参考文献二)
数据从文件读到内存以后,总得有个地方存吧,但无论从PgHdr1结构还是从PgHdr结构中,都找不到这样的指向页缓存的指针。
实际的实现是这样的:当为PgHdr1结构变量分配内存时,不是按照PgHdr1结构的大小来分配,而是分配一块更大的内存,包括4个部分,如下图:
图2-4 为PgHdr1结构分配的内存
在PCache1中,PCache1.szPage指的是前3部分空间的大小之和。
从源程序中对下面两个宏的定义及注释中可以看出页内存分配与使用的方法。
/*
当PgHdr1结构被分配时,PCache1.szPage字节大小的数据空间也会同时分配,即分配的总空间为sizeof(PgHdr1)+PCache1.szPage个字节。
(空注:从下面宏定义可以看出,在总空间中,页数据在前,PgHdr1结构在后。)
宏PGHDR1_TO_PAGE()将指向PgHdr1结构的指针转换为指向相关的页数据。
宏PAGE_TO_PGHDR1()的作用正好相反。
*/
#define PGHDR1_TO_PAGE(p) (void*)(((char*)p) - p->pCache->szPage)
#define PAGE_TO_PGHDR1(c, p) (PgHdr1*)(((char*)p) + c->szPage)
2.3.2.PgHdr1结构与PgHdr结构的关系
这两个结构其实是一回事,都代表一个数据库页,都能操作数据库中一页的数据。但它们在SQLite的不同层次上实现,互相之间是不透明的。它们在内存中的状态见图2-4,它们在SQLite中的不同层次见图2-2。
从下面3个函数(的部分程序中)可以看出这两个结构之间的关系。
/*
函数1:
尝试从缓冲区中获得一个页。
本函数在pcache.c中。
*/
int sqlite3PcacheFetch(
PCache *pCache, /* Obtain the page from this cache */
Pgno pgno, /* Page number to obtain */
int createFlag, /* 如果为true,当一个页不存在时则创建它 */
PgHdr **ppPage /* Write the page here */
){
PgHdr *pPage = 0;
int eCreate;
/* 如果可填充缓冲区(sqlite3_pcache*)还没有分配,则现在分配。 */
if( !pCache->pCache && createFlag ){
sqlite3_pcache *p;
int nByte;
/* 经过映射,xCreate方法调用的是pcache1.c中的pcache1Create()函数。
分析下面两句,结合被调函数,可以看出:
struct PCache和struct PCache1中的szPage值是不同的,这一点需要注意。
*/
nByte = pCache->szPage + pCache->szExtra + sizeof(PgHdr);
p = sqlite3GlobalConfig.pcache.xCreate(nByte, pCache->bPurgeable);
if( !p ){
return SQLITE_NOMEM;
}
sqlite3GlobalConfig.pcache.xCachesize(p, pCache->nMax);
pCache->pCache = p;
}
eCreate = createFlag * (1 + (!pCache->bPurgeable || !pCache->pDirty));
if( pCache->pCache ){
/* 从缓冲区取第pgno页 */
经过映射,xFetch方法调用的是pcache1.c中的pcache1Fetch()函数。
pPage = sqlite3GlobalConfig.pcache.xFetch(pCache->pCache, pgno, eCreate);
}
if( pPage ){
if( !pPage->pData ){
memset(pPage, 0, sizeof(PgHdr) + pCache->szExtra);
pPage->pExtra = (void*)&pPage[1];
/* 注意此处要求返回的是一个PgHdr结构指针 */
pPage->pData = (void *)&((char *)pPage)[sizeof(PgHdr) + pCache->szExtra];
pPage->pCache = pCache;
pPage->pgno = pgno;
}
}
*ppPage = pPage;
return (pPage==0 && eCreate) ? SQLITE_NOMEM : SQLITE_OK;
}
/*
函数2:
实现sqlite3_pcache.xCreate方法。
分配一个cache。
本函数在pcache1.c中。
*/
static sqlite3_pcache *pcache1Create(int szPage, int bPurgeable){
PCache1 *pCache;
pCache = (PCache1 *)sqlite3_malloc(sizeof(PCache1));
if( pCache ){
memset(pCache, 0, sizeof(PCache1));
/*
可以看出,struct PCache和struct PCache1中的szPage值是不同的。
*/
pCache->szPage = szPage;
pCache->bPurgeable = (bPurgeable ? 1 : 0);
if( bPurgeable ){
pCache->nMin = 10;
pcache1EnterMutex();
pcache1.nMinPage += pCache->nMin;
pcache1LeaveMutex();
}
}
return (sqlite3_pcache *)pCache;
}
/*
函数3:
实现sqlite3_pcache.xFetch方法。
按键值取一个页。
本函数在pcache1.c中。
*/
static void *pcache1Fetch(sqlite3_pcache *p, unsigned int iKey, int createFlag){
PCache1 *pCache = (PCache1 *)p;
PgHdr1 *pPage = 0;
/* 注意此处返回的是一个PgHdr1结构指针,但经过了换算。 */
return (pPage ? PGHDR1_TO_PAGE(pPage) : 0);
}
上述3个函数分别处于两个不同的层次,其中函数1处于Pager层,函数2、3处于OS Interface层。
2.3.3.sqlite3GlobalConfig全局变量
在函数1中使用了一个全局变量sqlite3GlobalConfig。在sqliteInt.h中有如下宏定义:
#define sqlite3GlobalConfig sqlite3Config
在global.c中为该全局变量赋初值,可见该变量是一个结构变量:
/*
** 这个单元包含了SQLite的全局配置信息。
*/
SQLITE_WSD struct Sqlite3Config sqlite3Config = {
SQLITE_DEFAULT_MEMSTATUS, /* bMemstat */
0, /* bCoreMutex */
…
};
结构struct Sqlite3Config的定义在sqliteInt.h中,有好多个域,此处我们只关心一个:
/*
保存SQLite的全局配置信息。
也包含一些状态信息。
*/
struct Sqlite3Config {
…
sqlite3_pcache_methods pcache; /* 底层页缓冲接口 */
…
};
用户可在该域中注册自定义的内存分配函数,默认的注册程序在pcache1.c中,如下:
/*
本函数在初始化期间(sqlite3_initialize())调用,注册默认的页缓冲模块。
*/
void sqlite3PCacheSetDefault(void){
static sqlite3_pcache_methods defaultMethods = {
0, /* pArg */
…
pcache1Create, /* xCreate */
pcache1Fetch, /* xFetch */
};
sqlite3_config(SQLITE_CONFIG_PCACHE, &defaultMethods);
}
2.3.4.pcache1_g全局结构变量
在函数2、3中使用了全局变量pcache1,该变量在OS_Interface层起作用。该变量的定义如下:
/*
本内存分配子系统所使用的全局数据。
*/
static SQLITE_WSD struct PCacheGlobal {
sqlite3_mutex *mutex; /* static mutex MUTEX_STATIC_LRU */
int nMaxPage; /* Sum of nMaxPage for purgeable caches */
int nMinPage; /* Sum of nMinPage for purgeable caches */
int nCurrentPage; /* Number of purgeable pages allocated */
PgHdr1 *pLruHead, *pLruTail; /* LRU双向链表的表头和表尾指针 */
/* 与SQLITE_CONFIG_PAGECACHE选项有关的设置。 */
int szSlot; /* 空闲单元的大小 */
void *pStart, *pEnd; /* 整个页缓冲区的边界 */
PgFreeslot *pFree; /* 空闲单元链表 */
int isInit; /* 如果已经初始化则为True */