Программирование AVR AVR GCC: атомарно и не атомарно исполняемые блоки кода Tue, April 23 2024  

Поделиться

Нашли опечатку?

Пожалуйста, сообщите об этом - просто выделите ошибочное слово или фразу и нажмите Shift Enter.

AVR GCC: атомарно и не атомарно исполняемые блоки кода Печать
Добавил(а) microsin   

Определения, которые имеются в подключаемом файле < util/atomic.h >:

#define ATOMIC_BLOCK(type)
#define NONATOMIC_BLOCK(type)
#define ATOMIC_RESTORESTATE
#define ATOMIC_FORCEON
#define NONATOMIC_RESTORESTATE
#define NONATOMIC_FORCEOFF

Примечание: макросы в этом заголовочном файле требуют фичи ISO/IEC 9899:1999 ("ISO C99") для переменных цикла for, чтобы переменные можно было декларировать в самом цикле for. По этой причине этот заголовочный файл можно использовать только если уровень стандарта компилятора (опция --std=) установлена в значение либо c99, либо gnu99.

Чтобы воспользоваться библиотечными директивами поддержки атомарности, подключите к коду заголовочный файл atomic.h:

#include < util/atomic.h >

Макросы в этом заголовочном файле работают с блоками кода, которые гарантированно будут выполнены атомарно или не атомарно. Термин "атомарный" в этом контексте означает невозможность прерывания выполнения атомарно выделенного блока кода.

Эти макросы работают через атомарную манипуляцию битом глобального разрешения прерываний (бит Global Interrupt Status, или бит I) регистра SREG. Выходные пути из обоих типов блоков (атомарных и не атомарных) обрабатываются автоматически без необходимости специального вмешательства программиста, например состояние разрешения прерывания будет восстановлено в то же самое значение, как оно было при входе в соответствующий блок.

Типовой пример, который требует атомарного доступа - 16-битные переменные (или переменные из большего количества бит), которые совместно используются как в основной программе, так и в ISR. Хотя декларирование этих переменных с атрибутом volatile гарантирует, что эта переменная не получит какой-либо оптимизированный тип доступа, но все равно не будет гарантирован атомарный доступ к ней. Т. е. просто определения переменной через volatile недостаточно, когда многобайтная переменная получает доступ как из основной программы, так и из ISR. Рассмотрим пример:

#include < inttypes.h >
#include < avr/interrupt.h >
#include < avr/io.h >
 
volatile uint16_t ctr;
 
ISR(TIMER1_OVF_vect)
{
   ctr--;
}
 
int main(void)
{
   ...
   ctr = 0x200;
   start_timer();
   while (ctr != 0)
   {
      // ожидание
   };
   ...
}

Есть некий шанс, что в контексте функции main произойдет непредвиденный выход из цикла ожидания (while (ctr != 0)), когда переменная просто дошла до значения 0xFF. Это произойдет по той причине, что компилятор не может обычным способом обеспечить 16-битный атомарный доступ к переменной, потому что код выполняется на 8-битном CPU. Так, например, если переменная находится в значении 0x0100, то компилятор в функции main проверит младший байт на 0, и эта проверка окажется успешной. Затем компилятор проверит старший байт, и если в этот момент сработает ISR (кто ему это запретит?), то контекст выполнения main будет прерван. ISR декрементирует переменную, и она перейдет из значения 0x0100 0x00FF, после чего код main продолжит выполнение. Теперь проверка старшего байта переменной также даст (теперь) 0, и это приведет к ошибочному определению, что вся 16-битная переменная равна 0 (младший байт переменной был уже успешно проверен на 0 ранее), что вызовет преждевременный выход из цикла.

При использовании макросов из вышеупомянутого заголовочного файла код может быть переписан следующим образом:

#include < inttypes.h >
#include < avr/interrupt.h >
#include < avr/io.h >
#include < util/atomic.h >
 
volatile uint16_t ctr;
 
ISR(TIMER1_OVF_vect)
{
   ctr--;
}
 
int main(void)
{
   ...
   ctr = 0x200;
   start_timer();
   sei();
   uint16_t ctr_copy;
   do
   {
      ATOMIC_BLOCK(ATOMIC_FORCEON)
      {
         ctr_copy = ctr;
      }
   }
   while (ctr_copy != 0);
   ...
}

В этом примере будет установлена надлежащая защита от прерывания перед доступом к переменной ctr, что гарантирует её последующую верную проверку. Если не известно заранее глобальное состояние разрешения прерываний до входа в ATOMIC_BLOCK, то он должен быть выполнен с параметром ATOMIC_RESTORESTATE вместо ATOMIC_FORCEON.

В некоторых случаях особое внимание должно быть уделено проблемам изменения последовательности выполнения кода, когда разрешаются опции оптимизации компилятора, см. [2].

Макросы ATOMIC_BLOCK() и NONATOMIC_BLOCK() используются и работают одинаково, но являются полными антиподами друг друга - первый гарантирует атомарность, другой гарантирует не атомарность кода.

ATOMIC_BLOCK() создает блок кода, который гарантированно будет выполнен атомарно. При входе в блок флаг глобального разрешения прерываний (бит Global Interrupt Status) в регистре SREG будет сброшен (прерывания будут запрещены), после чего на выходе из блока прерывания будут снова разрешены.

Для макроса ATOMIC_BLOCK() возможны два значения для параметра type: ATOMIC_RESTORESTATE и ATOMIC_FORCEON.

ATOMIC_FORCEON. Это один из возможных параметров для макроса ATOMIC_BLOCK(). Когда ATOMIC_BLOCK() создан с параметром ATOMIC_FORCEON, то это гарантирует разрешение прерывания на выходе из блока, т. е. для этого не нужно сохранять предыдущее состояние регистра SREG в начале блока.

Использование ATOMIC_FORCEON вместо ATOMIC_RESTORESTATE несколько экономнее по расходу памяти. Однако следует учитывать, что ATOMIC_FORCEON используют только тогда, когда заранее точно известно, что на входе в блок прерывания были разрешены, или когда заранее известно, что разрешение прерывания на выходе из блока не приведет к неожиданным последствиям.

ATOMIC_RESTORESTATE. Это другой из возможных параметров для макроса ATOMIC_BLOCK(). Когда ATOMIC_BLOCK() создан с параметром ATOMIC_RESTORESTATE, то это приведет к восстановлению предыдущего состояния регистра SREG, которое было сохранено перед тем, как флаг глобального разрешения прерываний будет запрещен. Результирующий эффект от такого использования макроса ATOMIC_BLOCK гарантирует атомарное выполнение блока кода, причем предыдущее состояние прерываний не будет нарушено на выходе из блока.

NONATOMIC_BLOCK() создает блок кода, который гарантированно не будет выполнен атомарно. При входе в блок флаг глобального разрешения прерываний (бит Global Interrupt Status) в регистре SREG будет установлен (прерывания будут разрешены), после чего на выходе из блока прерывания будут снова запрещены. На первый взгляд непонятно, зачем нужен этот макрос, однако он полезен, когда встраивается вовнутрь секций ATOMIC_BLOCK, позволяя неатомарное выполнение некоторых участков кода родительского ATOMIC_BLOCK.

Для макроса NONATOMIC_BLOCK() возможны два значения для параметра type: NONATOMIC_RESTORESTATE и NONATOMIC_FORCEOFF.

NONATOMIC_FORCEOFF. Это один из возможных параметров для макроса NONATOMIC_BLOCK(). Когда NONATOMIC_BLOCK() создан с параметром NONATOMIC_FORCEOFF, то это гарантирует запрещение прерывания на выходе из блока, т. е. для этого не нужно сохранять предыдущее состояние регистра SREG в начале блока.

Использование NONATOMIC_FORCEOFF вместо NONATOMIC_RESTORESTATE несколько экономнее по расходу памяти. Однако следует учитывать, что NONATOMIC_RESTORESTATE используют только тогда, когда заранее точно известно, что на входе в блок прерывания были запрещены, или когда заранее известно, что запрещение прерывания на выходе из блока не приведет к неожиданным последствиям.

NONATOMIC_RESTORESTATE. Это другой из возможных параметров для макроса NONATOMIC_BLOCK(). Когда NONATOMIC_BLOCK() создан с параметром NONATOMIC_RESTORESTATE, то это приведет к восстановлению предыдущего состояния регистра SREG, которое было сохранено перед тем, как флаг глобального разрешения прерываний будет разрешен. Результирующий эффект от такого использования макроса NONATOMIC_BLOCK гарантирует не атомарное выполнение блока кода, причем предыдущее состояние прерываний не будет нарушено на выходе из блока.

[Ссылки]

1. AVR Libc Reference Manual Compiler optimization site:atmel.com.
2. AVR GCC: оптимизация и проблема перетасовки кода.

 

Добавить комментарий


Защитный код
Обновить

Top of Page