Création de bibliothèques partagées dynamiques Z80

illustrations illustrations illustrations illustrations illustrations illustrations illustrations

Publié le 6 juillet 2023 par Andrew Owen (5 minutes)

post-thumb

Toute personne ayant utilisé le système d’exploitation Windows pendant un certain temps a probablement rencontré l’expression “l’enfer des DLL”, même si elle n’en a pas fait directement l’expérience. Le concept de bibliothèques partagées est commun à de nombreux systèmes d’exploitation. Mais, ce qui rendait la version originale de Windows différente, c’est que l’ensemble du système d’exploitation était composé de bibliothèques de liens dynamiques (DLL) fonctionnant au-dessus du système d’exploitation sur disque (MS-DOS). En règle générale, les incompatibilités entre les versions des DLL sont à l’origine des problèmes.

Traditionnellement, sur des microprocesseurs comme le Z80, où le code est compilé à une adresse fixe, les bibliothèques sont liées statiquement au moment de la compilation. Le code Z80 n’étant pas délocalisable, un éditeur de liens est utilisé pour créer une liste d’adresses dans le binaire à corriger lors de la construction de l’exécutable binaire. Mais, les bibliothèques dynamiques peuvent être chargées à n’importe quelle adresse dans l’espace mémoire au moment du chargement ou de l’exécution. Cela présente des avantages évidents dans un système d’exploitation multi-utilisateurs et multitâches, où les processus peuvent partager les bibliothèques et réduire la surcharge globale de la mémoire. Les bibliothèques dynamiques peuvent être chargées et déchargées selon les besoins.

En fait, il existe un système d’exploitation multitâche pour les microprocesseurs de la série Z80 appelé SymbOS qui fait exactement cela. Le code est compilé à l’adresse $0000 et un fichier patch est fourni pour modifier les adresses mémoire codées en dur. Cependant, le fichier patch ne contient que l’octet le plus significatif des adresses, ce qui signifie que le code doit être chargé sur un bord de page (256 octets).

Après avoir désossé un système d’exploitation sur disque (UnoDOS 3) et créé un interpréteur BASIC (SE Basic IV) pour le projet Chloe 280SE, je travaille maintenant sur un système d’exploitation de plus haut niveau (SE/OS). Cela inclut un ensemble d’API, l’ensemble optionnel de ressources sur disque (comme l’aide en ligne) et ainsi de suite. L’ensemble du microprogramme doit tenir dans moins de 24 kilooctets de mémoire au moment de l’exécution. Cela signifie que certaines des fonctions que j’aimerais inclure dans l’API sont tout simplement trop volumineuses pour être mises en œuvre.

La principale source d’inspiration pour les API a été la suite de démonstrations techniques que j’ai développées pour accompagner le micrologiciel. Il y en a une douzaine aujourd’hui, et elles contiennent beaucoup de code répété. Dans la mesure du possible, j’ai remplacé ce code par des appels d’API. Le seul élément de code que presque tous utilisent et qui ne peut être réduit à un appel d’API est le lecteur de musique. Il semble être un candidat idéal pour une bibliothèque.

Contrairement à la plupart des systèmes d’exploitation modernes, SE/OS est monolithique, mono-utilisateur et mono-tâche. L’objectif est de garder les choses relativement simples. Cependant, lorsque vous disposez d’un espace d’adressage de 64 kilo-octets, il y a toujours un avantage à pouvoir utiliser des bibliothèques dynamiques qui peuvent être chargées et déchargées sans avoir à paginer la mémoire. La question qui se pose alors est de savoir quel format doit prendre le fichier. J’ai envisagé la possibilité de RIFF, mais j’ai finalement opté pour une solution plus simple proposée par Chris Smith.

Le fichier doit comporter un en-tête composé d’un identifiant d’octet, d’un numéro de version, de l’adresse de départ et de la longueur du binaire, ainsi que de l’adresse de départ et de la longueur des données du correctif. En définissant explicitement ces propriétés, il devient possible d’étendre le format de l’en-tête à l’avenir sans rompre la compatibilité. Comme les données de correction se trouvent après le binaire, ce dernier peut être chargé et les données de correction peuvent être analysées au fur et à mesure qu’elles sont lues dans le fichier, sans qu’il soit nécessaire de placer l’ensemble du contenu dans un tampon. Tout ce qui se trouve après les données de correction sera ignoré. Un bloc d’information optionnel en texte brut peut donc être inclus.

Le binaire doit être compilé à $0000 et un ensemble d’adresses de correctifs doit être généré. L’API SE/OS devrait fournir une méthode API pour charger la DLL, en fournissant une adresse de départ. Si la DLL doit recevoir une adresse de données, celle-ci doit être gérée par la DLL elle-même. L’appel à l’API devrait renvoyer la longueur du binaire. Il ne me reste plus qu’à persuader mon sympathique développeur d’assembleur d’implémenter la prise en charge de ce format.

Mais je me suis dit que si je devais inclure des fichiers avec des en-têtes dans SE/OS, je devrais utiliser une approche uniforme. Et la plus logique semble être le Resource Interchange File Format (RIFF). Il s’agit d’un format d’enveloppe conçu par IBM et Microsoft en 1991. Il s’agit essentiellement d’une version little-endian de l’Interchange File Format (IFF) créé par Electronic Arts pour l’Amiga en 1985. Ce format s’inspirait des OSTypes à quatre caractères ASCII utilisés dans le système d’exploitation original du Macintosh en 1984.

Le RIFF a été utilisé pour toutes sortes de fichiers, des fichiers AVI aux fichiers WAV, et plus récemment par Google pour le format d’image WebP en 2010. Il existe quelques outils pour travailler avec RIFF, et en règle générale, je préfère éviter de réinventer la roue dans la mesure du possible.

Image: Original par Jonathan Borba.