Az anyag egy PVM elôadásom rövid kivonata - szabadon terjeszthetô. A leírás helyességéért, a használatából eredô esetleges károkért sem Csizmazia Balázs sem pedig a dokumentum más módosítója nem vállal felelôsséget.
Az anyag "unsupported", nekem a továbbiakban semmi érdekem/célom nem fűzôdik hozzá, nem fogom módosítani, stb ...
A Csizmazia Balázs által rögzített copyright feltételeken bárki tetszése szerinti módosításokat végezhet azzal a feltétellel, hogy az esetlegesen (akár anyagi ellenszolgáltatásért) vállalt kötelezettségei az eredeti szerzôre (Csizmazia Balázsra) nem vonatkoznak semmilyen körülmények között, és összhangban marad az eredeti PVM-copyrighttal.
Az anyag a PVM User's Guide egy részleges "kijegyzetelésének" tekinthetô; kisebb módosításokat ugyan végeztem rajta, de nem tartalmaz PVM programozási technikákat, példaprogramokat, nem tartalmazza a PVM függvényeinek referencia-kézikönyv szerű leírását.
AMENNYIBEN valaki ezeket a feltételeket nem tartja elfogadhatónak, akkor e dokumentumot nem használhatja, nem terjesztheti.
A PVM copyright-je (ami mind a szoftverre, mind pedig a vele szállított dokumentációra is vontakozik) angolul így hangzik [ide szebb lenne egy magyarra fordítás, de nincs jogi szakfordítói végzésem, így inkább ettôl eltekintek ...] :
NOTICE
Permission to use, copy, modify, and distribute this software and its documentation for any purpose and without fee is hereby granted provided that the above copyright notice appear in all copies and that both the copyright notice and this permission notice appear in supporting documentation.
Neither the Institutions (Emory University, Oak Ridge National Laboratory, and University of Tennessee) nor the Authors make any representations about the suitability of this software for any purpose. This software is provided ``as is'' without express or implied warranty.
PVM 3.2 was funded in part by the U.S. Department of Energy, the National Science Foundation and the State of Tennessee.
TARTALOM:
A PVM-rôl számos jó minôségű (ingyenes) dokumentáció elérhetô - többek közt a "PVM 3 USER'S GUIDE AND REFERENCE MANUAL", ami tartalmazza a PVM installálásához/alkalmazásához szükséges (talán) összes információt.
Ez az ismertetô egy áttekintést nyújt a PVM lehetôségeirôl - lényegében néhány elem bemutatásával. A rendszerrel végzett érdemi munkához valószínűleg szükség lesz az eredeti PVM leírásra valamint a PVM könyvtár függvényeirôl készült online leírásokra.
Ha például van két számítógépünk, amelyekbôl a PVM segítségével egyetlen virtuális gépet akarunk csinálni, azt megtehetjük úgy, hogy mindkét gépen elindítjuk a (megfelelôen felkonfigurált) PVM-démont, majd el kell indítani a PVM-et használó alkalmazásokat. Ha egy gépet több különbözô felhasználó akar egy virtuális géppé összekapcsolni, akkor mindegyik felhasználónak saját PVM-démont kell indítania, viszont egy felhasználónak elég csak egy PVM-démont futtatnia, ez megszervezi a felhasználó összes (akár több is!) elosztott alkalmazásának futtatását.
Elosztott alkalmazások készítésekor az egyes alkalmazás-komponenseket különálló programokként (mondjuk UNIX processzként ...) kell megírni, azok szinkronizációjára/adatcseréjére kell a PVM-et használnunk; vagyis a PVM nem biztosít eszközöket összetett alkalmazások komponenseinek automatikus párhuzamosításához. A PVM lehetôséget nyújt arra, hogy egy "mezei" UNIX processz bekapcsolódjon a virtuális gépbe, valamint arra is, hogy egy PVM-be kapcsolódott UNIX processz (a továbbiakban PVM-processz) kilépjen a PVM által biztosított virtuális gépbôl, valamint lehetôség van PVM-processzek egymás közti kommunikációjára, PVM-processzek úgynevezett PVM-processzcsoportokba kapcsolására, és biztosított egy-egy ilyen csoport összes tagja részére az üzenetküldés is, valamint egy-egy csoportba új tag felvétele és tagok törlése a csoportból.
Egy új PVM-processzt a pvm_spawn eljárással hozhatunk létre. Ennek alakja a következô:
int pvm_spawn(char *task, char **argv, int flag, char *where, int ntask, int *tids);
Az elsô argumentum (task) az elindítandó PVM-processzt tartalmazó végrehajtható fájl nevét adja meg; a második (argv) az átadandó program-argumentumokat tartalmazza; a harmadik paraméter kijelöli, hogy hol kell elindítani az új PVM-processzünket (pl. CPU-architektúrára tartalmazhat utalást, de gyakran a processzt indító számítógép CPU-ján kell az új PVM-processzt futtatni, ezért gyakori, hogy az ezt kijelölô PvmTaskDefault (0 értékű) konstans adják itt meg). Amennyiben az alkalmazásnak speciális igényei vannak az új PVM-processz elindításával kapcsolatban, akkor azt a flag argumentumban jelezheti, és ilyenkor kell a where argumentumban megadni azt, hogy (fizikailag) hol is akarjuk a programunkat elindítani. Az ntask paraméterben adhatjuk meg, hogy hány példányban akarjuk a PVM-processzt elindítani, majd a rendszer a tids tömbben adja vissza az elindított PVM-processzek azonosítóit (tid-jeit). A függvény visszatérési értéke a ténylegesen elindított új PVM-processzek száma.
Használatára példa:
numt = pvm_spawn( "progi1", NULL, PvmTaskHost, "macska", 1, &tids[0]);
Ami elindít a macska nevű hoston egy új PVM-processzt a progi1 nevű programból. Megjegyezzük, hogy a PvmTaskHost paraméter arra utal, hogy kijelöljük a negyedik argumentumban, hogymelyik hoston akarjuk elindítani az új PVM-processzt. Ha itt PvmTaskDefault értéket adtunk volna meg, akkor a PVM rendszer maga választhatott volna egy hostot, ahol a programot elindítja. A tids változó egy egészeket tartalmazó vektor.
Egy PVM-processz a pvm_exit eljárás végrehajtásával léphet ki a PVM felügyelete alól.
Egy PVM-processz a saját tid-jét ezzel kérdezheti le. A függvény visszatérési értéke az új PVM-processz tid-je. Nincs paramétere.
Egy PVM-processz a szülôjének a tud-jét kérdezheti le. Visszatérési értéke a szülôjének a tid-je. Nincs paramétere.
Lelövi azt a PVM-processzt, amelynek a tid-jét megadtuk az argumentumában.
A függvény segítségével egy adott signalt (UNIX signalt) küldhetünk valamelyik PVM-processznek. A függvény elsô paramétere a kívánt PVM-processz PVM tid-je (ez nem ugyanaz, mint a kill() UNIX rendszerhívás, ugyanis az nem képes pl. "hálózaton keresztül" más hoston futó processzeknek signal-t küldeni).
A függvény alakja a következô:
int pvm_notify(int about, int msgtag, int ntask, int *tids)
Hatása pedig az, hogy a késôbbiekben az about argumentumban specifikált esemény bekövetkeztekor egy olyan üzenetet küld a tids vektorban megadott tid-del azonosított PVM-processzeknek, amely üzenet msgtag része (ez az üzenetek egy komponensét jelöli) megegyezik a függvény második argumentumában megadott értékkel.
Az about paraméter lehet PvmTaskExit (ekkor az esemény egy PVM processz bvefejezôdése), PvmHostDelete (ekkor az esemény egy bekapcsolt host kiesése lesz), PvmHostAdd (ekkor az esemény egy új host PVM-be kapcsolása).
Az üzenetátadás során heterogén hálózatban (ahol több, különféle architekturájú host is elôfordulhat) felmerülhetnek adatok reprezentációjának eltérésébôl származó problémák (pl. a 2 vagy 4 byteos egész számoknál a reprezentáció bytesorrendje eltér például az i386 és Motorola 68000 architektúrákon -- ha pl. egy 4 byteos egész számot e két architektúra között kell átvinni, akkor a byteok átvitelén kívül gondoskodni kell a byteok sorrendjének megfelelô cseréjérôl is). A PVM rendszerben ezt a problémát úgy lehet megoldani, hogy az adatokat elküldés elôtt egy hálózati ("külsô", szabványosított) ábrázolási formára kell alakítani, az adat/üzenet fogadáskor pedig a fogadó állomás feladata lesz a hálózati ábrázolási formára történô adatkonverzió. A PVM rendszer hálózati adatábrázolásra a Sun XDR (eXternal Data Representation) ábrázolásmódját alkalmazza.
A PVM rendszerben üzenetküldés céljából több üzenet-buffert is allokálhatunk, és ezek közül kijelölhetjük, hogy melyik legyen az aktív küldési illetve aktív fogadási buffer: vagyis melyikbe akarunk adatokat bepakolni a bepakoló-függvényekkel (adatelküldés céljából) illetve melyikbôl akarunk adatokat kiolvasni (egy megkapott üzenetbôl). Megjegyezzük, hogy az adatbeolvasási és az adatküldési buffer megegyezhet.
Egy új üzenet-buffert a pvm_mkbuf függvénnyel hozhatunk létre. Ennek egyetlen argumentuma van (egy egész szám, amely a buffer tartalmának az összeállítási módját értelmezését/reprezentációját írja le), visszatérési értéke pedig (szintén egy egész szám) a létrehozott buffer egyértelmű azonosítóját tartalmazza.
A pvm_mkbuf argumentumának értéke a következô (makrókkal definiált konstans) értékek valamelyike lehet:
Ezzel az adatküldési buffert ki lehet üríteni, és ez egyben inicializálni is fogja azt. Ennek egyetlen argumentuma van: ugyanaz, mint az elôzôekben ismertetett pvm_mkbuf függvény argumentuma (ugyanazokat az értékeket veheti fel, és ugyanazt kell vele megadni).
Visszatérési értéke az egyedi buffer-azonosító.
Megjegyezzük, hogy ez a függvény meghívja a pvm_mkbuf függvényt egy új buffer létrehozására, és az újonnan létrehozott adatbuffert kijelöli új aktív adatküldési buffernek (annyivel tud többet).
Továbbá megjegyezzük, hogy ez a függvény felszabadítja az aktív küldési buffert a pvm_freebuf függvénnyel, így ez elôtt azt nem kell végrehajtani -- a pvm_initsend függvények ciklikus végrehajtása nem eredményezi a memória elfogyását.
Egyetlen argumentuma az eldobandó (továbbiakban már nem használt) üzenet-buffer egyedi azonosítója. A függvény eredményeként a paraméterben kijelölt buffer által lefoglalt memóriaterületeket a rendszer felszabadítja, visszarakja a memória szabad-listára.
Nincs argumentuma. Visszatérési értéke az aktuális (aktív) küldési buffer azonosítója.
Nincs argumentuma. Visszatérési értéke az aktuális (aktív) fogadási buffer azonosítója (ez az, amelyikbôl adatokat tudunk kiolvasni a késôbb bemutatásra kerülô adatbeviteli függvényekkel).
Meg lehet változtatni vele az aktív küldési buffert. Egyetlen argumentuma van, amely megadja az új aktív küldési buffer egyedi azonosítóját kell, hogy tartalmazza, és visszaadja a korábban aktív küldési buffer azonosítóját.
Meg lehet változtatni vele az aktív fogadási buffert. Egyetlen argumentuma van, amely megadja az új aktív fogadási buffer egyedi azonosítóját kell, hogy tartalmazza, és visszaadja a korábban aktív fogadási buffer azonosítóját.
Az adatok elküldése elôtt az elküldendô adatokat be kell pakolni a küldési bufferbe. Erre valók az alábbi függvények:
Az utolsó kivételével mindegyiket a következô paraméterekkel hívhatjuk:
int pvm_pkXXX( XXXtype *ptr, int nitems, int stride);
Ahol XXXtype típus a pvm_pkXXX-ben levô adattípusból származtatható. Az elküldendô adatokra az elsô argumentum mutat. A második argumentum adja meg, hogy az elsô argumentum által mutatott helyrôl hány adatelemet kell az aktuális küldési bufferbe átpakolni (a megfelelô adatábrázolási bufferbe). A stride argumentum értékben az adatpakolási lépéshosszt lehet megadni (ez az érték egyszerűen rendre hozzá lesz adva az elsô argumentumban megadott pointerhez (összesen nitems-szer a szokásos C pointer-aritmetikával!!), és az így érintett memóriacímeken levô adatokat pakolja be a küldési bufferbe).
A pvm_pkcplx függvény complex típusú adatot kezel, amelyet a PVM rendszerben deklaráltak egy rekord típusként, melynek két komponense van: r a valós, és f az imaginárius rész neve (mindkettô float típusú -- a dcplx esetén pedig double típusú).
A pvm_pkstr függvény prototípusa a következô:
int pvm_pkstr( char *str );
Megjegyezzük, hogy míg a Sun XDR lehetôséget nyújt rekord/unió típusú adat ábráolására, addig itt erre nincs automatikusan megoldás: ehelyett a struktúra elemeit nekünk kell egyenként feldolgoznunk -- a típusuknak megfelelô módon.
Ezeknek a függvényeknek az argumentumaik ugyanazok, mint az adatbepakoló párjuknál (de itt az elsô argumentum azt adja meg, hogy a kipakolt adatokat hová akarjuk tenni a memóriában). Ezek a függvények az aktív fogadási bufferbôl olvasnak adatokat.
A pvm_advise függvényrôl nem írok itt (ezzel lehet speciális kommunikációs útvonalat kiépíteni két PVM-processz között, mondjuk egy TCP csatornán, a hatékonyság növelése érdekében).
Egy művelettel ugyanazt az üzenetet több PVM processznek is elküldhetjük:
int pvm_mcast( int *tids, int ntask, int msgtag)
alakú az erre való művelet: itt az elsô argumentum egy egész (int) tömb azonosítója, amely a címzett PVM processzek azonosítóit tartalmazza; a benne levô taszkok számát pedig a második (ntask) paraméterben kell megadni.
A nem-blokkoló eljárás a következô alakú:
int pvm_nrecv( int tid, int msgtag)
Ahol tid arg-ban megadhatjuk, hogy kitôl várunk üzenetet; a msgtag-ban pedig megadhatjuk a várt üzenet osztályát. Bármelyik argumentumban megadhatunk -1 értéket, ami azt jelenti, hogy az argumentum értéke nem érdekel minket.
A blokkoló eljárás a következô alakú:
int pvm_nrecv( int tid, int msgtag)
Az argumentumai ugyanazok mint a nem-blokkoló párjánál.
Mindkét függvény visszatérési értéke a megkapott üzenet üzenet-bufferének az egyedi azonosítója, ha sikerült üzenetet olvasni. Ha nem volt beolvasandó üzenet, akkor 0-t ad vissza.
A hasonló paraméterezésű pvm_probe függvénnyel megnézhetjük, hogy érkezett-e adott küldôtôl adott osztálybeli üzenet; és ha igen, akkor visszakapjuk az üzenethez tartozó buffer azonosítóját, ahova a rendszer az üzenetet beolvassa. Az érkezô üzenetet bennmarad a "feldolgozatlan" üzenetek között (egy újabb hívása ugyanazt az üzenetet látja majd).
Egy üzenetbufferrôl (a benne tárolt üzenetrôl, osztálykódjáról, hosszáról) információkat a pvm_bufinfo függvénnyel nyerhetünk.
Egy PVM-processz a pvm_joingroup függvényhívással lehet tagja egy processz-csoportnak. A függvény egyetlen paramétere a csoport neve. A függvény visszatérési értéke egy csoporton belüli azonosító szám (instance id. - példány-azonosító).
Egy PVM-processz a pvm_lvgroup függvényhívással léphet ki egy csoportból. Egyetlen argumentuma a csoport neve, amibôl a PVM-processz ki akar lépni.
A fent említett példány-azonosító (instance id.) és a PVM processz-azonosítók (tid-ek) kapcsolatát a pvm_gettid és a pvm_getinst függvény teremti meg.
Egy csoport résztvevôinek a számát a pvm_gsize függvénnyel kérdezhetjük le: egyetlen argumentuma a csoport neve.
Egy csoport összes tagjának küldhetünk egy művelettel üzenetet a pvm_bcast függvénnyel. Ennek alakja a következô:
int pvm_bcast( char *group, int msgtag )
(itt az elsô argumentum a csoport neve; a második argumentum az üzenet típusának az azonosítója; visszatérési értéke negatív ha valami hiba történt.) Megjegyezzük, hogy a küldô nem kapja meg az így küldött üzenetet, ha tagja a megadott nevű csoportnak.
Egy csoport szinkronizálására szolgál a pvm_barrier függvény. Ennek az alakja a következô:
int pvm_barrier(char *group, int count)
Ahol az elsô a csoportnév; a második argumentum pedig egy szám. A függvény eredményeként a hívó PVM-processz blokkolni fog egészen addig, amíg (count) darab csoporttag meg nem hívja e függvényt. A többi processznek ugyanezekkel az argumentumokkal kell meghívnia e függvényt.
Ezzel például megoldható az, hogy egyszerre "induljanak el" egy processz-csoport tagjai: például tekintsünk egy olyan szolgáltatást, ahol a szolgáltatást 17 (ez a szám csak példa) darab PVM-processz nyújtja egy processz-csoportba szervezôdve. Azt szeretnénk, hogy a szolgáltatást nyújtó processzek ugyanabban az állapotban legyenek a működésük folyamán. Ehhez az is szükséges, hogy kezdetben már szinkronizálják az állapotukat, a hozzájuk érkezô üzeneteket mind megkapják ...
Megjegyzés: nem kötelezô, de ajánlott betartani a következôket: