c – 没有释放旧映射的Linux mremap?

前端之家收集整理的这篇文章主要介绍了c – 没有释放旧映射的Linux mremap?前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。
我需要一种方法页面从一个虚拟地址范围复制到另一个虚拟地址范围而不实际复制数据.范围很大,延迟很重要. mremap可以做到这一点,但问题是它还删除了旧的映射.因为我需要在多线程环境中执行此操作,所以我需要同时使用旧映射,稍后当我确定没有其他线程可以使用它时,我将释放它.这是否可行,无论如何hacky,而不修改内核?该解决方案只需要使用最新的 Linux内核.

解决方法

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

编辑添加AMD64 Architecture Programmer’s Manual vol. 2,System Programming,第7.8.7节更改内存类型,状态:

A physical page should not have differing cacheability types assigned to it through different virtual mappings; they should be either all of a cacheable type (WB,WT,WP) or all of a non-cacheable type (UC,WC,CD). Otherwise,this may result in a loss of cache coherency,leading to stale data and unpredictable behavior.

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

第一步是始终使用内存映射的文件备份.使用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系列内核,如果放大的映射将与现有映射冲突,则返回带有errno == ENOMEM的MAP_Failed.我希望所有的Linux内核都能以相同的方式运行,但除了在x86_64上测试3.8.0-30-generic外,没有任何证据.

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

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

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

编辑添加用户strcmp在评论中指出,默认情况下Linux mmap()将返回连续的地址,在这种情况下,增长映射将始终失败,除非它是最后一个,或者地图在那里未映射.

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

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

猜你在找的C&C++相关文章