I semafori (Quarta lezione)
Nella precedente lezione abbiamo esaurientemente spiegato le funzioni e le
procedure necessarie a gestire le porte messaggi. Oggi prenderemo in esame, come promesso, quelle riguardanti i semafori.
Vediamole:
- 1. ULONG Procure( struct SignalSemaphore *sigSem, struct SemaphoreMessage *bidMsg );
- 2. void Vacate( struct SignalSemaphore *sigSem, struct SemaphoreMessage *bidMsg );
- 3. void InitSemaphore( struct SignalSemaphore *sigSem );
- 4. void ObtainSemaphore( struct SignalSemaphore *sigSem );
- 5. void ReleaseSemaphore( struct SignalSemaphore *sigSem );
- 6. ULONG AttemptSemaphore( struct SignalSemaphore *sigSem );
- 7. void ObtainSemaphoreList( struct List *sigSem );
- 8. void ReleaseSemaphoreList( struct List *sigSem );
- 9. struct SignalSemaphore *FindSemaphore( UBYTE *sigSem );
- 10. void AddSemaphore( struct SignalSemaphore *sigSem );
- 11. void RemSemaphore( struct SignalSemaphore *sigSem );
- 12. void ObtainSemaphoreShared( struct SignalSemaphore *sigSem );
- 13. ULONG AttemptSemaphoreShared( struct SignalSemaphore *sigSem );
La funzione 3 serve per inizializzare un semaforo precedentemente
allocato (tramite AllocVec o AllocMem o altro).
Le funzioni 10 e 11 servono rispettivamente (come le consorelle AddPort
e RemPort) ad aggiungere e rimuovere un semaforo dalla lista di exec.
La 9, come FindPort per le porte, serve per ottenere l'indirizzo di un
determinato semaforo.
Queste sono le funzioni più "semplici". In pratica, per creare un
semaforo, basta allocare memoria per una struttura SignalSemaphore,
chiamare la InitSemaphore per inizializzarla, e inserirlo nella lista di
sistema con la AddSemaphore. A questo punto, per utilizzarlo, serve ottenerlo e poi poterlo
rilasciare.
Bene, l'AmigaOS ci mette a disposizione molteplici funzioni per ogni
possibilità.
Vediamo quelle per ottenere un semaforo (che devono ovviamente essere usate con le
loro corrispettive).
La funzione Procure e la sua relativa Vacate, fino alla versione V39 del
sistema operativo erano molto bacate e sconsigliate. Al loro posto
veniva caldeggiato l'uso della 7 e della 8, che in pratica svolgono le
stesse funzioni.
Però, mentre la ObtainSemaphore blocca il task se il semaforo non è
libero, la Procure invia una richiesta al semaforo e ritorna.
Quando il semaforo diventa disponibile, il precedente messaggio di
richista viene replicato ed il task ottiene il semaforo.
Questo sistema permette di aspettare su diversi semafori e di continuare
a lavorare in attesa che si liberino.
Ovviamente, un utilizzo del genere obbliga una maggior complessità del
task che non potrà assumere che il ritorno della Procure coincida con
l'effettivo possesso del semaforo richiesto, come avviene invece per la
ObtainSemaphore.
Esaminate quindi la Procure/Vacate e la
ObtainSemaphore/ReleaseSemaphore, passiamo a vedere le versioni evolute.
La ObtainSemaphoreList, differisce dalla ObtainSemaphore per il fatto di
richedere una lista di semafori anziché uno solo.
Il sistema operativo garantisce l'assenza di deadlock (vi ricordate la
prima puntata?) nel caso in cui uno o più dei semafori richiesti siano
già occupati, a patto
che non venga usata la ObtainSemaphore su uno o più dei semafori
presenti nella lista. Infatti si consiglia l'uso di un semaforo per
controllare l'accesso alla lista...
Inoltre, un altro accorgimento che deve essere preso, è che una lista
di semafori non può essere aggiunta alla lista dei semafori pubblici
per via di alcune incompatibilità di interpretazione di alcuni campi
della struttura SignalSemaphore tra le funzioni
Obtain/ReleaseSemaphoreList e le altre.
Ovviamente la ReleaseSemaphoreList serve per rilasciare la lista di
semafori precedentemente ottenuti.
La AttemptSemaphore è simile alla Procure, perché ritorna TRUE se è
riuscita a bloccare il semaforo, e FALSE altrimenti.
Valgono ovviamente gli stessi discorsi per la Procure in merito alla
complessità della logica del programma.
Abbiamo infine le varianti Shared della Obtain/Attempt/ReleaseSemaphore.
Queste tre funzioni, servono per effettuare un lock non esclusivo, ma
appunto condiviso, su di un semaforo.
Può essere utile quando una determinata struttura, per esempio una
lista, viene letta molte volte e solo raramente scritta. In questo modo
è possibile lasciare accedere i task che vogliono leggere quando nessun
task che scrive ha il semaforo ed essere sospesi se un task scrivente
sta accedendo alla struttura.
Queste ultime funzioni, essendo molto giovani, funzionano correttamente
solo a partire dalla versione V39 del sistema operativo, a cui il corso
fa e farà riferimento. Sugli amigaguide di exec sono presenti alcuni
accorgimenti per ottenere lo stesso risultato su versioni precedenti, che
però noi non prenderemo in esame.
Anche se la struttura SignalSemaphore, non necessita in pratica di
essere toccata dall'utente (fatta eccezione per impostare il nome e la
priorità del semaforo), è meglio vederla ugualmente per dovere di cronaca.
Prima, però, è meglio introdurre le liste di sistema e le relative strutture.
La exec.library, tra le sue 137 funzioni, ne mette a disposizione 8, e cioè:
- void Insert( struct List *list, struct Node *node, struct Node *pred );
- void AddHead( struct List *list, struct Node *node );
- void AddTail( struct List *list, struct Node *node );
- void Remove( struct Node *node );
- struct Node *RemHead( struct List *list );
- struct Node *RemTail( struct List *list );
- void Enqueue( struct List *list, struct Node *node );
- struct Node *FindName( struct List *list, UBYTE *name );
Adesso vediamo le strutture a cui si riferiscono:
struct Node {
struct Node *ln_Succ; /* Pointer to next (successor) */
struct Node *ln_Pred; /* Pointer to previous (predecessor) */
UBYTE ln_Type;
BYTE ln_Pri; /* Priority, for sorting */
char *ln_Name; /* ID string, null terminated */
}; /* Note: word aligned */
/* minimal node -- no type checking possible */
struct MinNode {
struct MinNode *mln_Succ;
struct MinNode *mln_Pred;
};
struct List {
struct Node *lh_Head;
struct Node *lh_Tail;
struct Node *lh_TailPred;
UBYTE lh_Type;
UBYTE l_pad;
}; /* word aligned */
/*
* Minimal List Header - no type checking
*/
struct MinList {
struct MinNode *mlh_Head;
struct MinNode *mlh_Tail;
struct MinNode *mlh_TailPred;
}; /* longword aligned */
Adesso, alcuni di voi si staranno chiedendo: "Ma le strutture MinList e
MinNode a che servono?". Bene, non lasciatevi ingannare dal tipo di dato
che accettano le otto funzioni di exec sulle liste.
In realtà, come vi avevo accennato precedentemente riguardo a queste funzioni,
lavorando sui primi tre campi della struttura List e sui primi due della
struttura Node (fatta eccezione per la FindName, la quale ovviamente
farà riferimento al campo ln_Name della struttura Node), permettono al
programmatore di includere all'inizio delle loro strutture una struttura
di tipo List (o MinList per ridurre lo spazio) e rispettivamente Node e MinNode.
Sarà poi sufficente un semplice cast per zittire il compilatore. O
meglio, usare le seguenti macro generiche:
#define LIST(list) ((struct List*)(list))
#define NODE(node) ((struct Node*)(node))
/* Inizializzazione di una lista */
#define NEW_LIST(l) LIST(l)->lh_Head = NODE(&LIST(l)->lh_Tail), \
LIST(l)->lh_TailPred = NODE(&LIST(l)->lh_Head), \
LIST(l)->lh_Tail = NULL
/* Inserimento del nodo in testa nella lista */
#define ADD_HEAD(l,n) AddHead(LIST(l),NODE(n))
/* Inserimento del nodo in coda nella lista */
#define ADD_TAIL(l,n) AddTail(LIST(l),NODE(n))
/* Inserimento del nodo nella lista dopo il nodo */
#define INSERT(l,n,p) Insert(LIST(l),NODE(n),NODE(p))
/*
** Rimozione del nodo di testa, di tipo , dalla lista
** Viene restituito il puntatore al nodo rimosso
*/
#define REM_HEAD(l,t) (struct t *)RemHead(LIST(l))
/*
** Rimozione del nodo di coda, di tipo , dalla lista
** Viene restituito il puntatore al nodo rimosso
*/
#define REM_TAIL(l,t) (struct t *)RemTail(LIST(l))
/*
** Rimozione del nodo . Il nodo viene rimosso dalla lista in cui si
** trova, qualunque essa sia. ATTENZIONE: il nodo DEVE essere inserito
** in una lista
*/
#define REMOVE(n) Remove(NODE(n))
/* Verifica se la lista non contiene elementi */
#define ISEMPTY(l) (LIST(l)->lh_Head->ln_Succ == NULL)
/* Verifica se il nodo e' il nodo fittizio di testa */
#define ISHEAD(n) (NODE(n)->ln_Pred == NULL)
/* Verifica se il nodo e' il primo della lista */
#define ISFIRST(n) (ISHEAD(NODE(n)->ln_Pred))
/* Verifica se il nodo e' il nodo fittizio di coda */
#define ISTAIL(n) (NODE(n)->ln_Succ == NULL)
/* Verifica se il nodo e' l'ultimo della lista */
#define ISLAST(n) (ISTAIL(NODE(n)->ln_Succ))
/* Valuta al primo elemento della lista , forzandone il tipo */
#define HEAD(l,t) ((struct t *)LIST(l)->lh_Head)
/* Valuta all'ultimo elemento della lista , forzandone il tipo */
#define TAIL(l,t) ((struct t *)LIST(l)->lh_TailPred)
/* Valuta all' elemento successore del nodo , forzandone il tipo
*/
#define SUCC(n,t) ((struct t *)NODE(n)->ln_Succ)
/* Valuta all' elemento predecessore del nodo
*/
#define PRED(n,t) ((struct t *)NODE(n)->ln_Pred)
e le liste diventeranno un piacevole e facile strumento per gestire
informazioni dinamiche.
Infatti, moltissime strutture di sistema (se non tutte), sfruttano
ovviamente le liste di sistema.
Una cosa che mi è sempre piaciuta dell'AmigaOS (ma credo che sia la
caratteristica di tutti gli OS) è come usi se stesso moltissime volte
oltre a mettere le sue funzioni a disposizione del programmatore (un po'
come se si usasse il linguaggio C per scrivere un compilatore C).
Fatta questa digressione, possiamo finalmente analizzare la struttura SignalSemaphore:
struct SignalSemaphore
{
struct Node ss_Link;
WORD ss_NestCount;
struct MinList ss_WaitQueue;
struct SemaphoreRequest ss_MultipleLink;
struct Task *ss_Owner;
WORD ss_QueueCount;
};
ss_Link = per memorizzare nome e priorità del semaforo, oltre per
linkarlo nella lista.
ss_NestCount = numero di lock effettuati dal task proprietario; il
semaforo viene rilasciato solo quando il numero di unlock è pari al
numero di lock effettuati.
ss_WaitQueue = lista dei task che aspettano impazienti :-)
ss_MultipleLink = struttura interna usata dalla ObtainSemaphore e soci.
ss_Owner = l'attuale vincitore nella corsa al semaforo :-)
ss_QueueCount = numero dei poverelli ancora in attesa.
Ovvio che modificare a mano questi campi non è consigliato!
Alla prossima.
|