如何使用仅手动同步到磁盘的mmap来获取检查点文件

我需要以最快的方式定期将文件与内存同步。

我想我想要的是有一个mmap’d文件,它只能手动同步到磁盘。 我不确定如何防止任何自动同步发生。

除非我手动指定,否则无法修改该文件。 关键是要有一个检查点文件,它将状态的快照保存在内存中。 我想尽可能避免复制,因为这需要相当频繁地调用,速度很重要。

mmap不能用于此目的。 无法阻止数据写入磁盘。 在实践中,使用mlock()使内存不可擦除可能会产生副作用,防止它被写入磁盘,除非您要求写入,但不能保证。 当然,如果另一个进程打开文件,它将看到内存中的副本缓存(包含您的最新更改),而不是物理磁盘上的副本。 在许多方面,您应该做的事情取决于您是否尝试与其他进程进行同步,或仅仅是为了在发生崩溃或电源故障时的安全性。

如果您的数据量很小,您可以尝试许多其他方法进行primefaces同步到磁盘。 一种方法是将整个数据集存储在文件名中,并按该名称创建一个空文件,然后删除旧文件。 如果启动时存在2个文件(由于崩溃时间极短),请删除较旧的文件并从较新的文件恢复。 如果您的数据大小小于文件系统块,页面大小或磁盘块, write() 也可能是primefaces的,但我不知道这种效果的任何保证。 你必须做一些研究。

另一个非常标准的方法,只要您的数据不是那么大,以至于2个副本不适合磁盘:只需创建一个带有临时名称的第二个副本,然后rename()rename()在旧版本之上。 rename()总是primefaces的。 这可能是最好的方法,除非你有理由不这样做。

您在文件的MAP_SHARED映射中写入内存的任何内容都被视为当时正在写入文件,就像您使用了write() 。 从这个意义上说, msync()fsync()完全不同 – 它只是确保你已经对文件所做的更改实际上被推送到永久存储器。 你不能改变它 – 它是如何定义mmap()工作的。

通常,执行此操作的安全方法是将完整一致的数据副本写入临时文件,同步临时文件,然后通过先前的检查点文件对其进行primefaces重命名。 这是确保检查点之间崩溃不会使文件不一致的唯一方法。 任何执行较少复制的解决方案都需要更复杂的事务日志样式文件格式,并且对应用程序的其余部分更具干扰性(需要在内存状态更改的每个位置调用特定的挂钩) 。

正如其他受访者所建议的那样,我不认为有一种便携的方式可以做到你想要的而不复制。 如果您希望在可以控制操作系统等的特殊用途环境中执行此操作,则可以在Linux下使用btrfs文件系统执行此操作。

btrfs支持新的reflink()操作,这对于写时复制文件系统副本至关重要。 您可以reflink()您的文件reflink()启动时临时, mmap()临时,然后msync()reflink()临时返回原来的检查点。

你可以将文件映射为写入时的副本,这样你在内存中执行的任何更新都不会写回文件,然后当你想要同步时,你可以:

A)创建一个不写入时复制的新内存映射,并将您修改后的页面复制到其中。

要么

B)使用直接io打开文件(常规文件打开)(块大小对齐大小读取和写入)并仅写入您修改的页面。 直接io会很好而且快速,因为你正在编写整个页面(内存页面大小是磁盘块大小的倍数)并且没有缓冲。 这种方法具有不使用地址空间的好处,因为你的mmap很大,并且没有空间来映射另一个巨大的文件。

同步之后,写入mmap的副本与磁盘文件相同,但内核仍然需要同步标记为非共享的页面(与磁盘一起)。 因此,您可以关闭并重新创建mmap(仍然是写入时复制),这样内核可以在必要时丢弃您的页面(而不是将它们分页到交换空间),如果有内存压力的话。

当然,您必须跟踪自己修改过哪些页面,因为我无法想到如何访问操作系统保存该信息的位置。 (这不是一个方便的系统调用)

– 编辑 –

实际上,请参阅可以从用户空间找到mmap页面的肮脏吗? 关于如何查看哪些页面是脏的想法

我非常怀疑任何操作系统都可能无法利用它,但操作系统可能会注意到以下优化:

 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 } 

操作系统可能利用此function的原因是,在您写入特定页面之前(并且没有其他人也这样做),操作系统可能只会使用该位置的实际文件页面作为该页面的交换。

然后,当您写入某些页面时,操作系统将为您的进程复制 写入这些页面,但仍保留原始文件备份的未写入页面。

然后,在调用write ,操作系统可能会注意到写入在内存和磁盘上都是块对齐的,然后它可能会注意到某些源内存页面已经与它们被写入的那些确切的文件系统页面同步。并且只写出已经改变的页面。

所有这一切,如果没有任何操作系统完成此优化,我不会感到惊讶,这种类型的代码最终会非常慢,并在您调用’write’时导致大量磁盘写入。 如果被利用的话会很酷。