Linux mremap没有释放旧的映射?

Elo*_*off 8 c linux

我需要一种方法将页面从一个虚拟地址范围复制到另一个虚拟地址范围而不实际复制数据.范围很大,延迟很重要.mremap可以做到这一点,但问题是它还会删除旧的映射.因为我需要在多线程环境中执行此操作,所以我需要同时使用旧映射,稍后当我确定没有其他线程可以使用它时,我将释放它.这有可能,但是hacky,没有修改内核?该解决方案只需要使用最新的Linux内核.

Nom*_*mal 11

虽然存在特定于体系结构的缓存一致性问题,但您可能需要考虑这些问题.某些体系结构根本不允许同时从多个虚拟地址访问同一页面而不会失去一致性.因此,一些架构将管理这一点,其他架构则不然.

编辑补充:AMD64架构程序员手册vol.2,系统编程,第7.8.7节更改内存类型,说明:

物理页面不应该通过不同的虚拟映射分配给它的不同缓存类型; 它们应该是所有可缓存类型(WB,WT,WP)或所有非缓存类型(UC,WC,CD).否则,这可能会导致缓存一致性丢失,从而导致过时的数据和不可预测的行为.

因此,在AMD64上,mmap()对于相同的文件或共享内存区域应该是安全的,只要相同prot并且flags被使用; 它应该使内核对每个映射使用相同的可缓存类型.


第一步是始终使用内存映射的文件备份.使用mmap(NULL, length, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_NORESERVE, fd, 0)以便映射不保留交换.(如果你忘记了这一点,你会比很多工作负载达到实际的实际限制更快地遇到交换限制.)文件支持造成的额外开销绝对可以忽略不计.

编辑添加:用户strcmp指出当前内核不会将地址空间随机化应用于地址.幸运的是,这很容易修复,只需提供随机生成的地址mmap()而不是NULL.在x86-64上,用户地址空间为47位,地址应该是页面对齐的; 您可以使用例如Xorshift*来生成地址,然后屏蔽掉不需要的位:例如,& 0x00007FFFFE00000将提供2097152字节对齐的47位地址.

由于支持是文件,因此在使用扩展后备文件后,可以创建到同一文件的第二个映射ftruncate().只有在适当的宽限期之后 - 当你知道没有线程正在使用映射时(可能使用原子计数器来跟踪它?) - ,你取消映射原始映射.

实际上,当需要放大映射时,首先放大后备文件,然后尝试mremap(mapping, oldsize, newsize, 0)查看映射是否可以增长,而无需移动映射.仅当就地重新映射失败时,您是否需要切换到新映射.

编辑添加:您肯定希望使用mremap()而不是仅使用mmap()MAP_FIXED创建更大的映射,因为mmap()取消映射(原子)任何现有映射,包括属于其他文件或共享内存区域的映射.有了mremap(),你如果放大映射将与现有的映射重叠的错误; 使用mmap()MAP_FIXED,忽略新映射重叠的任何现有映射(未映射).

不幸的是,我必须承认我没有验证内核是否检测到现有映射之间的冲突,或者它是否只是假设程序员知道这种冲突 - 毕竟,程序员必须知道每个映射的地址和长度,因此应该知道映射是否会与现有映射冲突.编辑补充:3.8系列内核做,返回MAP_FAILEDerrno==ENOMEM在放大映射将与现有的地图碰撞.我希望所有的Linux内核都能以相同的方式运行,但除了在x86_64上测试3.8.0-30-generic外,没有任何证据.

另外请注意,在Linux中,POSIX共享存储器是使用特殊的文件系统来实现,典型地安装在tmpfs的/dev/shm(或/run/shm/dev/shm作为一个符号链接).在shm_open()等人.al由C库实现.我没有使用大型POSIX共享内存功能,而是亲自使用特殊安装的tmpfs在自定义应用程序中使用.如果不是其他任何东西,安全控件(能够在那里创建新"文件"的用户和组)更容易管理.


如果映射是,并且必须是匿名的,您仍然可以使用它mremap(mapping, oldsize, newsize, 0)尝试调整它的大小; 它可能会失败.

即使有数十万个映射,64位地址空间也很大,故障情况很少见.因此,虽然您也必须处理故障情况,但它不一定非常. 编辑修改:在X86-64,地址空间是47位,和映射必须在页面边界开始(正常页12位,为2M大页面21位,和1G大页面30位),所以只有映射的地址空间中有35,26或17位可用.因此,即使建议使用随机地址,冲突也会更频繁.(对于2M映射,1024点的地图有一个偶然的碰撞,但在65536个地图,碰撞的概率(调整失败)约为2.3%.)

编辑补充:用户在的strcmp评论指出,默认的Linux mmap()将返回连续的地址,在这种情况下,越来越多的映射总是会失败,除非它是最后一个,或一个地图是映射的就在那里.

我所知道的在Linux中工作的方法很复杂,而且非常适合于架构.您可以将原始映射重新映射为只读,创建新的匿名映射,并在那里复制旧内容.你需要一个SIGSEGV处理器(SIGSEGV被上升信号,试图写入到现在只读映射的特定线程,为数不多的可收回这是一个SIGSEGV在Linux的情况下,即使POSIX不同意),用于检查导致问题的指令,模拟它(改为修改新映射的内容),然后跳过有问题的指令.在宽限期之后,当没有更多线程访问旧的,现在只读映射时,您可以拆除映射.

当然,所有的肮脏都在SIGSEGV处理程序中.它不仅必须能够解码所有机器指令并模拟它们(或者至少是那些写入内存的指令),而且它还必须忙 - 等待新映射尚未完全复制.它很复杂,绝对不可移植,而且非常具有架构特性......但可能.

  • @strcat:也许你应该考虑提供自己的答案,而不仅仅是因为你的"感受"而贬低.坦率地说,如果你无法指出问题是无用的还是小事,我会看到一个答案.我有点生气吗?我关心的唯一原因是我关心我的答案的*质量* - 我自己从不投票给其他答案 - 并且我随时准备承认我的错误,并尝试解决它.到目前为止,我有点喜欢在我的200个答案中只有两个downvotes.你的第三个,也是我根本无法理解的第一个. (2认同)