在Linux系统中,是否可以使用DMA代替memcpy?
如果您打算使用从先前已使用或计划在复制后由 DMA 控制器使用的内存中复制的数据,则必须执行与硬件相关的函数,以将内存中的数据与处理器或 DMA 控制器将从缓存中读取的数据同步。5. 这一点本质上重复了前一点的观点,清楚地表明,使用 ktime_get() 函数进行测量以进行比较分析是完全不可接受的,因为它非常不准确,尤其是在像示例中分析的复制这样短暂的操作的情况下。如果调用此函数的应用程序是多线
大家好!我是大聪明-PLUS!
让我们尝试弄清楚如何使用 DMA 接口来实现标准的 memcpy 复制操作,以及这样做是否合理。
在 Linux 系统中,通过调用 DMA 控制器来实现 memcpy 函数可能如下所示:
void memcpyWithDma (u16* dest, u16* src, size_t len)
{
dma_cookie_t cookie = dma_async_memcpy_buf_to_buf(chan, dest, src, len);
while (dma_async_is_tx_complete(chan, cookie, NULL, NULL) == DMA_IN_PROGRESS)
{
dma_sync_wait(chan, cookie);
}
}
以下是代码:
void foo ()
{
int index = 0;
dma_cookie_t cookie;
size_t len = 0x20000;
ktime_t start, end, end1, end2, end3;
s64 actual_time;
u16* dest;
u16* src;
dest = kmalloc(len, GFP_KERNEL);
src = kmalloc(len, GFP_KERNEL);
for (index = 0; index < len/2; index++)
{
dest[index] = 0xAA55;
src[index] = 0xDEAD;
}
start = ktime_get();
cookie = dma_async_memcpy_buf_to_buf(chan, dest, src, len);
while (dma_async_is_tx_complete(chan, cookie, NULL, NULL) == DMA_IN_PROGRESS)
{
dma_sync_wait(chan, cookie);
}
end = ktime_get();
actual_time = ktime_to_ns(ktime_sub(end, start));
printk("Time taken for function() execution dma: %lld\n",(long long)actual_time);
memset(dest, 0 , len);
start = ktime_get();
memcpy(dest, src, len);
end = ktime_get();
actual_time = ktime_to_ns(ktime_sub(end, start));
printk("Time taken for function() execution non-dma: %lld\n",(long long)actual_time);
}
我们的 memcpyWithDma() 是本示例中内联 DMA 访问代码的副本,以 C 函数的形式编写。
问题
这段代码的作者感到惊讶:
-
使用 DMA 调用进行复制的时间比使用 memcpy() 函数的传统复制方式的时间要长;
-
请求确认这是使用 DMA 功能的正确方法。
-
我想了解有哪些方法可以测量处理器负载(性能)。
-
他询问是否可以在用户应用程序级别执行 DMA 操作,因为他编写的用于比较两种复制实现的性能的测试是在内核模块中编写的。
专家的回答是什么?
专家表示,这些问题并不完全正确,因此他无法直接回答所列问题,但他概述了理论的某些方面,这些方面应该有助于正确理解与 DMA 功能实际应用相关的问题。
1. 首先,专家承认这种使用 DMA 进行数据复制的实现方式是正确的,也就是说,我们的 memcpyWithDma() 函数也可以像标准的 memcpy() 函数一样复制数据;
2. 然而,DMA 复制方法与 memcpy() 函数中实现的标准方法之间存在根本区别。这种区别在于,DMA 复制并非通过简单地加快复制速度来直接提高性能。决定这两种复制方法根本(主要)区别的因素有两个方面:
a. 使用 DMA 不会改变缓存状态(而使用传统的 memcpy 时,缓存会被复制操作所涉及的内存地址填充)。
b. DMA 复制由外部硬件模块(控制器)执行,处理器在复制过程中保持空闲,可以执行与复制无关的代码。不过,在我们的 memcpyWithDma() 实现中,我们完全没有使用这个特性。我们仍然执行与当前复制直接相关的代码:
while (dma_async_is_tx_complete(chan, cookie, NULL, NULL) == DMA_IN_PROGRESS)
{
dma_sync_wait(chan, cookie);
}
在这里,我们只需等待操作完成,但dma_sync_wait()实际上在某些情况下,这种调用可以提升性能。如果调用此函数的应用程序是多线程的,则此调用会停用执行它的当前线程,从而允许其他应用程序线程与此处启动的当前数据复制操作并行执行。因此,将从函数调用dma_sync_wait()到其返回的时间计入复制时间并不足以衡量处理器使用率,尽管在衡量数据复制持续时间时,这段时间肯定应该被考虑在内。也就是说,数据复制可能需要更长时间,但处理器在复制过程中实际上并不会处于繁忙状态。总的来说,这会给开发人员带来一个两难的选择:是选择实际的复制速度,还是选择在复制过程中并行运行处理器的能力。
3. 本节中,专家指出:“考虑到上一节(a)款,对于小于处理器缓存大小(即几十兆字节)的数据,使用DMA操作毫无意义。DMA通常用于快速的CPU外处理,也就是传输原本由外部设备(例如高速网卡、视频流/采集/编码硬件等)生成/使用的数据。”坦白说,我并不认为DMA的使用与处理器缓存参数如此绝对地挂钩,因为有些处理器没有缓存,但仍然使用DMA。或许需要指出的是,DMA通常是与某些外部设备或实现特定处理器功能的内置外围单元配合使用的。
4. 在本节中,专家指出,用绝对时间和通用时间来比较同步和异步操作是不正确的。时间的估算会因操作的不同方面和/或使用的不同系统组件而有所不同。这与我前面提到的应用程序是否为多线程的情况大致相同。专家还指出,由于从系统功能用户的角度来看,线程切换是随机发生的,因此多线程系统会给示例中使用的测量方法引入随机误差。
5. 这一点本质上重复了前一点的观点,清楚地表明,使用 ktime_get() 函数进行测量以进行比较分析是完全不可接受的,因为它非常不准确,尤其是在像示例中分析的复制这样短暂的操作的情况下。
6. 专家指出,测量现代处理器的“时钟周期”意义不大,尽管可以使用厂商提供的工具,例如 Intel VTune。我还要补充一点,在确定测量结果的可重复性之前,测量现代处理器的“时钟周期”毫无意义。有时这很简单;只需收集多次测量的统计数据即可。
7. 一位专家表示:“在应用层使用 DMA 复制操作几乎毫无意义——至少,我想不出任何实际应用场景值得这么做。” 我完全同意他的观点。“它不一定更快,更重要的是,我严重怀疑你的应用程序的性能瓶颈在于内存数据复制。要达到这个目的,通常其他所有操作都必须比常规内存数据复制更快,而我实在想不出在应用层有什么操作会比 memcpy 更快。” 因为任何 CPU 操作都需要以某种方式访问内存,因此从定义上讲,它比简单的复制操作要复杂得多。“如果我们讨论的是与 CPU 之外的其他数据处理设备交互,那么它就不是应用层的问题了,” 而是驱动程序层或系统内核模块层的问题。
8. 通常来说,内存数据复制(从一个物理内存位置到另一个物理内存位置)的性能受限于内存速度,即时钟频率和时序。你不会获得比普通 memcpy 操作更显著的性能提升,原因很简单,因为处理器上的 memcpy 操作速度已经相当快了,处理器的时钟频率通常比内存(内存访问总线)高 3 倍、5 倍甚至 10 倍。
9. 值得注意的是,DMA 直接访问内存,绕过了MMU子系统和缓存。这需要一种特殊的内存分配方法,如前文“ Linux 中 DMA 的内存分配”所述。如果您打算使用从先前已使用或计划在复制后由 DMA 控制器使用的内存中复制的数据,则必须执行与硬件相关的函数,以将内存中的数据与处理器或 DMA 控制器将从缓存中读取的数据同步。这些函数可能包括以下函数:
void ResetDataCache(void* address, int len);这样,缓存就会“忘记”指定区域的地址,并在下次访问时从物理内存读取数据。
void WriteBackDataCache(void* address, int len);确保在执行此函数时,缓存中的数据保存到物理内存中。
有些架构允许您将某些内存区域配置为“不可缓存”,这样就无需通过缓存进行内存管理,但只有在您不对这些内存区域中的数据执行任何计算工作时才应该这样做,否则此类计算工作的性能会受到影响。
结论
本文最重要的结论或许在于,理论上存在基于DMA的memcpy实现,但从实际角度来看,其效率远逊于传统的memcpy实现。此外,这种基于DMA的实现对内存分配方式以及复制前后内存的使用方式都存在诸多限制和/或要求。事实上,这种实现始终依赖于硬件,并且会一直给用户应用程序带来问题。
更多推荐



所有评论(0)