Cluster Beowulf
Cluster Beowulf
A.S. 2007/08
ITIS G. Vallauri
Progetto realizzato da
Fanti Marco Diana Lorenzo
Classe V B Informatica
1
Costruzione di un cluster Beowulf
Indice
1 Introduzione all'High Performance Computing 4
1.1 Algoritmi parallelizzabili 4
1.2 Architettura di un cluster HPC 4
2 Introduzione al boot da rete 5
2.1 PXE 5
3 Costruzione del cluster 6
3.1 Premesse 6
3.2 Installazione e configurazione del server 6
3.2.1 Installazione 6
3.2.2 Partizionamento dei dischi 6
3.2.3 Configurare la rete del server 7
3.2.4 NFS – Network File System 7
3.2.5 Configurare il server DHCP 7
3.2.6 Configurare il server TFTP 8
3.2.7 Configurare PXE 9
3.3 Installare e configurare i nodi client 10
3.3.1 Instsallare il sistema operativo 10
3.3.2 Configurare le schede di rete 10
3.3.3 Terminare l'installazione 10
4 MPI – Message Passing Interface 11
4.1 Introduzione a Lam-MPI 11
4.2 Composizione della System Service Interface 12
4.2.1 Modulo Boot 12
4.2.2 Modulo Coll 12
4.2.3 Modulo RPI 12
4.3 Installare Lam-MPI 13
4.3.1 Compilare Lam-MPI 13
4.3.2 Configurare SSH 13
4.4 Uso di Lam-MPI 14
4.4.1 Avvio del cluster 14
4.4.2 Compilazione dei programmi 14
2
Costruzione di un cluster Beowulf
3
Costruzione di un cluster Beowulf
4
Costruzione di un cluster Beowulf
2.1 PXE
PXE è un programmino
residente nel bios incaricato
di effettuare il boot tramite la
rete. All'avvio PXE ottiene
dinamicamente un indirizzo
IP tramite dhcp, utilizzando
solitamente la scheda
ethernet integrata nella
scheda madre. Tramite il
servizio dhcp il client viene a
conoscenza, oltre che del suo
indirizzo IP, subnet mask e
gateway, anche di due
parametri che spesso
vengono ignorati, ma che qui
sono fondamentali. Questi
due parametri sono il
Illustrazione 1: GNU/Linux mentre si avvia dalla rete grazie a PXE next_server e il boot_file,
utilizzati appunto da PXE
per iniziare il boot. Next_server infatti è l'indirizzo IP da cui scaricare, tramite protocollo TFTP, il
file situato in posizione boot_file. Una volta scaricato questo file, PXE lo esegue (è un file in
linguaggio macchina). Nel caso che il sistema operativo del client sia GNU/Linux, la procedura di
boot da qui in poi è la seguente:
1. Si scarica dal server, tramite protocollo TFTP, un file contenente la posizione del kernel e le
opzioni con cui esso va eseguito. Il nome di questo file può variare e sarà trattato più nel
dettaglio nella sezione 3.2.8 – Configurare PXELinux
2. Si scarica dal server, sempre tramite TFTP, il kernel da avviare e l'eventuale ramdisk
assocato
3. Si avvia il kernel a cui, nei parametri d'esecuzione, è stata passata la posizione del proprio
file system
4. Infine viene montato il file system tramite NFS e il boot viene completato
5
Costruzione di un cluster Beowulf
3.1. Premesse
Abbiamo deciso di utilizzare come client dei PC diskless, quindi senza disco rigido, che
effettueranno il boot tramite la rete. Sia il server che i client andranno configurati in modo
opportuno.
Come sistema operativo per il PC server abbiamo scelto Debian GNU/Linux nella sua versione
Stable attuale, cioè Etch. Questo sistema operativo è famoso per la sua leggerezza e stabilità in
ambito server, per questo lo abbiamo preferito rispetto ad altre distribuzioni GNU/Linux come
Ubuntu, che ci avrebbero permesso di raggiungere gli stessi risultati. Abbiamo scartato l'opzione di
utilizzare Microsoft Windows perché non sarebbe stato facile fare il boot tramite la rete per i PC
client, oltre al fatto che usare Linux porta a prestazioni maggiori, costa di meno ed è più didattico.
3.2.1. Installazione
Dopo che il server è stato assemblato a dovere abbiamo scaricato dal sito www.debian.org l'ultima
release stable di GNU/Linux Debian e l'abbiamo masterizzata su un CD. L'abbiamo installata
seguendo la normale procedura tra cui il partizionamento dei dischi; quest'ultimo passo necessita di
particolare attenzione.
6
Costruzione di un cluster Beowulf
La prima parte di ogni riga indica la cartella da esportare, la seconda indica le reti che vi potranno
accedere (nel nostro caso la lan 10/100 192.168.2.0/24), mentre nella terza parte ci sono le opzioni
con cui la cartella viene esportata.
Rw indica che la cartella sarà asportata con i permessi di lettura e scrittura, sync indica che ogni
modifica comandata da remoto dovrà avvenire immediatamente sull'hard disk del server. Senza
l'opzione no_root_squash il nodo diskless sarebbe visto dal server come l'utente nobody, e non
potrebbe lavorare correttamente sul file system condiviso..
La cartella /nodes/nfs/nodo2 conterrà, dopo l'installazione dei sistemi client, il file system utilizzato
dal secondo nodo, e dovrà esserci una linea simile, in cui cambia solo il numero del nodo, per ogni
nodo del cluster escluso il server. Le altre due entry del file non sono strettamente necessarie, ma
potrebbero tornare utili nella manutenzione del cluster.
7
Costruzione di un cluster Beowulf
8
Costruzione di un cluster Beowulf
# mkdir /tftpboot
# chmod 777 /tftpboot
9
Costruzione di un cluster Beowulf
specificati l'initramfs da usare (in questo caso quello che è stato creato prima e linkato nella
cartella /tftpboot) e la posizione del file system root da montare tramite nfs. Se si volesse far partire
un nodo con un kernel differente, basterebbe specificare in questo file un altro kernel e un altro
initramfs.
10
Costruzione di un cluster Beowulf
11
Costruzione di un cluster Beowulf
● coll: modulo che si occupa delle comunicazioni collettive tra i vari processi;
● rpi: modulo che si occupa delle comunicazioni punto-punto tra i processi;
● cr: fornisce la possibilità di creare dei checkpoint da cui far ripartire il cluster in caso di
blocchi.
12
Costruzione di un cluster Beowulf
13
Costruzione di un cluster Beowulf
$ cd ~/.ssh
$ ssh-keygen -t rsa
ssh impiegherà un po' di tempo per generare le chiavi. Dopo questo ssh chiederà il nome da dare
alle chiavi, lasciamo il nome proposto di default (id_rsa). Dopo questo, ssh chiede l'inserimento di
una password, bisogna lasciarla vuota, dando semplicemente invio. A questo punto bisogna
aggiungere la chiave pubblica appena creata (id_rsa.pub) alla lista delle chiavi il cui accesso è
autorizzato sui vari client.
$ cat id_dsa.pub >> /nodes/nfs/nodoX/home/nomeutente/.ssh/authorized_keys
Per terminare la configurazione di ssh a questo punto basta connettersi via remoto dal server ai vari
nodi con il comando
$ ssh [email protected]
e rispondere affermativamente alla domanda che compare a video. Per essere sicuri che Lam sarà in
grado di utilizzare ssh, bisogna essere sicuri che lo script di avvio della shell non vadano a scrivere
niente sullo standard error (su Debian Etch è già così). A seconda della distribuzione questi script si
chiamano .login, .profile, .cshrc o .bashrc e si trovano solitamente nella cartella home dell'utente. Se
contengono comandi che stampano qualcosa sullo standard error, essi vanno cancellati.
14
Costruzione di un cluster Beowulf
richiamare i compilatori originali aggiungendo il supporto alle librerie MPI. Questi compilatori
sono:
● mpicc, compilatore C;
● mpiCC compilatore C++;
● mpif77 compilatore fortran77.
Quindi per compilare un file sorgente scritto in C bisogna dare il comando:
$ mpicc -o nomeprogramma file_sorgente.c
Come risultato della compilazione si ha un programma pronto ad essere eseguito sul cluster.
15
Costruzione di un cluster Beowulf
16
Costruzione di un cluster Beowulf
L'oggetto communicator
Esiste il tipo MPI_Comm, detto communicator, che indica un gruppo di processi. Ad esempio si
potrebbero raggruppare in un oggetto communicator i processi in esecuzione sui nodi pari,
mentre in un altro quelli in esecuzione sui nodi dispari.
Quasi sempre è necessario passare alle funzioni MPI un oggetto communicator, in quanto le
funzioni devono sapere su quali processi devono lavorare.
Costanti:
● MPI_COMM_WORLD è una costante di tipo MPI_Comm che indica il gruppo di tutti i
processi.
17
Costruzione di un cluster Beowulf
● MPI_COMM_NULL, indica un gruppo vuoto, cioè di cui non fa parte nessun processo.
La struttura MPI_Status
Questo tipo di dato, chiamato MPI_Status, è una struttura che può contenere informazioni sullo
stato di una comunicazione. Ad esempio può contenere informazioni su quale processo ci ha
spedito un certo messaggio, ma questo verrà spiegato più dettagliatamente al momento di
spiegare la funzione MPI_Recv.
Il tipo MPI_Operation
Questo tipo di dato serve per indicare ad alcune funzioni MPI quale operazione va svolta sui dati
che devono elaborare. Il tipo di operazione solitamente è espresso sotto forma di una costante,
che appunto è una costante di tipo MPI_Operation.
Ogni costante di tipo MPI_Operation elencata qui di seguito indica una diversa operazione che si
può indicare di svolgere a particolari funzioni di MPI che lavorano su gruppi di dati:
● MPI_SUM esegue la somma;
● MPI_PROD esegue il prodotto;
● MPI_MAX cerca il valore massimo;
● MPI_MIN cerca il valore minimo;
● MPI_LAND logical AND;
● MPI_BAND esegue la AND bit a bit;
● MPI_LOR logical OR;
● MPI_BOR esegue la OR bit a bit;
● MPI_LXOR logical XOR ;
● MPI_BXOR esegue la XOR bit a bit;
● MPI_MAXLOC esegue la ricerca del valore massimo, in più ritorna anche un indice
che indica il processo su cui di trova il valore trovato;
● MPI_MINLOC esegue la ricerca del valore minimo, in più ritorna anche un indice
che indica il processo su cui di trova il valore trovato;
● MPI_OP_NULL indica un'operazione nulla;
Il tipo MPI_Datatype
Questo ultimo tipo di dato speciale serve per indicare alle funzioni MPI qual'è il tipo di dato che
stanno elaborando. Anche qui si usano delle costanti per comodità, in particolare le costanti di
tipo MPI_Datatype sono:
● MPI_BYTE = byte;
● MPI_CHAR = char;
● MPI_SHORT = short;
● MPI_INT = int;
18
Costruzione di un cluster Beowulf
19
Costruzione di un cluster Beowulf
MPI_Comm comm);
Questa funzione permette di spedire un vettore di dati da un processo ad un altro, non termina
fino a che il dato non è stato ricevuto dal destinatario.
I parametri hanno il seguente significato:
● *buf = indirizzo della prima cella del vettore da trasmettere;
● count = lunghezza del vettore (>0);
● dtype = tipo di dato da spedire, indicato con una costante di tipo MPI_Datatype;
● dest = indica il rank del processo a cui spedire il messaggio;
● tag = un qualsiasi numero che si vuole assegnare al messaggio;
● comm = oggetto communicator che indica all'interno di quale gruppo di processi
cercare il rank destinatario.
MPI_Recv
int MPI_Recv(void *buf, int count, MPI_Datatype dtype, int mitt, int tag,
MPI_Comm comm, MPI_Status *status);
Questa funzione permette di ricevere un vettore di dati da un processo che lo ha spedito, la
funzione non termina fino a quando i dati non sono stati ricevuti.
I parametri hanno il seguente significato:
● *buf = indirizzo della prima cella del vettore dove memorizzare i dati ricevuti;
● count = lunghezza del vettore (>0);
● dtype = tipo di dato da ricevere, indicato con una costante di tipo MPI_Datatype;
● mitt = indica il rank del processo da cui si attende il messaggio, si può indicare la
costante MPI_ANY_SOURCE che significa “da qualsiasi processo”;
● tag = indica il tag che deve avere il messaggio che si riceve, si può indicare
MPI_ANY_TAG che significa “con qualsiasi tag”;
● comm = oggetto communicator che indica all'interno di quale gruppo di processi
cercare il rank del mittente;
● *status struttura che contiene informazioni sul messaggio ricevuto, come il rank
del mittente (status.source) e il tag (status.tag);
Comunicazioni punto-punto asincrone
Esistono delle varianti delle funzioni MPI_Send e MPI_Recv che non rimangono in attesa della
ricezione del dato per terminare, ma che terminano subito, permettendo una comunicazione
“asincrona” tra i processi. Naturalmente queste istruzioni andranno usate con molta cautela,
tenendo conto che per poter funzionare correttamente bisogna impostare ssi per usare il modulo
rpi lamd (vedi sezioni 4.2.3 e 4.4.3).
Queste funzioni utilizzano un tipo di data MPI_Request, che serve per identificare la sessione di
trasmissione dei dati, in modo da controllare successivamente se i dati sono stati spediti o no.
Le funzioni invia e ricevi asincrone sono:
int MPI_Isend(void *buf, int count, MPI_Datatype dtype, int dest,
int tag, MPI_Comm comm, MPI_Request *request);
int MPI_Irecv(void *buf, int count, MPI_Datatype dtype, int mitt,
int tag, MPI_Comm comm, MPI_Request *request);
Per controllare l'avvenuta spedizione o ricezione del dato si possono utilizzare due funzioni, la
wait o la test. La funzione wait non termina finchè il messaggio non è stato spedito o ricevuto,
mentre la test controlla semplicemente se l'operazione è avvenuta, senza interrompere
l'esecuzione del programma.
20
Costruzione di un cluster Beowulf
Nella funzione di test il flag è 1 se l'operazione è stata completata con successo, mentre è 0 se
deve ancora in attesa di essere completata.
Esempio comunicazione punto-punto
Questo programma carica un vettore di interi nel processo 0 e lo invia al processo 1, che stampa
a video il vettore.
#include <stdio.h>
#include <mpi.h>
int myrank,i;
long aus;
MPI_Status status;
double a[10],b[10];
MPI_Init(&argc, &argv);
MPI_Comm_rank(MPI_COMM_WORLD, &myrank);
21
Costruzione di un cluster Beowulf
Messaggi di broadcast
Per messaggio di broadcast si intende l'invio di un vettore di dati ad un intero gruppo di processi.
La funzione è molto semplice:
int MPI_Bcast ( void * buffer, int count, MPI_Datatype tipo_di_dato,
int rank, MPI_Comm comm );
Il significato dei vari parametri è il seguente:
● *buffer = vettore che nel mittente contiene il vettore da trasmettere, nei riceventi
indica dove memorizzare i dati ricevuti;
● count = lunghezza del vettore;
● tipo_di_dato = tipo di dato contenuto nel vettore;
● rank = rank del processo mittente;
● comm = gruppo di processi a cui spedire i dati;
22
Costruzione di un cluster Beowulf
Operazione Reduce
Questa funzione è utilizzata per raccogliere un dato dai processi coinvolti, elaborarlo
ulteriormente e restituire in uscita un unico dato.
int MPI_Reduce ( void*send_buffer, void*recv_buffer, int count,
MPI_Datatype tipo_di_dato, MPI_Operation operazione,
int rank, Mpi_Comm comm )
Spiegazione dei parametri:
● *send_buffer = indirizzo del dato da raccogliere da ogni processo
● *recv_buffer = indirizzo di dove memorizzare il risultato ottenuto
dall'elaborazione di tutti i dati
● count = lunghezza dei vettori da cui si prendono i dati;
● tipo_di_dato = tipo di dato da elaborare
● operazione = operazione da svolgere sull'insieme dei dati, indicata con una
costante di tipo MPI_Operation;
● rank = rank del processo dove memorizzare il risultato;
● comm = gruppo di processi coinvolti;
Operazioni Gather e Scatter
23
Costruzione di un cluster Beowulf
5.3.4. Conclusioni
A questo punto abbiamo elencato le funzioni base che le librerie MPI rendono disponibili per il
24
Costruzione di un cluster Beowulf
linguaggio C. In questa nostra guida mancano tante funzioni, come quelle per lavorare sui file,
suddividere matrici di dati, definire gruppi di processi e molte altre ancora; crediamo comunque che
sia meglio non entrare nei dettagli.
double funzione(double,double,double,double,double);
double a0,a1,a2,a3,sin,des,ris,somma,i,s,d,step;
/* a0,a1,a2,a3 = coefficienti della funzione polinomiale
sin,des = limiti sinistro e destro dell'integrale
somma = dove alla fine viene memorizata la somme degli integrali
calcolata da tutti i processi
s,d = limiti sinistro e destro in cui ogni processo calcola
l'integrale
step = passo usato dal metodo dei trapezi, piu' e' piccolo piu' la
precisione e' elevata */
25
Costruzione di un cluster Beowulf
MPI_Barrier(MPI_COMM_WORLD);
26
Costruzione di un cluster Beowulf
}
Bisogna specificare che l'input (da tastiera) e l'output (a video) possono avvenire solo sul nodo da
cui viene lanciato il processo parallelo. Quindi se un processo in esecuzione su uno dei nodi client
esegue una printf, essa sarà visibile sullo schermo del nodo principale.
27