Pagina principale faq Amiga Life a Pianeta Amiga La redazione
Galleria Indice generale
Enigma Amiga Life

AmigaLife 121 ?

AmigaDev
Amiga C
di Gabriele Santilli

Quarta parte

L'appuntamento di questo mese riguarda la comunicazione interprocesso, che può essere considerata uno dei punti di forza di AmigaOS, grazie alla sua semplicità ed efficienza. Il meccanismo offerto da Exec è basato sulle porte e sui messaggi, che qui discuteremo.

I segnali

I segnali sono indicatori di eventi; essi servono, come è evidente dal nome, a segnalare un evento ad un task. Sono il meccanismo a più basso livello su cui si fonda la comunicazione interprocesso.

Ogni task ha a disposizione 32 diversi segnali, 16 dei quali sono usati dal sistema operativo. Ad ognuno dei 16 segnali "utente" può essere associato un significato dall'applicazione, tramite un meccanismo di allocazione. La funzione Wait(), che abbiamo incontrato nello scorso numero, non fa altro che attendere l'attivarsi di un segnale, che può essere causato da un altro task o dal sistema operativo.

In sintesi, ogni segnale è un bit di una parola lunga. Per questo la funzione Wait() accetta in ingresso una maschera a 32 bit che indica per quali segnali bisogna attendere, e ritorna 32 bit che indicano quali segnali si sono attivati.

Le porte

Le "message port" possono essere considerate punti di raccolta dei messaggi; quando un task vuole comunicare con un altro, invia il suo messaggio ad una porta posseduta da questo. Quando un messaggio arriva ad una porta viene eseguita un'azione (specificata dalla porta stessa) che può essere una delle seguenti: attivazione di un segnale, esecuzione di una interruzione software o nessuna operazione.

L'azione più comune associata all'arrivo di un messaggio è l'attivazione di un segnale. Il campo mp_SigBit della struttura MsgPort () indica il segnale da attivare, mentre mp_SigTask punta al task da segnalare (normalmente il task che ha creato la porta).

Il campo mp_Flags specifica l'azione da compiere all'arrivo di un messaggio; se vale PA_SIGNAL viene attivato il segnale mp_SigBit del task mp_SigTask; se vale PA_SOFTINT viene eseguita una interruzione software (mp_SigTask deve puntare, in questo caso, anziché ad una struttura Task, ad una struttura Interrupt); PA_IGNORE indica invece che non accadrà nulla all'arrivo dei messaggi.

Il campo mp_MsgList è una struttura List che raccoglie i messaggi arrivati alla porta. mp_Node, invece, serve ad agganciare la porta messaggi alla lista delle porte pubbliche e ad associarle un nome, in modo che possa essere trovata da altri task.

È importante notare che alla porta può essere associato un solo segnale, che può avere i due soli stati 1 e 0; dunque l'attivarsi del segnale non indica quanti messaggi sono arrivati alla porta, ma solo che sono arrivati dei messaggi.

Creare una porta messaggi

La funzione di Exec CreateMsgPort() (disponibile dalla versione 36 della libreria) può essere usata per allocare ed inizializzare una nuova porta messaggi. Essa alloca automaticamente un segnale per il task chiamante e imposta la porta affinché questo venga segnalato all'arrivo dei messaggi. Se poi si intende rendere pubblica la porta, è necessario associarle un nome (mp_Node.ln_Name) ed aggiungerla alla lista di sistema con la funzione AddPort().

La funzione FindPort() permette di trovare l'indirizzo di una porta pubblica dato il suo nome. Essa ritorna NULL se non esiste una porta con il nome specificato. L'utilizzo di questa funzione richiede alcune cautele particolari; i lettori interessati troveranno tutti i dettagli nell'esempio sul CD.

Prima di distruggere una porta, è necessario "rispondere" a tutti i messaggi che essa contiene (vedremo più avanti come); se essa era stata resa pubblica con AddPort() bisogna quindi chiamare RemPort() per rimuoverla dalla lista di sistema; infine, la funzione DeleteMsgPort() si occupa di deallocare le porte allocate con CreateMsgPort().

I messaggi

La struttura Message () contiene i campi mn_ReplyPort (porta messaggi a cui inviare la risposta), mn_Length (lunghezza totale del messaggio in byte, inclusa la struttura Message) e mn_Node (per collegare il messaggio nella lista delle porte). Essa è poi seguita dal messaggio vero e proprio che i due task si stanno scambiando.

Per inviare un messaggio, bisogna usare la funzione PutMsg(), specificando il puntatore alla porta di destinazione e il puntatore al messaggio. Se si richiede una risposta, bisogna impostare il campo mn_ReplyPort prima di chiamare PutMsg().

Per mandare il proprio task "a dormire" in attesa di un messaggio, è possibile usare la funzione WaitPort(), che ritorna il primo messaggio accodato alla porta (senza tuttavia rimuoverlo da essa). È quindi possibile usare la funzione GetMsg() per rimuovere il messaggio dalla porta e ottenere gli altri messaggi accodati (se presenti); essa ritorna un puntatore al messaggio o NULL se non ci sono più messaggi in attesa. Il modo più comune per ottenere tutti i messaggi in coda è chiamare GetMsg() in un ciclo while fino a che non viene ritornato NULL (vedere l'esempio).

Anziché usare WaitPort(), è possibile usare la funzione Wait() passandogli il bit di segnale della porta; con Wait() si ha il vantaggio di poter attendere più segnali contemporaneamente.

Una volta ricevuto un messaggio, è necessario rispondere al task che ce lo ha inviato con la funzione ReplyMsg(). In questo modo segnaliamo ad esso che abbiamo finito di usare i dati contenuti nel messaggio e quindi può riutilizzare quella struttura Message per un altro messaggio o può deallocarla.

La comunicazione

Riassumendo, la comunicazione tra due task avviene nel modo seguente. Almeno uno dei due task crea una porta messaggi (devono farlo entrambi se si vuole ottenere una risposta ai messaggi) e la rende pubblica associandogli un nome (a meno di avere un altro modo di comunicare l'indirizzo della porta all'altro task). L'altro task cerca la porta per nome con FindPort() e le invia un messaggio (PutMsg()). Se il primo task era in attesa, viene risvegliato e prende il messaggio con GetMsg(), compie le eventuali elaborazioni e risponde con ReplyMsg() rimandando i dati (eventualmente modificati) al mittente. Quest'ultimo riceve la risposta (viene svegliato se era in attesa, ecc.) e può quindi elaborarla.

Si noti che normalmente bisogna sempre richiedere una risposta, perché il mittente dovrà deallocare la memoria allocata per il messaggio quando il destinatario ha finito di usarla. Inoltre, è sempre opportuno rispondere il prima possibile ai messaggi, poiché il mittente potrebbe essere in attesa della risposta per poter deallocare la memoria e quindi uscire.

Per un esempio pratico e tutti i dettagli fate riferimento al CD allegato.

Torna al sommario

Copyright (C) 1999-2002, la redazione di AmigaLife.
Il logo e le copertine della rivista sono tratti dal sito Pluricom e sono Copyright (C) 1992-2001 Pluricom S.r.l.