在 mmap'ed 区域崩溃时使用 memcpy,for 循环不会

Posted

技术标签:

【中文标题】在 mmap\'ed 区域崩溃时使用 memcpy,for 循环不会【英文标题】:Using memcpy on mmap'ed region crashes, a for loop does not在 mmap'ed 区域崩溃时使用 memcpy,for 循环不会 【发布时间】:2018-09-04 09:02:55 【问题描述】:

我在载板上有一个 NVIDIA Tegra TK1 处理器模块,并有一个 PCI-e 插槽连接到它。在那个 PCIe 插槽中是一块 FPGA 板,它通过 PCIe 暴露一些寄存器和 64K 内存区域。

在 Tegra 板的 ARM CPU 上,正在运行最小的 Linux 安装。

我正在使用 /dev/mem 和 mmap 函数来获取指向寄存器结构和 64K 内存区域的用户空间指针。 不同的寄存器文件和内存块都分配了与 4KB 内存页对齐且不重叠的地址。 我用 mmap 显式映射整个页面,使用 getpagesize() 的结果,它也是 4096。

我可以很好地读取/写入那些暴露的寄存器。 我可以从内存区域(64KB)读取,在for循环中逐字读取uint32,就好了。 IE。读取内容正确。

但是,如果我在同一地址范围内使用 std::memcpy,Tegra CPU 总是会冻结。我没有看到任何错误消息,如果附加了 GDB,当尝试跨过 memcpy 行时,我在 Eclipse 中也看不到任何东西,它只是很难停止。而且我必须使用硬件重置按钮重置 CPU,因为远程控制台被冻结了。

这是没有优化 (-O0) 的调试版本,使用 gcc-linaro-6.3.1-2017.05-i686-mingw32_arm-linux-gnueabihf。有人告诉我 64K 区域可以按字节访问,但我没有明确尝试过。

是否存在我需要担心的实际(潜在)问题,或者是否有特定原因导致 memcpy 无法正常工作,并且可能不应该在这种情况下首先使用 - 我可以继续使用我的 for 循环,什么都不考虑?

编辑:观察到另一个效果:原始代码 sn-p 在复制 for 循环中缺少一个“重要的” printf,它出现在内存读取之前。删除后,我没有取回有效数据。我现在更新了代码 sn-p 以从同一地址而不是 printf 进行额外读取,这也产生了正确的数据。混乱加剧了。

这里是(我认为)正在发生的事情的重要摘录。稍作修改,如图所示,以这种“去绒毛”的形式。

// void* physicalAddr: PCIe "BAR0" address as reported by dmesg, added to the physical address offset of FPGA memory region
// long size: size of the physical region to be mapped 

//--------------------------------
// doing the memory mapping
//

const uint32_t pageSize = getpagesize();
assert( IsPowerOfTwo( pageSize ) );

const uint32_t physAddrNum = (uint32_t) physicalAddr;
const uint32_t offsetInPage = physAddrNum & (pageSize - 1);
const uint32_t firstMappedPageIdx = physAddrNum / pageSize;
const uint32_t lastMappedPageIdx = (physAddrNum + size - 1) / pageSize;
const uint32_t mappedPagesCount = 1 + lastMappedPageIdx - firstMappedPageIdx;
const uint32_t mappedSize = mappedPagesCount * pageSize;
const off_t targetOffset = physAddrNum & ~(off_t)(pageSize - 1);

m_fileID = open( "/dev/mem", O_RDWR | O_SYNC );
// addr passed as null means: we supply pages to map. Supplying non-null addr would mean, Linux takes it as a "hint" where to place.
void* mapAtPageStart = mmap( 0, mappedSize, PROT_READ | PROT_WRITE, MAP_SHARED, m_fileID, targetOffset );
if (MAP_FAILED != mapAtPageStart)

    m_userSpaceMappedAddr = (volatile void*) ( uint32_t(mapAtPageStart) + offsetInPage );


//--------------------------------
// Accessing the mapped memory
//

//void* m_rawData: <== m_userSpaceMappedAddr
//uint32_t* destination: points to a stack object
//int length: size in 32bit words of the stack object (a struct with only U32's in it)

// this crashes:
std::memcpy( destination, m_rawData, length * sizeof(uint32_t) );

// this does not, AND does yield correct memory contents - but only with a preceding extra read
for (int i=0; i<length; ++i)

    // This extra read makes the data gotten in the 2nd read below valid.
    // Commented out, the data read into destination will not be valid.
    uint32_t tmp = ((const volatile uint32_t*)m_rawData)[i];
    (void)tmp; //pacify compiler

    destination[i] = ((const volatile uint32_t*)m_rawData)[i];

【问题讨论】:

如果你能展示一些你正在做的事情的代码就好了。 欢迎来到 ***.com。请花一些时间阅读the help pages、阅读the SO tour、阅读有关how to ask good questions 和阅读this question checklist。最后请学习如何创建Minimal, Complete, and Verifiable Example。 如果你在memcpy 中设置一个断点并逐条执行会发生什么?它会崩溃吗?如果是,指令是什么,它崩溃的寄存器值是什么? 您不一定能够使用任意汇编指令访问映射到外部硬件的内存。有时 32 位宽的访问是可以的,而字节宽的访问或 64 位宽的访问不是。您需要了解您的硬件限制。我曾经使用过这样的设备,只有 DWORD 指令才能给出正确的结果。 geza:我现在在 memcpy 中指令步进了 10 分钟,我的 2 个手指现在累了;)(一个用于按下步骤,一个用于按下打印)它做了很多事情和跳跃周围没有崩溃,所以我还不知道它最终会在哪条指令下崩溃。 【参考方案1】:

根据描述,您的 FPGA 代码似乎没有正确响应从 FPGA 上的位置读取的加载指令,并导致 CPU 锁定。它不会崩溃,它会永久停止,因此需要硬重置。在 FPGA 上调试我的 PCIE 逻辑时,我也遇到了这个问题。

另一个表明您的逻辑没有正确响应的迹象是,您需要额外阅读才能获得正确的响应。

您的循环正在执行 32 位加载,但 memcpy 正在执行至少 64 位加载,这会改变您的逻辑响应方式。例如,如果完成的前 128 位和完成的第二个 128 位 TLP 中的后 32 位,则需要使用两个具有 32 位响应的 TLP。

我发现超级有用的是添加逻辑以将所有 PCIE 事务记录到 SRAM 中,并能够将 SRAM 转储出来以查看逻辑的行为或异常行为。我们有一个漂亮的实用程序pcieflat,它每行打印一个 PCIE TLP。它甚至还有documentation。

当 PCIE 接口不能正常工作时,我将日志以十六进制格式流式传输到 UART,该 UART 可由 pcieflat 解码。

此工具对于调试性能问题也很有用——您可以查看 DMA 读取和写入流水线的情况。

或者,如果您在 FPGA 上集成了逻辑分析仪或类似设备,您可以通过这种方式跟踪活动。但是根据 PCIE 协议解析 TLP 会更好。

【讨论】:

“需要从同一地址读取两次才能获得 1 个数据项”问题已消除,但 memcpy 仍然“崩溃”。在 for 循环中以 32 位方式读取 64K 缓冲区,我得到 ~ 2MB/s,对于我们的需求来说太慢了。那还没有使用 DMA,我正在研究它。 github上有一个名为“udmabuf”的项目,它允许分配一个内核缓冲区,供DMA写入,以及用户空间应用程序读取,让我们看看它是如何进行的。无论如何,我会转发你提供的信息,尤其是。调试提示,谢谢! 顺便说一句,我在其他地方读到,对于 PCIe BAR,需要设置预取位才能使 memcpy 工作。所以 FPGA 的人找到了一种方法来实现它。然后,dmseg 确实显示要预取 BAR,但 memcpy 仍然崩溃。 我们的portalmem 驱动程序还支持通过 ioctl 分配 DMA 缓冲区,以便您可以根据需要分配或释放任意数量的缓冲区。我们为 FPGA 实现了一个简单的 MMU,这样我们就可以拥有大缓冲区而不需要连续的内存。 github.com/cambridgehackers/connectal/tree/master/drivers/… 您的性能要求是什么?您的 PCIe 连接有多少条通道?您可能需要从 FPGA 读取/写入 DRAM 以获得高带宽传输。您需要同时处理大量请求才能接近 PCIe 的最大带宽。 略低于 50 MB/s。当前的评估板有 4 个通道,另一个只有一个。

以上是关于在 mmap'ed 区域崩溃时使用 memcpy,for 循环不会的主要内容,如果未能解决你的问题,请参考以下文章

memcpy不能复制内存重叠区域

memcpy memmove 函数

memcpy与memmove的区别

笔记-测试崩溃之memcpy_s

相邻内存区域上的 memcpy() 安全性

MFC-memcpy内存区域复制