Fájlok használata szemaforként
Amikor két program közösen használ egy eszközt (billentyûzet, nyomtató, soros port, stb.), akkor tudniuk kell azt, hogy mikor szabad az adott eszköz. Erre a célra használják a szemaforokat, amelyek jelzik az eszközökön végzett mûveletek kezdetét és végét. A szemaforok figyelésével elkerülhetô, hogy a programok egyszerre akarják használni valamelyik erôforrást. Sajnos a REXX nem támogatja a hagyományos értelemben vett szemaforokat, így nekünk kell gondoskodni a helyettesítésükrôl.
A legkézenfekvôbb megoldás, ha fájlokat használunk szemaforként. Természetesen a fájl neve és elhelyezkedése a használó programok számára ismert kell, hogy legyen. A fájlnak nem szükséges semmit tartalmaznia, hiszen puszta jelenléte (vagy éppen hiánya) jelzi, hogy a használni kívánt eszköz foglalt. A szemaforfájl meglétét egy hurokban szokás ellenôrizni:
flag = 0 DO UNTIL flag flag = (Stream(szemaforfajl, 'C', 'Query Exists') = '') END
Sajnos ennek a megoldásnak megvan az a hátránya, hogy a hurok igen sokszor végrehajtódik, ami feleslegesen terheli a processzort és lelassítja a többi alkalmazás futását. Sokkal jobban járunk, ha csak bizonyos idôközönként, pl. másodpercenként, vagy akár csak percenként ellenôrizzük a szemafort:
DO WHILE Stream(szemaforfajl, 'C', 'Query Exists') <> '' CALL SysSleep 1 /* Másodpercenkénti ellenôrzés */ END
Csatornák használata
Amennyiben egy alkalmazás kommunikálni szeretne egy másikkal, akkor azt a csatornák felhasználásával tehetik meg. A kommunikáló programok akár különbözô gépeken is futhatnak, és ilyenkor a csatorna a hálózaton keresztül köti össze ôket. A csatorna leginkább egy fájlhoz hasonlít, már ami a kezelést illeti. A csatornáknak speciális nevük van, ami mindig valami olyasmi, hogy \PIPE\csatornanév. A REXX programok sajnos nem tudnak csatornákat létrehozni, hacsak nincs valamilyen speciális kiegészítô csomagunk. Ha viszont egy másik program már létrehozott egy csatornát, és a REXX program ismeri a csatorna nevét, akkor minden további nélkül tud kommunikálni az alkalmazással. Jó példa erre a BocaSoft cég SystemSound programja, amely elindítás után rögtön létrehoz egy \PIPE\BS_SSND csatornát, amelyen keresztül meg lehet mondani neki, hogy melyik dallamot játssza le:
DO i = 1 TO 49 CALL CharOut , Right(i, 3) CALL Stream '\pipe\bs_ssnd', 'C', 'OPEN WRITE' CALL LineOut '\pipe\bs_ssnd', i CALL Stream '\pipe\bs_ssnd', 'C', 'CLOSE' CALL SysSleep 5 END
Figyeljük meg, hogy a csatornát mindig nyitjuk és zárjuk minden egyes adatküldés során. A nyitásra elvileg nem lenne szükség, mivel a LineOut automatikusan elvégezi a megnyitást is, s csak a kód szépsége kedvéért írtuk be.
A SysWaitNamedPipe segédfüggvény meghívásával tudjuk ellenôrizni a csatornák állapotát. A függvény 0 visszatérési értéke jelzi, hogy a csatorna szabad. A 2-es érték azt jelenti, hogy a csatorna nem létezik. A harmadik lehetséges visszatérési érték, a 231, ami a csatorna foglalt állapotával egyenértékû:
rc = SysWaitNamedPipe(csatornanév, [, türelmi idô])
A SysWaitNamedPipe-nak megadhatunk még egy türelmi idôt is, ami azt határozza meg, hogy meddig akarunk várni a csatorna felszabadulására. A -1-es érték végtelen türelmi idôt jelent, minden ettôl eltérô pozitív érték, a másodperc ezredrészében kifejezett várakozási idôt jelenti.
Várakozási sorok
A várakozási sort (queue) olyan, sorba rendezett adathalmazként definiálhatjuk, amely mindkét vége felôl megközelíthetô. A megfelelô REXX függvényekkel létrehozhatjuk, törölhetjük és kiolvashatjuk a várakozási sorokat. A kiolvasási sorrend kétféle lehet: LIFO (last in, first out = a legutoljára betett adat jön ki legelsônek), vagy FIFO (first in, first out = a legelôször betett adat jön ki legelsônek).
A REXX minden szekcióban létrehoz egy SESSION nevû várakozási sort, amelyet csak a szekcióban futó REXX program ér el. A SESSION várakozási sor tehát kiválóan alkalmas arra, hogy egy szekción belül, egymás után futtatott programok adatot cseréljenek. Külön szekcióban futtatott programok adatcseréjéhez úgynevezett privát várakozási sort kell definiálnunk. Ezekrôl a sorokról a késôbbiekben még részletesen is lesz szó.
Az OS/2 által létrehozott várakozási sorok sajnos nem kompatibilisek a REXX várakozási soraival, így azokat csak valamilyen REXX bôvítô csomaggal lehet elérni. OS/2-es szekcióból viszont írhatunk a SESSION várakozási sorba, az RxQueue parancs segítségével:
pstat | rxqueue
A példában a pstat parancs kimenetét tároljuk el az adott szekció SESSION várakozási sorában, így az adat elérhetô a késôbb ugyanebben a szekcióban futtatott REXX program számára. Már ismerjük a PULL parancsot, amelyrôl eddig úgy tudtuk, hogy alapesetben a standard bemenetrôl olvassa be az adatokat. Amennyiben a SESSION sor adatot tartalmaz, a PULL elôször innen olvassa ki az adatokat, s csak utána fordul a standard bemenethez.
Az RxQueue parancsnak megmondhatjuk, hogy LIFO vagy pedig FIFO sorrendben akarjuk eltárolni az adatokat, amennyiben használjuk a /LIFO vagy /FIFO paramétereket. Ezek megadása nélkül az RxQueue a FIFO sorrendet használja. Ha például a dir parancs kimenetének az utolsó sorát akarjuk elsônek beolvasni, akkor a következô parancsot kell a várakozási sor megtöltésekor használni:
dir | rxqueue /LIFO
A várakozási sor tartalma törölhetô is. Erre szolgál a /CLEAR paraméter. REXX programokból a PUSH és QUEUE parancsokat használhatjuk a sor feltöltésére. A különbség az, hogy a PUSH LIFO, a QUEUE viszont FIFO sorrendben tárol.
A LineIn és LineOut függvényekkel olvashatjuk és írhatjuk a várakozási sorokat, amennyiben a fájlnév paraméter helyett a QUEUE: kulcsszót adjuk meg. A LineIn függvénynek megvan az az elônye a PULL-lal szemben, hogy ilyenkor mindig a várakozási sorból olvas. Hasznos lehet még programjainkban a Queued függvény is, amely megadja, hogy hány adat van még a sorban. Használata nagyon hasonlít a Lines vagy a Chars függvényekéhez:
DO WHILE Queued() <> 0 PULL adat SAY adat END
A fenti kódrészlet beolvassa és megjeleníti a sorban található adatokat. Amikor elfogy az adat, akkor a Queued függvény 0-át ad vissza, így a hurok végrehajtása abbamarad.
Az RxQueue függvény segítségével privát várakozási sorokat hozhatunk létre és szüntethetünk meg. A privát sorokat a rendszeren futó bármelyik program használhatja, ehhez csak a sor nevét kell ismerni. Az RxQueue függvénnyel le lehet kérdezni az éppen aktív sor nevét és lehet a sorok között is váltani. A privát sorok egészen addig megmaradnak, amíg ki nem töröljük ôket, vagy amíg ki nem kapcsoljuk a rendszert. Az alábbi programrészlet azt mutatja be, hogy hogyan hozhatunk létre egy sorocska (sajnos nem söröcske :-) nevezetû privát várakozási sort:
sornev = RxQueue('Create', sorocska)
Ha a sorocska sor már létezne, akkor a REXX általa generált név alatt hozza létre a sort. Ezért is fontos, hogy összehasonlítsuk a megadott nevet a visszakapott névvel. Amennyiben használni is akarjuk a létrehozott várakozási sort, akkor át kell váltani rá:
regisor = RxQueue('Set', sornev)
Az átváltás után a regisor változóban az elôzôleg aktív sor nevét fogjuk megtalálni, s ha kedvünk van, akkor visszaválthatunk rá. Amennyiben nem "emlékeznénk" a régi sor nevére, akkor a SESSION kulcsszót is használhatjuk a név helyett. Az éppen aktív sor nevét a Get opcióval kérdezhetjük le:
Say RxQueue('Get')
Ha már nincs szükségünk egy sorra, akkor töröljük a Delete opcióval, hogy felszabadítsuk a lefoglalt memóriát:
rc = RxQueue('Delete', sornev)
A sor törlésekor a sorban elraktározott adatok is törlôdnek. A visszatérési értékbôl azt is megtudhatjuk, hogy tényleg sikerrel járt-e a törlés:
| Visszatérési érték: | Jelentés: |
| 0 | A sor sikeresen törölve. |
| 5 | A sor neve nem érvényes. |
| 9 | A sor nem létezik. |
| 10 | A sor foglalt (másik program használja). |
| 12 | Memóriahiba történt. |
| 1000 | Lehetséges, hogy korrupt az OS2.INI fájl. |
Végezetül tekintsünk meg két példaprogramot, amelyek várakozási sor segítségével kommunikálnak egymással:
/* Sor létrehozása és kiolvasása ***************************************** */
CALL RxFuncADD 'SysLoadFuncs', 'RexxUtil', 'SysLoadFuncs'
CALL SysLoadFuncs
SIGNAL ON Halt NAME Out
SIGNAL ON Syntax NAME Out
sor = 'SOR'
CALL RxQueue 'Create', sor
CALL RxQueue 'Set', sor
'@detach lecke06b.cmd' sor
CALL CharOut , 'Várom az adatokat...'
DO Until Queued() <> 0
CALL CharOut , '.'
CALL SysSleep 1
END
SAY
SAY
DO While Queued() <> 0
fajlnev = LineIn('QUEUE:')
SAY fajlnev
END
Out:
CALL RxQueue 'Delete', sor
EXIT
/* A sor feltöltése ****************************************************** */ CALL RxFuncADD 'SysLoadFuncs', 'RexxUtil', 'SysLoadFuncs' CALL SysLoadFuncs SIGNAL ON NotReady PARSE ARG sor CALL RxQueue 'Set', sor CALL SysFileTree 'C:\*.*', 'fajlok.', 'FO' DO i = 1 TO fajlok.0 CALL LineOut 'QUEUE:', fajlok.i say fajlok.i END EXIT NotReady: SAY 'Még nem kész!' EXIT
Az elsô program létrehozza a privát sort, és elindítja a második programot. A meghíváskor átadja a létrehozott sor nevét is. A második program kilistázza a C: meghajtó gyökerében található fájlokat, és a listát beteszi a létrehozott privát sorba. Az elsô program egészen addig vár, amíg meg nem jelennek az adatok a sorban, s majd kilistázza az innen kiolvasott fájlneveket.
REXX GYÍK:
K1. A sorok segítségével üzeneteket is válthatok más programokkal, így a szemaforokra nincs is szükség! Igaz ez?
V1. Amennyiben csak két, mindig egy rendszeren futó programnak kell kommunikálnia, akkor ez egy jó megoldás. Ha azonban kettônél több programnak kell üzenetet cserélnie, akkor ebbôl problémák adódhatnak, mivel az üzenet kiolvasása egyben azt is jelenti, hogy az üzenet eltûnik a sorból. Így más, esetleg más rendszeren futó programok nem fognak tudni hozzájutni.
Gyakorlatok:
1. A leckében szereplô két példaprogram nem minden esetben mûködik rendesen. Amennyiben lecke06b.cmd nem elég gyorsan tölti az adatokat a várakozási sorba, akkor a lecke06a.cmd adatokat kiolvasó hurka azt hiheti, hogy már nincs több adat, és így kiléphet. Írja át a programokat úgy, hogy ez ne történhessen meg!
| Kádár Zsolt 1998. 11. 07. | [ Elôzô lecke | Tartalom ] |