A PVM (Parallel Virtual Machine) egy szoftverrendszer, amellyel hálózatba kapcsolt (akár különböző típusú/architektúrájú) számítógépeket egyetlen nagy párhuzamos számítógépként lehet kezelni és vezérelni.
Viszonylag régi technológia (1989-2009), de az elosztott számítások implementálásának egy kiforrott modellje, amelyen keresztül gyorsan és könnyen megértheted és elsajátíthatod a kapcsolódó alapfogalmakat. C, C++ és Fortran nyelven is írhatsz olyan programokat, amik a PVM-re épülnek.
Az általad írt C++ program elosztott futtatását egy háttérrendszer biztosítja és koordinálja, amelynek része egy függvénykönyvtár, a démon és a konzol.
A PVM könyvtár (PVM programming library, libpvm3) függvényei biztosítják a párhuzamos/elosztott programok készítéséhez szükséges folyamatkezelési és kommunikációs programozási interfészt (pl. folyamat indítása, üzenetküldés, üzenetfogadás).
A PVM démon (PVM daemon, pvmd) az elosztott számításban részt vevő számítógépeken (nódusokon) futtatott vezérlő alkalmazás, amely gondoskodik a virtuális számítógép koordinálásáról, továbbá az adott nóduson indított folyamatok életciklusának és kommunikációjának kezeléséről.
A PVM konzol (PVM console, pvm) a párhuzamos virtuális számítógép konzolos interfésze.
Amikor a PVM-ben összekapcsolod a különböző számítási egységeket, akkor létrehozol egy nagy kapacitású, elosztott memóriával rendelkező virtuális számítógépet. A mi esetünkben például az atlasz klaszter hpc2005
partíciójába eső számítógépeket összekapcsolva egy 48db 2.2GHz-es processzormaggal és 48 GB memóriával rendelkező szuperszámítógépet kapunk.
Erre a számítógépre tudsz párhuzamos programokat írni úgy, hogy a folyamatok közötti szinkronizációt és kommunikációt a PVM kivitelezi helyetted, és a program valójában több számítógépen, elosztottan fut, a kommunikáció pedig Gigabit Etherneten keresztül zajlik.
A párhuzamos virtuális gép létrehozásához az összes csomópontban el kell indítani a PVM démont és össze kell őket kapcsolni, de ezzel nem lesz sok munkád, szinte automatikusan megy az egész.
Az a klaszter, amin mi dolgozunk az ELTE-n, a következő fejgépből és 12 dolgozóból áll:
atlasz.elte.hu
) a fejgép, ezen számításokat nem futtatunk, csak a koordinációs kód fut rajtablade01
, blade02
, ..., blade12
) a tényleges számításokat végző gépek, a szuperszámítógép számító nódusai
- A PVM démon a fejgépen (atlasz) automatikusan elindul, amikor a PVM konzolba belépsz. Ekkor csak ebből a gépből álló virtuális számítógép jön létre.
- A számító nódusokon automatikusan elindul a démon, amikor hozzáadod őket a konfigurációhoz az "add" parancs használatával. Ilyenkor ezek is a virtuális számítógép részeivé válnak.
A home
könyvtárad egyformán látszik minden nóduson, így bárhol is induljon el egy taszk, kezelni tudod belőle a fájljaidat.
A PVM az összes kari laborban elérhető Linux alatt, tehát a fejlesztést, funkcionális tesztelést a laboros gépeken is végezheted (illetve a saját gépedre is feltelepítheted a PVM-et). Fontos azonban, hogy az elosztottan futó szoftver éles tesztelését az ELTE atlasz klaszteren kell elvégezni, hogy megfigyelhető legyen a valódi elosztott viselkedés és a teljesítménynövekedés.
A PVM programod szabványos C/C++ nyelven íródik, közönséges UNIX folyamatként indul, de amikor átkerül a virtuális gépre, PVM taszk lesz belőle, és újabb taszkokat indíthat, amelyekkel kommunikálhat is. Természetesen több taszk is indítható, ettől válik párhuzamossá a program.
Az újonnan indított taszkok a PVM által koordinált sorrendben és helyen indulnak el, s ha másképp nem rendelkezünk, a PVM gondoskodik arról, hogy a virtuális számítógép minden fizikai csomópontjában arányos legyen a terhelés. Amikor a taszkok különböző számítógépeken indulnak el, elosztottá válik a program.
A taszk tehát egy folyamat (process), amely a PVM könyvtár segítéségével a PVM démont vezérelve tud újabb taszkokat indítani és azokkal kommunikálni.
A nagyszerű az egészben az, hogy a programozónak nem kell foglalkoznia azzal, melyik taszk melyik csomópontban fut, mivel a PVM egy nagy számítógépként kezeli az elosztott rendszert. Párhuzamos programot írunk, de elosztottan is futtathatjuk.
Ahhoz, hogy PVM programokat fordíts és futtass, a következő könyvtárszerkezetet érdemes létrehoznod a home
könyvtárad alatt (pl. /users/megfel_elek):
.
|
--- pvm3
|
--- src
|
--- bin
Segítségül, a következő parancs kiadása létrehozza ezeket a könyvtárakat a megfelelő helyen:
$ cd && mkdir -p pvm3/src pvm3/bin
A forrásfájlokat a pvm3/src
könyvtárba (vagy annak almappájába) kell majd elhelyezni, továbbá a forrásfájlok mellé kell másolni a Makefile.aimk
állományt is, amely innen letölthető.
A pvm3/bin
könyvtárba kerülnek azok a programok (futtatható állományok), amelyeket taszkként fog majd elindítani a PVM a virtuális gépen.
Amikor a programod fordítod, az src
könyvtárba navigálsz és ott adod ki a fordítási és linkelési parancsokat.
Az aimk
parancs hatására a fordítást vezérlő program elolvassa a Makefile.aimk
állományt és a paramétereknek megfelelően lefordítja a PVM programod.
$ aimk
Az
aimk
egy olyan program, amely amake
-re építve segít neked minél egyszerűbben helyesen lefordítani a PVM programod: a platformhoz és szoftverkörnyezethez igazodva készít futtatható állományt a forráskódból.Amikor új PVM programot fejlesztesz, csak át kell írnod a meglévő Makefile-ban a fordítandó program nevét (a fájlnév a
c
vagycc
kiterjesztés nélkül), például:BINS = forraskod1 forraskod2 forraskod3
A PVM programot a szokásos módon futtathatjuk, mint executable fájlt, vagy taszkként is elindíthatjuk a PVM-ből. Szem előtt kell tartanunk, hogy a programunk újabb taszkokat is fog indítani, amelyekhez tartozó programokat a PVM démonnak meg kell majd találnia. Alapértelmezett beállítások mellett a pvm3/bin/LINUX64
könyvtárban keresi a PVM a futtatható állományokat. Mivel mi nem ebben a könyvtárban dolgozunk, hanem az src alatt, a lefordított fájlokra szimbolikus linkeket (parancsikonokat) kell készíteni a bin könyvtárban.
$ aimk links
Az
aimk links
parancsot csak egyszer kell kiadnod, amikor elkezdesz dolgozni a programodon. Mindaddig amíg ugyanazokat a forráskódokat szerkeszted/fordítod, nem kell újabb linkeket létrehoznod.
A futtatás tehát történhet a szokásos shellből történő programindítással:
$ ~/pvm3/bin/LINUX64/hello
vagy PVM konzolból taszkindítással (a spawn parancs használatával):
pvm> spawn -> hello
A párhuzamos programunk két különálló C programból áll (hello
és hello_other
). Futtatás során ezek kapcsolatba lépnek: a hello
fogja elindítani a hello_other
programot PVM-en keresztül, és üzenetet is váltanak egymással.
hello.c
#include <stdio.h>
#include "pvm3.h"
int main() {
int tid;
int num;
printf("i'm t%x\n", pvm_mytid());
pvm_spawn("hello_other", (char**)0, 0, "", 1, &tid);
pvm_recv(-1, -1);
pvm_upkint(&num,1,1);
printf("from t%x: %d\n", tid, num);
pvm_exit();
return 0;
}
hello_other.c
#include "pvm3.h"
int main() {
int tid = pvm_mytid();
int ptid = pvm_parent();
pvm_initsend(PvmDataDefault);
pvm_pkint(&tid,1,1);
pvm_send(ptid, 1);
pvm_exit();
return 0;
}
Miben különböznek ezek egy hagyományos C/C++ kódtól?
pvm3.h
fejállományt illeszti be.pvm_*
nevű függvényre vonatkozik (ezek a PVM könyvtár hívásai). Ezeket a következőkben részletesen tárgyaljuk.A hello
program elindulás után lekérdezi a taszk-azonosítóját (ezzel regisztrálja magát a virtuális gépbe), majd elindítja a hello_other
programot, mint új taszkot. Ezek után egy üzenetre vár az új taszktól, amelyet egy int
típusú változóba tölt be, majd kiírja a kimenetre a másik taszk azonosítójával együtt, végül kilép a virtuális gépből és terminál.
A másik program, a hello_other
indulásakor lekérdezi a maga, illetve a szülője taszk azonosítóját, elküldi a hálózaton a szülőnek (hello
) az azonosítót, majd kilép a virtuális gépből és terminál.
$ aimk
making in LINUX64/ for LINUX64
cc -W -Wall -I/usr/lib/pvm3/include [...] -o hello ../hello.c -L/usr/lib/pvm3/lib/LINUX64 -lpvm3
cc -W -Wall -I/usr/lib/pvm3/include [...] -o hello_other ../hello_other.c -L/usr/lib/pvm3/lib/LINUX64 -lpvm3
$ aimk links
making in LINUX64/ for LINUX64
cd /users/daniel-h/pvm3/bin/LINUX64
ln -sf /users/daniel-h/pvm3/src/LINUX64/hello hello
ln -sf /users/daniel-h/pvm3/src/LINUX64/hello_other hello_other
PVM démon és konzol indítása:
$ pvm
A program taszkként való futtatása:
pvm> spawn -> hello
spawn -> hello
[1]
1 successful
t40002
pvm> [1:t40002] i'm t40002
[1:t40002] from t40003: 262147
[1:t40003] EOF
[1:t40002] EOF
[1] finished
pvm>
A konzolt a pvm
paranccsal indíthatod el, amely egyúttal a démont is elindítja. A konzol ezután a parancsaidra vár.
$ pvm
pvm>
A virtuális számítógéphez az add
paranccsal lehet újabb számító nódusokat hozzákapcsolni.
pvm> add blade01 blade02 blade03
add blade01 blade02 blade03
3 successful
HOST DTID
blade01 80000
blade02 c0000
blade03 100000
pvm>
A virtuális számítógép pillanatnyi felépítését (konfigurációját) a conf
paranccsal lehet lekérdezni.
pvm> conf
conf
4 hosts, 1 data format
HOST DTID ARCH SPEED DSIG
atlasz 40000 LINUX64 1 0x00408c41
blade01 80000 LINUX64 1000000 0x00408c41
blade02 c0000 LINUX64 1000000 0x00408c41
blade03 100000 LINUX64 1000000 0x00408c41
pvm>
Nódus törlésére is van lehetőség, erre a delete
parancs használható.
pvm> delete blade03
delete blade03
1 successful
HOST STATUS
blade03 deleted
Az Atlasz klaszteren hostfile használata nem javasolt; a PVM lehetőséget ad arra, hogy a konzolt egy parancssori argumentummal indítsuk, amelyben felsoroljuk azokat a hosztokat, amelyeket indításkor fel szeretnénk venni a virtuális gépbe. Szeretnénk azonban felhívni a figyelmed, hogy amennyiben hosztfájlt használsz az indításkor, nem jól konfigurálódik a virtuális gép: alapértelmezetten a fejgép ugyanolyan súlyt kap, mint a blade-ek. Tegyük fel, hogy a hosts
tartalma a 12 blade neve egymás után felsorolva.
$ pvm hosts
pvm> conf
conf
13 hosts, 1 data format
HOST DTID ARCH SPEED DSIG
atlasz 40000 LINUX64 1000 0x00408c41
blade01 80000 LINUX64 1000 0x00408c41
blade02 c0000 LINUX64 1000 0x00408c41
blade03 100000 LINUX64 1000 0x00408c41
...
blade12 340000 LINUX64 1000 0x00408c41
Nem javasoljuk hostfile használatát, mert amint látható, a fejgéphez és a számító nódusokhoz tartozó sebesség (SPEED) attribútum megegyezik (1000), így a PVM a fejgépen is megpróbálna taszkokat indítani, hogy a taszkok eloszlása a fizikai gépeken egyenletes legyen. Ám hallgatóként maximum 20 folyamatot indíthatsz el a fejgépen, s a legtöbb esetben abnormálisan terminál a programod a processz limit elérése miatt, ellehetetlenítve a megfelelően alapos tesztelést.
Készíthetsz egy hosztfájlhoz hasonló fájlt, amibe PVM konzolparancsokat írsz, majd ezt a fájlt a pvm
bemenetére irányítod a linux shell segítségével.
Legyen az add_hosts
fájl tartalma a következő parancs:
add blade01 blade02 blade03 blade04 blade05 blade06 blade07 blade08 blade09 blade10 blade11 blade12
Majd indítsuk a PVM-et úgy, hogy a fájl tartalmát a PVM konzolra irányítjuk.
$ pvm < add_hosts
pvm> 12 successful
HOST DTID
blade01 80000
blade02 c0000
blade03 100000
...
blade12 340000
pvm>
A nódusok rendben hozzáadódtak a virtuális géphez, de ami ennél is fontosabb, hogy a speed
értékeik is jól vannak beállítva:
pvm> conf
conf
13 hosts, 1 data format
HOST DTID ARCH SPEED DSIG
atlasz 40000 LINUX64 1 0x00408c41
blade01 80000 LINUX64 1000000 0x00408c41
blade02 c0000 LINUX64 1000000 0x00408c41
blade03 100000 LINUX64 1000000 0x00408c41
...
blade12 340000 LINUX64 1000000 0x00408c41
Taszkokat indítani a spawn
parancs segítségével lehet.
pvm> spawn -> program
A nyíl azért kell, hogy a program kimenetét a konzolra irányítsuk.
Taszkindításkor megadhatjuk azt is, hogy hol történjen (melyik nóduson), de általában nem szoktuk (hagyjuk, hogy a PVM döntsön).
a ps
kilistázza az épp futó taszkokat
a kill
segítségével kilőhetsz (terminálhatsz) taszkokat az azonosítójuk alapján
a reset
parancs azonnal terminálja az összes futó taszkot és "újraindítja" a virtuális gépet
Kétféleképp lehet a konzolból kilépni:
csak a konzolt terminálja, a démonok továbbra is futnak (fejlesztés közben ezt érdemes használni
a konzolt és a démonokat is terminálja, leállítja a virtuális gépet
Munkád befejeztével kijelentkezés előtt minden esetben lépj vissza a PVM konzolba és termináld a virtuális gépet a
halt
parancs segítségével!
A zárthelyin is használható dokumentum a pvmhelp.txt, amelyben megtalálod a legfontosabb függvényeket, paraméterezésükkel együtt.
Külső forrás: összefoglaló - Párhuzamos algoritmusok
int pvm_mytid()
a futtató taszk azonosítójának lekérdezése
int pvm_parent()
a szülő taszk azonosítójának lekérdezése
int pvm_siblings(int **)
a szülő taszk által indított összes taszk azonosítói
int pvm_spawn (char *, char **, int, char *, int, int *)
új taszk indítása
int pvm_kill(int)
taszk kilövése
int pvm_exit()
folyamat kivonása a PVM virtuális gépéből
int pvm_initsend(int)
üzenetpuffer kiürítése, encoding beállítása, felkészülés új üzenet összeállítására
int pvm_pk*(...)
Részletesebben:
int pvm_pkbyte (char * , int, int) int pvm_pkcplx (float * , int, int) int pvm_pkdcplx (double * , int, int) int pvm_pkdouble(double * , int, int) int pvm_pkfloat (float * , int, int) int pvm_pkint (int * , int, int) int pvm_pklong (long * , int, int) int pvm_pkshort (short * , int, int) int pvm_pkstr (char * ) int pvm_pkuint (unsigned int * , int, int) int pvm_pkulong (unsigned long * , int, int) int pvm_pkushort(unsigned short *, int, int)
int pvm_send(int, int)
Példa
pvm_initsend(PvmDataDefault);
pvm_pkint(array, 10, 1);
pvm_send(tid, 0);
Tipikus hiba az adatok szétdarabolása. Az összetartozó adatokat (pl. vektor, mátrix elemei) egy üzenetbe csomagold be ismételt pvm_pk* hívásokkal, és a teljes adatszerkezetet egyben küldd el!
int pvm_recv(int, int)
(létezik belőle nem blokkoló változat is: pvm_nrecv
)
int pvm_upk*(...)
adott típusú adat kicsomagolása az üzenetből
int pvm_upkbyte (char * , int, int) int pvm_upkcplx (float * , int, int) int pvm_upkdcplx (double * , int, int) int pvm_upkdouble(double * , int, int) int pvm_upkfloat (float * , int, int) int pvm_upkint (int * , int, int) int pvm_upklong (long * , int, int) int pvm_upkshort (short * , int, int) int pvm_upkstr (char * ) int pvm_upkuint (unsigned int * , int, int) int pvm_upkulong (unsigned long * , int, int) int pvm_upkushort(unsigned short *, int, int)
Példa
pvm_recv(tid, msgtag);
pvm_upkint(tid_array, 10, 1);
pvm_upkint(problem_size, 1, 1);
pvm_upkfloat(input_array, 100, 1);
További hasznos linkek:
Gyakorlatvezetők weboldalai: