SQLite 的自定义构建
或将 SQLite
移植到新操作系统
1.0 简介
对于大多数应用程序,推荐的构建 SQLite 的方法是使用合并代码文件sqlite3.c及其对应的头文件 sqlite3.h。sqlite3.c 代码文件应在任何 Unix、Windows 系统上编译和运行,无需任何更改或特殊编译器选项。大多数应用程序可以简单地将 sqlite3.c 文件与构成该应用程序的其他 C 代码文件一起包括在内,将它们全部编译在一起,并拥有工作良好且配置良好的 SQLite 版本。
大多数应用程序在 SQLite 的默认配置下都能很好地工作,并且没有特殊的编译时配置。大多数开发人员应该能够完全忽略此文档并简单地从 合并中构建 SQLite,而无需任何特殊知识,也无需采取任何特殊操作。
然而,高度调整和专业化的应用程序可能希望或需要用更适合应用程序需求的替代实现替换 SQLite 的一些内置系统接口。SQLite 旨在在编译时轻松重新配置以满足各个项目的特定需求。SQLite 的编译时配置选项包括:
用替代实现替换内置互斥子系统。
完全禁用在单线程应用程序中使用的所有互斥。
重新配置内存分配子系统以使用标准库中的 malloc() 实现以外的内存分配器。
重新调整内存分配子系统,使其根本不调用 malloc(),而是使用在启动时分配给 SQLite 的固定大小的内存缓冲区来满足所有内存请求。
用替代设计替换文件系统的接口。换句话说,覆盖 SQLite 进行的所有系统调用,以便使用一组完全不同的系统调用与磁盘对话。
覆盖其他操作系统接口,例如获取祖鲁语或本地时间的调用。
一般来说,SQLite 中有三个独立的子系统,可以在编译时修改或覆盖。mutex 子系统用于序列化对线程间共享的 SQLite 资源的访问。内存分配子系统用于分配 SQLite 对象和数据库缓存所需的内存。最后,虚拟文件系统子系统用于在 SQLite 和底层操作系统,尤其是文件系统之间提供一个可移植的接口。我们称这三个子系统为 SQLite 的“接口”子系统。
我们强调大多数应用程序都可以通过 SQLite 接口子系统的内置默认实现得到很好的服务。鼓励开发人员尽可能使用默认的内置实现,并在没有任何特殊编译时选项或参数的情况下构建 SQLite。然而,一些高度专业化的应用程序可能会受益于替换或修改这些内置 SQLite 接口子系统中的一个或多个。或者,如果 SQLite 在 Unix(Linux 或 Mac OS X)、Windows(Win32 或 WinCE)或 OS/2 以外的操作系统上使用,则 SQLite 中内置的接口子系统都将无法工作,应用程序将需要提供适合目标平台的替代实现。
2.0 配置或更换互斥子系统
在多线程环境中,SQLite 使用互斥体来序列化对共享资源的访问。只有从多线程访问 SQLite 的应用程序才需要 mutex 子系统。对于单线程应用程序,或仅从单线程调用 SQLite 的应用程序,可以通过使用以下选项重新编译来完全禁用互斥子系统:
-DSQLITE_THREADSAFE=0
互斥量很便宜但不是免费的,所以当完全禁用互斥量时性能会更好。由此产生的库占用空间也会小一些。在编译时禁用互斥锁是对有意义的应用程序的推荐优化。
当使用 SQLite 作为共享库时,应用程序可以使用sqlite3_threadsafe() API测试互斥锁是否被禁用 。在运行时链接到 SQLite 并从多个线程使用 SQLite 的应用程序可能应该检查此 API 以确保它们不会意外链接到禁用互斥锁的 SQLite 库版本。当然,无论 SQLite 是否配置为线程安全的,单线程应用程序都可以正常工作,尽管在使用禁用互斥锁的 SQLite 版本时它们会快一点。
SQLite 互斥也可以在运行时使用 sqlite3_config()接口禁用。要完全禁用所有互斥,应用程序可以调用:
sqlite3_config(SQLITE_CONFIG_SINGLETHREAD);
在运行时禁用互斥量不如在编译时禁用它们有效,因为 SQLite 仍然必须对布尔变量进行测试,以查看在可能需要互斥量的每个点是否启用或禁用了互斥量。但是在运行时禁用互斥量仍然有性能优势。
对于注意线程管理方式的多线程应用程序,SQLite 支持替代运行时配置,该配置介于不使用任何互斥锁和互斥所有可见的默认情况之间。这种中间互斥对齐可以按如下方式建立:
sqlite3_config(SQLITE_CONFIG_MULTITHREAD); sqlite3_config(SQLITE_CONFIG_MEMSTATUS, 0);
这里有两个单独的配置更改,可以一起使用或单独使用。SQLITE_CONFIG_MULTITHREAD设置禁用了序列化对数据库连接对象和 准备好的语句对象的访问的 互斥体。通过此设置,应用程序可以从多个线程自由使用 SQLite,但它必须确保没有两个线程同时尝试访问同一个数据库连接或与同一个数据库连接关联的 任何准备好的语句。两个线程可以同时使用SQLite,但必须使用单独的数据库连接。第二个SQLITE_CONFIG_MEMSTATUS设置禁用 SQLite 中跟踪所有未完成内存分配请求的总大小的机制。这省去了对sqlite3_malloc()和sqlite3_free()的每次调用互斥的需要,从而节省了大量的互斥操作。但禁用内存统计机制的一个后果是 sqlite3_memory_used()、sqlite3_memory_highwater()和 sqlite3_soft_heap_limit64()接口停止工作。
SQLite 在 Unix 上使用 pthreads 实现互斥锁,SQLite 需要递归互斥锁。大多数现代 pthread 实现都支持递归互斥锁,但并非全部支持。对于不支持递归互斥锁的系统,建议应用程序仅在单线程模式下运行。如果这不可能,SQLite 提供了一个替代的递归互斥实现,它建立在 pthreads 的标准“快速”互斥之上。只要 pthread_equal() 是原子的并且处理器具有连贯的数据缓存,这种替代实现应该可以正常工作。替代递归互斥体实现由以下编译器命令行开关启用:
-DSQLITE_HOMEGROWN_RECURSIVE_MUTEX=1
将 SQLite 移植到新操作系统时,通常需要用围绕新操作系统的互斥原语构建的替代方案完全替换内置互斥子系统。这是通过使用以下选项编译 SQLite 来完成的:
-DSQLITE_MUTEX_APPDEF=1
当使用 SQLITE_MUTEX_APPDEF=1 选项编译 SQLite 时,它完全省略了其 互斥原语函数的实现。但 SQLite 库仍会尝试在必要时调用这些函数,因此应用程序必须自己实现 互斥原语函数并将它们与 SQLite 链接在一起。
3.0 配置或更换内存分配子系统
默认情况下,SQLite 从标准库的 malloc()/free() 实现中获取对象和缓存所需的内存。还有正在进行的实验性内存分配器的工作,这些内存分配器可以满足在应用程序启动时从单个固定内存缓冲区传递给 SQLite 的所有内存请求。有关这些实验性内存分配器的更多信息将在本文档的未来修订版中提供。
SQLite 支持应用程序在运行时指定替代内存分配器的能力,方法是 使用指向替代实现例程的指针填充sqlite3_mem_methods对象的实例,然后使用sqlite3_config()接口注册新的替代实现。例如:
sqlite3_config(SQLITE_CONFIG_MALLOC, &my_malloc_implementation);
SQLite 复制sqlite3_mem_methods对象的内容,以便在sqlite3_config()调用返回后可以修改该对象。
4.0 添加新的虚拟文件系统
自版本 3.5.0 (2007-09-04) 以来,SQLite 支持称为 虚拟文件系统或“VFS”的接口。这个对象有点名不副实,因为它实际上是整个底层操作系统的接口,而不仅仅是文件系统。
VFS 接口的一个有趣特性是 SQLite 可以同时支持多个 VFS。当第一次使用sqlite3_open_v2()打开连接时,每个数据库连接都必须选择一个 VFS 供其使用。但是如果一个进程包含多个数据库连接,每个连接都可以选择不同的 VFS。可以使用 sqlite3_vfs_register()接口在运行时添加 VFS。
SQLite 在 Unix、Windows 和 OS/2 上的默认构建包括适合目标平台的 VFS。默认情况下,为其他操作系统构建的 SQLite 不包含 VFS,但应用程序可以在运行时注册一个或多个。
5.0 将 SQLite 移植到新的操作系统
为了将 SQLite 移植到新的操作系统——默认不支持的操作系统——应用程序必须提供……
- 一个工作的互斥子系统(但前提是它是多线程的),
- 工作内存分配子系统(假设其标准库中缺少 malloc()),以及
- 一个有效的 VFS 实现。
所有这些东西都可以在一个辅助 C 代码文件中提供,然后与现有的“sqlite3.c”代码文件链接,为目标操作系统生成一个有效的 SQLite 构建。除了备用互斥锁和内存分配子系统以及新的 VFS 之外,辅助 C 代码文件还应包含以下两个例程的实现:
“sqlite3.c”代码文件包含适用于 Unix、Windows 和 OS/2的 VFS 和sqlite3_initialize()和sqlite3_shutdown()函数的默认实现。为了防止在编译 sqlite3.c 时加载这些默认组件之一,有必要添加以下编译时选项:
-DSQLITE_OS_OTHER=1
SQLite 核心会提前调用sqlite3_initialize() 。辅助 C 代码文件可以包含 sqlite3_initialize() 的实现,它注册了一个合适的 VFS,也可能初始化一个替代的互斥系统(如果需要互斥)或进行任何所需的内存分配子系统初始化。SQLite 核心从不调用sqlite3_shutdown()但它是官方 SQLite API 的一部分,并且在使用 -DSQLITE_OS_OTHER=1 编译时不会以其他方式提供,因此辅助 C 代码文件可能应该提供它以确保完整性。