A NesC programozási nyelv

TinyGALS és galsC

TinyGALS és galsC

Bevezetés

A beágyazott hálózati rendszereket - mint amilyenek a vezeték nélküli szenzorhálózatok is - gyakran esemény-vezérelt programozási modellekben tervezik, így lehetővé válik reaktív, energiatakarékos rendszerek készítése. A többfeladatos beágyazott rendszerek programozásában a legnehezebb feladatot a párhuzamos folyamatok hatékony kezelése illetve a globális állapotok konzisztenciájának megőrzése jelenti. Ebben a leírásban bemutatunk egy ún. globálisan-aszinkron-lokálisan-szinkron (TinyGALS) programozási modellt, amelyet kifejezetten ilyen rendszerek készítéséhez terveztek. A modellben a szoftver komponensek modulokba szerveződnek és egy-egy modulon belül lokálisan szinkron módon kommunikálnak egymással. A rendszerben a modulok közötti kommunikáció aszinkron módon történik, ezáltal jól elkülöníthetőek a vezérlési szálak, ill. az egyes modulok feladatai jobban átláthatóak. Egy speciális, illeszkedő modellt (TinyGUYS) használunk a globális változók hatékony kezelésére. A TinyGALS modell úgy lett kitalálva, hogy a lelkét képező üzenetküldési mechanizmusok automatikusan generálhatóak a magas szintű specifikációkból. Ez azt jelenti, hogy hatékony, könnyen programozható nyelvek készíthetők hozzá, amelyekben a programozó a feladatra koncentrálhat anélkül, hogy a modell részleteiben elveszne. A cikk végén röviden bemutatjuk a TinyGALS modell egy implementációját: A galsC programozási nyelvet.

A TinyGALS programozási modell

A következőkben röviden ismertetjük a TinyGALS által használt alapfogalmakat, mint komponens, modul, rendszer, majd a modellben definiált, modulok közötti kommunikációról ejtünk szót, ennek kapcsán felvetjük az osztott állapotok hatékony kezelésének problémáját, és röviden bemutatjuk a problémát megoldó TinyGUYS modellt is. A programozási modellről [6][4] cikkekben olvashatunk részletesebben.

Komponensek

A komponensek jelentik a TinyGALS programok legkisebb építőköveit. Minden TinyGALS komponens egy rendezett hármas: C =< VC;XC; IC >, ahol a VC a saját változók halmaza; XC a külső változók halmaza; IC pedig az ezekkel a változókkal operáló metódusok halmaza. Az IC halmaz tovább bontható két diszjunkt részre: ACCEPTC tartalmazza azokat a metódusokat, amelyeket a C komponens szolgáltat más komponensek részére; USESC pedig a C komponens által használt - többnyire más komponensek által szolgáltatott - metódusokat tartalmazza. A komponensek tehát az objektum-orientált programozási nyelvek objektumaihoz hasonlítanak azzal a különbséggel, hogy a komponenseknek explicit módon kell megadniuk az általuk használt külső változókat és metódusokat. Szintaktikailag egy komponens definíció két részből áll. először a komponens interfészét deklaráljuk, majd következik az implementáció. A komponens egy impulzus frekvenciáját csökkenti a felére. A komponens interfésze két metódust szolgáltat (ACCEPTS) és egyet használ (USES). Az active egy belső változó. A komponens minden második fire() hívás hatására meghívja a fireOut()-ot ugyanazokkal a paraméterekkel, amelyekkel a fire()-t hívták. Vegyük észre, hogy komponens csak a fireOut() szignatúráját határozza meg, arról nem mond semmit, hogy mely másik komponensnek kell implementálnia azt. A CALL COMMAND kulcsszóval jelöljük, hogy a fireOut() hívása szinkron módon történik.

Modulok

A modulok egy vagy több TinyGALS komponensből állnak és a következő rendezett hatossal definiálhatjuk őket: M = < COMPONENTSM; INITM;INPORTSM;OUTPORTSM; PARAMETERSM; LINKSM >, ahol COMPONENTSM a modult alkotó komponensek halmaza; INITM a COMPONENTSM-ben szereplő komponensek metódusainak egy listája; INPORTSM és OUTPORTSM a modul bemeneti és kimeneti port-jainak halmazai; PARAMETERSM a komponensek külső változóit tartalmazó halmaz; LINKSM pedig a komponensek egymás közötti, ill. a komponensek és a modul be- és kimeneteinek kapcsolatait írja le. A kapcsolatot szinkron metódus hívások jelentik. Amint egy komponens külső metódust hív a CALL COMMAND segítségével, a vezérlés azonnal átadódik a hívott komponens megfelelő metódusára, vagy a modul megfelelő kimeneti port-jára. (Ez utóbbi jelentéséről később lesz még szó.) A következő példában egy A nevű modult definiáltunk, amely két komponenst tartalmaz, ACOMP1-et és ACOMP2-t. Az ACOMP1 komponens az init() és a fire() metódusokat szolgáltatja és a fireOut() metódust használja. Az ACOMP2 az exec()-et szolgáltatja és az output()-ot használja. A modul definíciójának végén lévő kapcsolatokat leíró rész mutatja, hogy amint input érkezik az A in portra az ACOMP1 komponens fire() metódusa kerÜl meghívásra. Hasonlóan az ACOMP1 fireOut() hívása az ACOMP2 exec() hívását jelenti, az ACOMP2 output() hívása pedig egy eseményt fog generálni az A out portra. Ezek a szinkron hívások mind azonnal kerÜlnek végrehajtásra.
A modul reprezentációja:
COMPONENT DownSample ACCEPTS { void init(void); void fire(int in); }; USES { void fireOut(int out); }; void init() { _active = true; } void fire(int in) { if (_active) { CALL_COMMAND(fireOut)(in); _active = false; } else { _active = true; } }


A rendszer

A TinyGALS modell legfelső szintjén modulok alkotják a programot. A modell a programot rendszernek nevezi, eszerint minden rendszer a következő rendezett ötös: S = < MODULESS;GLOBALSS; V AR MAPSS;CONNECTIONSS;STARTS >, ahol MODULESS a rendszer moduljainak halmaza; GLOBALSS a globális változók halmaza; V AR MAPSS olyan leképezés, amely minden (globális változó, modul) pároshoz egy nevet rendel, vagyis megmondja, hogy a globális változót milyen paraméter-néven lehet elérni az adott modulban. A globális változók a modul szintjén tehát a modul paramétereiként jelennek meg; CONNECTIONSS a modulok port-jainak egymáshoz való kapcsolatát írja le; STARTS pedig egy egyedi input port neve a rendszerben. Ezzel a port-tal csak egyetlen modul rendelkezhet, a rendszer e port aktiválásával indítható. Habár a modulokon belüli kapcsolatok esetén csak egyetlen hívó és egyetlen hívott fél szerepelhet, a modulok közötti kapcsolatoknál szerepelhet több bemeneti és/vagy kimeneti port is.
include components { ACOMP1; ACOMP2; }; init { ACOMP1:init; }; ports in { A_in; }; ports out { A_out; }; ACOMP1:fireOut ACOMP2:exec ACOMP2:output A_out A_in ACOMP1:fire

A modul A out kimeneti port-ja a B in és C in portokhoz van kötve. Mindkét utóbbi port-hoz tartozó FIFO sor mérete 10 egység. Az A out által létrehozott Összes token tehát meg lesz duplázva és egyaránt aktiválja a B in és a C in port-okat. A definíció utolsó sora jelzi, hogy a rendszer az A in port aktiválásával indítható.

Kommunikáció

A portok közötti kommunikáció FIFO sorok használatával valósul meg. Ami- kor egy komponens olyan metódust hív amely a modul kimeneti port-jára van kötve, a hívás paraméterei egy úgynevezett token-né konvertálódnak. Ezután a kimeneti port-hoz kapcsolódó minden bemeneti port FIFO sorába bemásolódik egy példány ebből a token-ből. Később a TinyGALS futtató környezet Ütemezője fogja kivenni a token-eket a FIFO sorokból, majd kicsomagolja belőlük a hívás paramétereit, végül végrehajtja az adott bemeneti port-hoz kapcsolt metódus hívását. A FIFO sorok tehát szétválasztják a végrehajtási szálakat; a kimeneti port-hoz kapcsolódó metódus hívása azonnal visszatér, így a hívó komponens folytathatja munkáját.
// Az S rendszer definíciója include modules { A; B; C; } A_out -> B_in 10 A_out -> C_in 10 @START A_in

A TinyGALS szemantikája nem definiálja pontosan, hogy mikor fog aktiválódni a bemeneti port, vagyis mikor hajtódik végre ténylegesen a modulok közötti aszinkron hívás. A legegyszerűbb implementáció a tokenek létrehozási sorrendjével azonos sorrendben aktiválja a bemeneti port-okat. Elképzelhetők azonban kifinomultabb megoldások is, amelyek figyelembe vesznek időzítési problémákat és az energiatakarékosság elvét is. A modulok közötti aszinkron kommunikáció lehetséges tényleges adat átvitele nélkül is. Ilyenkor egy Üres Üzenet közlekedik a modulok között, vagyis az aszinkron hívásnak nincsenek paraméterei, az csak valamilyen egyszerű jelzésre, kvázi szinkronizációra szolgál(A TinyOS, TinyGALS kÖrnyezetekben sosem beszélhetÜnk valódi szinkronizációról, hiszen valódi párhuzamosság sem létezik bennük. A TinyGALS modell Ütemezője legfelső szinten - az aktuális taszk végrehajtása után - bármelyik input port-ot kiválaszthatja aktiválásra, ezért a modellben az egyes modulokat Önálló, párhuzamosan működő egységekként fogjuk fel).

TinyGUYS

A TinyGALS programozási modell előnye, hogy az üzenetküldési mechanizmus következtében a modulok erősen szétválnak, így egymástól függetlenül fejleszthetők. Ez a mechanizmus azonban nem hatékony, ha globális változóink vannak, amelyeket gyakran módosítunk. Ilyenkor a programozónak kell gondoskodnia a rendszer állapotának konzisztenciájáról, amelyet többnyire őrváltozókkal valósítanak meg (Gondolhatunk szemaforra, de általánosabb - a kölcsönös kizárás melletti hozzáférést biztosító - nyelvi konstrukcióra is). Mivel a TinyGALS Ütemezőjének szemantikája nem köti meg, hogy milyen sorrendben választja ki az aktiválásra váró port-okat, gyakran választhat olyan modulokat, amelyek az őrváltozóra várakoznak. Ilyenkor az overhead jelentősen megnő, a rendszer hatékonysága leromlik. A TinyGUYS (Guarded Yet Synchronous) mechanizmus segítségével úgy tarthatjuk meg a konzisztenciát, hogy közben nem romlik a rendszer hatékonysága. A TinyGUYS mechanizmusban a globális változóink "őrzöttek". A modulok szinkron módon olvashatják a globális változókat késleltetés nélkül. A változók írása aszinkron módon történik abban az értelemben, hogy minden írási művelet eredménye egy pufferbe kerül. Ebből a pufferből másolja át az Ütemező az értéket a globális változóba az ún. biztonságos állapotokban. Ez tehát azt jelenti, hogy az az érték lesz a globális változó új értéke, amelyet utoljára írtak a pufferbe a globális változó értékének frissítése előtt. Biztonságos állapot például amikor egy modul befejezte taszkjainak futtatását és az Ütemezőnek újabb aktiválásra váró port-ot kell választania. Ezeknek az állapotoknak a tárgyalásával itt most nem foglalkozunk, részletes leírás található róluk [5]-ben. E cikkben található a módszer elméleti helyességének bizonyítása is. A TinyGUYS változók a modulok szintjén paraméterekre képződnek le, a paraméterek a komponensek szintjén pedig külső változók lesznek. A komponenseken belül a külső változókra - amelyek tehát a leképezések fordítása után TinyGUYS változók lesznek - speciális kulcsszavakkal hivatkozhatunk, ezek a PARAM GET ill. a PARAM PUT.

A galsC programozási nyelv

Ebben a fejezetben áttekintjük a galsC programozási nyelv [1] elemeit. Elsősorban a szintaktikus egységekre fogunk koncentrálni és megadjuk a TinyGALS modellel való kapcsolatot. A szemantikát a modell tartalmazza. A galsC és nesC programozási nyelvek mind C-szerű nyelvek, mert bár kiterjesztésnek tekinthetők, mindkettő tartalmaz megszorításokat is az eredeti C nyelvhez képest (Például a nesC függvények definíciójában a formális paraméterek megadására a C++ konvenciót követi és nem engedi meg a klasszikus C formát). Ezekkel a C-szerű nyelvi elemekkel most nem foglalkozunk, mert nem tartoznak szorosan tárgyhoz. A TinyGALS beágyazott hálózati rendszerekhez készített programozási modelljével és annak galsC implementációjával foglalkozunk (A TinyGALS programozási modell szempontjából tulajdonképpen a C-n kívül más alapnyelvet is választhattak volna. A választás oka a C fordítók által készített program hatékonysága és "gépközelisége"). Fontos még megjegyezni, hogy a galsC némileg eltér a modelltől és a TinyGUYS mechanizmushoz is a modelltől némileg eltérő implementációt ad.
TinyGUYS változók definíciója és használata:
// Rendszer definíció include modules { A; B; }; globals { statevar; }; statevar <=> A_param; statevar <=> B_param; ... // Az A modul definíciója include components { A1; }; parameters { int A_param; }; ... // Az A1 komponens implementációja ... void fire() { ... int a; a=PARAM_GET(A_param); a++; PARAM_PUT(A_param)(a); ... }

Nyelvi konstrukciók

Háromféle alapkonstrukció létezik galsC-ben: komponens, aktor, és alkalmazás. A komponensek a legkisebb építőkövek és nesC nyelven írjuk őket. Interfészeket szolgáltatnak és használnak. Az interfészek speciális metódusok gyűjteményei. Az interfész szolgáltatója implementációt ad az interfészben szereplő parancsok- hoz és az implementációban használja az interfész eseményeit, míg az interfész használója az eseményekhez ad implementációt és a parancsokat használja. Egy komponens kétféle lehet: modul vagy konfiguráció. A modulok tartalmazzák a tényleges kódot, a konfigurációk pedig a rendszer komponenseit sorolják fel és azok interfészeit kapcsolják Össze. Ez utóbbit hívják kötésnek (wiring). A következő példában a TimerC konfigurációt láthatjuk, amely beágyazza a TimerM modult. Az interfész metódusok implementációját a TimerM tartalmazza.
// TimerC.nc configuration TimerC { provides interface Timer[uint8_t id]; provides interface StdControl; } implementation { components TimerM, ClockC, ...; TimerM.Clock -> ClockC; ... StdControl = TimerM.StdControl; Timer = TimerM.Timer; } // TimerM.nc module TimerM { provides interface Timer[uint8_t id]; provides interface StdControl; uses interface Clock; ... } implementation { // Az interfészek implementációja... }

A galsC nyelvben aktoroknak hívják a TinyGALS modell moduljait. Az aktorokat már kizárólag galsC-ben implementáljuk. Egy aktor interfésze bemeneti és kimeneti portok, illetve paraméterek halmazából áll. A paraméterek globális változók amelyek egyaránt írhatóak és olvashatóak. Az aktor komponensek és azok kapcsolatainak listáját tartalmazza. Egy komponens interfész metódusát a következő elemekhez kapcsolhatjuk: (1) egy másik komponens interfész metódusához, (2) port-hoz, (3) paraméterhez, vagy (4) ezek kombinációjához. A kapcsolatok szemantikájáról részletesen lásd [5]. Ezenkívül minden aktor tartalmazhat egy ún. actorControl szakaszt, amely valamely komponenseinek StdControl interfészeit (Az StdControl interfész a nesC legfontosabb interfésze. Három parancsot tartalmaz, ezek az init(), a start() és a stop().) exportálja a galsC alkalmazás szintjére, hogy ott meghívhatóak legyenek a rendszer inicializálásakor. (Így lehet például a hardver komponenseket is inicializálni.) A galsC programot végül egy alkalmazás fájl írásával hozzuk létre. Ebben paraméterek, aktorok, és utóbbiak kapcsolatai szerepelnek. A kapcsolatok összeköthetnek alkalmazás-paramétereket (globális változókat) aktor paraméterekkel (aktorokra lokális változókkal), vagy aktorok kimeneti port-jait más aktorok bemeneti port-jaival (Elvileg lehetséges egy aktor kimeneti port-ját ugyanannak az aktornak egy bemeneti port-jára kötni). Utóbbi esetben megadható a port-hoz tartozó FIFO sor mérete is.
A SenseTag példa alkalmazás:
A SenseActor aktor és a SenseTag alkalmazás: // SenseActor.gc actor SenseActor { port { in trigger; out output; } parameter { uint16_t count; } implementation { components SenseToInt, Photo; SenseToInt.ADC -> Photo; SenseToInt.ADCControl -> Photo; trigger -> SenseToInt.trigger; (SenseToInt.IntOutput.output, count) -> output; actorControl { SenseToInt.StdControl; } } } // SenseTag.gc application SenseTag { parameter { uint16_t count = 0; } implementation { actor TimerActor, SenseActor, ...; count = TimerActor.count; count = SenseActor.count; TimerActor.trigger =[64]=> SenseActor.trigger; SenseActor.output => ...; appstart { SenseActor.trigger(); } } }


A galsC és a nesC kapcsolata

A nesC programozási nyelv a TinyOS operációs rendszer [2][3] programozási nyelve. Azonban a nesC által implementált programozási modellt is szokás TinyOS-nak, vagy TinyOS programozási modellnek nevezni. Ebben a modellben komponensekkel dolgozunk, amelyek kétfélék lehetnek: modulok, vagy konfigurációk. A modulok tartalmazzák a tényleges kódot, a konfigurációk pedig a komponensek egymással való kapcsolatait írják le. A kapcsolatok interfészek segítségével valósulnak meg. Egy-egy interfészben parancsokat és eseményeket (valójában parancs-kezelő és esemény-kezelő rutinokat) deklarálunk. Az interfészt szolgáltató komponens ad implementációt a parancsokhoz, és hívja az eseményeket, míg az interfészt használó komponens az eseményekhez ad implementációt, és a parancsokat hívja. Így tehát az interfészek kétirányúak. A TinyOS végrehajtási modelljében taszkok és hardvermegszakítás-kezelők szerepelnek. Mindkettőt a modulok belsejében definiálhatjuk. A taszkok indításuk sorrendjében futnak le egymás után és nem preemptálják egymást, vagyis nincs időszeletelés, így a taszkok között párhuzamossági problémák sem merülnek fel. Hardvermegszakítások megszakíthatják a taszkok futását és egy- mást is, ezért a TinyOS atomi szakaszok bevezetésével lehetővé teszi, hogy kölcsönös kizárást programozzunk ezekbe az aszinkron programrészekbe. A TinyGALS programozási modell független a TinyOS modelltől, azonban a galsC nyelv készítői fontosnak tartották, hogy a nesC komponensek felhasználhatóak legyenek a galsC programokban. Így a galsC a nesC kiterjesztésének tekinthető nyelvi szinten. Ezért a nesC és a galsC komponensei ugyanazok. A galsC nyelvi specifikációja nem mond semmit arról, hogy a komponensek hogyan épülnek fel, csak annyit köt meg, hogy azoknak metódusokat kell szolgáltatniuk és használniuk. A TinyGALS végrehajtási modellje eltér ugyan a TinyOS végrehajtási modelljétől, de ez nem okoz problémát, a TinyOS programok futnak a TinyGALS Ütemezőjével is.

Hivatkozások

[1] galsc home page. Web Page. http://galsc.sourceforge.net/
[2] Tinyos home page. Web Page. http://www.tinyos.net/
[3] Tinyos tutorial. Web Page. http://www.tinyos.net/tinyos-1.x/doc/tutorial/index.html
[4] Elaine Cheong. Design and implementation of tinygals: A programming model for event-driven embedded systems. Master's report, technical memorandum no. ucb/erl m03/14, University of California, Berkeley, CA, 94720, USA, May 23 2003. http://ptolemy.eecs.berkeley.edu/papers/03/TinyGALSreport
[5] Elaine Cheong and Jie Liu. galsc: A language for event-driven embedded systems. In Proceedings of Design, Automation and Test in Europe (DATE)
[6] Jie Liu Elaine Cheong, Judy Liebman and Feng Zhao. Tinygals: A programming model for event-driven embedded systems. In Proceedings of the 18th Annual ACM Symposium on Applied Computing (SAC'03), Melbourne, FL, Mar. 9-12 2003. http://ptolemy.eecs.berkeley.edu/papers/03/TinyGALS