控制C中存储器映射寄存器的读写访问宽度

时间:2010-06-14 18:51:22

标签: c embedded compiler-optimization memory-mapping

我正在使用基于x86的内核来操作32位内存映射寄存器。仅当CPU对该寄存器产生32位宽的读写操作时,我的硬件才能正常工作。寄存器在32位地址上对齐,不能以字节粒度进行寻址。

我能做些什么来保证我的C(或C99)编译器在所有情况下都只能生成完整的32位宽读写?

例如,如果我执行这样的读 - 修改 - 写操作:

volatile uint32_t* p_reg = 0xCAFE0000;
*p_reg |= 0x01;

我不希望编译器对只有底部字节发生变化并生成8位宽读/写的事实变得聪明。由于机器代码在x86上的8位操作通常更密集,我害怕不必要的优化。一般情况下禁用优化不是一种选择。

-----编辑-------
一篇有趣且非常相关的论文:http://www.cs.utah.edu/~regehr/papers/emsoft08-preprint.pdf

5 个答案:

答案 0 :(得分:6)

volatile限定符涵盖您的疑虑。

6.7.3 / 6“类型限定词”说:

  

具有volatile限定类型的对象可能会以实现未知的方式进行修改,或者具有其他未知的副作用。因此,任何涉及这种对象的表达都应严格按照抽象机的规则进行评估,如5.1.2.3所述。此外,在每个序列点,最后存储在对象中的值应与抽象机器规定的值一致,除非由前面提到的未知因素修改。什么构成对具有volatile限定类型的对象的访问是实现定义的。

5.1.2.3“程序执行”说(除其他外):

  

在抽象机器中,所有表达式都按语义指定进行评估。

接下来是一个通常被称为“as-if”规则的句子,如果最终结果相同,它允许实现不遵循抽象机器语义:

  

实际实现不需要评估表达式的一部分,如果它可以推断出它的值没有被使用并且没有产生所需的副作用(包括由调用函数或访问volatile对象引起的任何副作用)。

但是,6.7.3 / 6基本上表示表达式中使用的volatile限定类型不能应用“as-if”规则 - 必须遵循实际的抽象机器语义。因此,如果取消引用指向易失性32位类型的指针,则必须读取或写入完整的32位值(取决于操作)。

答案 1 :(得分:4)

保证编译器执行正确操作的唯一方法是在汇编程序中编写加载和存储例程并从C调用它们。多年来我使用的100%编译器可以并且会弄错( GCC包括)。

有时优化器会让你,例如你想要将一些常量存储在编译器中,就像一个小数字0x10一样,可以说是一个32位寄存器,这是你特别要求的,以及我看过的其他好的编译器试着做。一些编译器会认为执行8位写操作而不是32位写操作更便宜并更改指令。可变指令长度目标将使这更糟糕,因为编译器试图节省程序空间而不仅仅是内存周期,它可能假定总线是什么。 (xor ax,ax而不是mov eax,例如0)

对于像gcc一样不断发展的东西,今天有效的代码无法保证明天工作(你甚至无法使用当前版本的gcc编译某些版本的gcc)。同样,适用于您桌面编译器的代码可能无法普遍适用于其他人。

进行猜测和试验,并创建加载和存储函数。

这样做的另一个好处是你可以创建一个漂亮的抽象层,如果/当你想以某种方式模拟你的代码或让代码在应用程序空间而不是在金属上运行,反之亦然,汇编程序函数可以用模拟目标替换,或者用跨越网络的代码替换为具有设备的目标,等等。

答案 2 :(得分:0)

好吧,一般来说,如果你把寄存器输入为32位易失性,我不会指望它优化高位字节。由于使用了volatile关键字,编译器不能假设高位字节中的值是0x00。因此,即使您只使用8位字面值,它也必须写入完整的32位。我从来没有在0x86或Ti处理器或其他嵌入式处理器上遇到过这个问题。通常,volatile关键字就足够了。事情变得有点奇怪的唯一一次是处理器本身不支持您尝试写入的字大小,但对于32位数字而言,这不应该是0x86上的问题。

虽然编译器可以生成使用4位写操作的指令流,但在单个32位写操作中,这不会是处理器时间或指令空间的优化。

答案 3 :(得分:0)

如果在访问硬件时不使用字节(无符号字符)类型,则编译器更有可能不生成8位数据传输指令。

volatile uint32_t* p_reg = 0xCAFE0000;
const uint32_t value = 0x01;  // This trick tells the compiler the constant is 32 bits.
*p_reg |= value;

您必须将端口读取为32位值,修改该值,然后回写:

uint32_t reg_value = *p_reg;
reg_value |= 0x01;
*p_reg = reg_value;

答案 4 :(得分:0)

由于针对硬件的读 - 修改 - 写操作在几条指令中总是存在很大的风险,因此大多数处理器都提供了一条指令,用一条不能被中断的指令来操作寄存器/存储器。

根据您正在操作的寄存器类型,它可能会在修改阶段发生变化,然后您会回写错误值。

我建议dwelch建议在汇编中编写自己的读 - 修改 - 写函数,如果这是关键的话。

我从来没有听说过优化类型的编译器(为了优化而进行类型转换)。如果它被声明为int32,它总是一个int32,并且总是在内存中对齐。检查编译器文档以了解各种优化的工作原理。

我想我知道你的问题来自哪里,结构。通常将结构填充到最佳对齐。这就是为什么你需要在它们周围包装#pragma pack()以使它们按字节对齐。

您可以单步执行程序集,然后您将看到编译器如何翻译您的代码。我很确定它没有改变你的类型。