RTOS eseményjelzők (Signals)

A fejezet tartalma:
A programszálak közötti kommunikáció része az is, hogy a programszálak (és a megszakítások) eseményjelzőket tudnak küldeni valamelyik programszálnak, s a programszála várakozhatnak valamilyen eseményjelzőre. Amikor egy új programszálat definiálunk, annak vezérlő blokkjában a rendszer lefoglal egy 8 bites tárterülete. Egy-egy eseményjelző ennek valamelyik bitje lehet. Mivel az eseményjelző bitek a programszálhoz kötöttek, így kezelésük is a Thread objektumosztály vagy objektumpéldányainak tagfüggvényeivel végezhető.

Az eseményjelzők számát az mbed forráskódjának rtx/TARTGET_CORTEX_M/cmsis_os.h állománya definiálja:
define osFeature_Signals      16      //maximum number of Signal Flags available per thread
Eszerint programszálanként legfeljebb 16 eseményjelzőt használhatunk!

Várakozás eseményjelzőre

Az egy vagy több eseményjelzőre történő várakozást a Thread objektumosztály signal_wait() statikus publikus taggfüggvényének hívásával indíthatjuk. Ennek első, kötelező paramétere egy bitmaszk, amely megmondja, hogy mely eseményjelzőkre akarunk várakozni.

osEvent signal_wait(int32_t signals, uint32_t timeout=osWaitForever )
Várakozás egy, vagy több eseményjelzőre, megadott, vagy korlátlan ideig.

Paraméterek:

signals - bitmaszk, mely kijelöli, hogy mely eseményjelzőkre várakozunk. Ha a megadott érték nulla, akkor bármelyik eseményjelző beérkezése véget vet a várakozásnak.

timeout - ezredmásodpercekben megadott maximális várakozási idő. Ha nullát adunk meg, akkor nincs várakozás, ha pedig osWaitForever a megadott érték, akkor bármennyi ideig  tarthat a várakozás (ez utóbbi az alapértelmezett beállítás, ha nem adjuk meg a timeout paramétert).

Visszatérési érték:

A függvény visszatérési értéke az eseményjelző bitcsoport, vagy hiba esetén a megfelelő hibakód.
  • osOK: nem érkezett eseményjelző (a timeout 0-nak volt megadva)
  • osEventTimeout: nem érkezett eseményjelző az időtúllépésig
  • osEventSignal: eseményjelző érkezett, a visszatérési érték (amely egy osEvent struktúra) value.signals komponense tartalmazza a beérkezett eseményjelző biteket; ezek a jelzőbitek kiolvasáskor automatikusan törlődnek.
  • osErrorValue: a paraméter értéke kívül esik a megengedett tartományon.
  • osErrorISR: a signal_wait tagfüggvény megszakítási szinten nem hívható meg.
Megjegyzés: Ez a függvény megszakításból nem hívható meg!
 

Eseményjelző küldése

A szemaforral ellentétben az eseményjelző küldése "címzetten" történik, azaz egy bizonyos programszálnak küldjük. Ezért az eseményjelző, mint  kommunikációs eszköz, olyan esetekben használható, amikor a küldő programszál (vagy megszakítási rutin) tudja, hogy "kinek" kell küldeni.

Az eseményjelző küldése a Thread objektumpéldányok (a megcímzett programszál) signal_set() metódusával végezhető:
int32_t signal_set( int32_t signals)    
Paraméterek:

signals - bitmaszk, amely kijelöli, hogy melyik eseményjelzőt, vagy eseményjelzőket akarjuk elküldeni.

Visszatérési érték:

A függvény visszatérési értéke a küldés előtti állapotot jellemző eseményjelző bitcsoport, vagy hibás paraméter esetén a 0x80000000 hibakód.

Megjegyzés: Ez a függvény megszakításból is hívható.

Eseményjelző törlése

Bár az eseményjelzőre várakozó programszál által "felhasznált" eseményjelző bitek automatikusan törlődnek, az mbed API lehetőséget nyújt az elküldött eseményjelzők független törlésére is. Az eseményjelző törlése a Thread objektumpéldányok (a megcímzett programszál) signal_clr() metódusával végezhető:
int32_t signal_clr( int32_t signals)    
Paraméterek:

signals - bitmaszk, amely kijelöli, hogy melyik eseményjelzőt, vagy eseményjelzőket akarjuk törölni.

Visszatérési érték:

A függvény visszatérési értéke a küldés előtti állapotot jellemző eseményjelző bitcsoport, vagy hibás paraméter esetén a 0x80000000 hibakód.

Megjegyzés: Ez a függvény megszakításból nem hívható meg!

Mintapélda: Programszálak szinkronizálása eseményjelzővel

Az alábbi program az mbed Handbook mintapéldája. Ebben az első programszál (a main() függvény) rendszeres időközönként elküld egy eseményjelzőt a második programszálnak (thread2), amely mindaddig várakozik, amíg az eseményjelző meg nem érkezik. Minden eseményjelző megérkezésekor átbillenti LED1 állapotát.

Hardver követelmények:
1. lista: A 10_rtos_ledblink/main.cpp program listája
#include "mbed.h"
#include "rtos.h"

DigitalOut led(LED1);

void led_thread(void const *argument)
{
while (true) {
// Signal flags that are reported as event are automatically cleared.
osEvent evt = Thread::signal_wait(0x1); //Wait for a signal
led = !led; //toggle LED1 state
}
}

int main (void)
{
Thread thread2(led_thread);

while (true) {
Thread::wait(1000);
thread2.signal_set(0x1); //Send a signal for thread2
}
}
A programban a main() függvény törzse az első programszál, a második programszál törzsének pedig a led_thread függvényt definiáltuk. Az eseményjelzőre történő várakozást a Thread::signal_wait() függvényhívással indíthatjuk (a 0x1 bitmaszk a legkisebb sorszámú jelzőt jelöli ki használatra).

Az eseményjelző küldésénél a thread2.signal_set() függvényhívást kell használnunk. Itt thread2, mint az objektumpéldány azonosítója szabja meg, hogy "kinek" küldjük az eseményjelzőt. A függvényhívásnál megadott paraméter (0x1) ugyanaz, mint amit a másik programszálnál megadtunk a várakozás indításánál, tehát a fenti hívás hatására legkisebb helyiértékű biten tárolt jelzőbit lesz 1-be állítva. Természetesen, ha itt más bitmaszkot használnánk (pl. 0x2, vagy 0x8), akkor a thread2 programszálat nem tudnánk felkelteni Csipkerózsika álmából!

Mintapélda: Várakozás több, vagy bármely eseményjelzőre

Bővítsük ki az előző programot a kapott eseményjelzők és/vagy hibakódok kiíratásával, s próbáljunk több, másik, vagy bármilyen eseményjelzőre várakozni!

Hardver követelmények:
2. lista: A 10_rtos_signals/main.cpp program listája
#include "mbed.h"
#include "rtos.h"

DigitalOut led(LED1);

void led_thread(void const *argument) {
while (true) {
// Signal flags that are reported as event are automatically cleared.
osEvent evt = Thread::signal_wait(0); //Wait for any signal
switch(evt.status) {
case osOK:
printf("osOK\n"); //no error or event occurred
break;
case osEventSignal:
printf("osEventSignal = %#05x\n",evt.value.signals); //signal event occurred
break;
case osEventTimeout:
printf("osEventTimeout\n"); //timeout occurred
break;
case osErrorValue:
printf("osErrorValue\n"); //value of a parameter is out of range
 break;
default:
printf("Unknown error flag: %#08x\n",(uint32_t)evt.status);
break;
};
led = !led;
}
}

int main (void) {
int32_t signal_mask = 0x1;
Thread thread2(led_thread);
while (true) {
Thread::wait(1000);
thread2.signal_set(signal_mask);
signal_mask <<=1;
if(signal_mask > 0x8000) signal_mask=0x1;
}
}
Megjegyzések:
Feladatok:
  1. Töltsük le a 2. listán látható programot, indítsuk el, s egy terminálablakban figyeljük a kírásokat!
  2. Módosítsuk a programot úgy, hogy Thread::signal_wait(2,0) álljon benne! A függvényhívás első paramétere a 0x2 eseményjelző figyelését írja elő, a második paraméter szerint a várakozási idő nulla. Ekkor az osOK hibakód lesz a visszatérési érték, ha a fenti függvényhívás kiadásakor még nincs beállítva a 0x2 eseményjelző.
  3. Módosítsuk a programot úgy, hogy Thread::signal_wait(2,500) álljon benne! Ekkor az osEventTimeout hibakód lesz a visszatérési érték, ha a 0x2 eseményjelző 500 ezredmásodpercen belül nem kerül beállításra.
  4. Módosítsuk a programot úgy, hogy Thread::signal_wait(0x10000) álljon benne! Ez érvénytelen bitmaszk (a 16 biten túlterjeszkedik), ezért az osErrorValue hibakód lesz a visszatérési érték.
  5. Módosítsuk a programot úgy, hogy Thread::signal_wait(0x5), vagy bármilyen egynél több biten 1-et tartalmazó szám álljon benne! Ekkor csak az összes előírt eseményjelző beérkezése után szűnik meg a várakozás (LED1 ritkábban vált állapotot).
  6. Módosítsuk a programot úgy, hogy thread2.signal_set(signal_mask) függvényhívásnál 0x5, vagyis ugyanaz legyen a bitmaszk, amit az 5. feladatnál a Thread::signal_wait() paramétereként megadtunk! Ekkor egyszerre több eseményjelzőre várunk, de azok egyszerre, csoportos küldéssel meg is érkeznek, ezért LED1 ugyanolyan ütemben villog, mint az 1. feladatnál (állapotváltás 1 másodpercenként).

Eseményjelző küldése az első programszálnak

Komoly gonddal kell szembenéznünk, ha a main() függvényben, vagyis az alapértelmezett első programszálban indítunk várakozást eseményjelzőre. Hogyan tudunk az első programszálra hivatkozni, amikor annak nincs explicit módon megadott, Thread típusú neve? Nos, a megoldást a Thread objektumosztály gettid() statikus publikus tagfüggvénye nyújtja számunkra, amellyel lekérdezhető a futó programszál azonosítója. A lekérdezést természetesen a main() függvényben kell végezni, s az eredményt egy globális változóban kell eltárolni, hogy a többi programszál is hozzáférhessen.

A programszálak azonosítója osThreadId típusú. Ezt a típust a fentebb már említett cmsis_os.h definiálja, s lényegében egy programszál vezérlő blokkra mutató pointer. Ilyen struktúrára mutató pointer lesz tehát a Thread::gettid() függvényhívás értéke, amelyet az általunk deklarált mainThreadID nevű változóban tárolunk el.

A következő problémánk el lesz: hogyan hívjuk meg a névtelen első programszál signal_set() metódusát? Itt az mbed API alatti rétegekbe kell belenyúlni, s a CMSIS RTOS API kínál egy olyan függvényhívási lehetőséget: osSignalSet(), amelynek egy osThreadID típusú azonosító átadható, s vele a kívánt eseményjelző, vagy eseményjelző csoport beállítása elvégezhető. Szintaktikája:

int32_t osSignalSet(osThreadId thread_id,  int32_t signals)    
Paraméterek:

thread_id - a megcímzett programszál azonosítója, melyet pl. Thread::gettid() hívással kaphatunk meg.
 
signals - bitmaszk, amely kijelöli, hogy melyik eseményjezőt, vagy eseményjelzőket akarjuk elküldeni.

Visszatérési érték:

A függvény visszatérési értéke a küldés előtti állapotot jellemző eseményjelző bitcsoport, vagy hibás paraméter esetén a 0x80000000 hibakód.

Megjegyzés: Ez a függvény megszakításból is hívható.

Mintapélda: Eseményjelző küldése az első programszálnak

Az alábbi mintaprogram az első példánk megfordítása: itt a main() függvényben várunk eseményjelzőre és kapcsolgatjuk LED1-et, míg a második programszálban periodikusan (egy másodpercnyi várakozás után) küldünk eseményjelzőt a fő programszálnak.

Hardver követelmények:
3. lista: A 10_rtos_signals_to_main/main.cpp program listája
#include "mbed.h"
#include "rtos.h"

DigitalOut led(LED1);
osThreadId mainThreadID;

void signal_thread(void const *argument) {
while (true) {
Thread::wait(1000);
osSignalSet(mainThreadID, 0x1);
}
}

int main (void) {
mainThreadID = Thread::gettid();
Thread thread(signal_thread);

while (true) {
// Signal flags that are reported as event are automatically cleared.
osSignalWait(0x1, osWaitForever);
led = !led;
}
}