Obligement - L'Amiga au maximum

Vendredi 19 avril 2019 - 01:04  

Translate

En De Nl Nl
Es Pt It Nl


Rubriques

 · Accueil
 · A Propos
 · Articles
 · Galeries
 · Glossaire
 · Liens
 · Liste jeux Amiga
 · Quizz
 · Téléchargements
 · Trucs et astuces


Articles

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

 · Articles in english
 · Articles en d'autres langues


Twitter

Suivez-nous sur Twitter




Liens

 · Sites de téléchargements
 · Associations
 · Pages Personnelles
 · Matériel
 · Réparateurs
 · Revendeurs
 · Presse et médias
 · Programmation
 · Logiciels
 · Jeux
 · Scène démo
 · Divers


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


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


Partenaires

Annuaire Amiga

Amedia Computer

Relec

Hit Parade


Contact

David Brunet

Courriel

 


Programmation : C - Programmer réentrant
(Article écrit par Denis Jarril et extrait d'Amiga News Tech - septembre 1991)


Sur une machine multitâche telle que l'Amiga, la programmation réentrante n'est pas forcément souvent utilisée : un programme peut être chargé plusieurs fois et cohabiter en mémoire sans problème particulier avec une ou plusieurs copies de lui-même.

Ceci est plus particulièrement vrai pour les grosses applications (traitement de texte, MIDI...) et les utilitaires (copieur, économiseur d'écran, etc.). Il arrive cependant assez souvent que l'on ait besoin d'écrire un utilitaire particulier, assimilable à une commande AmigaDOS standard, par exemple un Copy amélioré ou un Dir plus souple. Dans ce cas, programmer réentrant permet par la suite de laisser ce programme résident en mémoire (commande Resident du CLI/Shell) avec tous les avantages que cela implique : fragmentation de la mémoire supprimée (une seule copie du programme étant chargée en mémoire) et accélération notable de l'exécution des fichiers Script (commande Execute) utilisant le programme. Le seul inconvénient de ce procédé réside dans le fait que la mémoire occupée par le programme résident n'est pas libérée lorsque celui-ci a terminé son travail ; AmigaDOS l'y laisse en prévision d'une autre invocation.

Il est également obligatoire, lorsque l'on écrit une bibliothèque Exec, que son code puisse être partagé par plusieurs tâches pouvant y accéder simultanément (c'est d'ailleurs pourquoi tout le système d'exploitation de l'Amiga repose sur le principe des listes et des structures).

Programmes résidents

Dans la version 1.3 du système d'exploitation, ni le CLI ni le Workbench n'utilisent les programmes résidents ; ils chargent quoiqu'il arrive une nouvelle copie du programme en mémoire et l'en suppriment lorsqu'il se termine. Le Shell, lui, commence par parcourir la liste des commandes rendues résidentes (dans la startup-sequence ou plus tard, manuellement) avant de décider s'il doit charger la commande appelée depuis le disque ou non.

Ainsi, la startup-sequence standard telle qu'elle est fournie par Commodore-Amiga contient normalement les lignes :

Resident c:Resident PURE ADD
Resident c:Mount PURE ADD

...qui ont pour effet de charger les commandes Resident et Mount en mémoire une bonne fois pour toutes. Plus tard dans la startup-sequence, lorsque l'on montera les périphériques logiques SPEAK:, PIPE: et AUX:, la copie résidente de Mount sera utilisée.

Dans la théorie, n'importe quel programme peut-être rendu résident : il suffit de positionner l'attribut de protection "p" pour que ça marche. Des problèmes risquent de survenir dès lors que plusieurs Shell essayent d'accéder à la même commande en même temps : on se retrouve alors dans le cas où deux tâches partagent les mêmes adresses, aussi bien pour le code que pour les données. Si l'on n'y prend pas garde lors de la rédaction du programme, cela peut causer de grosses difficultés, voire mener tout droit au Gourou.

Réentrance

Le principe de la réentrance est simple : il suffit de faire attention à ce que les variables globales, donc partagées par les différents accesseurs au code, ne soient pas jamais modifiées. Cela est bien entendu valable pour les pointeurs (par exemple sur des zones de mémoire allouées ou sur des fichiers ouverts) et pour les compteurs de boucles. Si l'assembleur permet d'écrire assez facilement des programmes réentrants (par l'utilisation des registres du processeur et de la pile), c'est beaucoup moins évident en C.

Dans ce langage, le problème se pose au niveau des variables globales : alors que les variables locales à une fonction sont allouées sur la pile (au moyen du couple d'instructions 68000 LINK et UNLK), les variables globales sont allouées statiquement, dans une section de type BSS. Cela peut bien sûr être évité, pour peu que l'on prenne la peine de se souvenir de quelques points importants. Voici donc les règles à suivre pour écrire un programme C réentrant.

Tout d'abord, le Startup-Code doit être modifié. C'est lui qui se charge d'ouvrir la dos.library et les canaux d'entrées-sorties standards stdin, stdout et stderr et de décomposer la ligne de commande CLI/Shell en un tableau de pointeurs nommé argv. Avec le Startup-Code standard, AStartup.obj, toutes ces valeurs sont stockées dans les variables globales statiques _DOSBase, _stdin, _stdout, _stderr, _argc et _argv. C'est pourquoi un nouveau Startup-Code a été écrit. Baptisé RStartup.obj, il accomplit les mêmes opérations mais alloue dynamiquement ces variables. Ainsi, à chaque nouvelle invocation du programme résident, elles seront correctement actualisées. Avec RStartup.obj, les variables globales _DOSBase et _SysBase sont les seules à pouvoir être utilisées sans risque. Pour _stdin, _stdout, _stderr, _argc et _argv, il faudra ruser.

Comme mentionné précédemment, les bibliothèques système sont réentrantes. Cela veut (heureusement !) dire que l'on peut continuer à les utiliser dans un programme réentrant sans risque de voir apparaître le Gourou. Le problème se pose toutefois au niveau des adresses de base des bibliothèques utilisées (IntuitionBase, GfxBase, etc.). En effet, l'éditeur de liens attend toujours qu'elles soient déposées dans des variables globales afin de pouvoir résoudre les références. La solution consiste donc à effectivement déclarer ces adresses de base deux fois : une première fois locales et une seconde fois globales. La variable locale recevra le résultat d'OpenLibrary() et n'initialisera la variable globale que si l'ouverture a réussi. Les adresses de base étant fixes (une fois la bibliothèque ouverte, elle n'est pas déplacée par Exec), cela fonctionnera très bien.

Revenons-en à _stdin, _stdout, _stderr, _argc et _argv. Ces deux dernières sont transmises à main() par la pile et peuvent être utilisées sans problème. Pour les trois premières, il conviendra par contre d'ouvrir explicitement les canaux d'entrées-sorties à chaque invocation du programme, avec les fonctions Input() et Output() de la dos.library. Ce qui impose également des restrictions au niveau des fonctions qui utilisent ces pointeurs, à savoir l'incontournable printf(), mais aussi getch(), etc. Il vaut mieux utiliser à la place fprint() avec le résultat d'Output() pour argument et fgetc() avec le résultat d'Input() pour argument.

Évidemment, garder une multitude de variables locales n'est pas forcément très pratique, surtout lorsqu'il s'agit de les passer en paramètres à des fonctions. C'est pourquoi je vous conseille fermement d'utiliser une seule structure, définie par vos soins et allouée dynamiquement, qui contiendra toutes vos variables normalement globales. Il vous suffit alors de conserver un pointeur local à main() sur cette structure, que vous passerez aux fonctions en ayant besoin. Le programme d'exemple en fin de cet article démontre ce procédé, inspiré d'un article de Carolyn Sheppner paru dans le Bus Commodore.

Les règles du jeu

Voici, en résumé, les règles à suivre pour écrire un programme réentrant sur Amiga :
  • Ne jamais utiliser les variables globales _stdin, _stdout, _stderr, _errno et _WBenchMsg.
  • Pour les entrées-sorties, utiliser des variantes des fonctions printf(), puts(), getchar(), etc. qui se servent de Output() et d'Input() plutôt que de _stdout et _stdin, ou bien utiliser fprintf() et fgetc().
  • Ne pas utiliser de variables globales ni statiques. Les seules globales acceptables sont les constantes : chaînes de caractères, structures NewWindow, NewScreen, Gadget, Menu, etc. qui ne seront pas modifiées par la suite.
  • Un programme réentrant n'a pas besoin d'être relogeable.
Le programme d'exemple

/*
 * Exemple de programme ré-entrant en C.
 * Se contente d'ouvrir une fenêtre et d'attendre
 * qu'on la ferme.
 *
 * Compilation (SAS/Lattice 5.10) :
 *  LC -d4 -v -Li -ck -tr -ms -y -N -La -Lc -Ld -Lv Reentrant.c
 *
 * Linkage :
 *       FROM RStartup.obj,Reentrant.o
 *         TO Reentrant
 *    LIBRARY LIB:amiga.lib,LIB:lc.lib
 *  SMALLCODE
 *  SMALLDATA
 *  [NODEBUG]
 *  [VERBOSE]
 */

#include <exec/types.h>
#include <exec/memory.h>
#include <intuition/intuition.h>
#include <intuition/intuitionbase.h>
#include <graphics/gfxbase.h>
#include <graphics/text.h>
#include <libraries/dos.h>

/* Voici la structure qui définit nos variables */
struct Variables
{	ULONG stdin;
	ULONG stdout;
	struct IntuitionBase *intbase;
	struct GfxBase *gfxbase;
	struct Window *win;
	struct IntuiMessage *imsg;
};

/* Les constantes peuvent être globales */
/* Ca, c'est pour ouvrir notre fenêtre */
struct NewWindow nw=
{	0, 11, 420, 40,
	0, 1,
	CLOSEWINDOW,
	WINDOWCLOSE|WINDOWDRAG|WINDOWDEPTH|NOCAREREFRESH,
	NULL, NULL,
	"Fermez moi",	/* Pas besoin d'être relogeable */
	NULL, NULL,
	-1, -1, -1, -1,
	WBENCHSCREEN
};

/* petit texte à afficher dans la fenêtre */
char *msg = "Programme réentrant : lancez-le d'un autre Shell !";

/* Deux variables gloables pour le linker */
struct IntuitionBase *IntuitionBase;
struct GfxBase *GfxBase;

/* Prototypes des fonctions */
VOID main(int argc, char **argv);
VOID CleanExit(char *str, struct Variables *VARS);

/* Et voilà main() */
void main(int argc, char **argv)
{
struct Variables *VARS;	/* VARS est local à main() */

/* Allocation du buffer des variables */
	VARS = (struct Variables *)AllocMem(sizeof(struct Variables), MEMF_CLEAR);
	if (!VARS)
		exit(RETURN_FAIL);

/* Recherche des canaux d'entrées-sorties */
	VARS->stdin  = Input();
	VARS->stdout = Output();

/*
 * Ouverture d'intuition.library
 * C'est notre variable locale VARS->intbase qui contient le pointeur
 * sur l'adresse de base de la bibliothèque. Toutefois, pour que le
 * linker soit content, il faudra également initialiser la variable
 * globale IntuitionBase si l'ouverture réussit
 */
	VARS->intbase = (struct IntuitionBase *)OpenLibrary("intuition.library", 33L);
	if (!VARS->intbase)
		CleanExit("Pas d'intuition.", VARS);
	else
		IntuitionBase = VARS->intbase;

/* Idem pour la graphics.library */
	VARS->gfxbase = (struct GfxBase *)OpenLibrary("graphics.library", 33L);
	if (!VARS->gfxbase)
		CleanExit("Pas de graphics.", VARS);
	else
		GfxBase = VARS->gfxbase;

/* Ouverture de la fenêtre */
	VARS->win = (struct Window *)OpenWindow(&nw);
	if (!VARS->win)
		CleanExit("Pas de fenêtre !", VARS);

/* Ecriture du message */
	SetAPen(VARS->win->RPort, 1);
	SetBPen(VARS->win->RPort, 0);
	SetDrMd(VARS->win->RPort, JAM2);
	Move(VARS->win->RPort, 5, 24);
	Text(VARS->win->RPort, msg, strlen(msg));

/* Attend la fermeture */
	WaitPort(VARS->win->UserPort);

/* Réponse normale aux messages Intuition */
	while( (VARS->imsg = (struct IntuiMessage *)GetMsg(VARS->win->UserPort)) )
		ReplyMsg((struct Message *)VARS->imsg);

/* C'est fini, on s'casse */
	CleanExit(NULL, VARS);
}

/*
 * CleanExit()
 */
VOID CleanExit(char *str, struct Variables *VARS)
{
UWORD rc;	/* encore une variable locale */

/* Fermeture de la fenêtre */
	if (VARS->win)
		CloseWindow(VARS->win);

/* Fermeture de graphics.library. On utilise la variable locale ç */
	if (VARS->gfxbase)
		CloseLibrary(VARS->gfxbase);

/* Fermeture d'intuition.library */
	if (VARS->intbase)
		CloseLibrary(VARS->intbase);

/* Affiche un texte dans la fenêtre Shell */
	if (str)
	{	fprintf(VARS->stdout, "%s\n", str);
		rc = RETURN_FAIL;
	}
	else
	{	fprintf(VARS->stdout, "Tout s'est bien passé.\n");
		rc = RETURN_OK;
	}

/* Ne pas oublier de libérer la mémoire des variables ! */
	FreeMem(VARS, sizeof(struct Variables));
	exit(rc);
}



[Retour en haut] / [Retour aux articles]