当前位置: 东星资源网 > 作文大全 > 想象作文 > 正文

[SQLite系统构架及虚拟机分析] 电子系统的典型构架

时间:2019-02-08 来源:东星资源网 本文已影响 手机版

   摘 要: 本文就目前广泛使用的轻量级数据库SQLite的构架进行分析,特别是对其中的虚拟数据库引擎(VDBE)做了原理性的剖析,并结合实例,展示了SQLite的应用,及SQLite内部VDBE指令程序的运行方式。
   关键词: 嵌入式; SQLite; 数据库; 构架; VDBE; 虚拟机
  中图分类号: TP316 文献标识码: A 文章编号: 1009-8631(2012)01-0143-03
  
   1 引言
   SQLite是遵守ACID的轻量级关系型数据库管理系统,完全免费,开源,无需任何配置也无需任何安装程序[1]。它广泛应用在各种嵌入式系统中,在iOS和Android等系统中都是集成在各自的库中的。
   虚拟机是现在比较流行的一种软件构架,特别是在解释性编程语言领域。在安全领域,虚拟机也被用于实现软件的加密,是公认的一种非常高效且使用的技术手段。SQLite用较小规模的代码用C语言实现了一个程序虚拟机,提高了代码的独立性降低了耦合性,同时保持了很高的效率。
   2 SQLite数据库构架
   下图是SQLite系统的总体构架图[2]。整体上SQLite可以分为前端和后端:前端负责从用户数据到平台不相关的指令的转换,后端处理的数据流,深入到具体数据库数据在磁盘上的操作,这些数据是和平台相关的。SQLite所做到的平台无关性,是通过其内部实现的虚拟数据库引擎(VDME, Virtual Database Engine)来完成的。总的来说,就是将SQL语句先翻译成一种专门设计的语言,然后下层再调用平台相关的系统API接口,完成相应的功能。
   SQLite的源代码由96个C语言(.c和.h)文件组成,在编译之前会由Makefile生成一个完整的文件,即可以在官方网站上下载的sqlite3.c和sqlite3.h等文件,然后编译形成所需要的库或者可执行文件。
   下图给出了SQLite的主要模块和相互之间的关系,我们将分别介绍各个部分的功能。
  
   2.1 接口(Interface)
   SQLite库提供的对外不调用的接口,大多数都在main.c,legacy.c和vdbeapi.c中,其他一些散布在源代码的不同部分。对接口的查询可以在文档中找到详细的介绍。为了避免命名上的冲突,所有外部可以调用的接口都是以sqlite3_开头的[3]。
   2.2 SQL编译器(SQL Compiler)
   这是一个比较完整的编译器构架,分别完成词法分析、语法分析和中间代码生成。词法分析器(Tokenizer)是由C语言实现的,包含在tokenize.c当中。
   语法分析器(Parser)由Lmon LALR(1)生成,这是个和YACC/BISON类似的分析器,不兼容,但是生成的代码是可重入且线程安全的,代码包含在parse.c中。
   代码生成器(code generator)生成虚拟机执行的中间代码,包含的文件相对较多,比如select.c,update.c等,大多和SQL命令同名对应。
   2.3 虚拟机(Virtual Machine)
   代码生成器生成的中间代码会通过VM执行。这部分后面会有更详细的分解。
   2.4 B-Tree(B-树)
   数据库在磁盘上的操作都是通过B-树的,对应于数据库中的每一个表或者索引有会有相应的B-树。实现和接口分别在btree.c和btree.c中[4]。
   2.5 页缓存(Page Cache)
   数据的读写都是以Chunk位单位进行的,这样可以提高效率。页缓存负责这部分工作,同时提供了回滚(rollback)等共能,并对数据库文件进行管理。实现和接口在pager.c和pager.h中。
   2.6 系统接口(OS Interface)
   SQLite提供了一个系统抽象层,定义在os.h中。每个支持的平台再有自己的对应的实现文件,比如os_uinx.c和os_win.c(及相应的头文件os_unix.h和os_win.h)。
   2.7 功能和测试(Utility和Test Code)
   3 VDBE框架及关键源码分析
   虚拟数据库引擎(Virtual Database Engine, VDBE),居于SQLite数据库的核心部分。从整个SQLite的整体构架我们就可以看出,它处在整个系统的中间部分:前端代码完成的事情就是一个对SQL语言的编译,相当于简化版本的一个编译器;后端做的事情是更加物理上的操作,利用B-tree和Pager对物理硬盘上的数据进行实际的操作。VDBE完成了这连个层次上的抽象链接。
   整个虚拟数据库引擎(VDBE)若干个C语言文件组成。VDBE的主题实现都包含在了vdbe.c(vdbe.h)当中。vdbeInt.h定义了VDBE内部使用的各种结构和函数原型。vdbeaux.c实现了VDBE内部和整个SQLite构建VDBE程序需要的其他的功能性函数代码。vebeaip.c包含了共外部接口函数(提供个SQLite库意外的应用程序使用的,如sqlite3_bind系列函数)使用的一些结构。vdbemen.c实现了在vdbe的存储管理。
   对于用户的SQL语句,编译器会生成一个虚拟机实例。虚拟机实例在内部和外部是不同的。对内看到的是一个vdbe结构的实例,这个结构定义在VdbeInt.h中,如下:
   struct Vdbe {
   sqlite3 *db; /* 数据库连接 */
   Op *aOp; /* 保存虚拟机的空间 */
   …… /* 其他指令 */
   int nOp; /* 生成的指令的条数 */
   char *zSql; /* SQL语句 */
   …… /* 其他指令 */
   SubProgram *pProgram; /* 虚拟机使用的其他子程序,链表 */
   };
   一个虚拟机实例可以有多个子程序,每个子程序中可以由多条指令组成。下面是子程序的结构,
  struct SubProgram {
   VdbeOp *aOp; /* 指令 */
   int nOp; /* 指令条数 */
   int nMem; /* 需要的内部空间 */
   int nCsr; /* 需要的游标 */
   void *token; /* 循环触发时需要的id */
   SubProgram *pNext; /* 链表的下一个*/
   };
   现在的SQLite有142条操作指令,都定义在opcodes.h中,在vdbe.c中有相应的源代码,我们将解析一些指令作为代表,详细的技术文档可以查看官方文档[5]。所有的指令大概可以分为三类:
   (1)数据操作:包含算术、逻辑运算,字符串操作等。
   (2)数据管理:主要关于内存和磁盘的操作。内存上如栈(stack)操作、数据的传送等,磁盘操作主要是B-tree和pager模块,包括打开及操作游标、事务的开始与结束等。
   (3)控制流:指令的跳转。SQL语句在生成的VDBE程序后,每条指令包含了一个操作码(opcode)和至多5个操作数(operands:P1,P2,P3,P4和P5)。其中:
   1.P1,P2,P3都是32位的带符号整数,他们通常引用的是寄存器。
   2.P2在所有的有跳转功能的指令中表示目的地址。例如我们上面的第2条指令,将会跳转到第10条指令,然后顺序执行。
   3.P4可以是32位或者64位的带符号整型数据、字符串、BLOB数据(二进制大对象)、函数指针等其他多样的对象。
   4.P5通常是无符号的字符,充当的是标志位。
   在SQLite的VDBE内部,所有的指令都是VdbeOp结构的一个实例(定义在vdbe.h中),结构的定义也主要是这5个操作数。
   struct VdbeOp {
   u8 opcode; /* 操作码类型 */
   ........... /* 其他数据接口 */
   signed char p4type; /* p4 的类型 */
   u8 p5; /* p5是无符号字符型 */
   int p1; /* 操作数1 */
   int p2; /* 操作数2,通常是跳转指令的目的 */
   int p3; /* 操作数3 */
   union { /* ... */ } p4; /* p4 是一个联合,可以有不同的类型 */
   ............ /* 其他数据接口 */
   };
   由代码生成器生成的程序然后交由VM执行。sqlite3_step()会触发内部vdbe解释生成的vdbe指令。指令的执行在如下的函数中进行(SQLITE_PRIVATE 即为static关键字),我们去掉了烦琐的细节,只是展示其中的关键结构和一个指令的执行:
   SQLITE_PRIVATE int sqlite3VdbeExec(
   Vdbe *p /* VDBE 实例 */
   ) {
   int pc; /* 程序计数器 */
   Op *aOp = p->aOp; /* 得到所有的指令 */
   Op *pOp; /* 当前指令 */
   int rc = SQLITE_OK; /* 返回值 */
   sqlite3* db = p->db; /* 数据库连接实例 */
   u8 encoding = ENC(db); /* UTF-8编码 */
   …… /* 其他初始化代码 */
   switch ( pOp->opcode ) { /* 在此之后就是一个非常大的case代码
   case OP_Goto: {
   CHECK_FOR_INTERRUPT;
   pc = pOp->p2 - 1; /* 调整程序计数器 */
   break;
   }
   …… /* 其他的case指令 */
   }
   …… /* 其他指令 */
   }
   这个函数是整个VDBE的核心执行函数,虽然重要,但是代码的原理非常简单,就是一系列的switch-case语句。在相应的case情况下,会执行相应的底层代码,进行数据库的磁盘操作。
   4 实验
   4.1 数据库编程接口
   SQLite的编程模型比较简单,下面的列子给出了一个基本的框架。
   #include “sqlite3.h”
   #include
   int main(int argc, char **argv)
   {
   char *file = "./test.db"; /* 数据库文件 */
   sqlite3 *db = NULL; /* 数据库连接实例 */
   int rc = 0; /* 返回值 */
   sqlite3_initialize(); /* 初始库 */
   rc = sqlite3_open_v2(file, &db, SQLITE_OPEN_READWRITE, NULL);
   /* 准备SQL语句,生成VDBE程序 */
   sqlite3_stmt *stmt = NULL:
   rc = sqlite3_prepare_v2(db,“SELECT * FROM FILM”, -1, &stmt, NULL);
   if (rc != SQLITE_OK) exit(-1);
   while (sqlite3_step(stmt) = SQLITE_ROW) {
   const char *data=(const char*)sqlite3_column_text(stmt, 0);
   printf(“%s\n”, data?data:“[NULL]”);
   }
   sqlite3_finalize(stmt);
   sqlite3_close(db); /* 关闭 */
   sqlite3_shutdown(); /* 释放资源 */
   }
   在上面的例子中,我们使用了sqlite3_prepare_v2()和sqlite3_step()函数,这两个就是和内部的虚拟机联系非常紧密的两个函数,也是了解SQLite虚拟机的连个点。sqlite3_prepare_v2()完成的是将SQL语句提交的SQL编译器,编译成VDBE指令程序,sqlite3_step()将驱动VDBE执行指令程序。
   从应用上来说,这仅仅是最简单的数据库应用框架,更多的接口信息可以查看官方的文档。
   4.2 VDBE程序分析
   在官方提供的下载中,有编译好的的命令行可执行程序,可以作为完全的SQLite数据库管理工具。同是,他也考虑了一些Debug和Test功能,我们可以利用他们深入了解SQLite的内部机制。由代码生成器生成的中间代码的形式,我们可以利用SQLite命令行程序的中的explain命令进行查看。这只需要在相应的SQL代码的前面加上explain就可以了。如以搜索(select)的命令行显示:(见图2,箭头表示实际执行顺序)
   左边的“addr”列是虚拟机的地址编号,并不是指令执行的顺序,由于跳转指令的存在,我们用箭头标示出了指令运行的实际顺序,也可以在SQLite编译的时候指定相应的选项,然后利用指令pragma vdbe_trace=on;可以详细地看到指令的运行过程和堆栈的变化情况。
   从指令0到指令12都是对SQLite数据库内部的准备:由指令1跳转到指令10,指令10(Transaction)开始一个事务,指令11(VerifyCookie)是在执行一个指令前检查数据库模式(database schema)是否发生了变化,当发生了变化时要重置,指令12(TableLock)将要读的数据库表锁起来,指令13(Goto)跳转到指令2。
   从指令2开始是实际的对数据库的操作了。指令2(OpenRead)会打开一个数据库表的只读的游标,P1作为这个游标的标志,P2是打开的数据库表的根页(root page),P3=0表明是主数据库,P4表明数据库有两列,P5说明是以P2的值作为根页。(OpenRead指令的各个操作数还可以有其他含义,这里只是针对这条SQL语句的解释,请查看技术文档。)指令3(Rewind)到指令7(Next)完成了对所有查询数据的遍历。指令8(Close)关掉游标,指令9(Halt)结束这个VDBE程序。
   VDBE对上层提供的就是这样的接口,而对下层将是调用相应的接口实现相应的功能,并由此完成了模块上的解耦合。
   5 总结
   由VDBE的定义、代码分析及以上的实验,我们可以总结出SQLite的整体构架:
   外部调用SQLite接口函数sqlite3_prepare(),SQL语句通过SQL编译器生成对应的VDBE指令程序;
   内部调用sqlite3_step()驱动,内部执行sqlite3VdbeExec(),switch-case语句执行相应指令。底层通过B-Tree和Pager实现对磁盘数据库文件的管理。(如图3)
   在实际的应用中,我们可以设计一个面向应用的指令集,利用程序虚拟机设计中间抽象层,达到提高平台通用性。同时程序虚拟机也是语言虚拟机、系统虚拟机及安全沙盒等技术提供了技术基础。
   参考文献:
   [1] Michael Owen. The Definitive Guide to SQLite.2006
   [2] Jay A. Kreibich. Using SQLite.2010
   [3] 李蔚,陈亚峰.嵌入式数据库SQLite及其应用研究[J].沿海企业与科技,2010(10).
   [4] 杜国祥,石俊杰.SQLite嵌入式数据库的应用[J].电脑编程技巧与维护,2010,14.
   [5] www.省略

标签:构架 虚拟机 分析 系统