Absztrakt programok megvalósításához a PVM szolgáltatásait használhatjuk. A PVM a Parallel Virtual Machine rövidítése, egy köztes réteg, amely az operációs rendszer és a felhasználó program között helyezkedik el. A PVM feladata, hogy programozási nyelvtől és operációs rendszertől független egységes felületet biztosítson elosztott programok komponenseinek együttműködéséhez. A PVM szolgáltatásait egy függvénykönyvtáron keresztül vehetjük igénybe. A könyvtár leglényegesebb elemei a pvm_mytid, a pvm_send, a pvm_recv, a pvm_spawn függvények, amelyek segítségével egy folyamat bejelentkezhet a PVM rendszerbe, üzenetet küldhet és fogadhat, illetve folymatot indíthat. A PVM használatához ismernünk kell azt a felületet is, amelyet az operációs rendszer nyújt a programok fordításához, összeszerkesztéséhez, futtatásához. A futtatás előtt össze kell állítanunk azon számítógépek halmazát, amelyen elosztott programunk működni fog. Az alábbiakban a Linux operációs rendszerre jellemző parancsokat mutatjuk be. Először létre kell hoznunk a pvm3/bin/LiNUX könyvtárat, ahol a PVM rendszer a futtatható állományokat keresi. A futtatható programot az aimk program segítségével állíthatjuk elő egy megfelelő Makefile alapján. Helyezzük el a forrászöveget és a make file-t egy alkönyvtárban majd adjuk ki az aimk parancsot. A futtatható állományra mutató hivatkozásokat helyezzük el a pvm3/bin/LiNUX könyvtárba. Indítsuk el a PVM konzolt a pvm paranccsalA pvm -nlocalhost paranccsal indíthatjuk el a konzolt, ha nincs hálózati összeköttetés más számítógépekkel. , majd bővítsük az igénybe vett számítógépek halmazát az add számítógépnév paranccsal. Végül futtassuk a programot a spawn -> programnév utasítással. A konzolt a halt utasítással állíthatjuk le.
pvm3/src/hello: hello.c, hello\_other.c, Makefile.aimk
aimk
aimk links
pvm
pvm> spawn -> hello
pvm> add nyl35
pvm> conf
pvm> halt
Példaként bemutatunk egy egyszerű C nyelvű programot, amely PVM egy printf függvényhívásban bejelentkezik a PVM rendszerbe és kiírja a saját folyamatazonosítóját a képernyőre, majd elindít egy másik folyamatot (hello_other) és üzenetet fogad tőle pvm_recv. A kapott üzenetet egész számként értelmezve kicsomagolja és elhelyezi a num változóban, majd kiírja a képernyőre.
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;
}
Az elindított gyernekfolyamat is bejelentkezik a PVM rendszerbe a pvm_mytid hívással, majd azonosítva azt őt indító folyamatot (pvm_parent) üzenetként elküldi ennek a folyamatnak az ő szonosítóját. Az üzenetküldés három lépésből áll, az üzenetküldő puffer inicializálásából (pvm_initsend), az üzenet becsaomagolásából (pvm_pkint) és magából az üzenetküldésből (pvm_send).
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;
}
Második példánk az asszociatív művelet eredményét
kiszámító absztarkt program egy lehetséges C/PVM megvalósítása.
A példaprogramban az adatok egész számok, a művelet az
összeadás. Az absztrakt program közvetlenül hivatkozhat a
mátrix elemeire, osztott változókat használ. PVM-ben ez nem lehetséges,
a folyamatok csak üzenetek útján cserélhetnek információt. Az
alábbi megvalósítás a . ábra minden oszlopához egy-egy folyamatot
rendel hozzá, amelyik rendre kiszámítja az oszlop elemeit felülről
lefelé. A következő elem kiszámításához mindig szükség van az
előző elemre és egy másik oszlopból (egy másik folyamattól)
egy további elemre, ha azt már meghatározták. Adatvezérelt
megoldást készítünk, azaz nem a szükséges adatok elkérésére
kerül sor, hanem az elkészült részeredmények kérés nélkül
jutnak el azokhoz a folyamatokhoz, amelyeknek szükségük van rá. Az
számítást végző folyamatok elején egész számok küldését és
fogadását megkönnyítő segédfüggvények találhatóak. Az
.
folyamat első lépésben saját és a többi folyamat azonosítóját
kapja meg az őt indító szülő folyamattól, majd ciklusban küld
részeredményket és fogad adatokat. Végül kiiírja az első
szám összegét.
assoc.c:
#include <stdio.h>
#include <stdlib.h>
#include "pvm3.h"
void sendInt( int to, int mit ){
pvm_initsend(PvmDataDefault);
pvm_pkint(&mit,1,1);
pvm_send(to,0);
}
int recvInt( int from ){
int data;
pvm_recv(from,0);
pvm_upkint(&data,1,1);
return data;
}
void main(){
int tasknum;
int *tids;
int id;
int data;
int t = 1;
pvm_mytid();
pvm_recv(pvm_parent(),0);
pvm_upkint(&tasknum,1,1);
tids = (int *)malloc(1+tasknum*sizeof(int));
pvm_upkint(&tids[1],tasknum,1);
pvm_upkint(&id,1,1);
pvm_upkint(&data,1,1);
while ( ( id+t <= tasknum ) || ( id-t >= 1 ) ) {
if ( id-t >= 1 )
sendInt(tids[id-t],data);
if ( id+t <= tasknum )
data += recvInt(tids[id+t]);
t<<=1;
}
printf("partial sum[%d..%d] =\t%d\n",id,tasknum,data);
pvm_exit();
}
A főprogram a parancssorból olvassa be az
vektor elemeit, majd a vektor méretének megfelelő számú folyamatot
indít. Egy for ciklusban minden gyermekfolyamatot inicializál, elküldve
annak saját és a többi folyamat azonosítóját, ill. a vektor megfelelő
elemét.
#include <stdio.h>
#include <stdlib.h>
#include "pvm3.h"
void main( int argc, char *argv[] ){
int tasknum = argc-1;
if (tasknum>0) {
int i;
int *tids = (int *)malloc(1+tasknum*sizeof(int));
pvm_mytid();
pvm_spawn("assoc",(char **)NULL,
"",tasknum,&tids[1]);
for (i=1;i<=tasknum;i++){
int data = atoi(argv[i]);
pvm_initsend(PvmDataDefault);
pvm_pkint(&tasknum,1,1);
pvm_pkint(&tids[1],tasknum,1);
pvm_pkint(&i,1,1);
pvm_pkint(&data,1,1);
pvm_send(tids[i],0);
}
pvm_exit();
} else fprintf(stderr,
"The numbers are given in the command line!\n");
}