如何从C或C ++语言级别安全地访问内存映射硬件寄存器?

时间:2016-12-16 18:03:42

标签: c++ c embedded

在C和C ++中,我通常使用众所周知的模式访问内存映射的硬件寄存器:

typedef unsigned int uint32_t;
*((volatile uint32_t*)0xABCDEDCB) = value;

据我所知,C或C ++标准唯一保证的是,根据抽象机器的规则严格评估对volatile变量的访问。

  1. 如何确保编译器不会为32位处理器的访问生成破坏的存储?例如,允许编译器发出两个16位存储而不是一个32位存储,不是吗?
  2. 这个区域是否有任何由gcc制作的保证?

4 个答案:

答案 0 :(得分:0)

在谈论MCU时,据我所知,没有这样的保证。甚至,访问HW寄存器的每种情况可以是特定于设备的,并且通常可以具有其自己的序列,规则和/或汇编指令集。它也取决于编译器的实现。 这里唯一对我有用的是阅读有关具体设备/编译器的数据表,并按照示例进行操作。

答案 1 :(得分:0)

Microsoft关于ISO兼容使用volatile

的评论

“C ++ 11 ISO标准代码中的volatile关键字仅用于硬件访问”

http://msdn.microsoft.com/en-us/library/12a04hfd.aspx

至少在Microsoft C ++的情况下(返回到Visual Studio 2005),显示了指向volatile类型的指针的示例:

http://msdn.microsoft.com/en-us/library/145yc477.aspx

另一个引用,在本例中为C,它还包含指向volatile类型的指针示例。

“静态易失性对象模型内存映射I / O端口,静态const volatile对象模型内存映射输入端口”

http://en.cppreference.com/w/c/language/volatile

不允许编译器或硬件对volatile类型的操作进行重新排序,这是对硬件内存映射访问的要求。但是,对易失性和非易失性类型的组合的操作最终可能会对非易失性类型进行重新排序操作,从而使它们非线程安全(变量的所有线程间共享将要求所有变量都是易失性的,以保证线程安全) 。即使两个线程只共享volatile类型,仍然存在数据争用问题(一个线程在另一个线程写入之前读取)。

Microsoft编译器对volatile的非可移植(对其他编译器)扩展,这使得它们的线程安全(/ volatile:ms - 特定于Microsoft,默认情况下使用,除了ARM处理器)。

回到最初的问题,在GCC的情况下,您可以让编译器生成汇编代码以验证操作是否安全。

答案 2 :(得分:0)

如果您真的担心使用内联汇编程序。单个汇编器指令在完成之前不会返回。

此外,您必须确保您正在写入的内存页面不会被缓存,否则写入可能无法完成。在ARM上,内存障碍可能也是必要的。

Volatile只是一条指令,它告诉编译器不要对内存的内容做出任何分析,因为这个值可能会在一个程序之外被改变但是没有效果或者读写顺序。如果这是一个问题,请使用记忆障碍或原子。

答案 3 :(得分:-1)

  

如何确保编译器不会为32位处理器的访问生成破坏的存储?例如,允许编译器发出两个16位存储而不是一个32位存储,不是吗?

通常,编译器可以在as-if规则下组合或拆分内存访问,只要程序的可观察行为不变,因为访问普通对象的可观察行为是对对象值的影响,并且不是内存访问本身。

但是,对volatile个对象的访问是程序可观察行为的一部分。因此,编译器不能再组合或拆分内存事务。在C ++标准定义“可观察行为”的部分中,它明确指出“严格根据抽象机器的规则来评估对易失性对象的访问。

请注意,显示的代码仍然是非可移植的C ++,因为C ++标准只关心访问的对象是volatile,而不关心用于形成所述访问的左值的指针上的修饰符。你需要做一些疯狂的事情,就像这个placement-new的例子一样,强制存在一个易变的对象:

 *(new volatile uint32 ((uint32*)0xABCDEDCB)) = value;