我们正在尝试更改嵌入式数据库系统 SQLite,以使用 mmap() 而不是通常的 read() 和 write() 调用来访问磁盘上的数据库文件。对整个文件使用单个大型映射。假设文件足够小,我们可以毫不费力地在虚拟内存中找到它的空间。
到目前为止,一切都很好。在许多情况下,使用 mmap() 似乎比 read() 和 write() 快一点。在某些情况下更快。
调整映射大小以提交扩展数据库文件的写入事务似乎是一个问题。为了扩展数据库文件,代码可以这样做:
ftruncate(); // extend the database file on disk
munmap(); // unmap the current mapping (it's now too small)
mmap(); // create a new, larger, mapping
然后将新数据复制到新内存映射的末尾。但是,munmap/mmap 是不可取的,因为这意味着下次访问数据库文件的每个页面时都会发生次要页面错误,并且系统必须在 OS 页面缓存中搜索正确的帧以与虚拟内存地址相关联。换句话说,它会减慢后续的数据库读取速度。
在 Linux 上,我们可以使用非标准的 mremap() 系统调用代替 munmap()/mmap() 来调整映射大小。这似乎避免了轻微的页面错误。
问题:在没有 mremap() 的其他系统(如 OSX)上应该如何处理?
目前我们有两个想法。还有一个关于每个问题的问题:
1) 创建大于数据库文件的映射。然后,在扩展数据库文件时,只需调用 ftruncate() 来扩展磁盘上的文件并继续使用相同的映射。
这将是理想的,并且似乎在实践中有效。但是,我们担心手册页中的这个警告:
“更改映射的基础文件大小对与文件的添加或删除区域相对应的页面的影响是未指定的。”
问题:这是我们应该担心的事情吗?还是在这一点上不合时宜?
2) 扩展数据库文件时,使用 mmap() 的第一个参数来请求与位于虚拟内存中当前映射之后的数据库文件的新页面相对应的映射。有效地扩展了初始映射。如果系统不能满足在第一次之后立即放置新映射的请求,则回退到 munmap/mmap。
在实践中,我们发现 OSX 以这种方式定位映射非常好,所以这个技巧在那里有效。
问题:如果系统确实在虚拟内存中的第一个映射之后立即分配第二个映射,那么最终使用对 munmap() 的一次大调用最终取消映射它们是否安全?