PVM¶
A gyakorlat anyaga¶
PVM (Parallel Virtual Machine) nem egy önálló nyelv, hanem egy alapnyelvre (pl. C) épített eljáráskönyvtár. Tulajdonképpen a PVM egy szoftverrendszer, amellyel hálózatba kapcsolt számítógépeket egyetlen nagy párhuzamos számítógépként lehet látni, és kezelni. A rendszerbe kapcsolhatunk több - akár különböző típusú - számítógépet. A rendszer a rajta futó programok szempontjából ezek után egy nagy, elosztott memóriájú virtuális számítógépnek látszik: különböző processzorokon, különböző programokat indíthatunk el, melyek közötti szinkronizációt, és kommunikációt a PVM könyvtári függvényekkel oldhatjuk meg.
A folyamatok a PVM terminológiában a task elnevezést kapták. Az elindított feladatok a szülő folyamattól és egymástól függetlenül, aszinkron módon futnak. Minden feladat egy egyedi feladatazonosítót kap. Az üzenetküldés közvetlen, nincsenek csatornák, a küldő és a fogadó utasítások a feladatazonosítót használják címzésre. A kommunikáció aszinkron módon zajlik, tehát a küldő nem várja be a fogadót, hanem az üzenet feladása után folytatja a futását. Az üzenetekkel kapcsolatban viszont biztosított, hogy sorrendjük megőrződik.
Végezetül fontos kitérni arra is, hogy esetenként a PVM program debug-olása nehézeskes lehet, hiszen az elosztott megvalósítás miatt az adott számítógépen futó program nem kap információt mi az oka a késlekedésnek (azaz várakozik egy adat fogadására, ami sosem fog megérkezni). A szintaktikai ellenőrzést a fordító elvégzi, a szemantikai hibák felderítése viszont minden esetben a programozó feladata.
PVM daemon¶
Egy számítógép azáltal kerül be a PVM rendszerbe, hogy rajta elindítjuk a PVM daemon szolgáltatását. Az adott gépen futó feladatok a lokális daemon-nal tartják a kapcsolatot, a daemon-ok pedig egymással kommunikálva hozzák létre a virtuális gépet. A rendszer kipróbálásához egy gép is elég, sőt teszteléshez leginkább ez ajánlott, így mi is hasonlóképp fogunk eljárni. A következő utasításokat fogjuk használni:
-
pmv
: elindítja a PVM deamon-t -
quit
: kilép a pvm deamon-ból (de a háttérben továbbra is futtatja) -
halt
: kilép a pvm deamon-ból (és a háttérben is bezárja)
Forrásfájlok fordításának menete: gcc -o program program.c -lpvm3
, majd futtatása: ./program
Figyeljünk arra, hogy a "server" és a "client" fájlt külön-külön le kell fordtanunk, illetve a spawn függvényben (lásd később) a megfelelő abszolút elérési útvonalat adjuk meg.
Kommunikációért és feladatkezelésért felelős függvények¶
A PVM függvények mind pvm_xxx
alakúak:
int pvm_mytid(void)
: saját feladat azonosítóint pvm_parent(void)
: Szülő feladat azonosító-
int pvm_exit(void)
: kilépés a PVM rendszerből -
int pvm_spawn(char* task, char** argv, int flag, char* where, int ntask, int* tids)
: feladat(ok) indítása. A hívó feladat az újonnan indított(ak) szülője lesztask
: Az inditandó feladat(ok) végrehajtható programjának elérési útja a fájlrendszerbenargv
: parancssori argumentumokflag
: meghatározza a következő paraméter értelmezését. PvmTaskDefault esetén a rendszer maga választja ki az indítandó feladatok helyétwhere
: itt adhatjuk meg azt a hosztgépet (amennyiben az előző paraméter PvmTaskHost-tal lett inicializálva), amelyen a feladatok indulnakntask
:feladatpéldányok számatids
:ntask
méretű tömb első elemére mutató pointer. Ide kerülnek az elindított feladatok azonosítói, hogy késöbb tudjunk rájuk hivatkozni
PVM-ben az üzenetküldés három lépésből áll, ezek: inicializálás, becsomagolás, tényleges küldés.
-
int pvm_initsend(int encoding)
: inicializálja az üzenetküldési buffer-t (azaz egy új üzenetet kezd el építeni). Az encoding-nak tipikusan akkor van szerepe, amikor több, különböző architektúrájú gép kommunikál, tehát az adatot olyan formába alakítani, amit az egyik gép elküldhet, a másik pedig megérthet. A PvmDataDefault egy szabványos, platformfüggetlen formátum, így bármilyen gép tudja fogadni az adatokat. PvmDataRaw csak azonos architektúra esetén ajánlott, mivel nincs adatkonverzió. -
int pvm_pk*( ...)
függvénycsalád szolgál a különböző alaptípusok becsomagolására. Ez minden esetben egy adott típusú tömböt csomagol be, ezért az nitem mondja meg, hogy mennyi elemet szeretnék becsomagolni a tömbből, a stride mondja meg, hogy mekkora lépésközzel vegye az elemeket a memóriából, míg a value a tömb első elemére mutató pointer:pvm_pkbyte(char* value, int nitem, int stride)
pvm_pkdouble(double* value, int nitem, int stride)
pvm_pkfloat(float* value, int nitem, int stride)
pvm_pkint(int* value, int nitem, int stride)
pvm_pklong(long* value, int nitem, int stride)
pvm_pkstr(char * )
-
int pvm_send( int tid, int msgtag)
: az aktív küldési pufferben található előkészített üzenetet a megadott címkével ellátva elküldi. A fogadóra nem vár, azonnal visszatér, amint a lokális deamon átvette az üzenetet. Atid
azonosító feladatnak elküldimsgtag
címkével. -
int pvm_mcast(int* tids, int ntask, int msgtag)
: az ntask méretű tids tömbben megadott azonosítójú összes taszknak elküldi az üzenetet. Ezt a módszert üzenetszórásnak (multicast) nevezzük.
Az üzenetfogadás első lépésben tulajdonképpen azt jelenti, hogy a levelesládából egy már beérkezett és ott eltárolt üzenetet kiveszünk feldolgozásra.
-
int pvm_recv( int tid, int msgtag)
: ennek a függvénynek a meghívása azt eredményezi, hogy a feladat addig várakozik, amíg egy a feltételeknek megfelelő üzenet nem érkezik.A feltételeket a két paraméter jelöli ki, atid
a küldő feladatok, amsgtag
pedig a címkék közötti szűrésre alkalmas. (-1,-1) paraméterekkel meghívva azt jelenti, hogy bármit elfogadunk bárhonnan. -
int pvm_upk*( ...)
: a kicsomagoló függvények, a megfelelőpvm_pk*
függvények párjai
Összességében elmondható, hogy a PVM függvényei támogatják a heterogén rendszerek egységbe szervezését, azonban az egyszerűség kedvéért mi lokálisan fogjuk futtatni a saját gépünkön, ez sokat könnyít a PVM függvényeinek paraméterezésein is. A gyakorlaton vett minden feladat kettő, párban álló fájlban lesz megtalálható. Az egyik fájl mindig egy szerver-szerű működést fog leírni, míg a másik egy kliens-szerű működést, de persze tetszőleges hálózati topológia leírható, ahogyan az a következő ábrán látható. Azaz mivel tudjuk, hogy PVM-ben üzenetküldés a taszkazonósító segítségével történik, így, amennyiben egy kliens ismeri egy másik kliens azonosítóját, a kliens-kliens üzenetváltás sincs kizárva, illetve az is megenegedett, hogy egy "kliens" hozzon létre újabb taskot a spawn sehítségével.
Gyakorlati példák¶
A következő kódrészlet egy szokásos Hello World megvalósítását mutatja be a PVM programok tipikus szerver-kliens struktúrájával. Gyakorlásnak indítsunk több kliens taszkot (több taszk spawn-olásával vagy akár külön fájlokban definiálva az egyes működéseket).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
|
A leggyakoribb hiba PVM programok esetén, hogy az adatküldések és fogadások száma nem egyezik meg, ezért ezeket érdemes külön függvényekbe kiszervezni, ahogyan az alábbi master-slave megvalósítás esetén is tesszük:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
|
Csővezetékes rendezés¶
A PVM lezárásaként megnézzük, hogyan tudjuk az Occam-ban is láttott ún. csővezetékes rendezést implementálni a PVM függvénykönyvtár segítségével. Fontos kiemelni mégegyszer azt, hogy a PVM asszinkron módon kommunikál, így a csővezeték egyes fázisai a saját ütemében, egymástól függetlenül fog haladni. A program helyes működése azon múlik, hogy a PVM garantálja, hogy az elküldött üzenetek sorrendje nem változik meg.
Első lépésként a csővezeték egyes komponeneseinek ismerniük kell azt a szomszédját, amelynek elküldi majd az adatot. Először tehát elküldjük minden feladatnak azt az azonosítót, hogy ő kinek fogja továbbítani. Értelemszerűen ha N db számot szeretnék rendezni, akkor N db feladatra lesz szükség. Az utolsó processz majd a szervernek küldi vissza mindig az adott számot.
Ezt követően az összes számot elküldjük az első feladatnak. Végezetül a szerver már a rendezett listát fogja megkapni, amit ki is iratunk. Az egyszerűség kevéért ismételten haszálni fogjuk a küldéshez és a fogadáshoz a sendint
-receiveint
függvényeket.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
|
Gyakorló feladat¶
Készíts egy olyan PVM programot, amely egy double értékeket tároló tömb elemein végez el hatványozást a következő módon:
-
a szerver bontsa fel a tömböt egyenlő részekre, majd ezeket az intervallumokat küldje el (egyesével vagy egyben) külön kliens taszkoknak
-
ezt követően üzenetszórás segítségével minden kliensnek küldjön el egy értéket, a kliensek pedig végezzék el a hatványozást a résztömb elemein
-
végül a kliensek küldjék vissza a módosított résztömböt, amit aztán a szerver ki tud írni a képernyőre
Szükség esetén küldhetsz extra információt a klienseknek (pl. a résztömb méretét).