【问题标题】:Do I need to use volatile keyword for memory access in critical section?我是否需要在关键部分使用 volatile 关键字进行内存访问?
【发布时间】:2015-01-11 04:34:04
【问题描述】:

我正在使用 gcc 为单处理器 32 位微控制器编写代码。

我需要使用链表中的时间戳对象。可能是异步的另一部分代码(可能在 ISR 中)将它们添加到列表中。

临界区是通过关闭中断并使用barrier()函数来实现的。

我很困惑 gcc 优化可能会通过缓存指向列表项(下一个要删除的最近项、列表头或空闲列表)的指针来破坏我的代码。我不希望 while 循环中的任何内容从循环周围的前一个时间缓存。内存屏障会保护我免受编译器决定在函数开始时加载一次指针并且不再重新加载它吗?所有这些列表指针都可能在生产者代码的关键部分(未显示)中进行修改。例如,我试图了解pqueue_first 是否应该是一个易失性指针。

大概,如果没有循环(添加到列表就是这种情况),如果函数中的所有代码都在关键部分,我就可以了?

请不要仅仅因为我已经阅读了很多关于易失性或关键部分的通用文章而指向我,但我无法了解如何将其应用于此特定代码。我知道 volatile 确保编译器每次引用它时都会重新加载变量。但我不了解可能的优化范围及其与内存屏障的交互。

typedef struct {
    EV_EventQueueEntry_t *pqueue_alloc; // allocation (never changes)
    EV_EventQueueEntry_t *pqueue_head; // head of active queue (ISR can change it)
    EV_EventQueueEntry_t *pqueue_free; // head of free list (ISR can change it)
    EV_EventQueueEntry_t *pqueue_first; // soonest item in queue (ISR can change it)
    EV_EventQueueEntry_t *pqueue_first_prev; // back pointer from soonest item (ISR can change it)
    EV_UInt_t max_event_count;
} EV_EventQueue_t;

void RunLoop(EV_EventQueue_t *pev)
{
    while(not timeout)
    {
        // Enter critical section
        disable_interrupts();
        barrier();

        // item with most recent timestamp
        // this can be changed by ISR add to queue operation
        EV_EventQueueEntry_t *pfirst = pev->pqueue_first;

        if(pfirst!=NULL && EV_PortIsFutureTime(pfirst->event.timestamp, EV_PortGetTime()))
        {
            // Copy out message
            EV_Event_t e = pfirst->event;

            // Remove event from queue
            if(pev->pqueue_first_prev != NULL)
                pev->pqueue_first_prev->pnext = pfirst->pnext;
            else
                pev->pqueue_head = pfirst->pnext;

            // Put event back on free list
            pfirst->pnext = pev->pqueue_free;
            pev->pqueue_free = pfirst;
            pfirst->event.message.type = EV_MESSAGE_NULL;

            // Find next soonest message to process after this one
            pev->pqueue_first = ...;
            pev->pqueue_first_prev = ...; // back pointer

            // Exit critical section
            barrier();
            enable_interrupts();

            // Dispatch message
            ...
        }
        else
        {
            // Exit critical section
            barrier();
            enable_interrupts();

            // waste some time
            ...
        }
    }
}

【问题讨论】:

  • 我要问的第一个问题是barrier 函数是否为所有处理器和操作系统定义/标准化。如果不是,那么第二个问题是:您正在使用的特定处理器和操作系统的官方文档在哪里。
  • 屏障函数似乎输出__asm__ __volatile__ ("": : :"memory"),如下面的答案所示。这是 AVR32。
  • 注:貌似在屏障内很久了。

标签: c volatile producer-consumer critical-section memory-barriers


【解决方案1】:

C++11 对此有一个标准特性:std::atomic_signal_fence。 C11 具有类似的功能,但没有命名空间限定符。如果您的程序仅使用单个线程并且您只是试图阻止编译器跨围栏移动加载/存储,则它是合适的。在临界区之前使用std::atomic_signal_fence(memory_order_acquire),在临界区之后使用std:atomic_signal_fence(memory_order_release)

如果您不使用 C++11 或 C11,而只是使用 gcc 或理解 gcc asms 的编译器,则可以使用 __asm__ __volatile__ ("": : :"memory") 作为编译器屏障。 asm 说它不能被删除并威胁要以神秘的方式修改内存,因此编译器将无法在其上移动加载/存储。

【讨论】:

    【解决方案2】:

    volatile 一词告诉编译器从内存而不是缓存中检索值,并将值存储在内存中而不是缓存中。当多个内核可以在同一内存上运行时会执行此操作,因此您不一定能保证缓存是新鲜的。

    在您的情况下,ISR 将修改内存,因此它访问的任何变量都应标记为易失性。真正会导致问题的并不是编译器优化。该问题是由 ISR 发生时发生的上下文切换引起的。处理器的状态(寄存器值)被存储并在 ISR 之后恢复,这意味着寄存器中的值将与 ISR 之前的值相同。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-02-08
      • 2018-09-25
      • 1970-01-01
      • 2012-11-03
      • 1970-01-01
      相关资源
      最近更新 更多