如何在 linux 内核模块中分配由 1GB HugePages 支持的 DMA 缓冲区? [英] How do I allocate a DMA buffer backed by 1GB HugePages in a linux kernel module?
问题描述
我正在尝试为 HPC 工作负载分配 DMA 缓冲区.它需要 64GB 的缓冲空间.在计算之间,一些数据被卸载到 PCIe 卡.与其将数据复制到由 pci_alloc_consistent 提供的一堆 4MB 小缓冲区中,我只想创建 64 个 1GB 缓冲区,并由 1GB HugePages 提供支持.
I'm trying to allocate a DMA buffer for a HPC workload. It requires 64GB of buffer space. In between computation, some data is offloaded to a PCIe card. Rather than copy data into a bunch of dinky 4MB buffers given by pci_alloc_consistent, I would like to just create 64 1GB buffers, backed by 1GB HugePages.
一些背景信息:内核版本:CentOS 6.4/2.6.32-358.el6.x86_64内核启动选项:hugepagesz=1g hugepages=64 default_hugepagesz=1g
Some background info: kernel version: CentOS 6.4 / 2.6.32-358.el6.x86_64 kernel boot options: hugepagesz=1g hugepages=64 default_hugepagesz=1g
/proc/meminfo 的相关部分:AnonHugePages:0 kBHugePages_Total: 64HugePages_Free: 64HugePages_Rsvd: 0HugePages_Surp: 0超大页面大小:1048576 kBDirectMap4k:848 KBDirectMap2M:2062336 KBDirectMap1G:132120576 kB
relevant portion of /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 已定义.
I can mount -t hugetlbfs nodev /mnt/hugepages. CONFIG_HUGETLB_PAGE is true. MAP_HUGETLB is defined.
我已经阅读了一些关于在用户空间中使用 libhugetlbfs 调用 get_huge_pages() 的信息,但理想情况下该缓冲区将分配在内核空间中.我尝试使用 MAP_HUGETLB 调用 do_mmap() 但它似乎没有改变免费大页面的数量,所以我认为它实际上并没有用大页面支持 mmap.
I have read some info on using libhugetlbfs to call get_huge_pages() in user space, but ideally this buffer would be allocated in kernel space. I tried calling do_mmap() with MAP_HUGETLB but it didn't seem to change the number of free hugepages, so I don't think it was actually backing the mmap with huge pages.
所以我猜我在想什么,有没有任何方法可以将缓冲区映射到内核空间中的 1GB HugePage,还是必须在用户空间中完成?或者,如果有人知道我可以获得大量(1-64GB)连续物理内存作为内核缓冲区的其他方法吗?
So I guess what I'm getting at, is there any way I can map a buffer to a 1GB HugePage in kernel space, or does it have to be done in user space? Or if anyone knows of any other way I can get an immense (1-64GB) amount of contiguous physical memory available as a kernel buffer?
推荐答案
PROBLEM
- 通常,如果您想分配 DMA 缓冲区或获取物理地址,这是在内核空间中完成的,因为用户代码永远不必处理物理地址.
- Hugetlbfs 只提供用户空间映射来分配 1GB 大页面,并获取用户空间虚拟地址
- 不存在将用户巨页虚拟地址映射到物理地址的函数
尤里卡
但是这个功能确实存在!埋在深处2.6内核源代码在于这个函数从一个虚拟地址获取一个结构页,标记为只用于测试"并用#if 0阻塞:
But the function does exist! Buried deep in the 2.6 kernel source code lies this function to get a struct page from a virtual address, marked as "just for testing" and blocked with #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;
}
解决方案:由于上面的函数实际上并未编译到内核中,因此您需要将其添加到驱动程序源中.
SOLUTION: Since the function above isn't actually compiled into the kernel, you will need to add it to your driver source.
用户端工作流程
- 使用内核启动选项在启动时分配 1gb 大页面
- 使用hugetlbfs 调用get_huge_pages() 以获取用户空间指针(虚拟地址)
- 将用户虚拟地址(转换为 unsigned long 的普通指针)传递给驱动程序 ioctl
内核驱动程序工作流程
- 通过ioctl接受用户虚拟地址
- 调用 follow_huge_addr 获取结构体页面*
- 在struct page*上调用page_to_phys获取物理地址
- 为设备提供物理地址以供 DMA 使用
- 如果您还需要内核虚拟指针,请在结构页面上调用 kmap*
免责声明
- 几年后正在回忆上述步骤.我无法访问原始源代码.尽职尽责,确保我不会忘记一步.
- 这样做的唯一原因是在启动时分配了 1GB 大页面,并且它们的物理地址被永久锁定.不要尝试将非 1GB 大页支持的用户虚拟地址映射到 DMA 物理地址!你会过得很糟糕!
- 在您的系统上仔细测试以确认您的 1GB 大页面实际上已锁定在物理内存中并且一切正常.这段代码在我的设置中完美无缺,但如果出现问题,这里会存在很大的危险.
- 此代码仅保证适用于 x86/x64 架构(其中物理地址 == 总线地址)和内核版本 2.6.XX.在以后的内核版本上可能有更简单的方法来执行此操作,或者现在可能完全不可能.
这篇关于如何在 linux 内核模块中分配由 1GB HugePages 支持的 DMA 缓冲区?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!