使用memcpy及其相关函数与内存映射I/O一起使用

5
我正在处理涉及内存映射FPGA寄存器的输入输出嵌入式项目。这些内存区域的指针需要标记为volatile,以便编译器不会通过缓存CPU寄存器中的值来“优化”对FPGA的读写。
在某些情况下,我们希望将一系列FPGA寄存器复制到缓冲区以供进一步使用。由于这些寄存器映射到连续的地址,因此memcpy似乎是合适的选择,但是把我们的volatile指针作为源参数传递会发出关于丢弃volatile限定符的警告。
是否可以安全地(并理智地)强制转换指针的volatile属性以抑制此警告?除非编译器进行某些神奇的操作,否则我无法想象调用memcpy失败执行实际复制的场景。另一种选择是只使用for循环一个字节一个字节地复制,但是memcpy实现可以根据复制的大小、对齐方式等来优化复制。

我会担心在首次使用memcpy进行MMIO操作。数据必须按特定顺序写入吗?会有副作用吗?你要写入的MMIO部分是否像内存一样工作? - tangrs
你提出了非常有价值的观点,我需要与FPGA团队再次确认,但据我所知,不是、不是、是。 - Matt Kline
1
使用memcpy写入MMIO寄存器组仍然让我感觉“不对劲”。据我所知,大多数需要移动大量数据的外设都采用DMA或通过重复读取寄存器来实现。我只会在MMIO上使用memcpy的情况是如果内存地址范围确实由RAM支持(但在这种情况下,为什么您需要首先使用volatile?编译器内存屏障就足够了)。 - tangrs
由于评论/答案中提到的所有原因,这并不安全,最重要的是,我曾经遇到过memcpy多次写入相同数据的情况。 - Alexis
2个回答

4
作为FPGA和嵌入式软件的开发者,只有一个明确的答案:不要使用memcpy等函数。
原因如下:
- 没有保证memcpy会按照任何特定顺序工作。 - 编译器很可能会将调用替换为内联代码。 - 这种访问通常需要特定的字长。memcpy不能保证这一点。 - 寄存器映射中的空隙可能导致未定义行为。
但是,您可以使用简单的for循环自己进行复制。如果寄存器是volatile的话,这是安全的(请参见下文)。
根据您的平台,仅使用volatile可能不足够。内存区域还必须是不可缓存的和严格有序的(可能还是非共享的)。否则,系统总线可能(对于某些平台)重新排序访问。
此外,您可能需要为CPU提供屏障/栅栏以防止重新排序访问。请非常仔细地阅读硬件规格说明书。
如果您需要更频繁地传输较大的块,请考虑使用DMA。如果FPGA使用PCI(e),您可以使用总线主DMA,例如散布/聚集(但是,这不容易实现;我自己做过,但可能值得努力)。
最佳(也是最理智)的方法实际上取决于多个因素,例如平台,所需速度等。在所有可能的方法中,我认为使用memcpy()是最不理智的之一(1)至少在某种程度上。(1):不确定这是否是正确的语法,但我希望您明白我的意思)。

那么接受volatile的std::copy怎么样?我不关心每个存储的顺序和大小,但我确实关心写入的确切字节数。 - Alexis

0
绝对不安全。memcpy 没有任何保证会按照什么顺序复制数据,以及每次会复制多少字节。

1
如果顺序不重要(FPGA将在读取期间保持寄存器中的相同值),那么这有什么关系呢? - Matt Kline
我不会说这是绝对不安全的。只要被复制到的MMIO地址范围像memcpy期望的那样(即作为内存),那么可能是可以的。 - tangrs

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