我需要以最快的方式定期将文件与内存同步。
我认为我想要的是一个mmap'd文件,它只能手动同步到磁盘。我不确定如何防止任何自动同步发生。
除非我手动指定,否则无法修改文件。关键是要有一个检查点文件,它将状态的快照保存在内存中。我想尽可能避免复制,因为这需要相当频繁地调用,速度很重要。
答案 0 :(得分:4)
您在文件的MAP_SHARED
映射中写入内存的任何内容都被视为当时正在写入文件,就像您使用write()
一样。 msync()
在这个意义上与fsync()
完全相同 - 它只是确保您已经已经对文件的更改实际上被推送到永久存储。你无法改变这一点 - 这就是定义mmap()
工作的方式。
通常,执行此操作的安全方法是将完整一致的数据副本写入临时文件,同步临时文件,然后通过先前的检查点文件对其进行原子重命名。这是确保检查点之间崩溃不会使文件不一致的唯一方法。任何执行较少复制的解决方案都需要更复杂的事务日志样式文件格式,并且对应用程序的其余部分更具干扰性(需要在内存状态更改的每个位置调用特定的挂钩)
答案 1 :(得分:2)
mmap
不能用于此目的。没有办法阻止数据写入磁盘。在实践中,使用mlock()
使内存不可浏览可能会产生副作用,防止它被写入磁盘,除非您要求写入它,但不能保证。当然,如果另一个进程打开文件,它会看到副本缓存在内存中(包含您的最新更改),而不是物理磁盘上的副本。在许多方面,您应该做的事情取决于您是否尝试与其他进程进行同步,或仅仅是为了在发生崩溃或电源故障时的安全性。
如果您的数据量很小,您可以尝试使用许多其他方法进行原子同步到磁盘。一种方法是将整个数据集存储在文件名中,并按该名称创建一个空文件,然后删除旧文件。如果启动时存在2个文件(由于崩溃时间极短),请删除较旧的文件并从较新的文件中恢复。如果您的数据大小小于文件系统块,页面大小或磁盘块,write()
可能也可能是原子的,但我不知道这种效果的任何保证。你必须做一些研究。
另一种非常标准的方法,只要您的数据不是那么大,以至于2个副本不适合磁盘:只需创建一个带有临时名称的第二个副本,然后rename()
将其放在顶部旧的。 rename()
总是原子的。这可能是最好的方法,除非你有理由不这样做。
答案 2 :(得分:2)
正如其他受访者所建议的那样,我不认为有一种便携的方法可以做到你想要的而不复制。如果您希望在可以控制操作系统等的特殊用途环境中执行此操作,则可以在Linux下使用btrfs文件系统执行此操作。
btrfs支持新的reflink()
操作,这对于写时复制文件系统副本至关重要。您可以reflink()
将您的文件设置为临时启动,mmap()
临时,然后msync()
和reflink()
临时返回到原始检查点。
答案 3 :(得分:2)
你可以在写入时将文件映射为副本,这样你在内存中执行的任何更新都不会写回文件,然后当你想要同步时,你可以:
A)创建一个不写入时复制的新内存映射,并将您修改后的页面复制到其中。
或者
B)使用直接io打开文件(常规文件打开)(块大小对齐大小读取和写入)并仅写入您修改的页面。 Direct io会很好而且快速,因为你正在编写整个页面(内存页面大小是磁盘块大小的倍数)并且没有缓冲。这种方法有利于不使用地址空间,因为你的mmap很大,而且没有空间去映射另一个巨大的文件。
同步后,写入mmap的副本与磁盘文件相同,但内核仍然需要同步的页面标记为非共享(与磁盘一起)。因此,您可以关闭并重新创建mmap(仍然是写入时复制),如果有内存压力,内核可以在必要时丢弃您的页面(而不是将它们分页到交换空间)。
当然,您必须跟踪自己修改过的页面,因为我无法想到如何访问操作系统保存该信息的位置。 (这不是一个方便的系统调用)
- 编辑 -
实际上,请参阅Can the dirtiness of pages of a mmap be found from userspace?,了解如何查看哪些页面是脏的
答案 4 :(得分:0)
我非常怀疑任何操作系统都可能无法利用它,但操作系统可能会注意到以下优化:
int fd = open("file", O_RDWR | O_SYNC | O_DIRECT);
size_t length = get_lenght(fd);
uint8_t * map_addr = mmap(NULL, length, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
...
// This represents all of the changes that could possibly happen before you
// want to update the on disk file.
change_various_data(map_addr);
if (is_time_to_update()) {
write(fd, map_addr, length);
lseek(fd, 0, SEEK_SET);
// you could have just used pwrite here and not seeked
}
操作系统可能可能利用这个的原因是,在您写入特定页面之前(并且没有其他任何人这样做),操作系统可能只是使用实际文件的页面location作为该页面的交换。
然后,当您写入某些页面时,操作系统将复制 开启 写入这些页面用于您的流程,但仍保留原始文件备份的未写入页面。
然后,在调用write
时,操作系统会注意到写入在内存和磁盘上都是块对齐的,然后它可能会注意到某些源内存页面已经与那些确切的文件系统同步他们被写入的页面,只写出已经改变的页面。
所有这一切,如果没有任何操作系统完成此优化,我不会感到惊讶,这种类型的代码最终会非常慢,并且当您调用'write'时会导致大量磁盘写入。如果被利用的话会很酷。