gpt4 book ai didi

c - mmap()用于远程文件

转载 作者:行者123 更新时间:2023-12-03 09:53:33 32 4
gpt4 key购买 nike

当前,我正在实现mmap()的版本,其目的是在客户端计算机上映射远程文件。对于该实现,我不能使用任何内置的或第三方库。话虽如此,我不确定实现是否将基于以下两个选择之一:

  • 从客户端读取文件内容后,将文件加载到客户端计算机上,并使用从客户端计算机或
  • 获得的文件描述符来使用 mmap()系统调用
  • 通过使用sbrk()
  • 为客户端接收的每个文件数据块分配内存

    任何建议将不胜感激!

    最佳答案

    在Linux中,甚至对于多线程进程,即使是线程安全的方式也可以做到这一点,但是您需要自己或使用某些库来实现一个非常困难的功能。

    您将需要使用类似于以下的接口(interface)来解码和模拟任何内存访问指令

    static void emulate(mcontext_t *const context,
    void (*fetch)(void *const data,
    const unsigned long addr,
    size_t bytes),
    void (*store)(const unsigned long addr,
    const void *const data,
    size_t bytes));

    解码指令位于x86上的 (void *)context->gregs[REG_IP]和x86-64上的 (void *)context->gregs[REG_RIP]。函数 必须通过增加context->gregs[REG_IP]/context->gregs[REG_RIP]/etc来使跳过指令。通过机器指令中的字节数。如果您不这样做, SIGSEGV将一次又一次地引发,程序代码将卡在该指令中!

    该函数必须仅使用 fetchstore回调来访问导致SEGV的内存。在您的情况下,它们将实现为与远程计算机联系的功能,要求远程计算机对指定的字节执行所需的操作。

    假设您实现了以上三个功能,其余的功能就微不足道了。为简单起见,假设您有
    static void   *map_base;
    static size_t map_size;
    static void *map_ends; /* (char *)map_base + map_size */

    static void sigsegv_handler(int signum, siginfo_t *info, void *context)
    {
    if (info->si_addr >= map_base && info->si_addr < map_ends) {
    const int saved_errno = errno;
    emulate(&((ucontext_t *)context)->uc_mcontext,
    your_load_function, your_store_function);
    errno = saved_errno;
    } else {
    struct sigaction act;
    sigemptyset(&act.sa_mask);
    act.sa_handler = SIG_DFL;
    act.sa_flags = 0;
    if (sigaction(SIGSEGV, &act, NULL) == 0)
    raise(SIGSEGV);
    else
    raise(SIGKILL);
    }
    }

    static int install_sigsegv_handler(void)
    {
    struct sigaction act;
    sigemptyset(&act.sa_mask);
    act.sa_sigaction = handle_sigsegv;
    act.sa_mask = SA_SIGINFO;
    if (sigaction(SIGSEGV, &act, NULL) == -1)
    return errno;
    return 0;
    }

    如果已经从远程计算机上获得了 map_size(并四舍五入为 sysconf(_SC_PAGESIZE)),那么您只需要执行
    if (install_sigsegv_handler()) {
    /* Failed; see errno. Abort. */
    }

    map_base = mmap(NULL, map_size, PROT_NONE,
    MAP_PRIVATE | MAP_ANONYMOUS, -1, (off_t)0);
    if ((void *)map_base != MAP_FAILED)
    map_ends = (void *)(map_size + (char *)map_base);
    else {
    /* Failed; see errno. Abort. */
    }

    既然我已经害怕每个人都从大脑中读到这篇文章,我很高兴地也提到有一种更简单,可移植的方式来做到这一点。它也往往更有效。

    这不是“映射远程文件的内存”,而是一种协作方案,其中多台计算机可以共享一个映射。 从用户的 Angular 来看,这几乎是同一件事,但是使用映射的所有各方都必须参与工作。

    不要尝试捕获对映射区域的每次访问,而应使用页面粒度并引入页面所有者的概念:映射的每个页面一次最多只能在一台拥有该页面的计算机上访问。

    内存映射作用于页面大小的单元(请参阅 sysconf(_SC_PAGESIZE) )。您不能将特定字节或任意字节范围设置为不可访问或只读-除非它与页面边界对齐。您可以将任何页面更改为可读和可写,仅可读或不可访问(分别为 PROT_READ|PROT_WRITEPROT_READPROT_NONE;请参阅 mmap() mprotect() )。

    所有者的概念很简单。当机器拥有页面时,它可以自由地对该页面进行读写操作,否则就不可以。注意:如果有文件支持,则很难以原子方式更新映射的文件内容。我真的推荐一种没有后备文件的方法-或者使用基于 fcntl()的租约或锁定以页面大小的块更新后备文件。)

    简而言之,映射中的每个页面恰好在一台机器上是 PROT_READ|PROT_WRITE,在其他所有机器上都是 PROT_NONE

    当有人尝试写入只读页面时,将触发该计算机上的 SIGSEGV处理程序。它与其他计算机联系,并请求该特定页面的所有权。当时的所有者接收到这样的消息,将其映射更改为 PROT_NONE,并将页面发送给新的所有者。新的所有者更新映射,将保护更改为 PROT_READ|PROT_WRITE,并从 SIGSEGV处理程序返回。

    一些注意事项:
  • 如果SIGSEGV处理程序在映射发生更改之前返回,则不会发生任何不良情况。 SIGSEGV信号只需立即通过同一指令重新产生。
  • 我建议使用一个单独的线程来接收页面,并更新映射的本地内容。然后,SIGSEGV处理程序仅需要确保已发送了对该页面和sched_yield()的所有权的请求,从而不必不必要地旋转或“打动拇指”。

    当该页面的映射更新时,程序将继续执行。 send()等是异步信号安全的,因此您可以直接从信号处理程序发送请求-但并不是不想在每个时间段(每秒100-1000次!)发送一次请求每一次。

    请记住:如果SIGSEGV信号处理程序不能解决问题,则不会造成任何伤害。通过相同的指令,SIGSEGV再次立即被提升。但是,我强烈建议您使用 sched_yield() ,以便机器上的其他线程和进程可以使用CPU,而不是浪费CPU时间每秒发出数百万次信号。
  • 如果写很少见,但读很常见,则可以将所有权概念扩展到读所有者和写所有者。每个页面可以由任意数量的读取所有者拥有,只要没有写所有者即可。要修改页面,一个页面必须是写拥有者,而该页面必须撤销所有读拥有者。

    这种逻辑使得任何线程都可以请求读取所有权。如果没有写拥有者,则会自动授予它;最后的写拥有者或任何现有的读拥有者都将发送只读页面内容。如果存在写所有者,它必须将其所有权降级为只读所有者,然后将现在的只读内容发送给请求者。要修改页面,一个页面必须已经是一个读取所有者,并简单地告诉所有其他读取所有者它们现在是写入所有者。

    在这种情况下,SIGSEGV处理程序并不复杂。如果页面保护是PROT_NONE,它将要求读取所有权。如果页面保护是PROT_READ,则它已经具有读取所有权,因此必须要求将其升级为写入所有权。注意:使用此方案,我们不需要检查指令是否尝试访问用于获取或存储的内存-实际上,这甚至没有关系。在最坏的情况下-写入该线程不以任何方式拥有的页面-SIGSEGV只会被提升两次:第一次获得读取所有权,第二次将其升级为写入所有权。

    请注意,您不能在SIGSEGV处理程序中将读取所有权升级为写入所有权。如果这样做,则在消息到达其他方之前,不同机器上的两个线程可以同时升级其读取所有权。所有状态更改只能在所有必要的确认TCP消息到达之后发生。

    (由于多对多消息仲裁非常复杂,因此最好由指定的仲裁程序(或“服务器”)处理每个 child 的所有请求。在成员之间,页面传输仍然可以直接进行,尽管您可以确实也需要将每次页面传输的通知发送给仲裁器/服务器。)
  • 如果没有备份文件-即MAP_ANONYMOUS-您可以原子替换任何页面的内容。

    接收页面时,首先使用mmap(NULL, page, PROT_READ[|PROT_WRITE], MAP_PRIVATE|MAP_ANONYMOUS, -1, (off_t)0)获得一个新的匿名页面,然后将新数据复制到其中。然后,使用 mremap() 将旧页面替换为新页面。 (有效地释放了旧页面,就好像调用了munmap()一样,但这都是原子发生的,因此没有线程看到任何中间状态。)

  • 这样,您将只发送页面大小的块。为了实现可移植性,您实际上应该使用所涉及的所有页面大小中的最小公倍数,以便每台计算机都可以参与其中,无论它们可能存在的页面大小差异如何。 (幸运的是,它们始终是2的幂,通常是4096的幂,尽管我似乎确实记得使用512、2048、8192、16384、32768、65536和2097152字节页的体系结构,所以请不要硬用-编码您的页面大小。)

    总体而言,这两种方法都有其好处。第一个(需要指令仿真器)允许任意数量的客户端访问一个服务器上的内存映射,而无需任何其他映射到服务器上相同文件的合作。第二个需要使用映射的各方合作,但是减少了多个连续访问的访问延迟;使用读取拥有者/写入拥有者逻辑,您应该获得非常高效的共享内存管理。

    如果您在一方面决定 brk()/ sbrk(),另一方面决定 mmap()时遇到困难,那么我确实担心这两种方法对您而言都太复杂了。您应该首先了解内存映射的固有限制(页面粒度等),甚至可能了解某些缓存理论(因为这实际上是缓存数据),以便您可以相对轻松地管理所涉及的概念。

    相信我,尝试编写一些您在概念上无法真正掌握的东西会导致沮丧。也就是说,捕获概念,花一些时间在编程时学习它们就可以了;您只需要花费时间和精力。

    有什么问题吗

    关于c - mmap()用于远程文件,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/24351359/

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