在用户空间使用DMA内存传输

6

有没有适用于用户空间的Linux DMA内存到内存复制机制?

我有一个Linux应用程序,需要频繁(每秒50-100次)地memcpy几兆(10+)的数据。通常情况下这不是问题,但我们开始发现它可能消耗了过多的CPU带宽。目前的测量结果表明我们正在移动大约1G字节/秒的数据。

我知道内核中有DMA功能,并且看到一些文档提到为了这个原因构建自定义驱动程序以进行大内存复制。但似乎已经有人为此构建了通用API。我错了吗?DMA是仅限于内核的功能吗?

我需要澄清的是,这是针对Intel X86架构而非嵌入式系统。


它是仅内核级别的。能够在用户空间执行DMA将成为一个巨大的安全漏洞。 - nobody
@AndrewMedico:暴露DMA memcpy系统调用会造成巨大的安全漏洞吗?几个可轻易获取的函数(包括splice和内核aio)代表用户空间程序执行DMA。 - Damon
如果您开始使用memcpy,那么显然您已经将其放置在您关心的进程的地址空间中 - 为什么不直接避开整个问题而不复制它? - Flexo
1
@Flexo:我有多个线程同时对空间进行更改,并生成额外的副本。 - Yeraze
2个回答

11
  • Linux的DMA API不允许内存到内存的传输,只能用于设备和内存之间的通信。在Documentation/DMA-API.txt中可以了解更多细节。

  • 在硬件层面上,x86 DMA控制器不允许内存到内存的传输。在这里进行了讨论:DMA transfer RAM-to-RAM

  • 考虑到内存总线通常比CPU慢,启动一个由内核驱动的内存复制有什么好处?你仍然需要等待传输完成,其持续时间仍然由内存带宽决定,与CPU驱动的复制完全相同。

  • 如果程序的性能完全取决于内存到内存的复制性能,那么就意味着可以通过尽可能避免复制或实现更智能的过程(例如写时复制)来大幅度改善。


谢谢,我本来就怀疑这是答案,但一直找不到证据。谢谢! - Yeraze
对于你的四个项目,都是不行和是的。DMA引擎可以用于通用内存复制(请参见DMA_PRIVATE标志),在x86上,一些控制器能够进行m2m传输。但实际上,这些都没有什么意义,因为你把它放在了最后一个项目中。 - 0andriy

3
看起来,您真正需要的是写时复制(copy-on-write)语义。这意味着默认情况下不会进行任何复制,但如果任何给定线程需要更改数据的某些部分,则在使用时将透明地对该页面进行复制。
写时复制将为您节省大量内存,如果您的数据足够大,这些memcpy调用将会产生影响:
- 相同数据(在页面级别上)不会重复——减少了工作集的大小 - 在实际需要之前,不会浪费提取/存储操作
DMA并不是解决此问题的方案,它主要用于设备主机或设备设备通信,而不是以可用方式向普通用户空间进程公开的内容。
相反,您可以使用POSIX共享内存来获得此行为。
#include <unistd.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <stdio.h>

int main() {
  // Once:
  int fd = shm_open("/cowalloc", O_RDWR|O_CREAT, 0600);
  shm_unlink("/cowalloc");
  ftruncate(fd, 1024); // This is the size of the COW regiona
  char *master = mmap(NULL, 1024, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);

  strcpy(master, "hello world, this is a demonstration of COW behaviour in Linux");

  // Per thread:
  char *thread = mmap(NULL, 1024, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_NORESERVE, fd, 0);

  // Demo
  printf("Master: %s\nThread: %s\n", master, thread);
  printf("\nChanging in thread:\n");
  strcpy(thread, "This is a private change");
  printf("Master: %s\nThread: %s\n", master, thread);

  return 0;
}

这里的基本思想是使用MAP_SHARED一次性进行所有数据的全局设置(假设从磁盘/网络或计算中加载)。然后,您可以再次使用相同的文件描述符调用mmap,为您认为可能需要写入本地副本的每个线程创建其他私有映射。
在这里使用MAP_NORESERVE标志是可选的 - 如果您只更改每个线程中成千上万个页面中的一个页面,则使用它以避免不必要地获取大量交换可能是有意义的。
(请注意,如果您正在从磁盘中加载,则可以通过简单地在文件上使用mmap来进一步优化此过程)。
当然,使用COW智能指针类型在对象级别上执行COW行为可能更加清晰和便携。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接