При налагодженні вбудованих додатків, найбільш складно відловити помилки, які виявляють себе не постійно, а лише час від часу. Одна з причин подібних багів: змінні, доступ до яких здійснюється асинхронно. Такі змінні повинні бути правильно визначені, і мати відповідний захист.
Визначення повинно включати ключове слово volatile. Воно інформує компілятор, про те, що змінна може бути змінена не тільки з поточного виконуваного коду, але і з інших місць. Тоді компілятор буде уникати певних оптимізацій цієї змінної.
Щоб захистити загальну змінну, кожна операція доступу до неї повинна бути атомарної. Тобто не повинна перериватися до свого закінчення. Наприклад, доступ до 32 або 16 розрядної змінної на 8-ми розрядної архітектурою не атомарний, оскільки операції читання або запису вимагають більше однієї інструкції.
Розглянемо типовий приклад загальнодоступною змінної - програмний таймер. У обробнику переривання його значення змінюється, а в основному коді - зчитується. Якщо в обробнику інші переривання заборонені, як, наприклад, по дефолту зроблено в мікроконтролерах AVR, то операція зміни змінної атомарний і ніяких косяків не трапиться.
З іншого боку в основному циклі програми переривання найчастіше дозволені, і варіант небезпечного коду міг би виглядати так:
Цей код небезпечний, тому що операція читання змінної system_timer НЕ атомарний. У той час як ми читаємо один з байтів змінної system_timer, може виникнути переривання TIMER0_COMP і обробник змінить її значення. Тоді, після повернення в основну програму, ми прочитаємо частину змінної вже від її нового значення. У ряді випадків мікс зі старого і нового значення не викличе збоїв, але в інших може сильно вплинути на поведінку програми. Ну, наприклад, якщо старе значення system_timer було 0x00ffffff, а нове 0x01000000.
Щоб захистити доступ до змінної system_timer, можна використовувати моніторну функцію, для цього перед ім'ям функції вказується ключове слово__monitor.
Мониторная функція - це функція, яка при вході зберігає регістр SREG, забороняє переривання на час свого виконання, а перед виходом відновлює вміст SREG.
Якщо потрібно, щоб переривання заборонялися в якомусь конкретному ділянці коду, можна використовувати intrinsic функції.
Засоби Сі ++ дозволяють вбудувати цю логіку в клас.
При створенні об'єкта m конструктор збереже регістр SREG, і заборонить переривання. По закінченню блоку - деструктор відновить вміст SREG. Красиво, да?
У загальному випадку принцип написання коду з атомарними операціями всюди один, а варіантів реалізації багато. Можна, наприклад, при доступі до змінної забороняти не всі переривання, а тільки ті, в яких використовується ця змінна.
Проблема можлива і з восьмібітной змінної. Операція на кшталт system_timer - = 100 компілюється в кілька ассемблерних інструкцій і в основному коді також може бути перервана між читанням system_timer і записом результату. Є ще один спосіб читання мультибайтних асинхронних лічильників (без заборони переривань) - вважати змінну два рази і порівняти всі байти, крім молодшого. Якщо байти в копіях рівні - беремо останнім лічений значення, якщо не рівні - зчитуємо до тих пір, поки в двох останніх лічених значеннях байти НЕ будуть рівні. Молодший байт лічильника між читаннями може встигнути змінитися без переносу, тому він в перевірці не бере.
Як видно з прикладів, код наведено для компілятора IAR. У WinAVR подібна проблема вирішується включенням файлу atomic.h, в якому визначені макроси для реалізації атомарного доступу. Наприклад так:
1. Атомарний доступ до змінних site: chipenable.ru - оригінал статті.