Під атомарним доступом будемо розуміти таке звернення (операції читання, модифікації, записи, або їх послідовність) до змінної або периферійному регістру, яке не призведе до конфлікту при виникненні переривання або пріоритетного витіснення завдання в RTOS системах під час виконання операції. Конфлікт може виникнути, коли в перериванні, або іншої задачі виконується доступ до цього ж ресурсу.
Простий приклад: необхідно інвертувати висновок RA0 контролера:
З точки зору синтаксису та логіки все правильно, але давайте подивимося на код, який вийде в результаті компіляції
У наведеному прикладі поточне значення регістра LATA завантажується в регістр ядра, модіціфіруется і потім зберігається назад в регістр LATA. Тепер уявіть, що після виконання інструкції
в порт запишеться значення, яке не враховує установку біта в перериванні. Графітовий стрижень не опустився. Бум!
В результаті ми отримаємо конфлікт доступу до ресурсу і, як наслідок, непрацездатну програму. Якщо ви вважаєте, що така ситуація надумана, то пройдіть по посиланню. Це реальний питання від реального людини, який натрапив на проблему атомарного доступу до периферії відразу ж після початку освоєння витісняє RTOS TNKernel.
Варіантів вирішення проблеми існує кілька. Кожен з них має свої плюси і мінуси.
Найпростіший і очевидний спосіб: щоб послідовність виконання інструкцій не порушилася, потрібно просто виключити саму можливість запуску конкуруючого коду. У цьому випадку операція инвертирования біта буде виконуватися атомарному. Однак, цей метод має безліч недоліків:
Збільшення обсягу коду за рахунок інструкцій забороняють і дозволяють переривання.
Зменшення швидкості виконання коду з тих же причин.
При такому способі реалізації атомарного доступу неможливо забезпечити детерміновану затримку входу в переривання - з'явиться джиттер. У деяких системах це неприпустимо.
Таке рішення не можна використовувати в системах з витісняє RTOS (там є свої методи).
Якщо цільова платформа включає в себе гнучкий контролер переривання, то побічні ефекти такого методу можуть бути досить сильними, так як необхідно виконати більше дій для заборони переривань. Наприклад, для PIC24 / dsPIC потрібно зберегти в тимчасовій змінної (в стеку) поточний пріоритет ядра, встановити пріоритет ядра на максимум, а після виконання атомарної операції доступу відновити пріоритет з тимчасової змінної. Це є єдино коректним способом заборони переривань при розробці ПЗ на мові Сі (використання інструкції disi не рекомендується).
Такий метод можна застосовувати при використанні витісняє RTOS. Критична секція - це частина коду, в якій заборонено перемикання контексту. Найчастіше це означає заборону переривань і (можливо) виконання додаткових дій над внутрішніми змінними планувальника.
Критична секція має ті ж недоліки, що і попередній спосіб. Крім того, використання критичних секцій не заохочується, так як в цьому випадку ви втручаєтеся в роботу планувальника і порушуєте принципи функціонування RTOS. Якщо доступ до ресурсу можна виділити у відносно великий шматок коду, тоді розумніше використовувати мютекси.
Мютекс - це об'єкт RTOS, призначений для реалізації конкурентного доступу до спільного для задач ресурсу.
Якщо у використовуваній вами RTOS механізм мютексов не реалізований, можна використовувати двійкові семафори, але в цьому випадку може виникнути інверсія пріоритетів - неприємна ситуація при якій більш пріоритетне завдання не може отримати доступ до ресурсу.
Використання мютексов - найбільш правильний метод забезпечення доступу до ресурсів, що розділяються. Однак, при обслуговуванні периферії такий метод може бути дуже накладним за обсягом і, що найголовніше, за швидкістю виконання коду.
Обмежувати мютексом рекомендується відносно велику частину коду, але ніяк не доступ до поля структури або біту порту.
До апаратних методів забезпечення атомарного доступу можна так само віднести інструкцію disi 16-бітних контролерів Microchip. Ця інструкція забороняє переривання на певну кількість командних тактів.
Ми розглянули загальну проблему конкурентного доступу до ресурсів програми. Однак, існує і окремий випадок цієї проблеми - доступ до бітових полів структури.
При доступі до цих двійкового полях компілятор може згенерувати атомарному інструкцію:
Але все стане набагато гірше, коли ви спробуєте записати в поле структури значення змінної:
Проблеми так само можуть з'явитися при доступі до битовому полю, розмір якого більше 1 біта:
Спосіб вирішення проблеми тут один - використовувати інструкцію xor. яка забезпечує атомарний доступ в будь-якому випадку. навіть якщо потрібно змінити велику бітове поле.
Розглянемо приклад: запис в молодші чотири біта регістра TRISB значення 0x000A.
перша інструкція завантажує значення TRISB в регістр W0;
друга інструкція атомарному і безпечно модифікує чотири молодших біта регістра W0;
третя інструкція накладає на регістр W0 маску, виділяючи 4 молодших біта
остання інструкція атомарному і безпечно (не чіпаючи інших бітів!) модифікує TRISB; TRISB = 0xFFFA;
Навіть якщо після виконання першої інструкції виникне переривання, яке змінить значення TRISB (природно, не молодших 4-х бітів), операція виконатися коректно. Наведений вище код є атомарним.
На мою скромну думку ці макроси є вищим пілотажем. У них використовується багато цікавих рішень які я розгляну нижче.
Виправлений макрос BFAR ()
Інструкції обслуговування одиничних бітів замінені на асемблерні, так при деяких рівня оптимізації виходив зовсім вже неоптимальний код
Додані макроси BFAM (). BFAMI (). BFAMD ()
Призначені для того ж, для чого і BFAR (). але біти, на які впливає операція увазиваются НЕ діапазоном, а маскою. Повний опис буде трохи пізніше
Виправлена помилка в макросі BFAR ()
При установці опції компіляції -mlarge-scalar (дозвіл розташовувати скалярні змінні в far області ОЗУ) видавалася помилка при використанні макросу BFAR () з довільної змінної (без атрибута SFR).
Виправлений макрос BFA ()
При використанні макросу BFA () видавалося попередження про безумовну перетворенні великий константи в unsigned int
Доданий макрос BFARD (). призначений для прямого доступу до будь-якої скалярної змінної в ОЗУ.
Змінено порядок передачі параметрів
Параметр, який вказує тип операції в макрос передається першим
Доданий макрос BFARI ()
Макрос BFARI () забезпечує доступ до структури за вказівником
Для всіх макросів додані нові операції: BFA_SET і BFA_CLR
Ці операції можуть використовуватися для установки або скидання бітів по масці в бітовому полі. Маска може передаватися як у вигляді константи, так і у вигляді змінної.
BFA_IV замінено на BFA_INV. Операція инвертирования тепер інвертує біти по переданої масці.
Перша версія. Тільки для компілятора Microchip C30
Архів містить заголовки bfa.h. який включає в себе чотири макросу, що реалізують атомарний доступ до поля структури або до будь-якої скалярної змінної. Один з макросів призначений для роботи зі структурами, іменування яких відповідає правилам Microchip C30 для периферійних регістрів. Решта макроси можуть використовуватися з будь-якою структурою або змінної розміром не більше машинного слова (int).
Для використання макросів необхідно і достатньо підключити до модуля файл bfa.h і включити оптимізацію не нижче -01.
Атомарний доступ до іменування полів периферійних регістрів PIC24 / dsPIC
Атомарний доступ до бітових полях периферійних регістрів і змінних. Бітове поле вказується як числовий діапазон. Мінлива повинна бути розташована в NEAR DATA SPACE (перші 8 r<ОЗУ)
Атомарний доступ до бітових полях периферійних регістрів і змінних за вказівником. Бітове поле вказується як числовий діапазон
Атомарний доступ до бітових полях периферійних регістрів і змінних. Бітове поле вказується як числовий діапазон. Змінна може бути розташована в будь-якому місці ОЗУ (NEAR або FAR DATA SPACE)
Макрос забезпечує атомарний доступ до іменування полів структур периферійних регістрів мікроконтролерів PIC24 / dsPIC.
інвертування бітів в бітовому полі за маскою
reg_name ім'я периферійного регістра, до якого здійснюється доступ, наприклад, PORTA. TRISB. CMCON і т.п. field_name ім'я бітового поля в структурі регістра (див. документацію на мікроконтролер та заголовки C30 для цього мікроконтролера). Наприклад, для регістра IPC0 це може бути T1IP. необов'язковий параметр, використовується тільки якщо comm = [BFA_WR, BFA_SET, BFA_CLR, BFA_INV]. Якщо comm = BFA_RD - параметр не вказується. Якщо comm = BFA_WR. параметр вказує значення, яке записується в бітове поле. В інших випадках параметр повинен бути рівний бітової масці. Параметр може бути змінної.
Макрос забезпечує атомарний доступ до битовому полю будь-якої структури або змінної, яка знаходиться в області NEAR DATA SPACE (перші 8 кБ ОЗУ). Бітове поле вказується у вигляді діапазону (младщій біт / старший біт).
comm тип доступу:
інвертування бітів в бітовому полі за маскою
reg_name ім'я регістра, до якого здійснюється доступ, наприклад, PORTA. TRISB. CMCON і т.п. Може бути будь-якої змінної програми, яка знаходиться в області near пам'яті даних (перші 8 кБ) lower молодший біт поля структури, від 0 до 15 upper старший біт поля структури, від 0 до 15, upper> = lower. необов'язковий параметр, використовується тільки якщо comm = [BFA_WR, BFA_SET, BFA_CLR, BFA_INV]. Якщо comm = BFA_RD - параметр не вказується. Якщо comm = BFA_WR. параметр вказує значення, яке записується в бітове поле. В інших випадках параметр повинен бути рівний бітової масці. Параметр може бути змінної.
Макрос забезпечує атомарний доступ до битовому полю будь-якої структури або змінної за вказівником. Бітове поле вказується у вигляді діапазону (младщій біт / старший біт). Змінна може перебувати як в NEAR, так і в FAR DATA SPACE.
comm тип доступу:
інвертування бітів в бітовому полі за маскою
pt покажчик на периферійний регістр або будь-яку змінну. Макрос автоматично призводить покажчик до типу int *. lower молодший біт поля структури, від 0 до 15 upper старший біт поля структури, від 0 до 15, upper> = lower. необов'язковий параметр, використовується тільки якщо comm = [BFA_WR, BFA_SET, BFA_CLR, BFA_INV]. Якщо comm = BFA_RD - параметр не вказується. Якщо comm = BFA_WR. параметр вказує значення, яке записується в бітове поле. В інших випадках параметр повинен бути рівний бітової масці. Параметр може бути змінної.
Макрос забезпечує атомарний доступ до битовому полю будь-якої структури або змінної, яка знаходиться в будь-якій області ОЗУ (NEAR або FAR). Бітове поле вказується у вигляді діапазону (младщій біт / старший біт).
comm тип доступу:
інвертування бітів в бітовому полі за маскою
val ім'я регістра, до якого здійснюється доступ, наприклад, PORTA. TRISB. CMCON і т.п. Може бути будь-скалярної змінної програми. lower молодший біт поля структури, від 0 до 15 upper старший біт поля структури, від 0 до 15, upper> = lower. необов'язковий параметр, використовується тільки якщо comm = [BFA_WR, BFA_SET, BFA_CLR, BFA_INV]. Якщо comm = BFA_RD - параметр не вказується. Якщо comm = BFA_WR. параметр вказує значення, яке записується в бітове поле. В інших випадках параметр повинен бути рівний бітової масці. Параметр може бути змінної.
Наведені вище макроси перевіряють передається параметр comm. вказує на метод доступу до поля структури. Решта відповідних установок перевіряються. Для comm визначені наступні дозволені значення:
Контроль передачі параметрів в макрос зроблений цікавим способом: в заголовки оголошені унікальні для кожного параметра типи:
Якщо передається в макрос параметр відрізняється від певних BFA_WR. BFA_RD і BFA_IV. то компілятор видасть помилку:
Використання умовного оператора. дозволило реалізувати за допомогою одного макросу як виконання операції, так і повернення значення. Якщо не вдаватися в деталі реалізації, все макроси виглядають наступним чином:
Якщо параметр comm (тип доступу до структури) дорівнює BFA_RD. то макрос генерує такий вираз:
Таким чином, написавши
після роботи препроцесора отримаємо просте присвоювання:
Звичайно, пряме використання макросів BFA () і BFAR () не дуже наочно. Проте це один з методів:
Якщо регістр використовується в вашому додатку тільки на запис, можна визначити наступний макрос:
і потім використовувати вже його:
Якщо поле структури використовується як на запис, так і на читання, можна визначити наступний макрос зі змінною кількістю параметрів:
Використовувати це макрос потрібно так:
Дуже часто необхідно отримати атомарний доступ до неоголошеної полю структури - наприклад, встановити певне значення на кількох висновках контролера. Для цього можна використовувати макрос BFAR ().
А далі очевидно:
Використання подібних макросів обов'язково для всіх, хто хоче писати швидкий і безпечний код для мікроконтролерів PIC24 / dsPIC з використанням компілятора C30.
Розглянутий підхід може бути реалізований і для будь-якої іншої архітектури та іншого компілятора. Для цього необхідно щоб:
компілятор мав гнучкий inline-асемблера (наприклад, як у gcc)
архітектура мала механізми прямого доступу до пам'яті
Останнє означає, що повинно виконуватися одне з двох умов:
Набір інструкцій повинен включати в себе інструкцію xor з прямим доступом до цікавій області пам'яті
Архітектура повинна мати спеціальні механізми прямого доступу. Прикладом може служити bit-band область у Cortex-M3 або регістри SET / CLR / INV для всієї периферії у PIC32.
До основних переваг макросів BFA () і BFAR () відноситься реалізація атомарного доступу до ресурсів, що розділяються. Звичайно, це не означає, що в перериванні і в основному коді можна безболісно ворушити однією і тією ж ногою контролера. Але доступ до різних бітам порту здійснюється безпечно.
При використанні макросів BFA () і BFAR () немає необхідності забороняти переривання або реалізовувати критичну секцію. Це дозволяє детермінувати час входу в переривання, зменшити обсяг займаного коду і швидкість виконання операцій.
Більш того при використанні макросів доступу до полів структури зменшується обсяг коду і час виконання в порівнянні з безпосереднім доступом: