5

以下是运行 Barrelfish 操作系统时使 Pandaboard 上的 LED 闪烁的一些代码。gpio_oe我的问题是,如果从and的定义中删除了 'volatile' 关键字,为什么 LED 不闪烁gpio_dataout

static volatile uint32_t *gpio_oe = (uint32_t *)(GPIO_BASE + 0x0134);
static volatile uint32_t *gpio_dataout = (uint32_t *)(GPIO_BASE + 0x013C);

void led_flash
{
    // Enable output
    *gpio_oe &= (~(1 << 8));

    // Toggle LED on and off till eternity
    while(true)
    {
      *gpio_dataout ^= (1 << 8);  // Set means LED on; Clear means LED off
       time_delay();  // To give blinking effect
    }
}

我知道如果变量的值可以通过程序外部的源自发更改,则需要使用 volatile。但我在这里看不到这样的案例。编译器执行了哪些优化,使整个 while 循环闪烁 LED 变得毫无意义?这种优化背后的逻辑是什么,即。这种优化有意义的合法案例?

4

4 回答 4

9

您还需要volatile强制执行内存写入和生成的代码访问volatile变量的顺序。使用常规变量,编译器可能会决定写入是不必要的,要么丢弃它们,要么只保留最后一个。

从评论中移出:如果编译器没有看到对变量的读取,它可能什么也不写,它甚至可能删除该变量。

于 2013-02-05T14:56:28.597 回答
5

据编译器所知,值*gpio_oe*gpio_dataout写入但从不被读取。对于普通数据存储器,这种访问模式是完全冗余的,因此可以优化。对于已读取但从未写入的位置也是如此。

然而,对于内存映射 I/O,访问“内存”位置会产生编译器不知道的副作用。声明位置volatile告诉编译器必须完全按照代码描述的方式显式访问该位置。

与内存映射 I/O 一样,在不同线程(例如 RTOS 任务或中断处理程序)之间共享内存时也会出现类似的问题,因为该语言同样不知道这些上下文。

Embedded.com 在许多文章中涵盖了该主题:

于 2013-02-05T20:04:20.370 回答
4

您正在尝试直接访问硬件寄存器,因此您希望每次访问都进入内存总线,而不是像普通变量一样停留在寄存器中。Volatile 将告诉编译器强制该变量的所有使用进入或来自内存总线。您仍然有数据缓存的问题,但这是一个单独的主题。

编辑:

会发生什么是在您的无限循环中,编译器可以将该变量优化为在寄存器中,这意味着永远不会进入内存总线,这意味着永远不会更改 gpio,这意味着 LED 不会闪烁。如果您删除易失性,编译然后反汇编(或编译为asm,我发现通过反汇编二进制文件更容易阅读),这应该很容易看到。

于 2013-02-05T15:36:30.837 回答
1

volatile阻止编译器优化对变量的读取和写入,没有它,编译器假定值永远不会改变,并且可以替换一个循环,其中通过一次调用读取标志,或者如果以后不使用变量,则删除写入,请参阅此问题:

为什么在 C 中需要 volatile?

于 2013-02-05T14:56:07.757 回答