Suivez-nous sur X

|
|
|
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
|
|
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
|
|
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
|
|
A propos d'Obligement
|
|
David Brunet
|
|
|
|
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 standard 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);
}
|
|