内核 RAM 外部 /dev/mem 上的 Memcpy 性能

Raf*_*ael 7 c linux mmap memcpy

我正在使用带有定制 Linux 的 SoC。我通过指定内核启动参数 mem=512M 保留了 1GB 总 RAM 的上 512MB。我可以通过打开 /dev/mem 并 mmap 内核未使用的上部 512MB 来从用户空间程序访问上部内存。我知道我想通过 memcpy() 复制该区域内的大块内存,但性能约为 50MB/sek。当我通过内核和 memcpy 在缓冲区之间分配缓冲区时,我可以达到大约 500MB/sek。我很确定这是由于我的特殊内存区域禁用了缓存,但不知道如何告诉内核在这里使用缓存。

有人知道如何解决这个问题吗?

Cra*_*tey 5

注意:其中很多内容都是由我的热门评论开头的,因此我会尽量避免逐字重复它们。

关于 DMA、内核访问和用户空间访问的缓冲区。可以通过任何合适的机制来分配缓冲区。

如前所述,在用户空间中使用mem=512Mand /dev/memwith 时mmapmem驱动程序可能不会设置最佳缓存策略。此外,mem=512M更通常用于告诉内核永远不要使用内存(例如,我们想要使用更少的系统内存进行测试),并且我们不会上面的 512M 用于任何事情。

更好的方法可能是像您提到的那样停止mem=512M并使用CMA。另一种方法可能是将驱动程序绑定到内核中,并让它在系统启动期间保留完整的内存块[可能使用CMA]。

内存区域可以通过内核命令行参数[from grub.cfg](例如mydev.area=和 )来选择mydev.size=。这对于必须在系统启动的“早期”阶段知道这些值的“绑定”驱动程序很有用。

所以,现在我们有了“大”区域。现在,我们需要有一种方法让设备获得访问权限并让应用程序对其进行映射。内核驱动程序可以做到这一点。当设备打开时,ioctl可以使用正确的内核策略设置映射。

因此,根据分配机制,可以由应用程序ioctl给出,可以将它们传递回应用程序[适当映射]。address/length

当我必须这样做时,我创建了一个描述内存区域/缓冲区的结构。可以是整个区域,也可以根据需要对大区域进行细分。malloc我发现固定大小的子池工作得更好,而不是使用等同于[就像你所写的]的可变长度、动态方案。在内核中,这称为“slab”分配器。

该结构有一个给定区域的“id”号。它还具有三个地址:应用程序可以使用的地址、内核驱动程序可以使用的地址以及将提供给硬件设备的地址。此外,在多个设备的情况下,它可能具有当前与其关联的特定设备的 ID。

所以,你把大面积像这样细分。5 台设备。Dev0需要10个1K缓冲区,Dev1需要10个20K缓冲区,Dev3需要10个2K缓冲区,...

应用程序和内核驱动程序将保留这些描述符结构的列表。应用程序将使用另一个ioctl需要描述符 ID 号的 DMA 来启动 DMA。对所有设备重复此操作。

然后应用程序可以发出ioctl等待完成的消息。驱动程序填写刚刚完成的操作的描述符。该应用程序处理数据并循环。它“就地”执行此操作 - 见下文。

您担心memcpy速度慢。正如我们所讨论的,这可能是由于您mmap/dev/mem.

但是,如果您从设备 DMA 到内存,CPU 缓存可能会变得陈旧,因此您必须考虑到这一点。真正的设备驱动程序有大量的内核支持例程来处理这个问题。

这是一个大问题为什么你需要做memcpy一个?如果设置正确,应用程序可以直接操作数据而无需复制数据。也就是说,DMA 操作将数据准确地放置在应用程序需要的位置。

据猜测,现在您已经在memcpy与该设备进行“竞赛”了。也就是说,您必须快速复制数据,以便可以启动下一个 DMA 而不会丢失任何数据。

“大”区域应该被细分[如上所述]并且内核驱动程序应该了解这些部分。因此,驱动程序启动 DMA 到 id 0。完成后,它立即[在 ISR 中]启动 DMA 到 id 1。完成后,它进入子池中的下一个。对于每个设备,这可以以类似的方式完成。应用程序可以轮询是否完成ioctl

这样,驱动程序可以保持所有设备以最大速度运行,并且应用程序可以有足够的时间来处理给定的缓冲区。而且,再一次,它不需要复制它。

还有一件事要谈。您设备上的 DMA 寄存器是否双缓冲?我假设您的设备不支持复杂的分散/聚集列表并且相对简单。

在我的特定情况下,在 H/W 的 rev 1 中,DMA 寄存器不是缓冲的。

因此,在缓冲区 0 上启动 DMA 后,驱动程序必须等到缓冲区 0 的完成中断,然后才能设置 DMA 寄存器以进行下一次传输到缓冲区 1。因此,驱动程序必须“竞相”为缓冲区 1 进行设置。下一个 DMA [并且完成此操作的时间窗口非常短]。启动缓冲区 0 后,如果驱动程序更改了设备上的 DMA 寄存器,则会中断已经处于活动状态的请求。

我们在 Rev 2 中通过双缓冲修复了这个问题。当驱动程序设置 DMA 寄存器时,它将访问“启动”端口。所有 DMA 端口立即被设备锁存。此时,驱动程序可以自由地对缓冲区 1 进行完整设置,并且当缓冲区 0 完成时,设备将自动切换到缓冲区 1(无需驱动程序干预)。驱动程序会收到中断,但可能会花费几乎整个传输时间来设置下一个请求。

因此,对于 rev 1 风格的系统,任何uio方法都行不通——速度太慢了。对于 rev 2,uio也许有可能,但即使有可能,我也不喜欢。

注意:就我而言,我们根本没有使用read(2)或到设备读/写回调。write(2)一切都是通过特殊ioctl调用来处理的,这些特殊调用采用如上所述的各种结构。在早期,我们确实以类似于使用它们的方式使用读/写uio。但是,我们发现映射是人为的且具有限制性[并且很麻烦],因此我们转换为“仅 ioctl”方法。

更重要的是,有什么要求?每秒传输的数据量。有多少台设备可以做什么?它们都是输入还是也有输出?

在我的案例中 [对广播质量 hidef H.264 视频进行 R/T 处理],我们能够在驱动程序应用程序空间以及自定义 FPGA 逻辑中进行处理。但是,我们使用了完整的[非 uio] 驱动程序方法,尽管在架构上它在某些地方看起来像 uio。

我们对可靠性、R/T 可预测性、保证延迟有严格的要求。我们必须每秒处理 60 个视频帧。如果我们跑过去,即使是一小部分,我们的顾客也会开始尖叫。uio无法为我们做到这一点。

因此,您以一种简单的方法开始了这一工作。但是,我可能会退后一步,看看需求、设备功能/限制、获取连续缓冲区的替代方法、R/T 吞吐量和延迟,然后重新评估。您当前的解决方案真的能满足所有需求吗?目前,您已经遇到了热点[应用程序和设备之间的数据竞争]和/或限制。或者,您会更好地使用为您提供更大灵活性的本机驱动程序(即可能有一个未知的因素会强制使用本机驱动程序)。

Xilinx 可能在其 SDK 中提供了合适的骨架完整驱动程序,您可以很快地破解它。