我正在研究AVR平台的定时循环,我正在倒计时ISR中的一个字节。由于此任务是我的程序的主要功能,我想永久保留一个处理器寄存器,以便ISR在通常的代码路径递减时不必触及内存屏障,与零相比,{{1 }}
The avr-libc docs显示如何将变量绑定到寄存器,并且我的工作没有问题。但是,由于此变量在主程序(用于启动计时器倒计时)和ISR(用于实际计数和信号完成)之间共享,因此它也应该是reti
以确保编译器不执行任何操作聪明地优化它。
在此上下文中(在整个单片构建中保留寄存器),组合volatile
在语义上对我有意义,因为“将此变量永久存储在寄存器volatile register
中,但不要优化检查,因为寄存器可能在外部修改“。然而,海湾合作委员会并不喜欢这样,并且emits a warning它可能会继续并且无论如何都要优化变量访问。
GCC中这种组合的错误历史表明,编译器团队根本不愿意考虑我所描述的场景类型,并认为提供它是没有意义的。我是否遗漏了rX
方法本身就是一个坏主意的一些基本原因,或者这是一个具有语义意义的情况,但编译器团队对处理不感兴趣?
答案 0 :(得分:4)
volatile
的语义 not 与您描述的“不优化远程检查因为寄存器可能在外部修改”但实际上更窄:尝试考虑它as“不要在寄存器中缓存来自RAM 的变量值。”
以这种方式看,将寄存器声明为volatile
没有任何意义,因为寄存器本身不能被“缓存”,因此不可能与变量的“实际”值不一致。
读取对volatile变量的访问通常不被优化掉的事实仅仅是上述语义的副作用,但不保证。
我认为GCC应该默认假设寄存器中的值“像volatile一样”,但我没有证实它实际上是这样。
编辑:
我刚做了一个小测试,发现:
假设全局寄存器变量实际上被认为是“不受支持的”,我并不感到惊讶gcc将它们视为 local 变量,具有已知含义。
我的测试代码如下:
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(;;){}
}
答案 1 :(得分:1)
为完整的编译单元保留一个变量的寄存器对于编译器的代码生成器可能过于严格。也就是说,每个C例程都不得使用该寄存器。
如果您的代码超出范围,您如何保证其他被调用的例程不使用该寄存器?即使像串行I / O例程这样的东西也不得使用那个保留寄存器。编译器不会根据用户程序中的数据定义重新编译其运行时库。
您的应用程序是否真的如此敏感,以至于可以检测到从L2或L3带来内存的额外延迟?如果是这样,那么你的ISR可能会如此频繁地运行以至于所需的内存位置始终可用(即它不会通过缓存向下翻页),因此不会遇到内存障碍(我假设你指的是内存障碍)如何通过缓存等方式实现cpu中的内存运行。但为了真的这样,up必须有一个相当大的L1缓存,ISR必须以非常高的频率运行。
最后,有时应用程序的要求使得必须在ASM中对其进行编码,在这种情况下,您可以完全按照您的要求进行操作!
答案 2 :(得分:1)
您在AVR变量上使用volatile
关键字的原因是,正如您所说,避免编译器优化对变量的访问。现在的问题是,这怎么会发生?
变量有两个可以驻留的位置。通用寄存器文件或RAM中的某个位置。考虑变量驻留在RAM中的情况。要访问变量的最新值,编译器会使用ld
指令的某种形式(例如lds r16, 0x000f
)从RAM加载变量。在这种情况下,变量存储在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中的变量进行的操作不同,这些操作可以被认为是原子操作,因为没有读取 - &gt;修改 - &gt;写周期来谈。如果在更新变量后触发中断,它将获得变量的新值,因为它将从绑定的寄存器中读取变量。
此外,由于变量绑定到寄存器,因此任何测试指令都将利用寄存器本身,并且不会因为编译器可能具有“预感”它是静态值而被优化掉,因为寄存器由它们寄存器非常自然是不稳定的。
现在,根据经验,当在AVR中使用中断时,我有时会注意到全局volatile变量从未达到RAM。编译器始终将它们保留在寄存器上,绕过read - &gt;修改 - &gt;一起写周期。然而,这是因为编译器优化,并且不应该依赖它。不同的编译器可以为同一段代码自由生成不同的程序集。您可以使用avr-objdump
实用程序生成最终文件或任何特定对象文件的反汇编。
干杯。