且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

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

更新时间:2023-11-08 09:59:52

PROBLEM

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

尤里卡

但是这个功能确实存在!埋在深处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.

用户端工作流程

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

内核驱动程序工作流程

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

免责声明

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