2.1. A szinkronizációs elemek A taskok működésük összehangolására, egymás futásának kölcsönös kizárására a monitor által biztosított un. szinkronizációs elemeket használhatják. Két ilyen elem van: a flag és a postaláda. A szinkronizációs elemek (de a később tárgyalandó erőforrások is) névvel (számmal) azonosítható logikai elemek. A ’logikai elem’ terminus azt jelenti, hogy fizikailag nem léteznek. Akármilyen kis darabokra szedünk szét egy számítógépet, nem fogjuk megtalálni őket. Létük az általuk kiváltott hatásban van, és ez a hatás nagyon is valóságos. A hatás úgy jön létre, hogy pontosan betartjuk a kezelésükre vonatkozó szabályokat, vagyis adott esetben nem tehetünk meg akármit, hanem csak úgy cselekedhetünk, hogy közben e fiktív objektumokra vonatkozó játékszabályok ne sérüljenek. 2.1.1. A flag (szemafor) A flag egy névvel azonosítható kétállapotú logikai elem, amely lehet SET („szabad”) és RESET („tilos”) állapotban. Egy flag (például) egy esemény leképezésére alkalmas: ha az esemény bekövetkezett, a flag SET, ha még nem, akkor RESET állapotú. Az esemény tartalma a flag kezelése szempontjából teljesen érdektelen, azt csak a taskoknak kell ismerniük. A flaggel négy művelet végezhető, mindegyik művelet egy-egy monitorhívással realizálható: • • • •
RQSTF: szabadra állítás (SET), RQCLF: tilosa állítás (RESET), RQASK: a flag állásának lekérdezése, RQWTF: várakozás a flag SET állapotára.
Minden monitorhívásnál paraméterként meg kell adni a flag számát. Egy flag közvetítésével mindig két task kerül kapcsolatba egymással. A taskok viszonya a flaghez aszimmetrikus: az egyik állítja, a másik vizsgálja, illetve várakozik a SET állapotra. Az RQSTF, RQCLF és az RQASK hívások nem vihetik WAIT állapotba a hívó taskot. Az RQWTF hívásnál a hívó várakozni kényszerül, ha a flag tilosra áll és csak akkor kerülhet újra aktív állapotba, ha a másik szabadra állítja a szemafort. Egy task lekérdezheti egy flag állapotát (RQASK). Ez nem okozhat várakozást, viszont tilos szemafor esetén a task nyilván nem csinálhatja azt a továbbiakban, amit szabad szemafor esetén csinálna. A flag használatára nézzünk egy egyszerű példát. Két task közösen használ egy memóriaterületet, az egyik (I) írja, a másik (O) olvassa. Amíg I be nem fejezte az írást, O nem olvashat, és fordítva. A kölcsönös kizárásra két flaget használnak: IE az írás engedélyező, OE az olvasás engedélyező flag. Az IE-t O állítja és I vizsgálja, az OE-t I állítja és O vizsgálja. I az írást csak akkor kezdheti meg, ha IE szabadra áll. Az írás megkezdése előtt OE-t tilosra, befejezésekor pedig szabadra kell állítania. O az olvasást csak akkor kezdheti, ha OE szabadra áll. Az olvasás megkezdése előtt IE-t tilosra, befejezésekor pedig szabadra kell állítania. A példa nagyon jól szemlélteti a flagek használatát, de maga a feladat más eszközzel sokkal elegánsabban megoldható. A monitor több (64, 128,…) flaget biztosít a taskok számára. A monitortáblákban minden flaghez egy bájt tartozik, amely a flag állapotát tartalmazza. (Az állapotinformáció tárolására egy bájt pazarlóan sok, viszont a szelektív bitkezelés igen hosszadalmas művelet és a monitor gyors működése még a hajdani, memóriaszűkös világban is sokkal fontosabb szempont volt, mint a memóriatakarékosság.)
2.1.2. A postaláda (exchange) A postaláda egy névvel azonosítható kétállapotú logikai elem, amely lehet „üres” és „nem-üres” állapotban. A postaládába lehet üzenetet küldeni, vagy (ha nem üres) lehet belőle üzenetet venni. Az exchange kezelésére két monitorhívás szolgál: • RQSEN: üzenetküldés, • RQWTE: üzenetvétel. Mindkét hívásnál paraméterként meg kell adni a postaláda számát, ezen kívül az RQSEN-nél a küldendő üzenet kezdőcímét. Az RQWTE hívás, ha van üzenet, annak kezdőcímét adja vissza. Az RQSEN hívásnál a hívó task nem kerülhet WAIT állapotba, az RQWTE-nél - üres postaláda esetén - igen. A várakozó állapotból a task akkor szabadul, ha üzenet érkezik az exchangebe. Az üzeneteket, ahogy a postán a leveleket, borítékban kell küldeni. A borítéknak itt az elhasznált (feldolgozott) üzenet felel meg. Így a küldőnek először kell vennie egy elhasznált üzenetet, annak tartalmát felül kell írnia az új adatokkal (aktualizálni kell), és ezt kell érvényes új üzenetként elküldenie. A vevő, miután feldolgozta a kapott üzenetet, nem dobhatja el, hanem elhasznált üzenetként el kell küldenie egy erre a célra fenntartott postaládába. A vázolt üzenetforgalom két task között két postaláda közvetítésével szervezhető meg (6-3. ábra).
6-1. ábra A két task szerepe nem szimmetrikus. Az egyik a termelő (T), ő küldi a feldolgozandó üzeneteket, a másik a fogyasztó (F), ő dolgozza fel a kapott üzeneteket. Az egyik postaládában (X1) a feldolgozandó (érvényes), a másikban (X0) az elhasznált üzenetek gyülekeznek. A termelő X0-ból vesz és X1-be küld, a fogyasztó X1-ből vesz és X0-ba küld. A körforgásban résztvevő üzenetek száma állandó. Ezt a számot a fogyasztó szabja meg oly módon, hogy a rendszer indulásakor adott számú üres borítékot („elhasznált” üzenetet) küld X0-ba. Később a borítékok száma már nem változhat, bármely időpontban az üzenetek egy része érvényes, más része elhasznált. Ha sok üzenet van forgalomban, a gyorsabb task
megszaladhat a lassúbbhoz képest. Mivel azonban az üzenetek összes száma állandó, egy idő után észre fogja venni, hogy a megfelelő exchange-ből nem kap üzenetet, így küldeni sem tud, és le fog fékeződni. (Egyébként a gyorsabb task - lassúbb task szereposztás nem örök érvényű, azt számtalan változó körülmény befolyásolja és dinamikusan változhat.) A forgalomban lévő üzenetek számának növelése rugalmasabbá, csökkentése merevebbé teszi a két task kapcsolatát. Ha csak egy üzenet van forgalomban, akkor a kölcsönös kizárás esete valósul meg. Maga az üzenet egy adott hosszúságú memóriaterület, amely két részből, az üzenetfejből és az üzenettörzsből áll. Az üzenetfejet a monitor használja, a taskoknak szigorúan tilos megváltoztatni. Az üzenettörzs az üzenet tartalmi része, ennek értelmezése az érintett taskok belügye. A postaláda egy, az üzenetfejjel azonos méretű, egy cím tárolására alkalmas memóriaterület (pl. két bájt, vagy egy szó). A monitor az üzeneteket láncolt FIFO-ba rendezve csatolja a postaládához oly módon, hogy minden üzenetfej a következő üzenet pointere (6-4. ábra).
6-2. ábra Az utolsó üzenet fejében un. láncvég-jel van. A postaláda az első üzenetre mutat. Ha a postaláda üres, akkor ő tartalmazza a láncvég-jelet. Az üzenetlánc FIFO jellege biztosítja, hogy a vevő task a küldés sorrendjében kapja meg az üzeneteket. A láncolási mechanizmus a következőképpen működik: üzenetküldéskor a monitor a lánc utolsó üzenetének fejéből kiveszi a láncvég-jelet és a küldött üzenet címét helyezi bele, míg a láncvég-jel az újonnan becsatolt üzenet fejrészébe íródik.. Üzenetvételkor a postaládában lévő címet kapja meg a hívó, majd a postaládába a kicsatolt üzenet fejében lévő cím íródik. A monitor a taskok számára több (64, 128,...) postaládát biztosít. Tevékenység: A fenti részfejezet áttanulmányozása után önállóan, segédeszköz használata nélkül; - Írja le/mondja el a szinkronizációs elem fogalmát és sorolja fel típusait! - Írja le/mondja el a flag fogalmát, használatát, és sorolja fel a flaget kezelő monitorhívásokat! - Gondolja meg, mi történne, ha termelő az érvényes üzeneteket a lejárt (üres) üzenetek ládájába küldené!
2.2. Az erőforrás Az erőforrás egy névvel azonosítható kétállapotú logikai elem, amely lehet „szabad”, vagy „foglalt” állapotban. Ez eddig a két állapot elnevezésétől (ami egyébként lényegtelen) eltekintve azonos a flag és a postaláda kapcsán mondottakkal, és fogalmunk sincs, hogy mi az erőforrás. Ha a lényeget akarjuk megragadni, a következőt kell mondanunk. Az erőforrás olyan logikai elem, amely egyidejűleg csak egy task birtokában lehet, de amelyet ugyanakkor több task konkurens módon igényel. Ebből már látható, hogy az erőforrás olyan valami (bármi), amiből adott időben kevesebb van, mint amennyi kellene, ezért versenyezni kell érte. Az erőforrás tehát a rendszer szűk keresztmetszeteit leképező, illetve azok kezelését lehetővé tevő elem. Abban a pillanatban, amikor a valamiből annyi lesz, amennyi kell, a valami elveszíti erőforrás jellegét, mert minden igénylőnek meglesz a saját valamije és senki nem verseng már érte. (Egyébként a rendszer legfőbb erőforrása a processzoridő, de ezt a monitor saját hatáskörén belül osztja el.) Az erőforrást lehet igényelni (lefoglalni) és elengedni (visszaadni, felszabadítani). E műveletekre két monitorhívás van: • RQRES: lefoglalás, • RQRLS: elengedés. Mindkét hívásnál paraméterként meg kell adni az erőforrás azonosítóját. Az RQRES híváskor, ha az erőforrás szabad, a hívó task megkapja (ezzel az erőforrás foglalttá válik), és futhat tovább. Ha az erőforrás nem szabad, a hívó WAIT állapotba kerül és a monitor bejegyzi az adott erőforrásra várakozók prioritás szerinti láncába. Ha az erőforrás felszabadul, akkor a rá váró tskok közül a legmagasabb prioritású kapja meg, a többinek pedig tovább kell várnia. Az RQRLS hívással a task visszaadja az általa birtokolt erőforrást. Ez a hívás nem viheti WAIT állapotba a taskot. Egy task csak általa birtokolt (megelőzőleg lefoglalt és megkapott) erőforrást engedhet el, vagyis nem lehet egy RQRLS hívással egy másik tasktól elvenni az erőforrást. A tasknak a már nem használt erőforrást azonnal fel kell szabadítania. Egy task egyszerre csak egy erőforrást foglalhat le, és amíg el nem engedte, másikat nem igényelhet. (Ezt a monitor nem ellenőrzi ugyan, de kötelező betartani.) Ellenkező esetben un. holtpont alakulhat ki és a szabályszegő task soha többé nem fog futni. Vizsgáljuk meg ezt részletesebben! Az A task fut, lefoglalja és megkapja az α erőforrást. Fut tovább és α előzetes elengedése nélkül kéri a β erőforrást. Ám az foglalt, egy B task foglalja, így A WAIT állapotba kerül. Egyszer csak B-re kerül a sor, ő fut, és mielőtt β-t elengedné, kéri α-t. Ám α foglalt az A által, így B is WAIT állapotba kerül. Most már mind a ketten várakoznak, és mivel sem α-t, sem β-t más nem szabadíthatja fel, örökre várakozni is fognak. Fokozza (ha még lehet) a bajt, hogy ez az ájulat tovább gyűrűzhet, mert bármely olyan task, amely a későbbiekben az említett két erőforrás valamelyikét igényli, szintén örökre várakozó állapotba zuhan. Néhány példa az erőforrás alkalmazására: • Több task akar nyomtatni, de csak egy nyomtató van. Hogy ne keveredhessenek össze az egyes szövegek karakterei, csak sorban egymás után nyomtathatnak. A nyomtatóhoz egy erőforrást kell rendelni. • Egy nem újrahívható szubrutint is használhat több task, ha erőforrás van hozzárendelve. Egy-egy hívó előbb lefoglalja, majd a szubrutin lefutása után elengedi, így azt nem lehet újrahívni. Viszont az erőforrás-jellegű szubrutint tilos közönséges szubrutinként hívni!
• A flaggel kapcsolatban bemutatott példa elegánsabb megoldása: a közös memóriaterületet erőforrásnak tekintjük. Az író task írás előtt lefoglalja, az írás végén felszabadítja (így írás alatt nem lehet beleolvasni). Az olvasó task olvasás előtt lefoglalja, utána felszabadítja (így nem lehet olvasás közben beleírni). Az ottani példában a kölcsönös kizárást két flaggel lehetett biztosítani, itt ugyanaz elérhető egy erőforrással. Szokás az erőforrást is a szinkronizációs elemek közé sorolni. Mi azért választottuk külön, mert más a funkciója. A szinkronizációs elemek közvetítésével rendszerint két task lép egymással kapcsolatba, míg az erőforrás esetében egy task az összes konkurens partnerével úgy, hogy átmenetileg kizárja őket a működésből. A monitor több (64, 128,…) erőforrást bocsát a taskok rendelkezésére. A monitortáblákban minden erőforráshoz egy állapotbájt (szabad/foglalt), egy, az aktuális birtokos task számát tartalmazó bájt, és az igénylők várakozási sora tartozik. Tevékenység: - Írja le/mondja el a postaláda és az üzenet fogalmát, használatát, és sorolja fel az üzenetforgalmat lebonyolító monitorhívásokat! - Írja le/mondja el az erőforrás fogalmát, használatát, és az erőforráskezelő monitorhívásokat!
2.3. Az I/O hívások Az I/O kezelés a real-time monitorok legkevésbé általánosítható jellemzője. Több monitor a perifériakezelő programok (un. handlerek) bő választékát tartalmazza, míg másokhoz ezeket külön meg kell írni és megszakítás által aktivált task-ként, bizonyos előírások betartásával a monitor alá kell generálni. Ami többé-kevésbé általános, az a következő: A handlerek a perifériákat megszakításosan kezelik, a beérkező, illetve kiküldendő adatokat a monitor csatornánként input, illetve output FIFO-kba gyűjti. A taskok a perifériákat egyáltalán nem látják, sőt, a FIFO-khoz is csak monitorhívásokkal férhetnek hozzá. Két tipikus I/O-kezelő monitorhívás van: • RQINP: egy bájt beolvasása adott csatornáról, • RQOUT: egy bájt kiküldése adott csatornára. Mindkét monitorhívásnál meg kell adni a csatornaszámot. Az RQINP-nél a task várakozó állapotba kerül, ha az input FIFO üres, ha nem, megkapja a legrégibb adatot és futhat tovább. Az RQOUT-nál paraméterként meg kell adni a kiküldendő adatot. A hívás után a task várakozó állaotba kerül, ha a FIFO tele van, ha még van hely, az adat bekerül a sorba és a task futhat tovább. Tevékenység: Ismertesse az I/O kezelés minitorhívásait! 2.4. Egy megjegyzés a taskok türelméről A tárgyalt monitorhívások között van négy olyan (az RQWTF, az RQWTE, az RQINP és az RQOUT), amelyek hívásakor a hívó tasknak paraméterként meg kell adnia egy időtartamot is, mellyel a monitor tudomására hozza, hogy WAIT állapotba kerülése esetén legfeljebb
mennyi ideig hajlandó várakozni. Ezt az időtartamot time-out-nak nevezik, magyarul úgy mondhatjuk: türelmi idő. A time-out lehet végtelen, valamilyen véges érték, vagy zérus. Ha a time-out végtelen, a task, amikor újra aktív állapotba kerül, biztos lehet abban, hogy a monitortól kért szolgáltatás rendben teljesült. Ha pl. üzenetre várt (RQWTE), továbbfutáskor biztos lehet abban, hogy megkapta az üzenetet. Ha a time-out nem végtelen, továbbfutáskor a tasknak tudnia kell, hogy most azért futhat, mert időközben teljesült a továbbfutás feltétele, vagy csak azért, mert a türelmi idő lejárt. A két különböző esetben ugyanis a task nyilvánvalóan nem folytathatja azonos módon a tevékenységét. A monitor ilyenkor visszaadott paraméterben közli a továbbfutás körülményeit, és a taskra van bízva, hogy mit tesz. 2.5. A monitor működése Említettük, hogy a monitor két esetben fut: óra-interruptkor, illetve - a köztes időben monitorhíváskor. Most, amikor megkíséreljük összegyűjteni a monitor konkrét tevékenységeit, a korábbiakra támaszkodva már igen egyszerű helyzetben vagyunk. A) Óra-interrupt: Az összes időtől függő tevékenység ekkor zajlik le. • A futó task megállítása, de aktív állapotban hagyása. • A szoftver-óra aktualizálása. • Az IDLE állapotban levő időzített taskok időzítés-számlálóinak aktualizálása. Amelyiknek letelt az időzítése, annak aktív állapotba helyezése. • A WAIT állapotban levő véges time-outos taskok time-out számlálóinak aktualizálása. Amelyiknek letelt a time-outja, annak aktív állapotba helyezése, és a nem-normális aktiválás jelzése. • A legmagasabb prioritású aktív task kiválasztása és futásának engedélyezése. B) Monitorhívás: Időtől függő tevékenység nincs, az megtörtént az előző óra-interruptnál. • A hívó task megállítása, de (egyelőre) aktív állapotban hagyása. • A monitorhívás jellegének megfelelő tevékenységek és állapotváltoztatások végrehajtása. Ha az állapotváltoztatás WAIT állapotban levő véges time-outos task aktiválásával jár, annak jelzése, hogy az aktiválás normális, tehát nem a time-out lejárta miatt következik be. • Az aktív taskok közül a legmagasabb prioritású kiválasztása és futásának engedélyezése. A második tevékenységsor leírása nagyon általánosnak és felületesnek tűnik (pedig éppen olyan pontos, mint az első). Ez természetes is, mert nem mondtuk meg, hogy milyen konkrét monitorhívásról van szó. Tevékenység: Nyomatékkal javasoljuk az Olvasónak, hogy néhány konkrét hívás esetében alaposan gondolja át a működés részleteit, vagyis töltse meg konkrét tartalommal a fenti, nagyon általánosan megfogalmazott utalásokat! Ennek kapcsán azt is gondolja át, hogy egy olyan ártatlan monitorhívásnál, amely biztosan nem viheti WAIT állapotba a hívót, vajon biztosan ő fog-e tovább futni, vagy olyan eset is előfordulhat, hogy csak aktív marad, de mégsem ő fut! Vagy plasztikusabban fogalmazva: előfordulhat-e, hogy valakit kihúzunk a gödörből és az hálából nem lök ugyan bele a gödörbe, de elveszi tőlünk a biciklit és otthagy?
- Sorolja fel a monitor tevékenységeit óra-interrupt, illetve monitorhívás esetén! - Írja le/mondja el a time-out fogalmát és a time-out-os monitorhívások speciális működését!