Obligement - L'Amiga au maximum

Vendredi 06 juin 2025 - 12:20  

Translate

En De Nl Nl
Es Pt It Nl


Rubriques

Actualité (récente)
Actualité (archive)
Comparatifs
Dossiers
Entrevues
Matériel (tests)
Matériel (bidouilles)
Points de vue
En pratique
Programmation
Reportages
Quizz
Tests de jeux
Tests de logiciels
Tests de compilations
Trucs et astuces
Articles divers

Articles in English


Réseaux sociaux

Suivez-nous sur X




Liste des jeux Amiga

0, A, B, C, D, E, F,
G, H, I, J, K, L, M,
N, O, P, Q, R, S, T,
U, V, W, X, Y, Z,
ALL


Trucs et astuces

0, A, B, C, D, E, F,
G, H, I, J, K, L, M,
N, O, P, Q, R, S, T,
U, V, W, X, Y, Z


Glossaire

0, A, B, C, D, E, F,
G, H, I, J, K, L, M,
N, O, P, Q, R, S, T,
U, V, W, X, Y, Z


Galeries

Menu des galeries

BD d'Amiga Spécial
Caricatures Dudai
Caricatures Jet d'ail
Diagrammes de Jay Miner
Images insolites
Fin de jeux (de A à E)
Fin de Jeux (de F à O)
Fin de jeux (de P à Z)
Galerie de Mike Dafunk
Logos d'Obligement
Pubs pour matériels
Systèmes d'exploitation
Trombinoscope Alchimie 7
Vidéos


Téléchargement

Documents
Jeux
Logiciels
Magazines
Divers


Liens

Associations
Jeux
Logiciels
Matériel
Magazines et médias
Pages personnelles
Réparateurs
Revendeurs
Scène démo
Sites de téléchargement
Divers


Partenaires

Annuaire Amiga

Amedia Computer

Relec


A Propos

A propos d'Obligement

A Propos


Contact

David Brunet

Courriel

 


Programmation : C - Communication intertâche (les bases)
(Article écrit par Max et extrait d'Amiga News Tech - juillet 1991)


Il est des cas fréquents, sur une machine multitâche, où plusieurs programmes ont besoin de communiquer entre eux : le domaine public regorge d'exemples d'utilitaires initialisant des tâches de fond, auxquelles le programme principal pourra plus tard transmettre diverses informations suivant ses besoins du moment.

Mais l'exemple le plus évident est encore Intuition et ses fameux drapeaux IDCMP, dont notre Pascal Amiable national vous entretient dès qu'il en a l'occasion, avec tout le brio qu'on lui connaît : chaque fois qu'Intuition a besoin de signaler à une application un évènement particulier, elle le fait par le biais d'IntuiMessages, qui contiennent toutes les informations nécessaires (type de l'évènement, position de la souris au moment où il est survenu, état du clavier et plus particulièrement des touches spéciales, heure système...).

Ce mécanisme de communication intertâche fait partie intégrante d'Exec, à travers ses signaux et ses ports de messages. Le principe est à la fois fort simple et ingénieux : les tâches communiquent entre elles au moyen de ports de messages (MsgPort), qui sont autant de boîtes aux lettres grâce auxquelles elles peuvent s'envoyer des petits mots d'amour (Messages). Et comme les tâches sont des gens très polis, elles se font un devoir de répondre à chaque message, juste histoire de signaler à l'envoyeur qu'il a bien été reçu. Le seul rôle d'Exec dans tout ça est de véhiculer les messages d'un port à l'autre. Pour continuer l'analogie ci-dessus, c'est Monsieur P&T en personne.

Mords-moi le noeud

L'une des particularités du système d'exploitation de l'Amiga est de ne disposer d'aucune adresse fixe (hormis l'adresse 4, qui contient un pointeur sur la structure ExecBase). Cela signifie plus précisément, qu'un programme ne peut jamais savoir où se trouve en mémoire une structure particulière sans d'abord la rechercher explicitement. C'est pourquoi Exec offre un mécanisme de localisation basé sur le principe des listes chaînées : chaque membre de la liste contient un pointeur sur son prédécesseur et sur son successeur. Ainsi, en parcourant la liste, on peut facilement retrouver un membre particulier.

Ce principe a un autre avantage, la rapidité : on peut facilement ajouter ou supprimer un membre de la liste simplement en modifiant quelques pointeurs, alors que dans le cas d'une liste séquentielle (les membres sont à la suite les uns des autres), on serait obligé de procéder à un déplacement de bloc mémoire coûteux en temps machine, et donc en performance du système.

Dans la terminologie de l'Amiga, un membre d'une liste est appelé un noeud (Node). Exec tout entier repose sur le principe des noeuds et des listes. Le lecteur averti - celui qui en vaut deux - pourra toujours sauter le paragraphe suivant s'il en connaît déjà le principe. Pour le novice, précisons simplement que les listes sont largement utilisées par les ports de messages et qu'il convient donc d'en saisir parfaitement le fonctionnement avant que d'aller plus loin.

On enchaîne

La sagesse populaire dit qu'il faut un début à tout : une liste chaînée n'échappe pas à cette règle. Comme on y est maintenant habitué avec l'Amiga, le début d'une liste est représenté par une structure C, que l'on trouve dans les includes "exec/lists.h" et "exec/lists.i" :

struct List
{   struct Node *lh_Head;
    struct Node *lh_Tail;
    struct Node *lh_TailPred;
    UBYTE  lh_Type;
    UBYTE  l_pad; 
};

"lh_Head" est un pointeur sur le premier noeud de la liste. "lh_Tail" est toujours NULL, et "lh_TailPred" pointe sur le dernier noeud de la liste. Une liste est dite vide lorsqu'elle ne contient aucun noeud, c'est-à-dire lorsque le champ "lh_TailPred" pointe sur une valeur nulle et "lh_Head" sur la liste elle-même. "lh_Type" contient un code indiquant le type de la liste, et "l_pad" fait en sorte que la structure contienne un nombre pair d'octets.

Note : le fichier "exec/lists.h" tel qu'il est fourni avec le compilateur SAS/Lattice C 5.10 fait état d'un champ "l_pad" alors que la logique voudrait que ce soit "lh_pad". Je préfère quant à moi m'en tenir à la version Lattice, puisque c'est elle qui sera utilisée en cas de référence à ce champ dans un programme.

Un noeud est également représenté par une structure, que l'on trouve cette fois-ci dans "exec/nodes.h" et "exec/nodes.i" :

struct Node
{   struct Node *ln_Succ;
    struct Node *ln_Pred;
    UBYTE  ln_Type;
    BYTE   ln_Pri;
    char   *ln_Name;
};

"ln_Succ" pointe sur le prochain noeud de la liste et "ln_Pred" sur le précédent. Le dernier noeud d'une liste est caractérisé par un champ "In_Succ" initialisé à 0 (NULL). "ln_Type" indique le type du noeud (évidemment, une liste d'un type donné ne peut contenir que des noeuds du même type) et "ln_Pri" sa priorité au sein de la liste. Enfin, "ln_Name" pointe sur une chaîne de caractères pouvant représenter le nom du noeud, défini au gré du programmeur, et qui pourra être utilisé pour retrouver un noeud particulier par son nom.

Voilà. La dernière chose que l'on puisse dire sur les listes, est que la bibliothèque exec.library fournit toute une ribambelle de fonctions pour leur gestion : initialisation, insertion, suppression et recherche de noeuds sont au menu du jour.

Signal sans fluor

Le signal est le niveau le plus simple et le plus performant à la fois de la communication intertâche. Leur utilisation la plus simple consiste simplement à synchroniser deux tâches entre elles, mais ils sont également utilisés, justement, dans les ports de messages.

Chaque tâche peut disposer d'un maximum de 32 signaux, qui correspondent à autant de bits du champ "tc_SigWait" de sa structure Task. Sur ces 32 signaux, 16 sont réservés par le système pour des évènements particuliers, par exemple les quatre "break" possibles (Ctrl/C, Ctrl/D, Ctrl/E et Ctrl/F). Les 16 autres sont à la disposition de la tâche, qui peut leur assigner les évènements qu'elle désire. Ceci est très important : le signal numéro 13 peut signifier pour une certaine tâche qu'un temps donné vient de s'écouler, alors que pour la tâche voisine, il signifiera peut-être qu'un message vient d'arriver sur son port.

Une tâche alloue un signal avec la fonction AllocSignal(). Il faut en effet savoir que chaque fois qu'une tâche crée, directement ou non, un port de message (par exemple, en ouvrant une fenêtre avec des drapeaux IDCMP), un nouveau signal lui est alloué par le système. En d'autres termes, si elle désire se réserver un signal particulier, elle doit le faire de manière à ce que ce signal ne soit pas réutilisé à son insu, d'où l'utilité d'AllocSignal(). Cette fonction demande en paramètre le numéro du signal à allouer, où -1 pour le prochain libre. En pratique, on transmet toujours -1, laissant ainsi à Exec le soin de choisir le bon signal. En retour, AllocSignal() transmet le numéro du signal alloué, ou -1 si aucun n'était disponible. En C, cela donne :

C

Lorsqu'un signal n'est plus nécessaire, il doit être libéré au moyen de la fonction FreeSignal() :

FreeSignal(signal);

Avant de pouvoir être utilisé, un signal doit être converti en masque, ceci afin d'accélérer le traitement de la demande par Exec. Ceci se fait de manière très simple, par l'instruction suivante :

ULONG signalmask;
signalmask = 1L << signal;

Une tâche peut alors attendre qu'un ou plusieurs signaux qu'elle a alloué surviennent (encore une fois, les signaux peuvent lui être alloués indirectement par le système) au moyen de la fonction Wait(), qui demande en paramètre le masque des signaux à attendre. Par exemple, imaginons un programme ayant ouvert une fenêtre avec un menu (disposant donc de drapeau IDCMP) ainsi que l'input.device, et qui peut se faire signaler par un serveur d'interruption qu'il a mis en place, qu'un temps donné vient de s'écouler. En C, cela ressemblera à quelque chose du genre :

C

Rappelez-vous qu'une tâche donnée peut attendre jusqu'à 16 signaux simultanément.

Enfin, pour envoyer un signal, une tâche doit d'abord savoir à qui elle va l'adresser, et bien sûr, quel signal envoyer (n'oubliez pas que chaque tâche dispose de ses propres signaux). C'est pourquoi deux tâches qui désirent communiquer ne peuvent le faire que si les deux sont parfaitement au courant de ce qui va se passer. Quand la tâche émettrice signale la tâche réceptrice, elle utilise la fonction Signal(), qui demande en paramètre l'adresse de la tâche à signaler, et le masque du signal à lui envoyer :

Signal(task, signalmask);

Les marins qui pissent

Dans le port de message, on retrouve le signal en tant que base de la communication. Un message est composé de deux parties distinctes une utilisée par Exec pour envoyer le message à bon port, l'autre étant le corps même du message. Cette partie est entièrement et librement définissable par le programmeur, qui peut donc en faire ce qu'il veut, dans la limite toutefois de... 64 ko !

Les messages sont envoyés sur des ports de messages (MsgPort), selon un système de file d'attente : le premier message arrivé sur un port en est le premier extrait (en anglais, first in first out, ou encore FIFO). Il n'y a aucune restriction autre que la quantité de mémoire disponible, quant au nombre de ports dont une tâche peut disposer et au nombre de messages qui peuvent arriver sur chaque port.

Pour des raisons de performance, Exec ne procède pas à la copie des messages dans une mémoire tampon. En clair, cela signifie que si une tâche A envoie un message à une tâche B, les deux tâches vont se partager - provisoirement - l'espace mémoire occupé par ce message. La tâche A doit donc faire attention à ne pas modifier le contenu du message avant que la tâche B en ait terminé avec celui-ci, ce qu'elle indique en renvoyant le même message à la tâche A. Exec offre bien entendu toute une panoplie de fonctions dédiées à l'envoi, l'attente, la réception et la réponse aux messages.

Salut les ports

Les ports de messages sont donc en quelque sorte des points de rendez-vous où tous les messages destinés à une tâche particulière arrivent. En fait et pour être plus précis, tous les messages destinés à une tâche particulière et envoyés par une autre tâche non moins particulière. En d'autres termes, si une tâche désire communiquer avec - par exemple - Intuition, le console.device et le trackdisk.device, elle devra posséder trois ports de messages, un par tâche communicante.

Encore une fois, c'est une structure C qui définit l'aspect d'un port de messages. On trouve sa définition dans les includes "exec/ports.h" et "exec/ports.i" :

struct MsgPort
{   struct Node   mp_Node;
    UBYTE         mp_Flags;
    UBYTE         mp_SigBit;
    struct Task  *mp_SigTask;
    struct List   mp_MsgList;
};

Le premier champ de cette structure, "mp_Node", est un noeud standard, qui peut être utilisé pour donner un nom à ce port (par l'intermédiaire du champ "ln_Name" de la structure Node). Ceci peut se révéler très pratique lorsqu'une tâche désire savoir si une autre tâche est déjà présente en mémoire : grâce à la fonction FindPort(nom), elle peut très aisément s'en rendre compte.

Le champ "mp_Flags" indique à Exec ce qu'il doit faire lorsqu'un message arrive sur ce port. Avec PA_SIGNAL, Exec signale - par la fonction Signal() - à la tâche désignée par "mp_SigTask", l'arrivée du message ; avec PA_SOFTINT, une interruption est déclenchée ; enfin, avec PA_IGNORE, rien ne se passe, le message est simplement inséré dans la file d'attente.

"mp_SigBit" contient le numéro du signal alloué à ce port. "mp_SigTask" est un pointeur sur la tâche devant être signalée lorsqu'un message arrive sur ce port (si mp_Flags == PA_SIGNAL), et "mp_MsgList" est le début de la liste des messages déjà arrivés sur ce port.

Pour se créer un port de messages, une tâche doit donc initialiser une structure MsgPort en remplissant correctement le noeud et en initialisant la liste. Si ce port doit être rendu publique, c'est-à-dire si d'autres tâches peuvent y accéder, elle devra de plus appeler la fonction AddPort() d'exec.library. Heureusement, la fonction CreatePort() contenue dans amiga.lib, simplifie tout ce processeur de création. Elle demande pour paramètre le nom du port à créer (ou NULL s'il ne doit pas être rendu publique) et sa priorité :

monPort = CreatePort("Max.Port", NULL);

Quand une tâche n'a plus besoin d'un port, elle doit le supprimer de la liste des ports publiques le cas échéant - c'est-à-dire s'il y avait été inséré avec AddPort() - au moyen de la fonction RemPort(). La fonction d'amiga.lib DeletePort() s'occupe de tout ça ; il suffit de lui transmettre un pointeur sur le port à supprimer :

DeletePort(monPort);

Tous les messages restant dans la file d'attente de ce port devront en avoir été enlevés, ce qui s'obtient en y répondant jusqu'à ce que la file soit vide. Mais je parle, je parle, et je constate que la place qui m'est impartie est bien remplie. Nous verrons donc le mois prochain les messages et leurs différents types (depuis les messages standard d'Exec et Intuition jusqu'aux messages personnels qu'une tâche peut envoyer) et mettrons enfin en pratique tout ce qui vient d'être dit. Asta la vista !


[Retour en haut] / [Retour aux articles] [Article suivant]