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

时间:2013-10-18 23:27:31

标签: 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)数量的连续物理内存可用作内核缓冲区?

3 个答案:

答案 0 :(得分: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。

答案 1 :(得分:1)

问题

  1. 通常,如果你想分配一个DMA缓冲区,或者获得一个物理地址,这是在内核空间中完成的,因为用户代码永远都不需要使用物理地址。
  2. Hugetlbfs仅提供用户空间映射以分配1GB大页面并获取用户空间虚拟地址
  3. 不存在将用户巨页虚拟地址映射到物理地址的功能
  4. EUREKA

    但功能确实存在!埋藏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 largepages
    2. 使用hugetlbfs调用get_huge_pages()以获取用户空间指针(虚拟地址)
    3. 将用户虚拟地址(正常指针强制转换为unsigned long)传递给驱动程序ioctl
    4. KERNEL DRIVER WORKFLOW

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

        声明

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

答案 2 :(得分:0)

如果从巨大空间中分配的用户空间获得了物理地址,则此函数将在内核空间中返回正确的虚拟地址。

static inline void * phys_to_virt(unsigned long address)

在内核代码上查找功能,已通过dpdk和内核模块进行了测试。