Articolo pubblicato sul n. 113 di MCmicrocomputer (Edizioni Technimedia Srl - Roma) nel dicembre 1991

MCmicrocomputer


 

Multitasking:
OCCAM: un esempio concreto
(prima parte)

di Andrea de Prisco

Come annunciato sullo scorso numero, questo mese commenteremo un po' la parte del software di rete di ADPnetwork scritta in OCCAM e quindi fatta per girare sul transputer delle schede di rete. Per meglio comprendere, pero', il funzionamento sarebbe opportuno che teniate sotto controllo anche l'articolo precedente a questo, in modo da non perdervi nei meandri dei troppi canali e processi di cui parleremo questo mese.Copertina del numero di MCmicrocomputer contenente l'articolo

Premessa

Inizialmente ADPnetwork, se ricorderete, nasceva per girare su macchine Amiga senza hardware di rete aggiunto. La comunicazione tra macchina e macchina avveniva utilizzando semplicemente  l'interfaccia seriale bidirezionale di cui ogni Amiga dispone. Non essendoci, allora, transputer di mezzo, tutto il software di rete era stato scritto in C, naturalmente utilizzando tutti (o quasi...) gli strumenti messi a disposizione da tale linguaggio. In piu', per la comunicazione inter process, veniva massivamente utilizzato il tool ADPmttb (multitasking toolbox) scritto anch'esso interamente da ADP.

Con la progettazione e realizzazione, nella seconda fase, da parte del sottoscritto e di Giuseppe Cardinale Ciccotti (con tra i piedi, pero', sempre ADP!) della scheda a transputer, parte del software di rete migrava sulla scheda e veniva tradotto ed adattato nel linguaggio OCCAM.

Volevamo inoltre mettere il meno possibile le mani nella parte di software ancora in esecuzione su Amiga, primo per risparmiare il piu' possibile tempo (lo SMAU 1990, sempre piu' prossimo, di certo non aspettava...), secondo per non modificare l'interfacciamento software con l'handler e il server di rete (scritti da Marco Ciuchini e Andrea Suatoni) che rimanevano cosi' unici per la rete software e per quella hardware+software (figura 1).

Non disponendo, l'OCCAM, dei tipi di dato strutturati (come i record) la maggior fatica dell'opera di traduzione e' stata proprio la manipolazione dei pacchetti non piu' come record, ma come semplici array in cui andare a zappare con gli indici per prelevare o inserire i vari campi. Array che, una volta recapitati ai processi Amiga, venivano nuovamente visti (e utilizzati) da questi come record. Troverete dunque nel sorgente OCCAM listato in queste pagine spesso costanti il cui nome ha l'estensione ".pos" (punto pos): sono definitte in un include a parte ed indicano, appunto, la posizione dei vari campi all'interno del pacchetto (visto da OCCAM come array) in arrivo, in transito o in partenza.

Ad esempio la riga scritta in 'C' :

cks = frame->CkSum;

che copia nella variabile ad 8 bit 'cks' il contenuto del campo CkSum, sempre ad 8 bit, della struttura puntata da 'frame', in OCCAM diventa:

cks := frame[CkSum.pos]

in cui 'cks' e' sempre una variabile, 'frame' un array di byte, 'CkSum.pos' la posizione di tale campo, espressa in byte, nel record originario. La cosa naturalmente si complica lievemente quando dobbiamo prelevare valori diversi da byte. Ad esempio, per leggere un campo a 16 bit in 'C' non cambia nulla:

mitt = frame->Mitt;

purche' la variabile 'mitt' sia sufficiente a contenere 'frame->Mitt' non abbiamo nessun tipo di problema. In OCCAM non e' possibile copiare direttamente in una variabile di tipo INT16 (intero a 16 bit) due posizioni contigue di un array di byte ma e' necessario fare il trasferimento accedendo due volte all'array:

mitt := (frame[Mitt.pos]<<8) + frame[Mitt.pos + 1]

L'operazione '<<8' esegue semplicemente uno shift a sinistra di 8 bit per fare posto al secondo elemento che completa cosi' la lettura.

Canali, processi, transputer

In figura  2 e' mostrato il "pallogramma" dei processi OCCAM in esecuzione sulla scheda di rete (governata, come detto e stradetto, da un transputer T222) e i relativi canali di comunicazione tra gli stessi. I canali 'prev.in' e 'next.out' sono mappati su due link fisici del transputer e tramite driver differenziali vengono collegati il primo alla macchina precedente, il secondo alla macchina successiva. I canali 'toamiga' e 'fromamiga' sono anch'essi mappati su un link fisico e da questo su un Link Adaptor per l'interfacciamento diretto con il bus dell'Amiga.

Due processi qualsiasi in comunicazione tra loro, ad esempio SenderAmiga e AmigaInterface, possono ovviamente chiamare i loro canali d'ingresso e d'uscita con nomi diversi (lo stesso canale e' chiamato 'amigarequest' dal primo e 'strobe' dal secondo). A rimettere le cose in ordine, ovvero a far si' che canali con nomi diversi in processi diversi identifichino lo stesso canale, ci pensera' il processo master 'NetPuter', presente in fondo al listato, che dichiara i canali necessari e li passa come parametri a tutti i processi che lancia in parallelo (se state guardando il listato, sono proprio le ultime 7 righe capeggiate da un bel 'PAR').

I parametri, invece, del processo NetPuter sono invece i link fisici (piu' l'indirizzo di rete) specificati in un apposito file di configurazione (non listato) utilizzato durante la fase di generazione eseguibile. Ricordiamo, infatti, che ne' nel sergente, ne' in fase di compilazione o di linking e' necessario fare alcuna assunzione riguardo il lancio di processi su singolo processore o su processori diversi. Una stessa applicazione multi processo, una volta compilata e linkata, a seconda delle specifiche indicate nel file di configurazione per la generazione dell'eseguibile, potra' ad esempio girare in multitasking su un solo transputer (mappando i processi sul medesimo chip e passando loro come parametri canali logici) oppure su piu' chip, in parallelismo reale, passando come parametri dei canali i link fisici dei transputer. In fase di debugging della rete, ad esempio, le prime prove sono state fatte su un solo transputer sul quale lanciavamo i processi relativi a quattro nodi distinti (in tutto 5x4=20 processi): dopo aver visto che il funzionamento corretto avveniva, senza ricompilare nulla abbiamo mappato i 4 sofware di rete su quattro transputer distinti avendo modo cosi' di saggiare anche le effettive performance dei link fisici. Insomma, vi assicuriamo, e' stato un bel divertimento!

Commentiamo il codice

Ma torniamo al listato e cominciamo dal primo processo "Dispatcher" che ha il compito di decidere se i pacchetti in arrivo sono per quel nodo (nel qual caso lo inoltra verso Amiga e genera il pacchetto di ACK corrispondente) o per altri nodi ovvero da ributtare sulla rete.

Le prime funzioni o procedure che troviamo nel listato sono utilizzate, come vedremo, dal vero e proprio processo "Dispatcher" il cui codice inizia un po' di righe piu' avanti e, quando necessario, dagli altri processi. Servono per calcolare il CheckSum sull'header del pacchetto, per risincronizzarsi su un pacchetto valido in caso di errore, per calcolare il codice CRC del corpo del pacchetto inviato o ricevuto. Quest'ultimo e' un algoritmo di pubblico dominio come ampiamente dichiarato nella finestra di commento nel sorgente della funzione "crc32".

Subito dopo inizia il processo "Dispatcher" i cui parametri sono l'identificatore di rete per quel nodo e i canali (logici e fisici) passati dal processo master prima menzionato.

Dopo le dichiarazioni iniziali d'obbligo, troviamo una procedura locale che serve per reinoltrare i pacchetti in transito previa marchiatura degli stessi. L'operazione, non necessaria ma fortemente consigliata, permette di ammazzare eventuali pacchetti "zombi" che potrebbero girare infinitamente sulla rete se il mittente di un messaggio ad un destinatario inesistente, dopo la spedizione esce dalla rete (ad esempio in seguito ad un guasto): in questo caso tutti gli altri nodi continuerebbero a reinoltrare all'infinito il pacchetto. Grazie alla marchiatura (effettuata per i soliti motivi di sicurezza da ben due nodi contigui diversi dal mittente) quando arriva un pacchetto da reinoltrare la procedura SendOut controlla se il campo 'Stamp1' o 'Stamp2' contiene il medesimo indirizzo di rete del proprio nodo: in caso affermativo vuol dire che il pacchetto e' gia' passato di li' una volta (facendo un giro completo della rete) senza trovare il destinatario, ed il mittente, ugualmente, e' assente (se no l'avrebbe assorbito lui). Nel caso invece che uno dei due campi 'Stamp1' o 'Stamp2' sono nulli, come detto prima, lo marchia. Nel terzo ed ultimo caso che i due 'Stamp' siano gia' stati marchiati da altri processi (quindi non contengono l'indirizzo di rete di quel nodo e non sono nulli) il pacchetto puo' sicuramente essere rispedito verso la macchina successiva tramite i processi "ReceiverAmiga" e "SenderLink" che commenteremo piu' avanti.

Torniamo al processo "Dispatcher". Il funzionamento, tutto sommato, e' piuttosto semplice: essendo i pacchetti di lunghezza variabile (multipla di 32 byte) la prima cosa che fa e' leggere dal canale 'prev.in' (tenete sempre sott'occhio la figura 2) un numero di byte pari alla costante MIN.LEN (pari appunto a 32). Nei primi 32 byte di un pacchetto troviamo infatti tutte le informazioni che ci interessano essendo li' contenuto l'intero header: tipo del pacchetto, lunghezza del pacchetto (espressa in multipli di 32 byte), mittente, destinatario, 'Stamp1', 'Stamp2', CheckSum, CRC ecc. ecc.

Segue il controllo del CheckSum per verificare la correttezza dell'header piu' altri controlli che permettono di diminuire le probabilita' di prendere per buono un header scassato (limiti di alcuni campi, tipo di informazione in essi contenuta ecc.) in cui casualmente la prova del CheckSum abbia dato esito positivo.

Dopo aver ricopiato in alcune variabili locali alcuni campi dell' header (con la tecnica illustrata ad inizio articolo) il processo "Dispatcher" si chiede se si tratta o meno di pacchetto di ACK (il che implica anche una lunghezza totale del pacchetto pari a MIN.LEN). In caso affermativo, se l'ACK e' per un altro nodo viene invocata la procedura "SendOut" prima descritta, se e' per il nodo in questione viene inoltrato verso il processo buffer "SenderAmiga".

Prima di continuare con l'analisi del pacchetto ed eventuali decisioni in merito (l'unica cosa assodata e' che non si tratta di un pacchetto di ACK ne' per noi per per altri nodi) e' necessario leggere dal canale di ingresso 'prev.in' l'eventuale rimanente porzione di pacchetto non ancora ricevuta.

Il successivo "IF len>1" provvede a leggere tutti i rimanenti byte del corpo del pacchetto in modo da poterlo gestire nella sua pienezza. Quindi calcolo dei byte validi all'interno del corpo e su questi controllo del CRC per testare la corretta ricezione del messaggio. Si aprono a questo punto tre possibilita': il pacchetto arrivato era stato spedito dallo stesso nodo in questione, arriva da un altro mittente ed e' per un altro destinatario, il nodo in questione e' effettivamente il destinatario del pacchetto. Nel primo caso se arriva un pacchetto spedito dallo stesso nodo, vuol dire che non esiste il destinatario dato che in una struttura circolare come quella di ADPnetwork un pacchetto che ritorna al mittente ha attraversato tutti i nodi collegati in rete e tutti l'hanno reinoltrato non riconoscendosi come destinatari dello stesso. Nel secondo caso il processo "Dispatcher", tramite la sua gia' citata funzione "SendOut" e relativi controlli di marchiatura, reinoltra su rete il pacchetto. Nel terzo caso il pacchetto e' inviato verso l'Amiga.

E qui apriamo una breve parentesi. La scheda di rete da noi realizzata disponeva di 64k byte (il massimo indirizzabile dal T222) utilizzati per un buon 90-95 % come buffer di ingresso e d'uscita per i pacchetti. Succedeva che, qualora il buffer d'ingresso su una scheda si riempiva tutti i pacchetti per quel nodo venivano uccisi fintantoche' non si faceva posto nel buffer. Questo lusso era possibile in quanto il software di rete in esecuzione sugli Amiga era in grado di accorgersi che un pacchetto non era arrivato a destinazione e quindi provvedeva automaticamente a rispedirlo dopo un breve intervallo di tempo. Cosi' il processo "Dispatcher" ogni volta che invia un pacchetto (reale o di ACK) al processo buffer "SenderAmiga" aspetta sul canale 'OK' conferma da questo se effettivamente l'ha bufferizzato o meno. Nel caso infatti, come listato piu' avanti nel processo "Dispatcher" (al commento "-- /* frame per me */"), di pacchetto per quel nodo, l'ACK corrispondente viene generato solo nel caso in cui il pacchetto e' effettivamente stato accettato dal processo "SenderAmiga" ('pushed" = TRUE). In caso negativo, non essendo generato l'ACK il processo mittente provvedera' a sue spese, come detto prima, a rispedire il pacchetto "ucciso" dal destinatario per mancanza di spazio nel buffer. Il tutto, naturalmente, non certo per colpa dei velocissimi transputer ma semmai degli Amiga 500 e 2000 che con il loro 68000 a 7.1 MHz non possono certo rivaleggiare con i 20 Mbit/s dei link fisici e i 25 MHz di funzionamento dei T222 effettivamente in rete.

Spazio tiranno

Contavamo di riuscire a commentare l'intero sorgente OCCAM di ADPnetwork in una sola puntata e invece, giunti a questo punto e constatato il livello di dettaglio imposto per una facile comprensione anche ai non esperti, siamo costretti a continuare il commento sul prossimo numero. Il listato pubblicato, comunque, contiene anche il sorgente dei rimanenti processi (in pratica tutti quelli mostrati nel "pallogramma" di figura 2) che gia' potete cominciare a guardare e, come crediamo, a capire. Arrivederci...


Articolo pubblicato su www.digiTANTO.it - per ulteriori informazioni clicca qui