1

我正在为 AVR 平台开发一个时序循环,我正在倒计时 ISR 中的单个字节。由于此任务是我的程序的主要功能,我想永久保留一个处理器寄存器,以便 ISR 在其通常的代码路径递减时不必遇到内存屏障,比较为零,并且reti.

avr-libc 文档展示了如何将变量绑定到寄存器,并且我可以毫无问题地工作。但是,由于此变量在主程序(用于启动计时器倒计时)和 ISR(用于实际计数和发出完成信号)之间共享,因此还应该volatile确保编译器在优化它时不会做任何过于聪明的事情。

在这种情况下(在整个整体构建中保留一个寄存器),这种组合volatile register在语义上对我来说是有意义的,因为“将此变量永久存储在 register 中rX,但不要优化检查,因为该寄存器可能会在外部修改”。然而,GCC 不喜欢这样,并发出警告说它可能会继续优化变量访问。

GCC 中这种组合的错误历史表明编译器团队根本不愿意考虑我所描述的场景类型,并认为提供它是没有意义的。我是否遗漏了该方法本身就是一个坏主意的一些根本原因volatile register,或者这是一个在语义上有意义但编译器团队对处理不感兴趣的案例?

4

3 回答 3

4

的语义volatile并不完全如您所描述的“不要优化检查,因为寄存器可能会在外部修改”,但实际上更窄:尝试将其视为“不要将RAM中的变量值缓存在寄存器中” .

这样看来,声明寄存器没有任何意义,volatile因为寄存器本身不能被“缓存”,因此不可能与变量的“实际”值不一致。

对 volatile 变量的读取访问通常不会被优化掉,这只是上述语义的副作用,但不能保证。

我认为 GCC 应该默认假设寄存器中的值“像 volatile”,但我还没有验证它是否确实如此。

编辑:

我只是做了一个小测试,发现:

  1. avr-gcc 4.6.2 不会全局寄存器变量视为 volatile 在读取访问方面,并且
  2. Atmel Studio 的 Naggy 扩展在我的代码中检测到错误:“不支持全局寄存器变量”。

假设全局寄存器变量实际上被认为是“不受支持的”,我并不感到惊讶 gcc 将它们视为局部变量,具有已知的含义。

我的测试代码如下所示:

uint8_t var;

volatile uint8_t volVar;

register uint8_t regVar asm("r13");

#define NOP asm volatile ("nop\r\n":::)

int main(void)
{
    var = 1; // <-- kept
    if ( var == 0 ) {
        NOP; // <-- optimized away, var is not volatile
    }

    volVar = 1; // <-- kept
    if ( volVar == 0 ) {
        NOP; // <-- kept, volVar *is* volatile
    }

    regVar = 1; // <-- optimized away, regVar is treated like a local variable
    if ( regVar == 0 ) {
        NOP; // <-- optimized away consequently
    }

    for(;;){}
}
于 2013-08-13T10:51:13.603 回答
1

volatile正如您所说,您在 AVR 变量上使用关键字的原因是避免编译器优化对变量的访问。现在的问题是,这是怎么发生的呢?

一个变量有两个可以驻留的地方。在通用寄存器文件中或在 RAM 中的某个位置。考虑变量驻留在 RAM 中的情况。为了访问变量的最新值,编译器使用某种形式的ld指令从 RAM 加载变量,比如lds r16, 0x000f. 在这种情况下,变量存储在 RAM 位置0x000f,程序在r16. 现在,如果启用中断,事情就会变得有趣。假设加载变量后,发生以下情况inc r16,然后触发中断并运行其相应的ISR。在 ISR 中,也使用该变量。然而,有一个问题。该变量存在于两个不同的版本中,一个在 RAM 中,一个在r16. 理想情况下,编译器应该使用 中的版本r16,但不保证这个版本存在,所以它改为从 RAM 加载它,现在,代码无法按需要运行。然后输入volatile关键字。变量仍然存储在 RAM 中,但是,编译器必须确保变量在 RAM 中更新,然后再发生其他任何事情,因此可能会生成以下程序集:

cli
lds r16, 0x000f
inc r16
sei
sts 0x000f, r16

首先,中断被禁用。然后,将变量加载到 r16 中。增加变量,启用中断,然后存储变量。在将变量存储回 RAM 之前启用全局中断标志可能看起来令人困惑,但来自指令集手册:

SEI 之后的指令将在任何未决中断之前执行。

这意味着sts指令将在任何中断再次触发之前执行,并且中断被禁用的时间尽可能短。

现在考虑变量绑定到寄存器的情况。对变量进行的任何操作都直接在寄存器上进行。这些操作与对 RAM 中的变量执行的操作不同,可以被认为是原子的,因为没有读 -> 修改 -> 写周期可言。如果在变量更新后触发中断,它将获取变量的新值,因为它将从绑定的寄存器中读取变量。

此外,由于变量绑定到寄存器,因此任何测试指令都将使用寄存器本身,并且不会因为编译器有“预感”而优化掉,这是一个静态值,因为寄存器本质上是易挥发的。

现在,根据经验,在 AVR 中使用中断时,我有时会注意到全局 volatile 变量永远不会影响 RAM。编译器一直将它们保存在寄存器上,完全绕过了读取->修改->写入循环。然而,这是由于编译器优化造成的,不应依赖它。不同的编译器可以自由地为同一段代码生成不同的程序集。您可以使用该avr-objdump实用程序生成最终文件或任何特定目标文件的反汇编。

干杯。

于 2013-08-08T14:33:53.407 回答
0

为一个完整的编译单元保留一个变量的寄存器对于编译器的代码生成器来说可能过于严格。也就是说,每个 C 例程都必须不使用该寄存器。

一旦您的代码超出范围,您如何保证其他被调用的例程不使用该寄存器?甚至像串行 i/o 例程这样的东西也必须不使用该保留寄存器。编译器不会根据用户程序中的数据定义重新编译其运行时库。

您的应用程序真的对时间如此敏感,以至于可以检测到从 L2 或 L3 启动内存的额外延迟吗?如果是这样,那么您的 ISR 可能运行得如此频繁,以至于所需的内存位置始终可用(即它不会通过缓存向下分页),因此不会遇到内存障碍(我假设您指的是内存障碍)通过缓存等来了解 CPU 中的内存如何真正运行)。但要真正做到这一点,up 必须有一个相当大的 L1 缓存,并且 ISR 必须以非常高的频率运行。

最后,有时应用程序的要求使得有必要在 ASM 中对其进行编码,在这种情况下,您可以完全按照您的要求进行操作!

于 2013-08-07T16:24:18.030 回答