2

Я пишу код для одного процессора 32-битного микроконтроллера с использованием gcc.Нужно ли использовать ключевое слово volatile для доступа к памяти в критическом разделе?

Мне нужно использовать объекты с меткой времени из связанного списка. Другая часть кода, которая может быть асинхронной (возможно, в 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 
      ... 
     } 
    } 
} 
+0

Первый вопрос, который я задал бы, заключается в том, определена ли функция «барьер»/стандартизована для всех процессоров и операционных систем. Если нет, то второй вопрос: где находится официальная документация для конкретного процессора и операционной системы, которые вы используете. – user3386109

+0

Функция барьера выводит '__asm__ __volatile__ (" ":::" memory ")', как в ответе ниже. Это AVR32. – Robotbugs

+0

Примечание: долгое время выглядит в пределах барьера. – chux

ответ

4

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 или компилятор, который понимает gms asms, вы можете использовать __asm__ __volatile__ ("": : :"memory") для барьера компилятора. Asm говорит, что он не может быть удален и угрожает модифицировать память загадочными способами, поэтому компилятор не сможет перемещать грузы/магазины поверх него.

2

Слово volatile сообщает компилятору, что оно извлекает значение из памяти, а не из кеша, и сохраняет значения в памяти, а не в кеше. Это делается, когда несколько ядер могут работать в одной и той же памяти, поэтому вам не обязательно гарантировано, что кеш свежий.

В вашем случае ISR будет изменять память, поэтому любые переменные, к которым он обращается, должны быть помечены как изменчивые. Это не оптимизация компилятора, которая вызовет проблему. Проблема вызвана переключением контекста, который возникает, когда происходит ваш ISR. Состояние процессора (значения регистра) сохраняется и после восстановления ISR, что означает, что значения в регистрах будут такими, какими они были до ISR.