mremap(2) 与 HugeTLB 改变虚拟地址?

mremap(2) with HugeTLB to change virtual address?

Linuxmremap(2)函数是否可以将mmap()获得的HugeTLB的虚拟地址更改为新的固定虚拟地址?

(背景:我想根据我得到的内存的物理地址重新映射虚拟地址。这是通过直接检查指针地址来有效地执行虚拟地址到物理地址的转换。我将使用内存进行 DMA来自用户空间的硬件。)

这似乎不适用于我的简单测试程序:

#define _GNU_SOURCE
#include <stdio.h>
#include <sys/mman.h>
#include <stdint.h>

#define LARGE_PAGE_SIZE (1024*1024*1024)

int main() {
  void *p1;
  void *p2;
  p1 = mmap(NULL, LARGE_PAGE_SIZE, PROT_READ|PROT_WRITE,
    MAP_SHARED|MAP_ANONYMOUS|MAP_HUGETLB|MAP_LOCKED,
    0, 0);
  if (p1 == MAP_FAILED) {
perror("mmap");
return 1;
  }
  printf("p1 = %p\n", p1);
  p2 = mremap(p1, LARGE_PAGE_SIZE, LARGE_PAGE_SIZE,
      MREMAP_MAYMOVE|MREMAP_FIXED,
      (void*)(((uint64_t)p1) | 0x500000000000ULL));
  if (p2 == MAP_FAILED) {
perror("mremap");
return 1;
  }
  printf("p2 = %p\n", p2);
}

mmap() 成功,mremap() 失败:

$ gcc -o mremap_hugetlb mremap_hugetlb.c && sudo ./mremap_hugetlb
p1 = 0x2aaac0000000
mremap: Invalid argument

注意新地址是从原来的mmap()得到的地址计算出来的。这很重要。无法提前知道所需的地址,因此我不能简单地将 MAP_FIXED 传递给 mmap()。

我目前使用的解决方法是使 mmap() 文件支持,这样我就可以在固定地址再次对它进行 mmap(),并 munmap() 旧映射。这是次优的,因为它需要我找到一个挂载的 hugetlbfs 文件系统,我不喜欢这种依赖的复杂性。

基于解决方法的当前代码: https://github.com/lukego/snabbswitch/blob/straightline/src/core/memory.c#L56

现在看来您必须使用 hugetlbfs。

除非我记错了,问题出现在Linux内核因为mm/mremap.c:mremap_to() calls mm/mremap.c:vma_to_resize(), which fails with EINVAL for huge pages.

也许测试不正确,或者函数缺少正确处理大页面的代码。我想知道是否应该联系 linux-kernel and linux-mm 邮件列表,看看这是否是一个 should/could 可以轻松修复的错误。但是,这对依赖当前(和旧版)内核的用户没有帮助。

请记住,在文件描述符上使用 mmap() 时,您通常会使用不同的代码路径,因为每个文件系统都可以指定自己的 mmap 处理程序。对于 hugetlbfs,代码在 fs/hugetlbfs/inode.c:hugetlbfs_file_mmap() 中。 而且,正如您所说,该代码路径似乎适合您。

请注意,最好让用户配置 hugetlbfs 挂载点,而不是从 /proc/mounts 扫描一个挂载点,因为这样系统管理员可以配置多个 hugetlbfs 挂载点,每个都有不同的配置,用于服务器上的每个服务 运行。 (我希望您的服务不需要 运行 作为 root。)

我找到了一个看起来更好的解决方案:POSIX 共享内存 (shm)。

shm API 能够分配 HugeTLB 页面并多次映射它们,即使没有 hugetlbfs 文件系统可用。我使用 shmget 分配 HugeTLB,然后可以使用 shmat.

对其进行任意次映射