Facciamo un piccolo esempio. Supponiamo che esistano due semafori: uno che regola l'accesso al disco ed uno alla stampante. Nel sistema stanno girando due task, A e B.
A chiede ed ottiene di leggere il disco, nel frattempo (come in ogni caso sfigato che si rispetti) il task A viene sospeso e viene schedulato il task B che fa richiesta ed ottiene di usare la stampante. A questo punto anche il task B viene sospeso e viene ripreso il task A, che disgrazia vuole che anche lui voglia accedere al disco per stampare dei dati!
Adesso è lui che chiede l'accesso alla stampante e gli viene negato poiché è in uso dal task B. Il task A viene sospeso e viene ripreso il task B.
Anche B voleva stampare dei dati, ma la logica del programma prevedeva prima il lock della stampante e poi del disco che ora è occupato da A: blocco! A non può rilasciare il disco se prima non ha ottenuto la stampante e B non può liberare la stampante se prima non ha letto il disco.
Questo è ovviamente un caso limite, ma se pensate che in media i task che girano in un sistema non sono due ma svariate decine, si può facilmente vedere che non è poi un'eventualità tanto improbabile.
Esistono poi diversi algoritmi per cercare di evitare il deadlock, ma questi discorsi esulano dal presente corso.
Ritornando quindi al discorso dei semafori, l'AmigaOS permette al programmatore di crearli liberamente. In questo modo la probabilità di blocco del sistema è sempre bassa o comunque limitabile ai processi che gestiscono il semaforo.
Prima di approfondire i semafori, però, analizziamo un'altra struttura che invece è ampiamente usata dal sistema: le porte messaggi.
Questo utilissimo e praticissimo meccanismo consente ai task di dialogare fra loro scambiandosi messaggi come se fossero in rete. Ovviamente è necessario a priori stabilire il formato e le informazioni contenute nel messaggio, in pratica bisogna fissare il protocollo di comunicazione.
Con questo meccanismo sono realizzati il 99% dei task Amiga (sto parlando ovviamente dei programmi che usano l'OS).
Anche perché l'unico modo con cui intuition comunica con i processi utenti è tramite l'invio di messaggi! Ecco perché è necessario introdurre il discorso sulle porte messaggi.
Vediamo dunque le funzioni che ci mette a disposizione exec:
CreateMsgPort()
PutMsg()
GetMsg()
WaitPort()
ReplyMsg()
DeleteMsgPort()
Queste servono per creare una porta messaggi, mandare un messaggio, prendere un messaggio, sospendere il task fino all'arrivo di un messaggio dalla porta specificata, replicare un messaggio, cancellare la porta.
I messaggi, in generale, se sono mandati dal sistema devono sempre essere replicati, per indicare l'avvenuta ricezione e gestione.
La differenza tra la funzioni GetMsg e WaitPort è che la prima non sospende il task se non ci sono messaggi sulla porta ma semplicente ritorna NULL, mentre la seconda (come spiega il nome) sospende il task in assenza di messaggi e, quando un messaggio arriva, lo risveglia senza rimuovere il messaggio dalla porta. Sarà compito del task risvegliato prendere il messaggio, gestirlo e rimandarlo al mittente.
Bene, concludo questa seconda lezione con un programmino che illustra l'utilizzo delle funzioni di exec per gestire la memoria del programma.
#include <exec/memory.h>
#include <proto/exec.h>
void main(void)
{
APTR point;
if(point = AllocMem(sizeof(UWORD) * 20,MEMF_CLEAR))
{
[...]
FreeMem(point,(sizeof(UWORD) * 20);
}
}
La funzione AllocMem, come la sua cugina AllocVec, richiede il numero di bytes da allocare e il tipo di memoria da allocare. Nel file exec/memory.h vi sono specificati le varie define MEMF_XXXXX da utilizzare. Nel nostro caso, MEMF_CLEAR, richiede al sistema di allocare 20 word e di azzerarle.
La funzione FreeMem, richiede il puntatore e la quantità di memoria allocata. Specificare una dimensione differente può provocare diversi scompensi, per evitare i quali è nato un tool di debug specifico atto a rilevare questo tipo di errore: mungwall.
La FreeVec richiede solo il puntatore poiché le funzioni AllocVec/FreeVec, leggermente più lente delle cugine, eseguono il tracciamento delle dimensioni delle allocazioni, permettendo quindi, all'atto del rilascio, di non specificare la dimensione del blocco precedentemente allocato.
Per questa volta è tutto, alla prossima.