本文介绍了 SQLite 操作系统可移植性层或“VFS”——位于 SQLite 实现堆栈底部的模块,它提供了跨操作系统的可移植性。
SQLite 库的内部组织可以看作是右侧所示的模块堆栈。分词器、解析器和代码生成器组件用于处理 SQL 语句,并将其转换为虚拟机语言或字节码中的可执行程序。粗略地说,这三个顶层实现了 sqlite3_prepare_v2()。由前三个层生成的字节码是 预处理语句。虚拟机模块负责运行 SQL 语句字节码。B 树模块将数据库文件组织成多个具有有序键和对数性能的键值存储。Pager 模块负责将数据库文件的页面加载到内存中,实现和控制事务,以及创建和维护防止数据库在崩溃或电源故障后损坏的日志文件。操作系统接口是一个薄抽象层,提供了一组通用的例程,用于使 SQLite 能够在不同的操作系统上运行。粗略地说,底部的四个层实现了 sqlite3_step()。
本文讨论的是底层。
操作系统接口——也称为“VFS”——是 SQLite 能够跨操作系统移植的原因。每当 SQLite 中的任何其他模块需要与操作系统通信时,它们都会调用 VFS 中的方法。然后,VFS 调用满足请求所需的特定于操作系统的代码。因此,将 SQLite 移植到新的操作系统只需编写新的操作系统接口层或“VFS”。
标准 SQLite 源代码树包含用于 unix 和 windows 的内置 VFS。可以使用 sqlite3_vfs_register() 接口在启动时或运行时添加备用 VFS。
可以同时注册多个 VFS。每个 VFS 都有一个唯一的名称。同一进程内的独立 数据库连接 可以同时使用不同的 VFS。就此而言,如果单个数据库连接使用 ATTACH 命令打开了多个数据库文件,则每个附加的数据库可能都使用不同的 VFS。
Unix 版本附带多个内置 VFS。unix 的默认 VFS 称为“unix”,在大多数应用程序中使用。其他可能在 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”,除非有令人信服的理由这样做。
Windows 版本也附带多个内置 VFS。默认的 Windows VFS 称为“win32”,在大多数应用程序中使用。其他可能在 Windows 版本中找到的 VFS 包括
win32-longpath - 类似于“win32”,但路径名长度可以达到 65534 字节,而“win32”中的路径名最大为 1040 字节。
win32-none - 所有文件锁定操作都是空操作。
win32-longpath-none - “win32-longpath”和“win32-none”的组合——支持长路径名,所有锁定操作都是空操作。
与 unix 一样,各种 Windows VFS 的大部分代码都是共享的。
始终存在一个 VFS 作为默认 VFS。在 unix 系统上,“unix”VFS 作为默认值出现,在 windows 上为“win32”。如果未采取其他操作,新的数据库连接将使用默认 VFS。
可以使用 sqlite3_vfs_register() 接口(第二个参数为 1)注册或重新注册 VFS 来更改默认 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 到现有数据库连接时。例如
ATTACH 'file:demo2.db?vfs=unix-none' AS demo2;
由 URI 指定的 VFS 具有最高优先级。接下来是作为 sqlite3_open_v2() 的第四个参数指定的 VFS。如果未另行指定 VFS,则使用默认 VFS。
从 SQLite 堆栈上层来看,每个打开的数据库文件都使用一个 VFS。但在实践中,特定 VFS 可能只是围绕执行实际工作的另一个 VFS 的一个薄包装器。我们将包装器 VFS 称为“适配器”。
适配器的一个简单示例是“vfstrace”VFS。这是一个 VFS(在 test_vfstrace.c 源文件中实现),它将与每个 VFS 方法调用关联的消息写入日志文件,然后将控制权传递给另一个 VFS 以执行实际工作。
以下是公共 SQLite 源代码树中可用的其他 VFS 实现
appendvfs.c - 此 VFS 允许将 SQLite 数据库附加到某些其他文件的末尾。例如,这可以用于将 SQLite 数据库附加到可执行文件的末尾,以便在运行时可以轻松找到附加的数据库。命令行 shell 如果使用 --append 选项启动,将使用此 VFS,并且其 .archive 命令在给定 --append 标志时将使用它。
test_demovfs.c - 此文件实现了一个名为“demo”的非常简单的 VFS,它使用 POSIX 函数(如 open()、read()、write()、fsync()、close()、fsync()、sleep()、time() 等)。此 VFS 仅适用于 unix 系统。但它并非旨在作为 unix 平台上默认使用的标准“unix”VFS 的替代品。“demo”VFS 故意保持非常简单,以便可以用作学习辅助工具或作为构建其他 VFS 或将 SQLite 移植到新操作系统的模板。
test_quota.c - 此文件实现了一个名为“quota”的适配器,它对数据库文件的集合施加累积文件大小限制。辅助接口用于定义“配额组”。配额组是一组文件(数据库文件、日志文件和临时文件),其名称都与 GLOB 模式匹配。跟踪每个配额组中所有文件大小的总和,如果该总和超过为配额组定义的阈值,则会调用回调函数。该回调函数可以增加阈值,也可以导致超出配额的操作失败并出现 SQLITE_FULL 错误。此适配器的一种用途是用于在 Firefox 中对应用程序数据库实施资源限制。
test_multiplex.c - 此文件实现了一个适配器,允许数据库文件超过底层文件系统的最大文件大小。此适配器向 SQLite 的上六层提供了一个接口,使其看起来像正在使用非常大的文件,而实际上每个这样的文件都被拆分为底层系统上的许多较小的文件。例如,此适配器已用于允许数据库在 FAT16 文件系统上增长到超过 2 gibibyte。
test_onefile.c - 此文件实现了一个名为“fs”的演示 VFS,它展示了如何在没有文件系统的嵌入式设备上使用 SQLite。内容直接写入底层介质。从这个演示代码派生的 VFS 可以由具有有限闪存量的设备使用,以使 SQLite 充当设备上闪存的文件系统。
test_journal.c - 此文件实现了一个在 SQLite 测试期间使用的适配器,它验证数据库和回滚日志是否按正确的顺序写入,并且在适当的时间“同步”,以确保数据库可以随时从电源故障或硬重置中恢复。适配器检查数据库和回滚日志的操作上的几个不变式,并在任何不变式被违反时引发异常。这些不变式反过来确保数据库始终可恢复。使用此适配器运行大量测试用例可以额外确保 SQLite 数据库不会因意外的电源故障或设备重置而损坏。
test_vfs.c - 此文件实现了一个可以用来模拟文件系统故障的适配器。此适配器在测试期间用于验证 SQLite 对硬件故障或其他错误条件(例如耗尽文件系统空间)的响应是否合理,这些错误条件在真实系统上很难测试。
核心 SQLite 源代码库和可用扩展中还有其他 VFS 实现。以上列表并非旨在详尽无遗,而仅仅是代表可以使用 VFS 接口实现的各种功能。
新的 VFS 是通过对三个对象进行子类化来实现的
一个 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 对象的实例。
此页面上次修改于 2024-02-22 15:53:45 UTC