Chapter 6 Compléments PVM
Dans ce chapitre, on présente quelques autres fonctionnalités avancées
de PVM, en particulier l'organisation en ``grappes'' de processus, le contrôle
dynamique de la machine virtuelle PVM, les signaux distribués et les
mécanismes de tolérance aux pannes.
6.1 Les groupes de processus
Un groupe est un
ensemble de processus,
- auquel on peut ajouter ou enlever dynamiquement de nouveaux membres
(pvm_joingroup, pvm_lvgroup),
- dans lequel on peut gérer des processus individuels (pvm_getinst, pvm_gettid),
- dont on peut synchroniser tous les membres (pvm_barrier),
- auxquels on peut ``broadcaster'' un message (pvm_bcast)
ou répartir un ensemble de données (pvm_scatter, etc.),
- qui peuvent exécuter une même opération sur leurs données
(pvm_reduce).
Il y a un intérêt particulier dans le cas d'une utilisation de XPVM (interface
graphique de PVM):
XPVM est un processus PVM, donc on ne peut plus
distinguer la racine des processus utilisateurs par PvmNoParent.
Une solution est de mettre les processus utilisateurs dans
un groupe ``utilisateur''.
6.1.1 pvm_joingroup
Cette instruction permet d'ajouter la tâche qui a appelé pvm_joingroup
au groupe qui a pour nom la chaîne de caractères group:
int inum;
inum=pvm_joingroup(char *group);
Elle renvoie son numéro ``d'instance'' dans le groupe. C'est un numéro
d'ordre entier commençant à 0 et s'incrémentant à chaque nouveau
membre.
Il y a un cas d'Erreur si inum < 0.
6.1.2 pvm_lvgroup
Cette instruction retire le processus appelant du groupe group:
int info;
info=pvm_lvgroup(char *group);
Il y a un cas d'Erreur si info < 0.
6.1.3 pvm_gsize
Cette instruction retourne le nombre de membres (à l'instant de l'exécution!)
du groupe group:
int size;
size=pvm_gsize(char *group);
Il y a un cas d'Erreur si info < 0.
6.1.4 pvm_gettid
Cette instruction renvoie l'identificateur de tâche du processus qui a pour numéro
d'ordre inum dans le groupe group:
int tid;
tid=pvm_gettid(char *group,int inum);
Cela permet ainsi d'utiliser les primitives d'envoi et de réception
de messages qui ont besoin de l'identificateur de tâche de l'expéditeur
ou du destinataire.
Il y a un cas d'Erreur si tid < 0.
6.1.5 pvm_getinst
C'est le contraire de pvm_gettid: renvoie le numéro d'ordre
dans le groupe group correspondant à l'identificateur de
tâche tid:
int inum;
inum=pvm_getinst(char *group,int tid);
Il y a un cas d'Erreur si inum < 0.
6.1.6 pvm_barrier (barrière de synchronisation)
Cette instruction bloque le processus exécutant pvm_barrier jusqu'à ce
que count membres du groupe group se soient également
bloqués:
int info;
info=pvm_barrier(char *group,int count);
Cela permet de synchroniser tout un groupe. count permet de
gérer le fait que d'autres processus ont pu s'ajouter au groupe pendant
l'attente.
Il y a un cas d'Erreur si info < 0.
6.1.7 pvm_bcast
Cette instruction ``Broadcast'' le tampon d'envoi précedemment rempli par des
pvm_pk... vers tous les membres du groupe group, avec le
tag msgtag:
int info;
info=pvm_bcast(char *group,int msgtag);
Il y a un cas d'Erreur si info < 0.
6.1.8 Exemple
On désire calculer les sommes des 100 premiers entiers, de leurs carrés et
de leurs cubes par décomposition fonctionnelle.
#include <stdio.h>
#include "pvm3.h"
#define NG 9 /* taille du groupe = NG+1 */
#define GROUP "GR" /* nom du groupe */
#define ROOT 0 /* index du premier membre du groupe */
#define COUNT 10 /* Taille des sommes partielles */
#define N 100 /* (NG+1) x COUNT */
#define MSGTAG 1
#define ANYTASK -1
int main()
{
int gid, tids[NG], TG[N], TL[COUNT], i; long S[3];
/* Lancer la tache sous PVM */
if (pvm_mytid() < 0) { pvm_perror(); exit(-1); }
/* Rejoindre le groupe de nom GROUP */
if ((gid = pvm_joingroup(GROUP)) < 0)
{
pvm_exit(); exit(-1);
}
if ( gid == ROOT ) {
/* La premiere tache lance les autres membres... */
if ( pvm_spawn("Sommes",NULL,PvmTaskDefault,NULL,NG,tids)<NG)
{
pvm_exit(); exit(-1);
}
/* ... et initialise les donnees a diffuser */
for (i=0; i<N; i++) TG[i] = i;
}
/* Apres synchronisation, diffuser les donnees */
pvm_barrier(GROUP, NG+1);
if ( gid == ROOT )
{
pvm_scatter(&TL[0],&TG[0],COUNT,PVM_INT,MSGTAG,GROUP,ROOT);
} else {
pvm_scatter(&TL[0],NULL,COUNT,PVM_INT,MSGTAG,GROUP,ROOT);
}
/* Calculer les sommes partielles locales */
S[0] = S[1] = S[2] = 0;
for (i=0; i<COUNT; i++)
{
S[0] = S[0] + TL[i];
S[1] = S[1] + TL[i]*TL[i];
S[2] = S[2] + TL[i]*TL[i]*TL[i];
}
/* Sommer globalement les sommes partielles locales */
pvm_reduce(PvmSum,&S[0],3,PVM_LONG,MSGTAG,GROUP,ROOT);
/* Quitter le groupe */
pvm_barrier(GROUP, NG+1);
pvm_lvgroup(GROUP);
/* Resultat */
if ( gid == ROOT )
{
printf("1 + ... + %d = %d\n", N, S[0]);
printf("1^2 + ... + %d^2 = %d\n", N, S[1]);
printf("1^3 + ... + %d^3 = %d\n", N, S[2]);
}
pvm_exit(); exit(0);
}
6.2 Contrôle de la machine PVM
6.2.1 pvm_addhosts
Cette instruction
ajoute les nhost hôtes du tableau de chaînes hosts à la
machine PVM:
int info;
info=pvm_addhosts(char **hosts,int nhost,int *infos);
Le tableau d'entiers infos contient l'état de l'ajout
pour chaque hôte.
Il y a un cas
d'Erreur pour l'hôte numéro i si infos[i] < 0. info retourne le nombre d'hôtes effectivement ajoutés.
6.2.2 pvm_delhosts
Cette instruction
enlève les nhost hôtes du tableau de chaînes hosts à
la machine PVM:
int info;
info=pvm_delhosts(char **hosts,int nhost,int *infos);
Le tableau d'entiers infos contient l'état de l'enlèvement
pour chaque hôte.
Il y a un cas
d'Erreur pour l'hôte numéro i si infos[i] < 0. info retourne le nombre d'hôtes effectivement enlevés.
6.3 Signaux
On peut gérer des interruptions avec la librairie PVM, étendant en
cela la gestion des signaux C standards à un ensemble de processus.
6.3.1 Rappel: les signaux en C
L'instruction void signal(int, void (*)(int)) permet de définir
ce que le programme doit faire en cas d'exception. Le premier argument
est le type d'interruption, le deuxième est la fonction à appeler
quand l'interruption arrive (``handler'').
Les types d'interruptions sont définis dans sys/signal.h:
#define SIGHUP 1 /* hangup */
#define SIGINT 2 /* interrupt (rubout) */
#define SIGQUIT 3 /* quit (ASCII FS) */
#define SIGILL 4 /* illegal instruction (not reset when caught) */
#define SIGTRAP 5 /* trace trap (not reset when caught) */
#define SIGIOT 6 /* IOT instruction */
#define SIGABRT 6 /* used by abort, replace SIGIOT in the future */
#define SIGEMT 7 /* EMT instruction */
#define SIGFPE 8 /* floating point exception */
#define SIGKILL 9 /* kill (cannot be caught or ignored) */
#define SIGBUS 10 /* bus error */
#define SIGSEGV 11 /* segmentation violation */
#define SIGSYS 12 /* bad argument to system call */
#define SIGPIPE 13 /* write on a pipe with no one to read it */
#define SIGALRM 14 /* alarm clock */
#define SIGTERM 15 /* software termination signal from kill */
#define SIGUSR1 16 /* user defined signal 1 */
#define SIGUSR2 17 /* user defined signal 2 */
#define SIGCLD 18 /* child status change */
#define SIGCHLD 18 /* child status change alias POSIX */
#define SIGPWR 19 /* power-fail restart */
#define SIGWINCH 20 /* window size change */
#define SIGURG 21 /* urgent socket condition */
#define SIGPOLL 22 /* pollable event occurred */
#define SIGIO SIGPOLL /* socket I/O possible (SIGPOLL alias) */
#define SIGSTOP 23 /* stop (cannot be caught or ignored) */
#define SIGTSTP 24 /* user stop requested from tty */
#define SIGCONT 25 /* stopped process has been continued */
#define SIGTTIN 26 /* background tty read attempted */
#define SIGTTOU 27 /* background tty write attempted */
#define SIGVTALRM 28 /* virtual timer expired */
#define SIGPROF 29 /* profiling timer expired */
#define SIGXCPU 30 /* exceeded cpu limit */
#define SIGXFSZ 31 /* exceeded file size limit */
#define SIGWAITING 32 /* process's lwps are blocked */
#define SIGLWP 33 /* special signal used by thread library */
...
Par exemple:
/* sig1clavier - P. Cousot */
/* intercepter les signaux du clavier */
/* arreter par ^\ */
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
int i = 0;
/* traitement des signaux */
void on_SIG( int code ) {
fprintf(stdout, "\nSignal (code = %d,i = %d)\n",code,i);
fflush(stdout);
if (code == SIGQUIT) exit(1);
}
int main( void ) {
/* interception des signaux */
signal(SIGINT, on_SIG); /* ^C */
signal(SIGTERM, on_SIG); /* kill PID si arriere plan */
signal(SIGQUIT, on_SIG); /* ^\ */
/* boucle infinie */
for(;i<100000000;i++ ){
if ((i % 100000) == 0)
{
putchar('.');
fflush(stdout);};
}
fprintf(stdout, "Fin\n");
}
Qui s'exécute comme suit:
eider:/home/eider/goubault 62 % a.out
.................^C
Signal (code = 2, i = 1674042)
......................^\
Signal (code = 3, i = 3878990)
eider:/home/eider/goubault 63 %
6.3.2 Signaux sous PVM
pvm_sendsig
Cette instruction envoie le signal d'interruption qui a pour numéro
signum au processus
tid:
int info;
info=pvm_sendsig(int tid,int signum);
Il y a un cas d'Erreur si info < 0.
Exemple PVM
On considère le programme maître suivant, qui lance un esclave
sig2 puis lui envoie SIGTERM et SIGQUIT:
#include <stdio.h>
#include <pvm3.h>
#include <signal.h>
main()
{
int mtid,ctid;
if ((mtid=pvm_mytid()) < 0)
{
pvm_perror("pvmd?");
pvm_exit();
}
fprintf(stdout,"I am spawning a child\n");
pvm_spawn("sig2",(char **) 0,PvmTaskDefault,(char *) 0,1,&ctid);
fflush(stdout);
sleep(1);
fprintf(stdout,"I am sending SIGTERM\n");
pvm_sendsig(ctid,SIGTERM);
fflush(stdout);
sleep(2);
fprintf(stdout,"I am sending SIGQUIT\n");
pvm_sendsig(ctid,SIGQUIT);
fflush(stdout);
sleep(1);
pvm_exit();
exit(1);
}
L'esclave ``sig2'' essaie d'intercepter les signaux envoyés par le maître:
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <pvm3.h>
int i = 0;
/* traitement des signaux */
void on_SIG( int code ) {
fprintf(stdout, "\nSignal (code=%d,i=%d)\n",code,i);
fflush(stdout);
if (code == SIGQUIT) { pvm_exit(); exit(1);}
}
int main( void ) {
/* interception des signaux */
signal(SIGINT, on_SIG); /* ^C */
signal(SIGTERM, on_SIG); /* kill PID si arriere plan */
signal(SIGQUIT, on_SIG); /* ^\ */
/* boucle infinie */
for(;i<100000000;i++ ){
if ((i % 100000) == 0)
{
putchar('.');
fflush(stdout);};
}
fprintf(stdout, "Fin\n");
fflush(stdout);
pvm_exit();
exit(1);
}
L'exécution du programme est alors:
pvm> spawn -1 -> sig1
[4]
1 successful
t40013
pvm> [4:t40013] I am spawning a child
[4:t40013] I am sending SIGTERM
[4:t40014] ..................
[4:t40014] Signal (code = 15, i = 1795243)
[4:t40013] I am sending SIGQUIT
[4:t40014] ................................
[4:t40014] Signal (code = 3, i = 4974635)
[4:t40014] EOF
[4:t40013] EOF
[4] finished
pvm> jobs
pvm>
6.4 Gestion des erreurs
On peut connaître le type d'erreur PVM d'une commande.
On peut également être averti de la mort d'une machine composant
la machine PVM ou d'un processus.
6.4.1 pvm_perror
Cette instruction affiche un message d'erreur:
int info;
info=pvm_perror(char *msg);
6.4.2 pvm_notify
Cette instruction demande à PVM de notifier l'appelant de l'apparition de certains
événements: what peut être PvmTaskExist (une
tâche quitte PVM), PvmHostD
elete (une des machines de la machine
PVM n'est plus là), PvmHostAdd (une nouvelle machine est ajoutée
à la machine PVM). A part le dernier événement, les processus
auxquels on s'intéresse sont dans le tableau tids. cnt ne
sert que dans le cas PvmHostAdd.
Quand un de ces événements apparaît, un certain nombre de
messages avec le tag msgtag sont envoyés à l'appelant:
int info;
info=pvm_notify(int what,int msgtag,int cnt,int *tids);
Il y a un cas d'Erreur si info < 0.
Par exemple (tiré de la documentation PVM), le maître:
/* Failure notification example
Demonstrates how to tell when a task exits */
#include <stdio.h>
#include <pvm3.h>
/* Maximum number of children this program will spawn */
#define MAXNCHILD 20
/* Tag to use for the task done message */
#define TASKDIED 11
int main(int argc, char* argv[])
{
int ntask,info,mytid,child[MAXNCHILD],i,deadtid,tid;
ntask = 3;
mytid = pvm_mytid();
if (mytid < 0)
{
pvm_perror(argv[0]);
return -1;
}
/* find out how many tasks to spawn */
if (argc == 2) ntask = atoi(argv[1]);
/* make sure ntask is legal */
if ((ntask < 1) || (ntask > MAXNCHILD))
{
pvm_perror("wrong number of tasks");
pvm_exit(); return 0;
}
/* spawn the child tasks */
info = pvm_spawn("notify_child",(char**)0,PvmTaskDefault,(char*)0,
ntask,child);
printf("I have spawned %d tasks\n",info);
fflush(stdout);
if (info != ntask)
{
pvm_perror("Pb in spawn");
pvm_exit(); return -1;
}
for (i = 0; i < ntask; i++) printf("t%x\t",child[i]);
putchar('\n');
fflush(stdout);
/* ask for notification when child exits */
info = pvm_notify(PvmTaskExit, TASKDIED, ntask, child);
if (info < 0)
{
pvm_perror("notify"); pvm_exit();
return -1;
}
/* reap the middle child */
info = pvm_kill(child[ntask/2]);
if (info < 0)
{
pvm_perror("kill"); pvm_exit();
return -1;
}
/* wait for the notification */
info = pvm_recv(-1, TASKDIED);
if (info < 0)
{
pvm_perror("recv"); pvm_exit();
return -1;
}
info = pvm_upkint(&deadtid, 1, 1);
if (info < 0) pvm_perror("calling pvm_upkint");
/* should be the middle child */
printf("Task t%x has exited.\n", deadtid);
printf("Task t%x is middle child.\n", child[ntask/2]);
fflush(stdout);
pvm_exit();
return 0;
}
Les esclaves ``notify_child'':
#include <stdio.h>
#include <pvm3.h>
main()
{ /* i'm a child */
printf("I am not root and my parent is t%x\n",pvm_parent());
sleep(10);
pvm_exit();
return 0; }
L'exécution du programme est la suivante:
pvm> spawn -1 -> notify 10
[2]
1 successful
t40006
pvm> [2:t40006] I have spawned 10 tasks
[2:t40006] t40007 t40008 t40009 t4000a t4000b
t4000c t4000d t4000e t4000f t40010
[2:t4000c] EOF
[2:t40006] Task t4000c has exited.
[2:t40006] Task t4000c is middle child.
[2:t40006] EOF
[2:t40007] I am not root and my parent is t40006
[2:t40007] EOF
[2:t40008] I am not root and my parent is t40006
[2:t40008] EOF
[2:t4000a] I am not root and my parent is t40006
[2:t4000a] EOF
[2:t40009] I am not root and my parent is t40006
[2:t40009] EOF
[2:t4000e] I am not root and my parent is t40006
[2:t4000e] EOF
[2:t4000b] I am not root and my parent is t40006
[2:t4000b] EOF
[2:t4000d] I am not root and my parent is t40006
[2:t4000d] EOF
[2:t40010] I am not root and my parent is t40006
[2:t40010] EOF
[2:t4000f] I am not root and my parent is t40006
[2:t4000f] EOF
[2] finished