在线文档教程
Sqlite
其他 | Miscellaneous

The OS Backend (VFS) To SQLite

SQLite OS 界面或 “VFS”

本文介绍 SQLite OS 可移植性层或 “VFS” - SQLite 实施堆栈底部的模块,它提供跨操作系统的可移植性。

1.0 VFS 与 SQLite 其余部分的关系

SQLite 库的内部组织可以被视为右侧显示的模块堆栈。Tokenizer,Parser 和 Code Generator 组件用于处理 SQL 语句并将其转换为虚拟机语言或字节码中的可执行程序。粗略地说,这三个层次实现了 sqlite3_prepare_v2()。由前三层生成的字节码是一个准备好的语句。虚拟机模块负责运行 SQL 语句字节码。B-Tree 模块将数据库文件组织成具有有序密钥和对数性能的多个键/值存储库。寻呼机模块负责将数据库文件的页面加载到内存中,用于实现和控制事务,并用于创建和维护日志文件,以防止崩溃或电源故障后数据库损坏。操作系统界面是一个简单的抽象概念,它提供了一套常用的例程,用于使 SQLite 适应不同的操作系统。粗略地说,最下面四层实现 sqlite3_step()。

这篇文章是关于底层。

操作系统界面(也称为“VFS”)是 SQLite 跨操作系统移植的原因。只要 SQLite 中的任何其他模块需要与操作系统进行通信,就会调用 VFS 中的方法。然后 VFS 调用满足请求所需的操作特定代码。因此,将 SQLite 移植到新的操作系统只需编写一个新的 OS 界面层或 “VFS”。

2.0 多个VFS

标准的 SQLite 源代码树包含用于 unix 和 windows 的内置 VFS。可以使用 sqlite3_vfs_register()接口在启动时或运行时添加备用 VFSes。

多个 VFS 可以同时注册。每个 VFS 都有一个唯一的名称。同一进程内的单独数据库连接可以同时使用不同的 VFS。对于这一点,如果单个数据库连接使用 ATTACH 命令打开多个数据库文件,则每个连接的数据库可能使用不同的 VFS。

Unix 版本内置了多个 VFS。UNIX 的默认 VFS 称为 “unix”,是绝大多数应用程序中使用的 VFS。其他可以在 unix 中找到的 VFS 可能包括:

  • unix-dotfile - 使用点文件锁而不是 POSIX 顾问锁。

  • unix-excl - 获取并保存数据库文件的排他锁,防止其他进程访问数据库。还将 wal-index 保存在堆中而不是共享内存中。

  • unix-none - 所有文件锁定操作都是空操作。

  • unix-namedsem - 使用已命名的信号量进行文件锁定。仅限 VXWorks。

各种 unix VFS 的区别仅在于它们处理文件锁定的方式 - 它们共享大部分实现,并且都位于相同的SQLite源文件中:os_unix.c。请注意,除 “unix” 和 “unix-excl” 以外,各种 unix VFS 都使用不兼容的锁定实现。如果两个进程使用不同的 unix VFS 访问相同的 SQLite 数据库,则它们可能不会看到其他每个锁,并可能最终相互干扰,从而导致数据库损坏。特别是 “unix-none”VFS 根本不会锁定,如果同时使用两个或多个数据库连接,将很容易导致数据库损坏。鼓励程序员只使用 “unix” 或 “unix-excl”

2.1 指定要使用哪个 VFS

总是有一个 VFS 是默认的 VFS。在 unix 系统上,“unix”VFS 默认启动,在 windows 上它是 “win32”。如果不采取其他措施,新的数据库连接将使用默认的 VFS。

默认的 VFS 可以通过使用带有第二个参数的 sqlite3_vfs_register()接口注册或重新注册 VFS 来更改。因此,如果(unix)进程想要始终使用 “unix-nolock”VFS 来代替 “ unix“,下面的代码可以工作:

sqlite3_vfs_register(sqlite3_vfs_find("unix-nolock"), 1

另一个 VFS 也可以被指定为 sqlite3_open_v2()函数的第四个参数。例如:

int rc = sqlite3_open_v2("demo.db", &db, SQLITE_OPEN_READWRITE, "unix-nolock"

最后,如果已启用 URI 文件名,则可以使用 URI 上的 “vfs =” 参数指定替代 VFS。这项技术适用于 sqlite3_open(),sqlite3_open16(),sqlite3_open_v2(),并且当一个新的数据库被连接到现有的数据库连接时。例如:

ATTACH 'file:demo2.db?vfs=unix-none' AS demo2;

由 URI 指定的 VFS 具有最高的优先级。之后,将有一个 VFS 指定为 sqlite3_open_v2()的第四个参数。如果没有指定 VFS,则使用默认 VFS。

2.2 VFS 垫片

从 SQLite 堆栈的 uppers 层的角度来看,每个打开的数据库文件只使用一个 VFS。但实际上,一个特定的 VFS 可能仅仅是另一个真正工作的 VFS 的简单包装。我们称之为包装 VFS ​​为“垫片”。

垫片的一个简单例子就是 “vfstrace” VFS。这是一个 VFS(在 test_vfstrace.c 源文件中实现),它将与每个 VFS 方法调用关联的消息写入日志文件,然后将控制权交给另一个 VFS 执行实际工作。

2.3 其他示例 VFSes

以下是公共 SQLite 源代码树中可用的其他VFS实现:

  • test_demovfs.c - 这个文件实现了一个非常简单的名为 “demo” 的 VFS,它使用了诸如 open(),read(),write(),fsync(),close(),fsync(),sleep(),POSIX ()等等。这个 VFS 只适用于 unix 系统。但它不能替代 unix 平台上默认使用的标准 “unix”VFS。“演示” VFS 故意保持非常简单,以便它可以用作学习辅助或作为模板来构建其他 VFS 或将 SQLite 移植到新的操作系统。

  • test_quota.c - 该文件实现称为“配额”的填充,该填充强制对数据库文件集合实施累积文件大小限制。辅助界面用于定义“报价组”。配额组是一组文件(数据库文件,日志和临时文件),其名称均与 GLOB 模式匹配。跟踪每个配额组中所有文件大小的总和,如果该总和超过为配额组定义的阈值,则会调用回调函数。该回调可能会增加阈值,或导致超出配额的操作失败并出现 SQLITE_FULL 错误。此填充程序的用途之一是用于强制执行 Firefox 中应用程序数据库的资源限制。

  • test_multiplex.c - 这个文件实现了一个 shim,它允许数据库文件超过底层文件系统的最大文件大小。这个 Shim 提供了一个接口,让它看起来像正在使用的非常大的文件,实际上每个这样的大文件被分割成底层系统上的许多小文件。例如,该填充程序已被用于允许数据库在 FAT16文件系统上增长到大于2千兆字节。

  • test_onefile.c - 该文件实现了名为 “fs” 的演示 VFS,该演示显示了 SQLite 如何在缺少文件系统的嵌入式设备上使用。内容直接写入底层媒体。这个演示代码派生的 VFS 可以被闪存数量有限的小工具使用,以使 SQLite 充当设备上闪存的文件系统。

  • test_journal.c - 该文件实现了在 SQLite 测试期间使用的填充程序,该填充程序验证数据库和回滚日志是否按正确的顺序写入,并在适当的时间被“同步”,以确保数据库可以从功率损失恢复随时重置。Shim 检查数据库和回滚日志的操作的几个不变量,并在这些不变量的任何一个被违反时引发异常。这些不变式反过来确保数据库始终可恢复。使用此垫片运行大量测试用例可以确保 SQLite 数据库不会因意外的电源故障或设备重置而受损。

  • test_vfs.c - 该文件实现了一个可用于模拟文件系统故障的垫片。该测试在测试过程中用于验证 SQLite 响应是否理解为硬件故障或其他错误情况,例如在实际系统上难以测试的文件系统空间用尽。

在核心 SQLite 源代码库和可用扩展中还有其他 VFS 实现。上面的列表并不是详尽的,而仅仅是可以使用 VFS 接口实现的各种功能的代表。

3.0 VFS 实现

一个新的 VFS 通过继承三个对象来实现:

  • sqlite3_vfs

  • sqlite3_io_methods

  • sqlite3_file

sqlite3_vfs 对象定义了 VFS 的名称和实现操作系统接口的核心方法,比如检查文件是否存在,删除文件,创建文件,打开和读取和/或写入,将文件名转换为规范形成。sqlite3_vfs 对象还包含用于从操作系统获取随机性的方法,用于暂停进程(休眠)并查找当前日期和时间。

sqlite3_file 对象表示一个打开的文件。当打开文件时,sqlite3_vfs 的 xOpen 方法构造一个 sqlite3_file 对象。sqlite3_file 会在打开文件时跟踪文件的状态。

sqlite3_io_methods 对象包含用于与打开的文件进行交互的方法。每个 sqlite3_file 都包含一个指向适合它所代表的文件的 sqlite3_io_methods 对象的指针。sqlite3_io_methods 对象包含一些方法,用于执行文件的读取和写入,截断文件,刷新对永久存储的任何更改,查找文件的大小,锁定和解锁文件以及关闭文件和销毁 sqlite3_file 对象。

编写新 VFS 的代码涉及为 sqlite3_vfs 对象构建一个子类,然后使用对 sqlite3_vfs_register()的调用注册该 VFS 对象。VFS实现还为 sqlite3_file和sqlite3_io_methods 提供了子类,但这些对象不是直接用SQLite注册的。相反,sqlite3_file 对象是从 sqlite3_vfs 的 xOpen 方法返回的,而 sqlite3_file 对象指向 sqlite3_io_methods 对象的一个​​实例。