时间:2021-07-01 10:21:17 帮助过:12人阅读
虚拟机
所谓虚拟机是指对真实计算机资源环境的一个抽象,它为语言程序提供了一套完整的计算机接口。比如我们熟悉的JAVA语言,我们在跑JAVA程序时,其实是运行在JVM(JAVA Virtual Machine)环境中,所有的JAVA程序首先被编译为.class类文件,这种类文件在虚拟机上执行,也就是说class文件并不与操作系统指令对应,而是经过虚拟机间接与操作系统交互。SQLite的虚拟机也是如此,编译SQL产生的指令流只有SQLite虚拟机(Virtual Database Engine,简称VDBE)能识别,由虚拟机与底层的存储(表,索引)交互,这种方式使得SQLite内部模块分工非常清晰,耦合度很低。如下图所示,我们可以看到VDBE的位置,它处于编译器与Btree模块的中间,是SQLite的核心,负责SQL到数据存取的交互。后面我提到的虚拟机都是指SQLite虚拟机(Virtual Machine,VM),VM模块将底层存储看作是记录维度的文件系统,通过执行指令流,来读写表上的记录。
VDBE数据结构和API
- <span style="font-size: 14px;"><span style="color: #0000ff;">struct</span><span style="color: #000000;"> Vdbe{
- sqlite3 </span>*db; <span style="color: #008000;">/*</span><span style="color: #008000;"> The database connection that owns this statement </span><span style="color: #008000;">*/</span><span style="color: #000000;">
- Op </span>*aOp; <span style="color: #008000;">/*</span><span style="color: #008000;"> Space to hold the virtual machine‘s program </span><span style="color: #008000;">*/</span>
- <span style="color: #0000ff;">int</span> nOp; <span style="color: #008000;">/*</span><span style="color: #008000;"> Number of instructions in the program </span><span style="color: #008000;">*/</span><span style="color: #000000;">
- Mem </span>**apArg; <span style="color: #008000;">/*</span><span style="color: #008000;"> Arguments to currently executing user function </span><span style="color: #008000;">*/</span><span style="color: #000000;">
- Parse </span>*pParse; <span style="color: #008000;">/*</span><span style="color: #008000;"> Parsing context used to create this Vdbe </span><span style="color: #008000;">*/</span>
- <span style="color: #0000ff;">int</span> pc; <span style="color: #008000;">/*</span><span style="color: #008000;"> The program counter </span><span style="color: #008000;">*/</span><span style="color: #000000;">
- Mem </span>*aMem; <span style="color: #008000;">/*</span><span style="color: #008000;"> The memory locations </span><span style="color: #008000;">*/</span>
- <span style="color: #0000ff;">int</span> nMem; <span style="color: #008000;">/*</span><span style="color: #008000;"> Number of memory locations currently allocated </span><span style="color: #008000;">*/</span><span style="color: #000000;">
- Mem </span>*aColName; <span style="color: #008000;">/*</span><span style="color: #008000;"> Column names to return </span><span style="color: #008000;">*/</span><span style="color: #000000;">
- u16 nResColumn; </span><span style="color: #008000;">/*</span><span style="color: #008000;"> Number of columns in one row of the result set </span><span style="color: #008000;">*/</span>
- <span style="color: #0000ff;">char</span> *zSql; <span style="color: #008000;">/*</span><span style="color: #008000;"> Text of the SQL statement that generated this </span><span style="color: #008000;">*/</span><span style="color: #000000;">
- }</span></span>
我从源码中选取了比较重要的对象,主要包括数据库对象(db),指令流对象(aOp,nOp),绑定输入的参数值(apArg),解析SQL的对象(pParse),指令流计数器(pc),存储临时变量的寄存器(aMem,nMem),返回结果集集的列名和列信息(aColName,nResColumn)以及执行的产生虚拟机指令的SQL(zSql)等。这些基本就是虚拟机对象的全部,有指令,有寄存器,有指令计数器,与汇编语言非常相似,只不过VDBE里面的指令是sqlite内部识别的指令,而汇编语言指令是与机器指令对应的。如果想了解VDBE所有的对象,可以参考vdbeInt.h中关于该结构的定义,另外关于sqlite3结构和Parse结构可以参考sqliteInt.h文件。
了解了Vdbe数据结构,我们再来看看我们平时常用的API是如何与VDBE交换数据的。通常我们要执行一个语句,会执行如下几个步骤。
1.调用sqlite3_prepare_*来编译生成指令流,返回一个sqlite3_stmt对象,其实这个对象就是vdbe对象。
2.调用sqlite3_bind_*来将参数传递给vdbe,
3.调用sqlite3_step进行执行,这时候会启动虚拟机执行一条条指令,直到遇到中断或者停止指令为止
4.调用sqlite3_column_*来获取上一步准备好的结果集
5.调用sqlite3_finalize,销毁vdbe对象,结束这次执行。
此外我们还可能用到sqlite3_reset接口,这个接口将指令流回退到第一条指令,用户可以调用sqlite3_step重新执行。有关API的详细说明,可以参考文件vdbeapi.c。
虚拟机指令
虚拟机核心就是扁平化指令,SQLite定义了一系列指令语言,每个指令做一小部分动作,虚拟机通过执行一些列指令达到查询,修改数据库的目的。每一条指令包含一个操作符和5个操作数,形式如下:<opcode,P1,P2,P3,P4,P5>。P1,P2,P3是一个32位有符号整数,P1一般是游标编号,P2一般是指令需要跳转的指令位置,P4是一个32位/64位整数,64位的浮点数,或者是指向字符串的指针,或者是二进制等,P5是一个无编号的字符。不是每条指令都使用了全部5个操作数,有的指令只需要2到3个操作数。后面一篇文章我会结合实例详细讲解指令的作用,以及对应操作数的含义。
虚拟机执行流程
虚拟机的核心流程在sqlite3VdbeExec函数中,我们调用sqlite3_step时就会调用到该函数。由于这个函数比较大,大概有6000行代码,里面包含了每条指令的执行过程,为了方便说明,我会简化函数内容来说明这个函数的逻辑,抽象的代码如下。从代码流程来看,逻辑非常简单,通过循环遍历指令数组中的每条指令逐一执行,直到遇到中断或终止指令为止。如果需要逐条了解每条指令的含义,还需要仔细阅读代码。
- <span style="font-size: 14px;">sqlite3VdbeExec(Vdbe *<span style="color: #000000;">p)
- {
- Op </span>*aOp = p->aOp; <span style="color: #008000;">/*</span><span style="color: #008000;"> Copy of p->aOp </span><span style="color: #008000;">*/</span><span style="color: #000000;">
- Op </span>*pOp = aOp; <span style="color: #008000;">/*</span><span style="color: #008000;"> Current operation </span><span style="color: #008000;">*/</span>
- <span style="color: #0000ff;">for</span>(pOp=&aOp[p->pc]; rc==SQLITE_OK; pOp++<span style="color: #000000;">){
- </span><span style="color: #0000ff;">switch</span>(pOp-><span style="color: #000000;">opcode){
- </span><span style="color: #0000ff;">case</span> OP_Goto: <span style="color: #008000;">//</span><span style="color: #008000;">jump to P2指向的指令</span>
- <span style="color: #000000;">{
- pOp </span>= &aOp[pOp->p2 - <span style="color: #800080;">1</span><span style="color: #000000;">];
- </span><span style="color: #0000ff;">break</span><span style="color: #000000;">;
- }
- </span><span style="color: #0000ff;">case</span> OP_Integer: <span style="color: #008000;">//</span><span style="color: #008000;"> value P1 is written into register P2.</span>
- <span style="color: #000000;">{
- pOut </span>=<span style="color: #000000;"> out2Prerelease(p, pOp);
- pOut</span>->u.i = pOp-><span style="color: #000000;">p1;
- </span><span style="color: #0000ff;">break</span><span style="color: #000000;">;
- }
- </span><span style="color: #0000ff;">case</span><span style="color: #000000;"> OP_Real:
- {
- ......
- </span><span style="color: #0000ff;">break</span><span style="color: #000000;">;
- }
- </span><span style="color: #0000ff;">case</span><span style="color: #000000;"> OP_Halt:
- {
- ......
- </span><span style="color: #0000ff;">break</span><span style="color: #000000;">;
- }
- ...
- }</span><span style="color: #008000;">//</span><span style="color: #008000;"> end of switch</span>
- } <span style="color: #008000;">//</span><span style="color: #008000;"> end of for<br></span>}</span>
小结
本文介绍了SQLite虚拟机以及对应的指令流。通过介绍vdbe的存储结构,我们了解到vdbe对象所包含的内容;通过介绍API,我们了解到API与虚拟机的关系;通过介绍函数sqlite3VdbeExec的实现,我们知道虚拟机执行流程非常清晰,通过执行一系列指令流,就可以实现查询,更新数据。
SQLite学习笔记(十一)&&Sqlite虚拟机原理
标签: