根据mmap()联机帮助页:
MAP_PRIVATE
创建一个私有的写时复制映射。映射的更新对于映射同一文件的其他进程不可见,也不会传递到基础文件。 尚不确定在映射区域中是否可以看到mmap()调用后对该文件所做的更改。
问题:如何防止mmap()-使文件在我的程序中可见之后对基础文件的更改?
背景:我正在为文本编辑器设计一种数据结构,该结构旨在允许高效地编辑大型文本文件。数据结构类似于磁盘rope,但实际的字符串是指向原始文件中mmap()-ed范围的指针。
由于文件可能很大,因此设计存在一些限制:
不得将整个文件加载到RAM中,因为文件可能大于可用的物理RAM
在打开文件时切勿复制文件,因为这会使打开新文件的速度变慢
必须在不支持写时复制(cp --reflink
/ ioctl_ficlone
)的ext4之类的文件系统上工作
绝不依赖强制性文件锁定,因为它已被弃用,并且需要文件系统中的特定挂载选项-o mand
只要我的mmap()中看不到更改,就可以在文件系统上更改基础文件了
只需要支持最新的Linux,并且可以使用特定于Linux的系统API
我正在设计的数据结构通过将范围的开始和结束索引存储到mmap()-ed缓冲区中来跟踪文件中未编辑和已编辑范围的列表。当用户浏览文件时,用户从未修改过的文本范围将直接从原始文件的mmap()
中读取,而交换文件将存储已编辑的文本范围由用户但尚未保存。
当用户保存文件时,数据结构将使用copy_file_range来拼接交换文件,并使用原始文件来组合新文件。为了使该拼接生效,在整个编辑过程中,我的程序所看到的原始文件必须保持不变。
问题:在我的文本编辑器中进行未保存的更改后,用户可能同时有其他程序修改同一文件,可能还有其他文本编辑器或一些其他就地修改了文本文件的程序。
在这种情况下,编辑器可以使用inotify来检测这种外部更改,然后我想给用户两个有关如何继续执行此操作的选择:
放弃所有未保存的更改,然后从磁盘重新读取文件,实现此选项非常简单
允许用户继续编辑文件,以后用户应该能够将未保存的更改保存在新位置或覆盖其他程序所做的更改,但是实现起来似乎很棘手
由于我的编辑器在打开文件时未复制文件,因此当其他程序覆盖文件时,由于磁盘上的数据已更改,因此我的数据结构正在跟踪的文本范围可能无效。现在可以通过我的mmap()
看到更改。这意味着,如果我的编辑器试图在文件从另一个进程修改后写入未保存的更改,则可能是使用新文件中的数据将旧文件中的文本范围拼接起来,这可能意味着我的编辑器可能正在生成一个保存未保存的更改时文件损坏。
我不认为在所有情况下顾问锁都不会挽救局面,因为其他程序可能不遵守顾问锁。
我的理想解决方案是制作一个文件,以便当其他程序覆盖文件时,系统应透明地复制文件,以使我的程序可以继续查看旧版本,而其他程序完成对磁盘的写入并制作其版本在文件系统中可见。我认为ioctl_ficlone可以做到这一点,但据我了解,这仅适用于像btrfs这样的写时复制文件系统。
这可能吗?
任何其他解决此问题的建议也将受到欢迎。
答案 0 :(得分:2)
mmap
无法实现您想做的事情,而且我不确定您的约束是否完全可以实现。
映射区域时,内核可能会或可能不会将所有区域实际加载到内存中。缺少数据的内存区域实际上将包含一个无效页面,因此,当您访问它时,内核会出现页面错误并将该区域映射到内存中。发生页面错误时,该区域可能包含文件那部分中的任何内容。有一个MAP_LOCKED
选项,它会尝试对所有页面进行预故障处理,但是并不能保证会自动出错,因此您不能依靠它来工作。
通常,您不能阻止其他进程将文件从您身下更改出来。有些工具(包括编辑器)会将新文件写到一边,调用rename
来覆盖文件,而一些工具会就地重写文件。前者是您想要的,但是许多编辑者选择后者,因为它保留了ACL和无法恢复的权限等特征。
此外,您真的不想在无法完全控制的任何文件上使用mmap
,因为如果另一个进程将文件截断并且您尝试访问缓冲区的该部分,则该进程将死掉与SIGBUS
。捕获此信号是不确定的行为,唯一明智的做法是死亡。 (此外,它还可以在其他情况下发送,例如未对齐的访问,您将很难区分它们。)
最终,如果您对复制文件不感兴趣,则不能保证别人不会在您下面进行更改,因此您需要为此做好准备。