Previous Next Contents

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, 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

Previous Next Contents