2021SC@SDUSC SQLite源码分析(五)————VDBE初步分析
2021SC@SDUSC2021SC@SDUSC SQLite源码分析(四)————sqlite的虚拟机前言一、初步分析二、 VdbeCursor 结构三、VDBE字节码编程语言前言代码生成器生成的代码由虚拟机来执行。关于虚拟机更详细的信息可参考http://sqlite.org/opcode.html 。虚拟机实现一个专为操作数据库文件而设计的抽象计算引擎。它有一个存储中间数据的存储栈,每条指令
2021SC@SDUSC
2021SC@SDUSC SQLite源码分析(四)————sqlite的虚拟机
前言
代码生成器生成的代码由虚拟机来执行。关于虚拟机更详细的信息可参考http://sqlite.org/opcode.html 。虚拟机实现一个专为操作数据库文件而设计的抽象计算引擎。它有一个存储中间数据的存储栈,每条指令包含一个操作码和不超过三个额外的操作数。
一、初步分析
虚拟机本身被包含在一个单独的文件vdbe.c中,它也有自己的头文件,其中vdbe.h定义虚拟机与SQLite库其他部分之间的接口,vdbeInt.h定义虚拟机私有的数据结构。文件vdbeaux.c包含被虚拟机使用的一些工具,和被库的其他部分用来构建VM程序的一些接口模块。文件vdbeapi.c包含虚拟机的外部接口。单独的值(字符串、整数、浮点数、BLOB对象)被存储在一个叫Mem的内部对象中,在vdbemem.c中可找到它的实现。
SQLite使用回调风格的C语言程序来实现SQL函数,每个内建的SQL函数都用这种方式来实现。大多数内建的SQL函数(例如coalesce(), count(), substr(), 等等)可在func.c中找到。日期和时间转换函数可在date.c中找到。
VDBE结构的定义如下:
/*
虚拟机的一个实例。
此结构包含了虚拟机的全部状态。
位于 vdbeInt.h
*/
struct Vdbe {
sqlite3 *db; /* The whole database */
Vdbe *pPrev,*pNext; /* 数据库连接中虚拟机链表的指针域 */
FILE *trace; /* Write an execution trace here, if not NULL */
int nOp; /* Number of instructions in the program(指令的条数) */
int nOpAlloc; /* Number of slots allocated for aOp[]*/
Op *aOp; /* 存放虚拟机程序的空间*/
int nLabel; /* Number of labels used */
int nLabelAlloc; /* Number of slots allocated in aLabel[] */
int *aLabel; /* Space to hold the labels */
Mem *aStack; /* The operand stack, except string values(栈空间) */
Mem *pTos; /* Top entry in the operand stack(栈顶指针) */
Mem **apArg; /* Arguments to currently executing user function */
Mem *aColName; /* 返回的各字段的名称 */
int nCursor; /* Number of slots in apCsr[] */
Cursor **apCsr; /* 此数组中的每个元素为一个打开的游标 */
int nVar; /* Number of entries in aVar[] */
Mem *aVar; /* Values for the OP_Variable opcode*/
char **azVar; /* Name of variables */
int okVar; /* True if azVar[] has been initialized */
int magic; /* Magic number for sanity checking */
int nMem; /* Number of memory locations currently allocated */
Mem *aMem; /* The memory locations(保存临时变量的Mem)*/
int nCallback; /* Number of callbacks invoked so far(回调的次数) */
int cacheCtr; /* Cursor row cache generation counter */
Fifo sFifo; /* A list of ROWIDs */
int contextStackTop; /* Index of top element in the context stack */
int contextStackDepth; /* The size of the "context" stack */
Context *contextStack; /* Stack used by opcodes ContextPush & ContextPop*/
int pc; /* The program counter(初始程序计数器) */
int rc; /* Value to return(返回结果) */
unsigned uniqueCnt; /* Used by OP_MakeRecord when P2!=0 */
int errorAction; /* Recovery action to do in case of an error */
int inTempTrans; /* True if temp database is transactioned */
int returnStack[100]; /* Return address stack for OP_Gosub & OP_Return */
int returnDepth; /* Next unused element in returnStack[] */
int nResColumn; /* Number of columns in one row of the result set */
char **azResColumn; /* Values for one row of result */
int popStack; /* Pop the stack this much on entry to VdbeExec()(出栈的项数) */
char *zErrMsg; /* Error message written here */
u8 resOnStack; /* True if there are result values on the stack(有结果在栈上则为真)*/
u8 explain; /* True if EXPLAIN present on SQL command */
u8 changeCntOn; /* True to update the change-counter */
u8 aborted; /* True if ROLLBACK in another VM causes an abort */
u8 expired; /* True if the VM needs to be recompiled */
u8 minWriteFileFormat; /* Minimum file format for writable database files */
int nChange; /* Number of db changes made since last reset */
i64 startTime; /* Time when query started - used for profiling */
#ifdef SQLITE_SSE
int fetchId; /* Statement number used by sqlite3_fetch_statement */
int lru; /* Counter used for LRU cache replacement */
#endif
};
在SQLite 中,用户发出的SQL语句,都会由编译器生成一个虚拟机实例。变量sql代表的SQL语句经过sqlite3_prepare()处理后,便生成一个虚拟机实例——stmt。虚拟机实例从外部看到的结构是sqlite3_stmt所代表的数据结构,而在内部,是一个vdbe数据结构代表的实例。
二、 VdbeCursor 结构
游标在虚拟机一层的表现形式。
/*
游标是数据库中特定的 BTree 指针。
游标可以用特定的键值在 BTree 中搜寻入口,或循环处理所有的入口。
你可以向 BTree 中插入新的入口或从游标当前指向的入口中取出键值或数据值。
虚拟机打开的每个游标为此结构的一个实例。
*/
struct VdbeCursor {
BtCursor *pCursor; /* 后台游标结构 */
Btree *pBt; /* Separate file holding temporary table */
int nField; /* 字段数量 */
/* 游标当前指向的数据记录的信息。
仅在 cacheStatus与 Vdbe.cacheCtr匹配时有效。
*/
u32 cacheStatus; /* 如果此值与 Vdbe.cacheCtr匹配,说明缓冲区可用 */
int payloadSize; /* Payload大小,以字节为单位。 */
u32 *aType; /* 各字段的数据类型值 */
u32 *aOffset; /* 各字段数据的偏移量 */
u8 *aRow; /* 当前行的数据(如果它们都在同一个页中) */
};
三、VDBE字节码编程语言
SQLite定义了一种 内部编程语言 来预处理字节码程序,这种语言类似汇编语言,它会定义字节码的结构:<opcode, P1, P2, P3>。
Opcode指明了一个确定的操作,p1,p2,p3是该操作的操作数。P1是一个32位有符号整型变量。P2是一个31位非负整型变量,对于可能导致跳转的操作,p2总是存储跳转的目的地址。P3是一个指向无结尾的字符串,或者指向结构体,或者指向NULL。一些操作码使用这3个操作数,一些只使用一个或两个操作数,甚至不使用操作数。
操作码是虚拟机内部操作的名字,并不是SQLite接口规格的一部分。所以,不同的版本操作码的语义可能不一样。SQLite开发小组不推荐用户自己书写字节码。
下表展示了等价于SELECT * FROM t1的一个字节码程序。表t1有两列:x,y。表的第一行不是程序的一部分,所有其他行都是字节码指令。
虚拟机是一个翻译器,以下是它的结构:
for (; pc < nOp && rc == SQLITE_OK; pc++){
switch (aOp[pc].opcode){
case OP_Add:
/* Implementation of the ADD operation here */
break;
case OP_Goto:
pc = op[pc].p2-1;
break;
case OP_Halt:
pc = nOp;
break;
/* other cases for other opcodes */
}
}
翻译器是一个简单的循环,包括了大量的switch语句。每个case语句实现一个字节码指令。(操作码以OP_为前缀)在每次迭代中,虚拟机从字节码程序中取到下一个字节码指令,例如,使用pc作为索引从aOp数组中取得(pc和aOp都是Vdbe实体的成员)。虚拟机对指令进行解码并且执行该指令。虚拟机从指令编号为0的字节码程序开始执行。
虚拟机通过一个游标进入数据库,游标是指向单个B+树(表)或者B树(索引)的指针。游标可以通过键值查找数据,也可以遍历整个树。虚拟机通过游标执行数据项的插入、读取、删除操作。
虚拟机使用操作数栈和一定数量的编号过的内存空间来保存中间结果。操作码使用栈中的操作数,计算结果也存储在栈中。
虚拟机依次执行字节码程序直到遇到halt指令或者发生错误(在翻译器程序中,rc变量保存了指令执行的状态),或者程序计数器已经超过的最后一条指令的编号。如果是错误导致的执行终止,虚拟机会终止事务或者子事务,去除事务(子事务)对数据库的影响。
更多推荐
所有评论(0)