8

我正在尝试为 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 为真。MAP_HUGETLB 已定义。

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

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

4

3 回答 3

4

问题

  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;
}

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

用户端工作流程

  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。在以后的内核版本上可能有更简单的方法来执行此操作,或者现在可能完全不可能。
于 2017-06-26T09:35:53.480 回答
2

This is not commonly done in the kernel space, so not too many examples.

Just like any other page, huge pages are allocated with alloc_pages, to the tune:

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

HPAGE_PMD_ORDER is a macro, defining an order of a single huge page in terms of normal pages. The above implies that transparent huge pages are enabled in kernel.

Then you can proceed mapping the obtained page pointer in the usual fashion with kmap().

Disclaimer: I never tried it myself, so you may have to do some experimenting around. One thing to check for is this: HPAGE_PMD_SHIFT represents an order of a smaller "huge" page. If you want to use those giant 1GB pages, you will probably need to try a different order, probably PUD_SHIFT - PAGE_SHIFT.

于 2013-10-31T02:04:44.263 回答
0

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

static inline void * phys_to_virt(unsigned long address)

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

于 2019-04-05T15:41:59.813 回答