如何在Linux内核模块中分配由1GB HugePages支持的DMA缓冲区?

muu*_*lla 8 c linux linux-kernel dma huge-pages

我正在尝试为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_Free:64 HugePages_Rsvd:0 HugePages_Surp:0 Hugepagesize:1048576 kB DirectMap4k:848 kB DirectMap2M:2062336 kB DirectMap1G:132120576 kB

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

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

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

muu*_*lla 5

问题

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

尤里卡

但这个功能确实存在!这个函数深埋在 2.6 内核源代码中,用于从虚拟地址获取结构页,标记为“仅用于测试”并用 #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;
}
Run Code Online (Sandbox Code Playgroud)

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

用户端工作流程

  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 上工作。在更高的内核版本上可能有更简单的方法来做到这一点,或者现在可能完全不可能。