Lie*_*yan 6 linux locking mmap file copy-on-write
根据mmap()手册页:
MAP_PRIVATE
创建私有写时复制映射。映射的更新对于映射同一文件的其他进程来说是不可见的,并且不会传递到底层文件。 未指定 mmap() 调用后对文件所做的更改是否在映射区域中可见。
问题:如何防止在 mmap() 处理文件后对底层文件的更改对我的程序可见?
背景:我正在为文本编辑器设计一个数据结构,旨在允许有效地编辑巨大的文本文件。数据结构类似于磁盘上的绳索,但实际字符串是指向原始文件中 mmap() 范围的指针。
由于文件可能非常大,因此设计有一些限制:
不得将整个文件加载到 RAM 中,因为该文件可能大于可用的物理 RAM
不得在打开时复制文件,因为这会使打开新文件变得非常慢
必须适用于不支持写时复制 ( cp --reflink/ ioctl_ficlone)的文件系统,例如 ext4
不得依赖强制文件锁定,因为这已被弃用,并且需要-o mand文件系统中的特定挂载选项
只要更改在我的 mmap() 中不可见,底层文件就可以在文件系统上更改
只需要支持最近的Linux并且使用Linux特定的系统API就可以了
我正在设计的数据结构将通过将范围的开始和结束索引存储到 mmap()-ed 缓冲区中来跟踪文件中未编辑和已编辑范围的列表。当用户浏览文件时,从未被用户修改过的文本范围将直接从mmap()原始文件中读取,而交换文件将存储用户已编辑但已更改的文本范围。没有被拯救。
当用户保存文件时,数据结构会使用copy_file_range来拼接交换文件和原始文件以组装新文件。为了使这种拼接发挥作用,我的程序所看到的原始文件必须在整个编辑会话期间保持不变。
问题:在我的文本编辑器中进行未保存的更改后,用户可能同时让其他程序修改同一文件,可能是其他文本编辑器或就地修改文本文件的其他程序。
在这种情况下,编辑器可以使用 inotify 检测此类外部更改,然后我想为用户提供两个关于如何继续的选项:
放弃所有未保存的更改并从磁盘重新读取文件,实现此选项相当简单
允许用户继续编辑文件,稍后用户应该能够将未保存的更改保存在新位置或覆盖其他程序所做的更改,但实现这一点似乎很棘手
由于我的编辑器在打开文件时没有复制该文件,因此当其他程序覆盖该文件时,我的数据结构正在跟踪的文本范围可能会变得无效,因为磁盘上的数据已更改,并且这些更改现在已更改通过我可见mmap()。这意味着,如果我的编辑器在从另一个进程修改文件后尝试写入未保存的更改,则可能会使用新文件中的数据来拼接旧文件中的文本范围,这可能意味着我的编辑器可能会生成保存未保存的更改时损坏的文件。
我不认为咨询锁在所有情况下都能挽救这种情况,因为其他程序可能不支持咨询锁。
我理想的解决方案是,当其他程序覆盖该文件时,系统应该透明地复制该文件,以允许我的程序继续看到旧版本,而其他程序完成对磁盘的写入并使其版本在文件中可见。文件系统。我认为 ioctl_ficlone 可以使这成为可能,但据我了解,这只适用于像 btrfs 这样的写时复制文件系统。
这样的事可能吗?
任何其他解决此问题的建议也将受到欢迎。
你想要做的事情是不可能的mmap,而且我不确定你的限制是否可能。
当您映射一个区域时,内核实际上可能会也可能不会将其全部加载到内存中。缺少数据的内存区域实际上包含无效页面,因此当您访问它时,内核会发生页面错误并将该区域映射到内存中。该区域可能包含发生页面错误时文件该部分中的所有内容。有一个选项 ,MAP_LOCKED它会尝试对所有页面进行预故障处理,但不能保证它成功,因此您不能依赖它正常工作。
一般来说,您无法阻止其他进程更改您下的文件。有些工具(包括编辑器)会在旁边写入一个新文件,调用rename覆盖该文件,有些工具会就地重写文件。前者是您想要的,但许多编辑器选择后者,因为它保留了您无法恢复的 ACL 和权限等特征。
此外,您确实不想mmap在任何无法完全控制的文件上使用,因为如果另一个进程截断该文件并且您尝试访问缓冲区的该部分,您的进程将因SIGBUS. 捕捉到这个信号是未定义的行为,唯一明智的做法就是死。(此外,它也可以在其他情况下发送,例如未对齐的访问,并且您将很难区分它们。)
最终,如果您对复制文件不感兴趣,则无法保证您下面的人不会进行更改,并且您需要为此做好准备。