linux - 如何有效地将大页面支持的缓冲区传递给 Linux 中的 BM DMA 设备?
问题描述
我需要为在 FPGA 中实现的总线主控 DMA PCIe 设备提供一个巨大的循环缓冲区(几 GB)。
缓冲区不应在引导时保留。因此,缓冲区可能不连续。
该器件支持分散-聚集 (SG) 操作,但出于性能原因,缓冲区的连续连续段的地址和长度存储在 FPGA 内部。因此,使用标准 4KB 页面是不可接受的(每 1GB 缓冲区最多有 262144 个段)。
正确的解决方案应该在用户空间中分配由 2MB 大页面组成的缓冲区(将最大段数减少 512 倍)。缓冲区的虚拟地址应通过 ioctl 传输到内核驱动程序。然后应该计算段的地址和长度并将其写入FPGA。
理论上,我可以使用get_user_pages创建页面列表,然后调用sg_alloc_table_from_pages来获取适合在 FPGA 中编程 DMA 引擎的 SG 列表。不幸的是,在这种方法中,我必须准备每 1GB 缓冲区长度为 262144 页的页面结构的中间列表。该列表存储在 RAM 中,而不是 FPGA 中,因此问题较少,但无论如何最好避免它。
事实上,我不需要为内核保留页面映射,因为大页面受到保护以防换出,并且它们被映射给将处理接收到的数据的用户空间应用程序。
所以我正在寻找的是一个函数sg_alloc_table_from_user_hugepages
,它可以获取基于大页面的内存缓冲区的这样一个用户空间地址,并将其直接传输到正确的分散列表中,而无需为内核执行不必要的和消耗内存的映射。当然,这样的函数应该验证缓冲区确实由大页面组成。
我找到并阅读了这些帖子:(A),(B),但找不到好的答案。在当前的 Linux 内核中是否有任何官方方法可以做到这一点?
解决方案
目前我有一个非常低效的解决方案,基于get_user_pages_fast
:
int sgt_prepare(const char __user *buf, size_t count,
struct sg_table * sgt, struct page *** a_pages,
int * a_n_pages)
{
int res = 0;
int n_pages;
struct page ** pages = NULL;
const unsigned long offset = ((unsigned long)buf) & (PAGE_SIZE-1);
//Calculate number of pages
n_pages = (offset + count + PAGE_SIZE - 1) >> PAGE_SHIFT;
printk(KERN_ALERT "n_pages: %d",n_pages);
//Allocate the table for pages
pages = vzalloc(sizeof(* pages) * n_pages);
printk(KERN_ALERT "pages: %p",pages);
if(pages == NULL) {
res = -ENOMEM;
goto sglm_err1;
}
//Now pin the pages
res = get_user_pages_fast(((unsigned long)buf & PAGE_MASK), n_pages, 0, pages);
printk(KERN_ALERT "gupf: %d",res);
if(res < n_pages) {
int i;
for(i=0; i<res; i++)
put_page(pages[i]);
res = -ENOMEM;
goto sglm_err1;
}
//Now create the sg-list
res = sg_alloc_table_from_pages(sgt, pages, n_pages, offset, count, GFP_KERNEL);
printk(KERN_ALERT "satf: %d",res);
if(res < 0)
goto sglm_err2;
*a_pages = pages;
*a_n_pages = n_pages;
return res;
sglm_err2:
//Here we jump if we know that the pages are pinned
{
int i;
for(i=0; i<n_pages; i++)
put_page(pages[i]);
}
sglm_err1:
if(sgt) sg_free_table(sgt);
if(pages) kfree(pages);
* a_pages = NULL;
* a_n_pages = 0;
return res;
}
void sgt_destroy(struct sg_table * sgt, struct page ** pages, int n_pages)
{
int i;
//Free the sg list
if(sgt->sgl)
sg_free_table(sgt);
//Unpin pages
for(i=0; i < n_pages; i++) {
set_page_dirty(pages[i]);
put_page(pages[i]);
}
}
该sgt_prepare
函数构建了sg_table
我可以用来创建 DMA 映射的 sgt 结构。我已经验证它包含的条目数等于使用的大页面数。
不幸的是,它需要创建页面列表(通过a_pages
指针参数分配和返回),并在使用缓冲区时保留。
因此,我真的不喜欢这种解决方案。现在我有 256 个 2MB 大页面用作 DMA 缓冲区。这意味着我必须创建并保留不必要的 128*1024 页面结构。我还浪费了 512 MB 的内核地址空间用于不必要的内核映射。
有趣的问题是,是否a_pages
只能暂时保留(直到创建 sg-list)?理论上应该是可能的,因为页面仍然被锁定......
推荐阅读
- sql - 删除插入查询中的引用字符串
- android - 娱乐音乐播放器应用程序后在哪里设置媒体样式通知栏
- flutter - NoSuchMethodError 被抛出构建 Home(dirty, state: HomeState#0a71e): The getter 'displayName' was called on null
- azure - 可视化选项在 Azure 机器学习服务上未激活
- java - 通过 Amazonica 或 Cognitect AWS API 连接到进程内 DynamoDBLocal?
- docker - 如何将 GeoDjango 添加到我的 alpine dockerfile?
- javascript - 如何将外部字体添加到使用 JavaScript 中的 CSS 对其进行自定义的 iframe 中?
- javascript - 为什么我的 javascript 表单验证不起作用?
- javascript - iOS Progressive Web App 暗模式启动图像?
- python - 如何使用 python 从套接字客户端返回烧瓶服务器端的 json 对象?