gpt4 book ai didi

java - 将文件用于共享内存 IPC

转载 作者:行者123 更新时间:2023-12-01 13:37:20 25 4
gpt4 key购买 nike

在我的应用程序中,有一个进程将数据写入文件,然后响应接收请求,将通过网络将(部分)数据发送到请求进程。这个问题的基础是看当两个进程碰巧在同一台主机上时,我们是否可以加快通信速度。 (在我的例子中,流程是 Java,但我认为这个讨论可以更广泛地应用。)

有一些项目使用 Java 的 FileChannel.map() 返回的 MappedByteBuffers 作为在同一主机上的 JVM 之间共享内存 IPC 的一种方式(请参阅 Chronicle Queue、Aeron IPC 等)。

加速同一主机通信的一种方法是让我的应用程序使用其中一种技术来为同一主机通信提供请求-响应路径,或者结合现有的写入数据文件的机制,或者通过提供通信和写入文件的统一方式。

另一种方法是允许请求进程直接访问数据文件。

我倾向于支持第二种方法——假设它是正确的——因为它更容易实现,并且似乎比为每个请求复制/传输数据副本更有效(假设我们没有替换现有的写入机制到文件)。

本质上,我想了解当两个进程可以访问同一个文件并使用它进行通信时究竟会发生什么,特别是 Java (1.8) 和 Linux (3.10)。

据我了解,如果两个进程同时打开同一个文件,它们之间的“通信”基本上将通过“共享内存”。

请注意,此问题与是否使用 MappedByteBuffer 的性能影响无关 - 与读取和写入文件相比,使用映射缓冲区以及减少复制和系统调用似乎很有可能会减少开销,但是可能需要对应用程序进行重大更改。

以下是我的理解:

  • 当 Linux 从磁盘加载文件时,它会将该文件的内容复制到内存中的页面。该内存区域称为页面缓存。据我所知,无论使用哪种 Java 方法(FileInputStream.read()、RandomAccessFile.read()、FileChannel.read()、FileChannel.map())或 native 方法来读取文件,它都会执行此操作(观察“免费”并监控“缓存”值)。
  • 如果另一个进程尝试加载相同的文件(当它仍然驻留在缓存中时),内核会检测到这一点并且不需要重新加载文件。如果页面缓存已满,页面将被逐出——脏页面被写回磁盘。 (如果有明确的磁盘刷新,页面也会被写回,并且定期使用内核线程)。
  • 缓存中已经有一个(大)文件可以显着提高性能,这比基于我们使用哪些 Java 方法打开/读取该文件的差异要大得多。
  • 如果使用 mmap 系统调用 (C) 或通过 FileChannel.map() (Java) 加载文件,则基本上文件的页面(在缓存中)被直接加载到进程的地址空间中。使用其他方法打开文件,将文件加载到不在进程地址空间中的页面中,然后读取/写入该文件的各种方法将一些字节从/到这些页面复制到进程地址空间中的缓冲区中.避免该副本有明显的性能优势,但我的问题与性能无关。

  • 所以总而言之,如果我理解正确的话——虽然映射提供了性能优势,但它似乎并没有提供任何我们还没有从 Linux 和页面缓存的性质中获得的“共享内存”功能。

    所以,请让我知道我的理解在哪里。

    谢谢。

    最佳答案

    Essentially, I'm trying to understand what happens when two processes have the same file open at the same time, and if one could use this to safely and performantly offer communication between to processes.



    如果您使用常规文件,请使用 readwrite操作(即不是内存映射它们)然后两个进程不共享任何内存。
  • Java 中的用户空间内存 Buffer与文件关联的对象不会跨地址空间共享。
  • 当一个 write进行系统调用时,数据从一个进程地址空间中的页面复制到内核空间中的页面。 (这些可能是页面缓存中的页面。这是特定于操作系统的。)
  • 当一个 read进行系统调用时,数据从内核空间中的页面复制到读取进程地址空间中的页面。

  • 必须这样做。如果操作系统在后台共享与读写器相关的页面处理缓冲区,那么这将是一个安全/信息泄漏漏洞:
  • 读者将能够通过 write(...) 看到作者地址空间中尚未写入的数据。 ,也许永远不会。
  • 写入器将能够看到读取器(假设地)写入其读取缓冲区的数据。
  • 由于内存保护的粒度是页而不是read(...) 的粒度,因此不可能通过巧妙地使用内存保护来解决问题。和 write(...)它只有一个字节。

  • 当然:您可以安全地使用读写文件在两个进程之间传输数据。但是你需要定义一个协议(protocol),让读者知道作者写了多少数据。读者知道作者何时写了一些东西可能需要轮询;例如查看文件是否已被修改。

    如果您仅从通信“ channel ”中的数据复制来看这一点
  • 使用内存映射文件,您可以将数据从应用程序堆对象复制(序列化)到映射缓冲区,然后再从映射缓冲区复制(反序列化)到应用程序堆对象。
  • 对于普通文件,有两个额外的副本:1)从写入进程(非映射)缓冲区到内核空间页面(例如在页面缓存中),2)从内核空间页面到读取进程(非映射)缓冲区.

  • 下面的文章解释了传统的读/写和内存映射发生了什么。 (它是在复制文件和“零复制”的上下文中,但您可以忽略它。)

    引用:
  • Zero Copy I: User-Mode Perspective
  • 关于java - 将文件用于共享内存 IPC,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/61962399/

    25 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com