Corso di AmigaOS

Torna all'elenco delle lezioniPer dubbi, consigli o richieste, potete mandare un'e-mail ad Andrea Carolfi.
Ringraziamo Amiga Transactor Mailing List per questo tangibile contributo!


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.

Lezione precedente Indice delle lezioni Lezione successiva
Copyright AMiWoRLD Ph0ton