【问题标题】:How do I allocate a DMA buffer backed by 1GB HugePages in a linux kernel module?如何在 linux 内核模块中分配由 1GB HugePages 支持的 DMA 缓冲区?
【发布时间】:2013-10-27 22:33:02
【问题描述】:

我正在尝试为 HPC 工作负载分配 DMA 缓冲区。它需要 64GB 的缓冲区空间。在计算之间,一些数据被卸载到 PCIe 卡上。我不想将数据复制到 pci_alloc_consistent 给定的一堆 4MB 缓冲区,而是创建 64 个 1GB 缓冲区,由 1GB HugePages 支持。

一些背景信息: 内核版本:CentOS 6.4 / 2.6.32-358.el6.x86_64 内核启动选项:hugepagesz=1g hugepages=64 default_hugepagesz=1g

/proc/meminfo 的相关部分: AnonHugePages:0 kB HugePages_Total:64 HugePages_免费:64 HugePages_Rsvd:0 HugePages_Surp: 0 巨页大小:1048576 kB DirectMap4k:848 kB DirectMap2M:2062336 kB DirectMap1G:132120576 kB

我可以挂载 -t hugetlbfs nodev /mnt/hugepages。 CONFIG_HUGETLB_PAGE 为真。 MAP_HUGETLB 已定义。

我已经阅读了一些关于在用户空间中使用 libhugetlbfs 调用 get_huge_pages() 的信息,但理想情况下,该缓冲区将分配在内核空间中。我尝试使用 MAP_HUGETLB 调用 do_mmap() ,但它似乎并没有改变空闲大页面的数量,所以我认为它实际上并没有支持带有大页面的 mmap。

所以我猜我在说什么,有没有任何方法可以将缓冲区映射到内核空间中的 1GB HugePage,还是必须在用户空间中完成?或者,如果有人知道我可以获得大量(1-64GB)连续物理内存作为内核缓冲区的其他方式吗?

【问题讨论】:

  • 有趣的问题,你的目标主要是避免内核和用户空间之间的复制吗?
  • 所有这些 API 都是针对用户空间的。看看hugetlbfs是怎么实现的,尤其是hugetlbfs_file_mmap
  • @muusbolla 你能找到答案吗?
  • CL.,谢谢,hugetlbfs_file_mmap的定义在这里linux/fs/hugetlbfs/inode.c:lxr.free-electrons.com/source/fs/hugetlbfs/inode.c?v=3.18#L100
  • @ChuckCottrill 是的,目标是在移动大量数据时获得最大性能。因此需要使用可以由设备和用户代码直接读取和访问的巨大的静态 DMA 缓冲区。

标签: c linux linux-kernel dma huge-pages


【解决方案1】:

问题

  1. 通常,如果您想分配 DMA 缓冲区或获取物理地址,这将在内核空间中完成,因为用户代码永远不必乱用物理地址。
  2. Hugetlbfs 仅提供用户空间映射来分配 1GB 大页面,并获取用户空间虚拟地址
  3. 不存在将用户大页面虚拟地址映射到物理地址的函数

尤里卡

但功能确实存在! Buried deep in the 2.6 kernel source code 在于这个函数从一个虚拟地址获取一个结构页面,标记为“仅用于测试”并用#if 0 阻止:

#if 0   /* This is just for testing */
struct page *
follow_huge_addr(struct mm_struct *mm, unsigned long address, int write)
{
    unsigned long start = address;
    int length = 1;
    int nr;
    struct page *page;
    struct vm_area_struct *vma;

    vma = find_vma(mm, addr);
    if (!vma || !is_vm_hugetlb_page(vma))
        return ERR_PTR(-EINVAL);

    pte = huge_pte_offset(mm, address);

    /* hugetlb should be locked, and hence, prefaulted */
    WARN_ON(!pte || pte_none(*pte));

    page = &pte_page(*pte)[vpfn % (HPAGE_SIZE/PAGE_SIZE)];

    WARN_ON(!PageHead(page));

    return page;
}

解决方案: 由于上述函数实际上并未编译到内核中,因此您需要将其添加到驱动程序源中。

用户端工作流程

  1. 使用内核引导选项在引导时分配 1gb 巨页
  2. 使用hugetlbfs调用get_huge_pages()获取用户空间指针(虚拟地址)
  3. 将用户虚拟地址(普通指针转换为 unsigned long)传递给驱动程序 ioctl

内核驱动程序工作流程

  1. 通过 ioctl 接受用户虚拟地址
  2. 调用follow_huge_addr获取struct page*
  3. 在struct page*上调用page_to_phys获取物理地址
  4. 为 DMA 设备提供物理地址
  5. 如果您还需要内核虚拟指针,请在 struct page* 上调用 kmap

免责声明

  • 几年后正在重新收集上述步骤。我无法访问原始源代码。尽职尽责,确保我没有忘记一步。
  • 唯一可行的原因是在引导时分配了 1GB 大页面,并且它们的物理地址被永久锁定。不要尝试将非 1GBhugepage 支持的用户虚拟地址映射到 DMA 物理地址!你会过得很糟糕的!
  • 在您的系统上仔细测试,以确认您的 1GB 大页面实际上已锁定在物理内存中,并且一切正常。这段代码在我的设置中完美运行,但如果出现问题,这里会有很大的危险。
  • 此代码仅保证适用于 x86/x64 架构(其中物理地址 == 总线地址)和内核版本 2.6.XX。在以后的内核版本上可能有更简单的方法来执行此操作,或者现在可能完全不可能。

【讨论】:

  • Don't try to map a non-1GBhugepage-backed user virtual address into a DMA physical address 除非您在可能的情况下固定大页内存。 (get_user_page())
【解决方案2】:

这在内核空间中并不常见,所以例子不多。

就像任何其他页面一样,大页面使用 alloc_pages 分配,这很合适:

struct page *p = alloc_pages(GFP_TRANSHUGE, HPAGE_PMD_ORDER);

HPAGE_PMD_ORDER 是一个宏,根据普通页面定义单个大页面的顺序。以上暗示在内核中启用了透明大页面。

然后你可以用kmap()以通常的方式映射获得的页面指针。

免责声明:我自己从未尝试过,因此您可能需要进行一些试验。要检查的一件事是:HPAGE_PMD_SHIFT 表示较小的“巨大”页面的顺序。如果你想使用那些巨大的 1GB 页面,你可能需要尝试不同的顺序,可能是 PUD_SHIFT - PAGE_SHIFT。

【讨论】:

  • 1GB 页面是否支持透明大页面?
【解决方案3】:

如果给定的物理地址来自分配在hugespace中的用户空间,则此函数返回内核空间中的正确虚拟地址。

static inline void * phys_to_virt(unsigned long address)

在内核代码上寻找函数,用dpdk和内核模块测试。

【讨论】:

    猜你喜欢
    • 2019-11-10
    • 1970-01-01
    • 2014-11-11
    • 1970-01-01
    • 2011-02-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多