简体   繁体   English

如果全局变量被中断修改,真的需要volatile修饰符吗?

[英]Is volatile modifier really needed if global variables are modified by an interrupt?

There was a lot said and written about volatile variables, and their use. 有很多关于易变量及其用途的说和写。 In those articles, two slightly different ideas can be found: 在这些文章中,可以找到两个略有不同的想法:

1 - Volatile should be used when variable is getting changed outside of the compiled program. 1 - 当变量在编译程序之外被更改时,应该使用Volatile。
2 - Volatile should be used when variable is getting changed outside the normal flow of the function. 2 - 当变量在函数的正常流量之外变化时,应使用Volatile。

First statement limits volatile usage to memory-mapped registers etc. and multi-threaded stuff, but the second one actually adds interrupts into the scope. 第一个语句限制了对内存映射寄存器等的易失性使用和多线程的东西,但第二个实际上将中断添加到作用域中。

This article ( http://www.barrgroup.com/Embedded-Systems/How-To/C-Volatile-Keyword ) for example explicitly states that volatile modifier should be used for globals changed during the interrupt, and provides this example: 例如,本文( http://www.barrgroup.com/Embedded-Systems/How-To/C-Volatile-Keyword )明确指出volatile修饰符应该用于中断期间更改的全局变量,并提供此示例:

int etx_rcvd = FALSE;

void main() 
{
    ... 
    while (!ext_rcvd) 
    {
        // Wait
    } 
    ...
}

interrupt void rx_isr(void) 
{
    ... 
    if (ETX == rx_char) 
    {
    etx_rcvd = TRUE;
    } 
    ...
}

Note how setting up a rx_isr() as a callback is conveniently omitted here. 注意如何在这里方便地省略设置rx_isr()作为回调。 Hence I wrote my own example: 因此我写了自己的例子:

#include <stdio.h>
#include <time.h>
#include <signal.h>

void f();

int n = 0;

void main() 
{
    signal(2,f);
    time_t tLastCalled = 0;
    printf("Entering the loop\n");
    while (n == 0) 
    {
        if (time(NULL) - tLastCalled > 1)
        {
            printf("Still here...\n");
            tLastCalled = time(NULL);
        }
    }
    printf ("Done\n");
}

void f() 
{
    n = 1;
}

Compiled with gcc on linux with various levels of optimizations, every time loop exited and I saw "Done" when I pressed ctrl+c, which means that really gcc compiler is smart enough not to optimize variable n here. 用linux上的gcc编译并进行各种优化级别,每次循环退出时我都按下ctrl + c看到“完成”,这意味着真正的gcc编译器足够聪明,不能在这里优化变量n。

That said, my question is: 那就是说,我的问题是:
If compiler can really optimize global variables modified by an interrupt service routine, then: 如果编译器可以真正优化由中断服务例程修改的全局变量,那么:
1. Why it has a right of optimizing a global variable in the first place when it can possibly be called from another file? 1.当它可能从另一个文件中调用时,为什么它首先有权优化全局变量?
2. Why the example article and many others on the internet state that the compiler will not "notice" the interrupt callback function? 2.为什么互联网上的示例文章和许多其他文章表明编译器不会“注意到”中断回调函数?
3. How do I modify my code to accomplish this? 3.如何修改代码来完成此操作?

Because you have a function call to an external function, the while loop does check n every time. 因为您有一个函数调用外部函数,所以while循环每次都会检查n However, if you remove those function calls the optimizer may registerize or do away with any checks of n . 但是,如果删除这些函数调用,优化程序可能会注册或取消对n任何检查。

Ex (gcc x86_64 -O3): Ex(gcc x86_64 -O3):

volatile int n;

int main() {
    while(n==0) {}
    return 0;
}

becomes: 变为:

.L3:
        movl    n(%rip), %eax
        testl   %eax, %eax
        je      .L3
        xorl    %eax, %eax
        ret

But

int n;

int main() {
    while(n==0) {}
    return 0;
}

becomes: 变为:

        movl    n(%rip), %eax
        testl   %eax, %eax
        jne     .L2
.L3:
        jmp     .L3

In this case, n is never looked at in the infinite loop. 在这种情况下, n永远不会在无限循环中查看。

If there is a signal handler that modifies a global, you really should label that global volatile. 如果有一个修改全局的信号处理程序,你真的应该标记全局volatile。 You might not get in trouble by skipping this, but you are either getting lucky or you are counting on the optimizer not being able to verify whether or not a global is being touched. 你跳过这个可能不会遇到麻烦,但是你要么运气好,要么指望优化者无法验证全局是否被触及。

There is some movement in cross module optimization at link time ( llvm ), so someday an optimizer may be able to tell that calls to time or printf aren't touching globals in your file. 在链接时( llvm ),交叉模块优化中存在一些移动,因此有一天优化器可能会告诉对timeprintf调用没有触及文件中的全局变量。 When that happens, missing the volatile keyword may cause problems even if you have external function calls. 发生这种情况时,即使您有外部函数调用,错过volatile关键字也可能会导致问题。

If compiler can really optimize global variables modified by an interrupt service routine, then: 如果编译器可以真正优化由中断服务例程修改的全局变量,那么:

  1. Why it has a right of optimizing a global variable in the first place when it can possibly be called from another file? 为什么它有可能在可能从另一个文件调用时首先优化全局变量?

The key here is that in a "normal", single-threaded program with no interrupts, the global variable cannot be modified by at any time. 这里的关键是在没有中断的“普通”单线程程序中,全局变量不能随时修改。 All access to the variable are sequenced in a predictable manner, no matter which file that does the access. 无论访问哪个文件,所有对变量的访问都以可预测的方式排序。

And the optimizations may be subtle. 优化可能很微妙。 It is not as simple as "ah ok this global doesn't seem to be used, lets remove it entirely". 它不是那么简单,“啊,好像这个全局似乎没有被使用,让我们完全删除它”。 Rather, for some code like 相反,对于某些代码来说

while(global) 
{ 
  do_stuff(global); 
} 

the optimizer might create something behaving like: 优化器可能会创建如下行为:

register tmp = global;
loop:
  do_stuff(tmp);
goto loop;

Which changes the meaning of the program completely. 这完全改变了程序的含义。 How such bugs caused by the lack of volatile manifest themselves is always different from case-to-case. 由于缺乏挥发性而导致的这种错误如何总是与案例有所不同。 They are very hard to find. 他们很难找到。


  1. Why the example article and many others on the internet state that the compiler will not "notice" the interrupt callback function? 为什么互联网上的示例文章和许多其他文章都说编译器不会“注意到”中断回调函数?

Because embedded compilers are traditionally stupid when it comes to this aspect. 因为嵌入式编译器在这方面传统上是愚蠢的。 Traditionally, when a compiler spots your non-standard interrupt keyword, it will just do 2 things: 传统上,当编译器发现你的非标准中断关键字时,它只会做两件事:

  • Generate the specific return code from that function, since interrupts usually have different calling conventions compared to regular function calls. 从该函数生成特定的返回码,因为与常规函数调用相比,中断通常具有不同的调用约定。
  • Ensure that the function gets linked even though it is never called from the program. 即使从程序中调用函数,也要确保该函数已链接。 Possibly allocated in a separate memory segment. 可能分配在单独的内存段中。 This is actually done by the linker and not the compiler. 这实际上是由链接器而不是编译器完成的。

There might nowadays be smarter compilers. 现在可能有更聪明的编译器。 PC/desktop compilers face the very same issue when dealing with callback functions/threads, but they are usually smart enough to realize that they shouldn't assume things about global variables shared with a callback. PC /桌面编译器在处理回调函数/线程时面临同样的问题,但它们通常足够聪明,意识到它们不应该假设与回调共享的全局变量。

Embedded compilers are traditionally far dumber than PC/desktop compilers when it comes to optimizations. 在优化方面,嵌入式编译器传统上比PC /桌面编译器更加笨重。 They are generally of lower quality and worse at standard compliance. 它们通常质量较差,而且标准合规性较差。 If you are one of just a few compiler vendors supporting a specific target, or perhaps the only vendor, then the lack of competition means that you don't have to worry much about quality. 如果您是支持特定目标或可能是唯一供应商的少数编译器供应商之一,那么缺乏竞争意味着您不必担心质量问题。 You can sell crap and charge a lot for it. 你可以出售废话并为此收取很多费用。

But even good compilers can struggle with such scenarios, especially multi-platform ones that don't know anything about how interrupts etc work specifically in "target x". 但即使是优秀的编译器也可能会遇到这种情况,尤其是那些对“目标x”中的中断等工作方式一无所知的多平台编译器。

So you have the case where the good, multi-platform compiler is too generic to handle this bug. 因此,您可以获得良好的多平台编译器过于通用以处理此错误的情况。 While at the same time, the bad, narrow compiler for "target x" is too poorly written to handle it, even though it supposedly knows all about how interrupts work on "target x". 与此同时,“目标x”的糟糕,狭窄的编译器编写得太差,无法处理它,即使它应该知道中断如何在“目标x”上工作。


  1. How do I modify my code to accomplish this? 如何修改代码才能完成此操作?

Make such globals volatile . 使这些全局变量volatile

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM