我正在使用带有自定义Linux的SoC。我通过指定内核启动参数mem = 512M保留了512MB的1GB总RAM。 我可以通过打开/ dev / mem来访问用户空间程序的高端内存,然后mmap内核不使用的高位512MB。 知道我想通过memcpy()在这个区域内复制大块内存,但性能大约是50MB / sek。当我通过内核和memcpy分配缓冲区时,我可以达到大约500MB / sek。 我很确定是由于我的特殊内存区域禁用了缓存,但是不知道如何告诉内核在这里使用缓存。
有谁知道如何解决这个问题?
答案 0 :(得分:3)
注意:其中很多都以我的评论开头,所以我会尽量避免逐字重复。
关于DMA,内核访问和用户空间访问的缓冲区。缓冲区可以通过任何合适的机制进行分配。
如前所述,在用户空间中使用mem=512M
和/dev/mem
与mmap
,mem
驱动程序可能无法设置最佳缓存策略。此外,mem=512M
更常用于告诉内核从不使用内存(例如,我们想用更少的系统内存进行测试)并且我们不会 将使用上部512M进行任何操作。
如您所述,更好的方法是暂停mem=512M
并使用CMA
。另一种方法可能是将驱动程序绑定到内核并让它在系统启动期间保留完整的内存块[可能使用CMA
]。
可以通过内核命令行参数[来自grub.cfg
]选择内存区域,例如mydev.area=
和mydev.size=
。这对" bound"非常有用。在"早期"期间必须知道这些价值的司机系统启动阶段。
所以,现在我们有了#34;大"区域。现在,我们需要让设备获得访问权限,并让应用程序对其进行映射。内核驱动程序可以执行此操作。打开设备时,ioctl
可以使用正确的内核策略设置映射。
因此,根据分配机制,应用程序可以为ioctl
提供address/length
,或可以将它们传递回应用程序[适当映射]。
当我不得不这样做时,我创建了一个描述内存区/缓冲区的结构。它可以是整个区域,也可以根据需要细分大区域。我发现固定大小的子池工作得更好,而不是使用等同于malloc
的{{1}} [类似于你所写的]的可变长度动态方案。在内核中,这被称为" slab"分配器。
该结构有一个" id"给定区域的编号。它还有三个地址:地址应用程序可以使用,地址内核驱动程序可以使用,以及将给予H / W设备的地址。此外,在多个设备的情况下,它可能具有当前与其关联的特定设备的ID。
所以,你采取大面积并像这样细分。 5个设备。 Dev0需要10个1K缓冲区,Dev1需要10个20K缓冲区,Dev3需要10个2K缓冲区,......
应用程序和内核驱动程序将保留这些描述符结构的列表。应用程序将使用另一个ioctl
启动DMA,该ioctl
将获取描述符ID号。对所有设备重复此操作。
然后,应用程序可以发出等待完成的memcpy
。驱动程序填写刚刚完成的操作的描述符。该应用程序处理数据和循环。它就是这样做的,就地“#34; - 见下文。
您担心mmap
速度变慢。正如我们已经讨论过的,这可能是由于您在/dev/mem
上使用memcpy
的方式。
但是,如果您正在从设备进行内存DMA操作,则CPU缓存可能会变得陈旧,因此您必须考虑到这一点。真正的设备驱动程序有很多内核支持例程来处理这个问题。
这是一个很大的:为什么你需要在所有进行memcpy
?如果设置正确,应用程序可以直接在数据上运行,而无需复制它。也就是说,DMA操作将数据放在应用程序所需的位置。
猜测,现在,您已经获得了ioctl
"赛车"对设备。也就是说,您必须快速复制数据,这样您就可以启动下一个DMA而不会丢失任何数据。
"大"区域应该细分[如上所述],内核驱动程序应该知道这些部分。因此,驱动程序启动DMA到id 0.完成后,它立即[在ISR中]启动DMA到id 1.完成后,它将进入其子池中的下一个。对于每个设备,这可以以类似的方式完成。该应用程序可以使用uio
这样,驱动程序可以使所有设备以最大速度运行,并且应用程序可以有足够的时间来处理给定的缓冲区。而且,再一次,它不需要复制它。
要谈的另一件事。您设备上的DMA寄存器是否经过双重缓冲?我假设您的设备不支持复杂的分散/收集列表,并且相对简单。
在我的特殊情况下,在H / W的rev 1中,DMA寄存器不是双缓冲。
因此,在缓冲区0上启动DMA之后,驱动程序必须等到缓冲区0的完成中断,然后再将DMA寄存器设置为下一次传输到缓冲区1.因此,驱动程序必须"竞争&# 34;为下一个DMA做设置[并且有一个非常短的时间窗口]。启动缓冲区0后,如果驱动程序更改了设备上的DMA寄存器,则会中断已经激活的请求。
我们通过双缓冲在rev 2中修复了这个问题。当驱动程序设置DMA regs时,它会点击" start"港口。所有DMA端口都被设备立即锁存。此时,驱动程序可以自由地为缓冲区1进行完整设置,当缓冲区0完成时,设备将自动切换到[无需驱动程序干预]。驱动程序会得到一个中断,但几乎可以花费整个传输时间来设置下一个请求。
因此,对于rev 1风格的系统,uio
方法可能不已经有效 - 这将太慢了。使用rev 2,read(2)
可能是可能的,但即使可能,我也不是粉丝。
注意:在我的情况下,我们不使用write(2)
或ioctl
来完成设备的读/写回调。一切都是通过特殊的uio
调用来处理的,这些调用采用了上面提到的各种结构。在早期的某个时刻,我们 以类似于uio
使用它们的方式使用读/写。但是,我们发现映射是人为的并且限制[并且麻烦],所以我们转换为"只有ioctl"方法
更重要的是,有什么要求?每秒传输的数据量。做什么设备的数量?它们都是输入还是输出?
在我的情况下[进行了广播质量hidef H.264视频的R / T处理],我们能够在驱动程序和应用程序空间以及自定义FPGA逻辑中进行处理。但是,我们使用了完整的[非uio]驱动程序方法,尽管从架构上来说它看起来像是uio。
我们对可靠性,R / T可预测性,保证延迟有严格要求。我们必须处理60个视频帧/秒。如果我们跑了过来,即使只是一小部分,我们的客户也开始尖叫。 {{1}}无法为我们做到这一点。
所以,你用一个简单的方法开始了这个。但是,我可能会退后一步,查看需求,设备功能/限制,获取连续缓冲区的替代方法,R / T吞吐量和延迟,以及重新评估事物。您当前的解决方案是否真正满足了所有需求?目前,您已经遇到热点[应用和设备之间的数据竞争]和/或限制。或者,您是否会更好地使用本机驱动程序,为您提供更大的灵活性(即,可能还有一个未知的将强制本机驱动程序)。
Xilinx可能在他们的SDK中提供了一个合适的骨架完整驱动程序,你可以很快地破解它。
答案 1 :(得分:2)
非常感谢你的时间。你的答案对我来说非常有用。我喜欢从驱动程序本身管理缓冲区(dma缓冲区)的想法。
当你通过 / dev / mem 的源代码澄清时,当我在内核中使用mem = 512M排除的区域上使用mmap时,内核威胁就像设备内存并禁用缓存。
我找到了一个中间解决方案。我删除了内核启动参数,并在我的设备树中添加了一个保留内存区域,如下所示:
/ {
reserved-memory {
#address-cells = <1>;
#size-cells = <1>;
ranges;
my_reserved: databuffer@20000000 {
reg = <0x20000000 0x20000000>;
};
};
};
这样做,我从 cat / proc / iomem 获得0x00000000 - 0x3fffffff的系统ram。 cat / proc / meminfo 给我的空闲内存只有500 MB,所以我的区域没有使用。
当我现在打开 / dev / mem 和 mmap 这个区域时,我从 memcpy()获得大约260 MB / sek,大约1200来自 memset()的MB / sek。该区域被视为内存和缓存。我不知道为什么它只是 malloc 区域性能的一半,但要好得多。
我认为对于我的案例来说,完美的解决方案可能是像/ dev / cma设备驱动程序更好的/ dev / mem,它可以从我在bootargs中定义的cma区域分配缓冲区。 在那个设备上,我可以通过ioctl()设置缓存,一致性策略之类的东西。这将使我有机会在该区域自己设置偏好。
我在这个问题上找到了有趣的帖子,其他人如何解决它。 Continous memory on ARM and cache coherency
答案 2 :(得分:0)
之前,它将介绍我为您创建的设备驱动程序。请参考。
https://github.com/ikwzm/udmabuf
udmabuf分配的DMA缓冲区可以通过设备文件(例如/ dev / udmabuf0)和映射到用户存储空间,或者使用read()/ write()函数从用户空间访问。
打开设备文件时,可以通过设置O_SYNC
标志来禁用分配的DMA缓冲区的CPU缓存。在保持CPU缓存启用的同时,还可以刷新或使CPU缓存无效。
udmabuf分配的DMA缓冲区的物理地址可以通过阅读/sys/class/udmabuf/udmabuf0/phys_addr
获得。
可以在加载设备驱动程序时指定DMA缓冲区的大小和设备次要编号(例如,通过insmod
命令加载时)。某些平台允许在设备树中指定它们。
图1.架构