windows - 在Windows上混淆物理内存

标签 windows database-design shared-memory virtual-memory

我有两个线程和一个大型数据集。线程R不断从数据集中读取数据,并向用户展示数据 View 。线程W不断接收远程数据,对其执行一些工作并将其发布到数据集。

线程R需要控制接收数据集一致 View 的粒度。一种解决方案是双重缓冲。 W写一个副本,而R从另一个副本读取,并且当R准备更新时,要么将W的副本原子复制到R(禁止,因为数据集很大且几乎没有变化),要么它们原子地交换副本,并且W带回R的旧版本通过重新应用自上次交换以来的增量更改来复制最新数据(讨厌跟踪这些变化,并且讨厌所有增量都要处理两次)。

我想做的是以下几点:

  • 两个线程独立地保留虚拟只读内存范围,并且两个范围都映射到同一页物理页
  • 线程W安装了一个异常处理程序,该处理程序将写入捕获为只读页面,捕获一个新的物理块,将其映射为可读写,然后让该写入重新尝试
  • ,当R想要原子更新时(R的 View 中已替换W的任何物理页面被释放(或返回到池中),然后这些虚拟内存地址被W的新物理页面支持,然后W将其整个范围标记为只读再次)。

  • 这样可以避免多余的内存副本,跟踪和重新应用增量等需求。

    但是,虽然AFAICT确实允许Windows创建共享内存区域(甚至是自动写时复制内存区域),但它似乎过时了,无法以任何可用的方式显式映射物理页面由W向R发布新 View 。

    我有什么想念的吗? -是否有可能实现这样的事情,即仅通过更改页面映射而无需存储副本即可实现的发布步骤?

    最佳答案

    我认为应该可以通过一些技巧来做一些接近您要求的事情。

    首先,我将描述我认为最简单,最有效,但最不灵活的方法,并将其称为方法A 。要使用这种方法,数据必须按块排列,并且每个块必须完全包含在一个页面中:

  • 使用相同的文件映射对象为W创建一个读/写 View ,为R创建一个写时复制 View 。
  • 每当W想要修改数据块时,它首先会对写时复制 View 中的相应块执行伪写入。

    注意:我相信即使写入并未实际更改内容,写入页面也会导致写入时复制,但是为了安全起见,我建议您避免这种假设,您可以通过在每个页面中都包含一个虚拟字节来做到这一点。数据块,即R将忽略的数据块。然后,W可以递增虚拟字节以确保复制相应的页面。
  • 要进行同步,请丢弃现有的写时复制 View 并创建一个新 View 。

  • 我希望不必要的虚拟写入的开销可以忽略不计,但是对齐块以使它们不与页面边界重叠可能会很不方便。

    如果是这样,方法B 与方法A相同,除了有多个伪字节,它们以一定间隔放置,以确保与该块重叠的每一页至少包含一个伪字节。这会增加虚拟写入的开销,但我不希望它过多。

    但是,W在每次需要进行更改时都要显式地进行这些伪写操作可能很尴尬,例如,如果数据实际上不是按块排列的,或者每个块内都有多个伪字节会很不方便。因此,我们应该考虑使用方法C :
  • 为W创建一个只读 View 和一个读写 View ,以及为R创建一个写时复制 View 。让W使用只读 View 读取数据,但使用读写 View 写入数据。
  • 使用VirtualProtect和PAGE_GUARD保护读写 View 中的所有页面。
  • 触发保护页错误时,让异常处理程序对写时复制 View 中的相应页面进行虚拟写入。在我看来,矢量异常处理程序看起来像最干净的选项。

    注意:我的研究表明,尽管它涉及在页面错误处理程序中故意调用页面错误,但并不是很确定。应该支持它,因为没有任何异常处理程序可以确保它不会引用被调出的数据,但是由于我没有找到明确的声明来建议这样做,因此建议进行一些实验。

  • 方法C的效率可能不如A或B,因为它需要处理额外的页面错误异常,并具有往返内核模式和返回的相应额外往返过程。我也不确定跟踪保护页面所涉及的页面表开销。但是,这可能会更方便,因为从处理代码中消除伪写入会减少该代码需要注意缓冲的程度。

    最终的变体避免了通过使用单个 View 来使处理代码完全意识到缓冲的需要,而不管W是正在读取还是正在写入。 方法D 如下:
  • 为W创建一个读/写 View ,为R创建一个写时复制 View 。
  • 使用VirtualProtect将读/写 View 的所有页面上的权限更改为只读。
  • 触发页面错误时,让异常处理程序将有故障的页面上的权限更改为读/写,并在写时复制 View 中对相应页面进行虚拟写操作。

  • 我认为这种方法效率最低,因为我希望显式更改块的权限比使用保护页面要慢得多。这也可能导致页表的更多碎片。但是,如果事实证明它可以充分发挥作用,那几乎肯定是最方便的解决方案。

    一些附加说明:

    我相信所有这些方法都应该起作用,但要注意一个警告,即处理第一个页面错误时触发第二个页面错误会发生什么。我对不同变体的比较效率没有信心。进行一些比较测试可能是明智的。

    文件映射对象可能应该由页面文件支持,并且您可能想尝试使用大页面。这将增加需要复制的数据量,但会减少页表上的负载。同样,比较测试可能是适当的。

    我认为您已经考虑了这一点,但是 future 的读者应该注意,根据数据的性质,根本不宜使用映射。例如:
  • 可以为数据块提供两个修订号,一个修订号表示该块何时生效,另一个指示何时将其删除。这种方法的时间开销很小,R在处理一个块时只需要检查修订号,以便它可以跳过太新的块并删除过时的块。这涉及较少的数据复制:W只需要复制它正在处理的数据块,而不是整个页面,并且添加/删除块根本不需要复制任何数据。
  • 如果需要按特定顺序链接块,则修订号可能不够,但是您可以为R和W设置单独的链。同步将要求您重新链接块,但是与修改相比,它仍然可能更快。页表。
  • 关于windows - 在Windows上混淆物理内存,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/33152126/

    相关文章:

    c++ win32如何重命名文件夹/目录

    java - 动态查询 : values and number of attributes determined by user

    c++ - 关于Boost共享内存的问题

    c++ - 使用 api "CreateFileMapping"创建后是否可以增加共享内存大小?

    c - Linux C : Accessing shared memory fails with `Invalid Argument` even though it was just created

    windows - Eclipse maven-jar-plugin 未签名,因为重复条目 pom.xml 和 pom.properties 仅在 Windows 上

    windows - 如何禁用 Qt QMessageBox 播放的声音?

    java - POI - 在 Excel 中打开时无法写入文件?

    database-design - 数字属性维度表中的空值

    Mongodb SaaS 单数据库与多数据库