Пояснення прикладу 1-EventFlag
Як працює цей приклад scmRTOS і що взагалі там відбувається?
Ілюстроване пояснення.
Пояснення базується на avr-gcc (WinAVR) порті системи для мікроконтролерів AVR, але може бути корисним для розуміння будь-якого порта системи.
Для детальної демонстрації процесів, що відбуваються в системі, приклад 1-EventFlag дещо розширено. В функції Exec() процесів, IdleProcessUserHook(), SystemTimerUserHook(), TIMER1_COMPA_vect() (файл main.cpp) а також в код перемикання контекстів (файл OS_Target_asm.S) додано команди зміни стану виводів мікроконтролера.
Виводи, що керуються з коду користувача scmRTOS, призначаються у файлі main.cpp:
55 56 57 58 59 60 61 62 63 64 65 | //------------------------------------------------------------------- // "Hello, scope!" pins (pin numbers for ATmega168 in DIP28 package) // Define pins in form PORT_LETTER,PORT_PIN,ACTIVE_LEVEL // (Ascold Volkov - type "pin_macro.h" notation) #define TIMER1_ISR_PIN C,4,H // pin 27 #define T1PROC1_PIN B,0,H // pin 14 #define PROC1_PIN B,1,H // pin 15 #define PROC2_PIN B,2,H // pin 16 #define PROC3_PIN B,3,H // pin 17 #define TIMERHOOK_PIN B,4,H // pin 18 #define IDLEHOOK_PIN B,5,H // pin 19 |
Вивід контролера для керування з перемикача контекстів визначається в makefile:
11 12 13 | # Define contect switch "Hello, scope!" pin in form PORT_LETTER,PORT_PIN # C,5 for pin5 of PORTC SWITCH_PIN=C,5 |
Виводи підключено до логічного аналізатора. Контролер тактується від внутрішнього RC-генератора на 8 МГц, один тік системного таймера відповідає приблизно двом мілісекундам.
Приклади в репозиторії scmRTOS та її код можуть змінюватися. Крім того, перемикач контекстів в робочому коді не може містити команд керування виводами мікроконтролера, їх прибрано з коду в репозиторії. Звідси можна завантажити архів 1-EventFlag-explanation.zip з модифікованою scmRTOS та прикладом, які відповідають пропонованому тексту.
Приклад розраховано на компілятор avr-gcc (WinAVR) та на мікросхему з лінійки ATmega48/ATmega88/ATmega168/ATmega328, для інших мікроконтролерів може знадобитися внести зміни відповідно до використовуваних таймерів та виводів.
Розподіл входів аналізатора
| D5 | IDLEHOOK |
Перекидається у протилежний стан в
IdleProcessUserHook(). |
|---|---|---|
| D4 | TIMERHOOK |
Перекидається у протилежний стан в
SystemTimerUserHook() |
| D3 | PROC3 |
Піднімається та опускається в
TProc3::Exec(). |
| D2 | PROC2 |
Піднімається та опускається в
TProc2::Exec(). |
| D1 | SWITCH |
Піднімається першою командою перемикача контексту, опускається перед виконанням команди виходу з перемикача (безпосередньо перед
reti). |
| D0 | T1PROC1 |
Піднімається в прериванні
TIMER1_COMPA_vect(), опускається в функції TProc1::Exec(), яка чекає сигналу від цього переривання. |
Взаємна робота Proc2 та Proc3 — з пташиного польоту
Параметри компіляції прикладу:
main.cpp
86 | #define PROC2_LONG_PULSE 1 |
Proc2 генерує імпульс довжиною в один тік системного таймера. Всі інші параметри компіляції при вивченні системи в такому масштабі неважливі.

-
Сигнал IDLEHOOK, який є меандром з періодом в декілька мікросекунд з короткими перервами на роботу процесів, в цьому масштабі відображається суцільною смугою.
-
Оскільки значення сигналу TIMERHOOK інвертується в функції
OS::SystemTimerUserHook(), на відповідній лінії спостерігається меандр з періодом в два тіки системного таймера, близько чотирьох мілісекунд. Події на лініях PROC2 та PROC3 відбуваються синхронно зі зіміною стану на лінії TIMERHOOK. -
В функції
TProc2::Exec()є два виклики функції засинання просесу:Sleep(1)визначає довжину імпульса на лінії PROC2,Sleep(9)визначає час між імпульсами. Таким чином, Proc2 є мультивібратором з періодом імпульсів 10 тіків системного таймера (20 мс) та довжиною імпульсу 1 тік (2 мс). -
Перед опусканням лінії PROC2 та засинанням на десять тіків Proc2 викликом
ef.Signal();пробуджує Proc3. Отримавши керування,TProc3::Exec()піднімає рівень на лінії PROC3 та знову засинає, тепер по викликуSleep(). Час сну вибирається виходячи з стану біту 9 лічильника системних тіків, отриманого викликомOS::GetTickCount();. Протягом 512 тіків таймера (близько секунди) процес спить два тіки таймера, протягом іншої секунди сон триває три тіки таймера. -
Прокидаючись після сну по системному таймеру Proc3 опускає сигнал на лінії PROC3 та викликом
ef.Wait();знову засинає в очікуванні сигналу від Proc2. Фактично Proc3 є одновібратором, що запускається по спаду імпульса на лінії PROC2. -
Імпульси на лінії T1PROC1 не синхронізовані з системним таймером, бо вони породжуються в прериванні
TIMER1_COMPA_vect(), яке активується з частотою, не кратною системному таймеру. Детальніше робота таймера 1 та Proc1 розглянута нижче. -
На лінії SWITCH бачимо маркери перемикання процесів. Частина з них відповідає перепадам на лініях PROC2 та PROC3, частина — на лінії T1PROC1.
Перемикання з Proc2 на Proc3 — під мікроскопом
Параметри компіляції OC та прикладу (спільна частина для обох варантів розвитку подій):
scmRTOS_CONFIG.h
124 | #define scmRTOS_CONTEXT_SWITCH_SCHEME 1 |
Перемикання контекстів виконується за допомогою переривання найнижчого рівня пріоритету, запит якого формується програмно. Перемикання, заплановане в результаті обробки інших преривань, починає працювати після виходу з цих преривань.
main.cpp
86 | #define PROC2_LONG_PULSE 0 |
Для детального розгляду з коду Proc2 видалено затримку в один тік системного таймера при генерації імпульсу на лінії PROC2. Довжина імпульсу тепер визначається часом роботи функції ef.Signal() та, у варіанті 2, часом перемикання задач та роботи Proc3.
Варіант 1
Додаткові параметри компіляції:
main.cpp
85 | #define PROC2_HIGHER_THAN_PROC3 1 |
Пріоритет Proc2 вищий, ніж Proc3.

-
При переході на підпрограму переривання від системного таймера припиняється генерація меандру на лінії IDLEHOOK в функції
IdleProcessUserHook(). -
Після закінчення роботи системної частини підпрограми переривання викликається функція користувача
SystemTimerUserHook(), в якій інвертується стан лінії TIMERHOOK. -
Відбувається вихід з підпрограми переривання системного таймеру та вхід в переривання перемикання контексту. Між двома перериваннями може виконатися одна команда з коду перерваного процесу. На цій осцилограмі трапилося так, що цією командою було інвертування стану лінії IDLEHOOK.
-
Перемикання контексту, яке передає керування Proc2 в точку виходу з виклику
Sleep(9). На початку перемикання піднімаєтьcя рівень на лінії SWITCH, по закінченні перемикання лінія SWITCH повертається до низького рівня. Довжина імпульсу показує час, необхідний власне для перемиканя контексту, без врахування витрат часу на диспетчеризацію. -
В функції
TProc2::Exec()піднімається лінія PROC2, викликаєтьсяef.Signal()і опускається лінія PROC2. По довжині імпульсу можна визначити час, необхідний для виконання функціїSignal(). ТеперTProc2::Exec()робить викликSleep(10)і засинає, планувальник scmRTOS шукає готовий до виконання процес. Таким процесом єTProc3, який щойно отримав сигнал. -
Відбувається чергове перемикання процесів (другий імпульс на лінії SWITCH), керування отримує
TProc3::Exec(). -
Proc3 піднімає лінію PROC3, зчитує значення системного лічильника тіків і засинає на відповідний час, як це описано в розділі «Взаємна робота Proc2 та Proc3 — з пташиного польоту».
-
Знову перепланування процесів і, оскільки інших готових до виконання процесів немає, керування передається на
IdleProcess(третій імпульс на лінії SWITCH). -
Керування отримала функція
TIdleProcess::Exec(), відновлюється перекидання лінії IDLEHOOK. Довжина паузи в меандрі показує повний час на обробку апаратного переривання, перепланування процесів, перемикання контекстів та виконання процесами своєї роботи, перемикання назад на процесIdleProcess.
Перемикання процесів відбувається по шляху IdleProcess → Proc2 → Proc3 → IdleProcess, разом три перемикання процесів.
Варіант 2
Додаткові параметри компіляції:
main.cpp
85 | #define PROC2_HIGHER_THAN_PROC3 0 |
Пріоритет Procп2 нижчий, ніж у Proc3. Події розгортаються схожим на «Варіант 1» чином, але є відмінності після виклику ef.Signal() в TProc2::Exec().
Осцилограми змінюються наступним чином:

-
При переході на підпрограму переривання від системного таймера припиняється генерація меандру на лінії IDLEHOOK в функції
IdleProcessUserHook(). -
Після закінчення роботи системної частини підпрограми переривання викликається
SystemTimerUserHook(), в якій інвертується значення лінії TIMERHOOK. -
Перше переривання перемикання контексту передає керування Proc2 в точку виходу з виклику
Sleep(9). -
В функції
TProc2::Exec()піднімається лінія PROC2, викликаєтьсяef.Signal(). І тут, на відміну від першого варіанту, повернення з викликаної системної функції дещо затримується. В даному варіанті пріоритет Proc3 вищий, ніж Proc2, тому перепланування в кінціef.Signal()активує перемикання на Proc3. -
Перемикання процесів (другий імпульс на лінії SWITCH), керування отримує
TProc3::Exec(). -
Proc3 піднімає лінію PROC3, зчитує значення системного лічильника тіків і засинає.
-
Залишається ще один активний процес — Proc2, контекст перемикається назад на
TProc2::Exec()(третій імпульс на лінії SWITCH). -
І от лише тепер відбувається «повернення» в
TProc2::Exec()з функціїef.Signal(). Нарешті опускається лінія PROC2. На цій осцилограмі довжина імпульсу на лінії PROC2 набагато більша, ніж для попереднього варіанту, в нього ввійшли робота Proc3 та два перемикання процесів.
ТеперTProc2::Exec()знову робить викликSleep(9), scmRTOS шукає готовий до виконання процес. Але для даного варіанту ним є не Proc3, він вже свою роботу зробив, а фоновий процесIdleProcess. -
Останнє перемикання контекстів, керування передається на
TIdleProcess::Exec(). -
Керування отримала функція
TIdleProcess::Exec(), відновлюється перекидання лінії IDLEHOOK.
При такому співвідношенні пріоритетів виконання іде по шляху
IdleProcess → Proc2 → Proc3→ Proc2 → IdleProcess
Маємо чотири перемикання процесів.
З одного боку, роботу IdleProcess було перервано на довший час. В реальній системі перерваним міг би бути процес, який виконував би кориснішу роботу, ніж перекидання стану IO-ніжки у високому темпі.
З іншого боку, Proc3 отримав керування дещо раніше. Знову ж таки, в реальній системі, де між відправкою сигналу за допомогою ef.Signal() та засинанням Proc2 робив би більше роботи, в першому варіанті затримка до початку роботи Proc3 була б ще більша.
Який варіант кращий, залежить від конкретних умов, від логіки роботи системи. Цей приклад лише показує, що від вибору пріоритетів процесів залежатимуть затримки від подій до передачі керування потрібному процесові та час роботи процесів.
Передача сигналу від переривання таймера 1 до процесу 1
Ця частина прикладу відрізняється від попередніх лише тим, що передача сигналу через OS::TEventFlag Timer1_Ovf; відбувається не від одного процесу до іншого, а від підпрограми обробки переривання до процесу.

-
Як і на попередніх осцилограмах, меандр на лінії IDLEHOOK припиняється при виникненні переривання.
-
На вході в підрограму обробки переривання
TIMER1_COMPA_vect()піднімається лінія T1PROC1, після чого викликомTimer1_Ovf.SignalISR();посилається сигнал Proc1. Планувальник бачить необхідність перемикання процесів і активує запит переривання перемикання контекстів. -
Після виходу з переривання
TIMER1_COMPA_vect()відпрацьовує переривання перемикання контекстів, керування отримуєTProc1::Exec(). -
Proc1 опускає рівень на лінії T1PROC1 і знову засинає викликом
Timer1_Ovf.Wait();до приходу сигналу. Довжина імпульсу на лініїT1PROC1дає уявлення про мінімальний час затримки від апаратної події до того моменту, коли почне працювати код високопріоритетного процесу, що чекав на цю подію. -
В даному випадку інших готових до виконання процесів нема і другим перемиканням контекстів керування повертається до
IdleProcess. -
TIdleProcess::Exec()відновлює роботу і на лінії IDLEHOOK знову з’являється меандр.
Запитання по цьому прикладу та порту scmRTOS для AVR та компілятора avr-gcc (WinAVR) можна задавати тут, в коментарях до сторінки.
Запитання по «scmRTOS взагалі» краще задавати у відповідному розділі на форумі electronix.ru.
© 2008-2010, Oleksandr Redchuk aka ReAl
українська
русский
english