我是否需要使用 volatile 关键字来访问临界区的内存? [英] Do I need to use volatile keyword for memory access in critical section?

查看:71
本文介绍了我是否需要使用 volatile 关键字来访问临界区的内存?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I am writing code for a single processor 32 bit microcontroller using gcc.

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

I need to consume time-stamped objects from a linked list. Another part of the code which could be asynchronous (maybe in an ISR) adds them to the list.

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

The critical section is implemented by turning interrupts off and using the barrier() function.

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

I'm confused where gcc optimization could break my code by cacheing pointers to the list items (next most recent item to remove, list head, or free list). I dont want anything inside the while loop to be cached from a previous time around the loop. Will the memory barrier protect me from the compiler deciding to load a pointer once at the start of the function and never reload it again? All these list pointers might be modified in the critical section of the producer code (not shown). I am trying to understand if pqueue_first should be a volatile pointer, for example.

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

Presumably, if there was no loop (which is the case for adding to the list), I am ok if all the code in a function is in a critical section?

请不要因为我已经阅读了大量文章而只向我指出一些关于 volatile 或关键部分的通用文章,但我在了解如何将其应用于此特定代码时遇到了麻烦.我知道 volatile 确保编译器每次引用变量时都会重新加载变量.但我不明白优化的可能范围及其与内存屏障的相互作用.

Please don't just point me to some generic article about volatile or critical sections because I have read a bunch of them, but I am having trouble seeing how to apply it to this specific code. I understand that volatile ensures that the compiler will reload the variable every time it is referenced. But I don't understand the likely scope of optimization and its interaction with memory barriers.

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
            ...
        }
    }
}

推荐答案

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

C++11 has a standard feature for this: std::atomic_signal_fence. C11 has a similar feature, without the namespace qualifier. It is suitable if your program uses only a single thread and you are thus just trying to stop the compiler from moving loads/stores across the fence. Use std::atomic_signal_fence(memory_order_acquire) before the critical section and std:atomic_signal_fence(memory_order_release) after the critical section.

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

If you're not using C++11 or C11, but are just using gcc or a compiler that understands gcc asms, you can use __asm__ __volatile__ ("": : :"memory") for a compiler barrier. The asm says that it can't be removed and threatens to modify memory in mysterious ways, hence the compiler won't be able to move loads/stores over it.

这篇关于我是否需要使用 volatile 关键字来访问临界区的内存?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆