7

我正在使用基于 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

4

5 回答 5

6

volatile限定符涵盖了您的担忧。

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

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

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

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

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

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

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

于 2010-06-14T22:45:55.677 回答
4

保证编译器会做正确的事情的唯一方法是在汇编程序中编写加载和存储例程并从 C 中调用它们。我多年来使用的 100% 的编译器都可能并且会出错(包括 GCC) .

有时优化器会得到你,例如你想将一些在编译器中显示为小数字 0x10 的常量存储到一个 32 位寄存器中,这是你特别要求的,也是我所看到的,否则好的编译器会尝试做. 一些编译器会决定执行 8 位写入而不是 32 位写入更便宜,并更改指令。可变指令长度目标将使情况变得更糟,因为编译器试图节省程序空间,而不仅仅是它可能假设总线的内存周期。(例如 xor ax,ax 而不是 mov eax,0)

对于像 gcc 这样不断发展的东西,今天可以工作的代码并不能保证明天可以工作(你甚至不能用当前版本的 gcc 编译某些版本的 gcc)。同样,在您办公桌上的编译器上运行的代码可能无法普遍适用于其他人。

摆脱猜测和实验,并创建加载和存储函数。

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

于 2010-06-14T22:35:33.387 回答
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;
于 2010-06-14T20:17:13.270 回答
0

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

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

于 2010-06-14T19:11:27.457 回答
0

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

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

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

我从未听说过优化类型的编译器(为了优化而进行类型转换)。如果它被声明为 int32,则它始终是 int32,并且将始终在内存中右对齐。检查您的编译器文档以了解各种优化是如何工作的。

我想我知道你的担忧来自哪里,结构。结构通常被填充到最佳对齐。这就是为什么您需要在它们周围包裹一个#pragma pack() 以使它们字节对齐。

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

于 2010-06-14T23:11:49.287 回答