* Si les paramètres du programme utilisateur pointent vers une partie de la mémoire à la page 3 (dernière page), il y a un conflit car le noyau mappera toujours sa page RAM à l'intérieur de cette même page lors d'un système. Ainsi, il remémorera la page 3 de l'utilisateur dans la page 2 (troisième page) pour accéder aux paramètres du programme. Bien sûr, dans le cas où les paramètres sont des pointeurs, ils seront modifiés pour les laisser pointer vers la nouvelle adresse virtuelle (en d'autres termes, un pointeur sera soustrait par 16 Ko pour le laisser pointer vers la page 2).
Pour pouvoir porter un système d'exploitation 8 bits sur le zèle vers des ordinateurs à base de Z80 qui n'ont pas de maman MMU / mémoire organisé comme indiqué ci-dessus, le noyau a un nouveau mode qui peut être choisi via le menuconfig : no-MMU.
Dans ce mode, le code OS devrait toujours être mappé dans les 16 Ko de la mémoire, de 0x0000 à 0x3FFF et le reste devrait être RAM.
Idéalement, 48 Ko de RAM devraient être cartographiés à partir de 0x4000 et allait jusqu'à 0xFFFF , mais en pratique, il est possible de configurer le noyau pour attendre moins que cela. Pour ce faire, deux entrées dans le menuconfig doivent être configurées de manière appropriée:
KERNEL_STACK_ADDR : Cela marque la fin de la zone RAM du noyau et, comme son nom, sera le bas de la pile du noyau.KERNEL_RAM_START : Cela marque l'adresse de début de la RAM du noyau où la pile, toutes les variables utilisées par le noyau et les pilotes seront stockées. Bien sûr, il doit être suffisamment grand pour stocker toutes ces données. Pour plus d'informations, la taille actuelle de la section BSS du noyau est d'environ 1 Ko. La profondeur de pile dépend de l'implémentation des pilotes cibles. L'allocation de 1KB pour la pile devrait être plus que suffisante tant qu'aucun (gros) tampon ne soit stocké dessus. L'allocation globale au moins 3 Ko pour le RAM du noyau devrait être sûre et à l'épreuve de l'avenir.Pour résumer, voici un diagramme pour montrer l'utilisation de la mémoire:
En ce qui concerne les programmes utilisateur, l'adresse de pile sera toujours définie sur KERNEL_RAM_START - 1 par le noyau avant l'exécution. Il correspond également à l'adresse de son dernier octet disponible dans son espace d'adressage utilisable. Cela signifie qu'un programme peut déterminer la taille de la RAM disponible en effectuant SP - 0x4000 , qui donne, en assemblage:
ld hl, 0
add hl, sp
ld bc, -0x4000
add hl, bc
; HL contains the size of the available RAM for the program, which includes the program's code and its stack.
Z80 présente plusieurs registres à usage général, tous ne sont pas utilisés dans le noyau, voici la portée de chacun d'eux:
| Registre | Portée |
|---|---|
| AF, BC, DE, HL | Système et application |
| Af ', bc', de ', hl' | Interrompre les gestionnaires |
| Ix, iy | Application (inutilisée dans le système d'exploitation) |
Cela signifie que le système d'exploitation ne modifiera pas les registres IX et IY, afin qu'ils puissent être utilisés librement dans l'application.
Les registres alternatifs (noms suivis de ' ) ne peuvent être utilisés que dans les gestionnaires d'interruption 1 . Une application ne doit pas utiliser ces registres. Si pour une raison quelconque, vous devez toujours les utiliser, veuillez envisager de désactiver les interruptions pendant le temps où ils sont utilisés:
my_routine:
di ; disable interrupt
ex af, af' ; exchange af with alternate af' registers
[...] ; use af'
ex af, af' ; exchange them back
ei ; re-enable interrupts
Gardez à l'esprit que la désactivation des interruptions pendant trop longtemps peut être nocive car le système ne recevra aucun signal du matériel (minuteries, clavier, gpios ...)
Le Z80 fournit 8 vecteurs de réinitialisation distincts, car le système est censé être toujours stocké dans la première page virtuelle de mémoire, tous sont réservés au système d'exploitation:
| Vecteur | Usage |
|---|---|
| 00 $ | Réinitialisation du logiciel |
| 08 $ | Système |
| 10 $ | Saute à l'adresse dans HL (peut être utilisé pour appeler HL) |
| 18 $ | Inutilisé |
| 20 $ | Inutilisé |
| 28 $ | Inutilisé |
| 30 $ | Inutilisé |
| 38 $ | Réservé pour le mode d'interruption 1, utilisable par l'implémentation cible |
Lorsqu'un programme utilisateur est exécuté, le noyau alloue 3 pages de RAM (48KB), lit le fichier binaire à exécuter et le charge à partir de l'adresse virtuelle 0x4000 par défaut. Cette adresse virtuelle de point d'entrée est configurable via le menuconfig avec l'option KERNEL_INIT_EXECUTABLE_ADDR , mais gardez à l'esprit que les programmes existants ne fonctionneront plus sans être recompilés car ils ne sont pas relocables au moment de l'exécution.
Comme décrit ci-dessous, l' exec Syscall prend deux paramètres: un nom de fichier binaire à exécuter et un paramètre.
Ce paramètre doit être une chaîne à terminaison nulle qui sera copiée et transmise au binaire pour exécuter via les registres DE et BC :
DE contient l'adresse de la chaîne. Cette chaîne sera copiée dans l'espace mémoire du nouveau programme, généralement au-dessus de la pile.BC contient la longueur de cette chaîne (donc, à l'exclusion du nul-octet). Si BC est 0, DE doit être rejeté par le programme utilisateur. Le système s'appuie sur des systèmes pour effectuer des demandes entre le programme utilisateur et le noyau. Ainsi, ce sera le moyen d'effectuer des opérations sur le matériel. Les opérations possibles sont répertoriées dans le tableau ci-dessous.
| Nobs | Nom | Param. 1 | Param. 2 | Param. 3 |
|---|---|---|---|---|
| 0 | lire | U8 Dev | u16 buf | Taille U16 |
| 1 | écrire | U8 Dev | u16 buf | Taille U16 |
| 2 | ouvrir | Nom U16 | drapeaux U8 | |
| 3 | fermer | U8 Dev | ||
| 4 | dstat | U8 Dev | U16 DST | |
| 5 | stat | Nom U16 | U16 DST | |
| 6 | chercher | U8 Dev | Offset U32 | u8 d'où |
| 7 | ioctl | U8 Dev | CMD U8 | U16 Arg |
| 8 | mkdir | chemin U16 | ||
| 9 | chdir | chemin U16 | ||
| 10 | curdir | chemin U16 | ||
| 11 | openir | chemin U16 | ||
| 12 | readdir | U8 Dev | U16 DST | |
| 13 | RM | chemin U16 | ||
| 14 | monter | U8 Dev | lettre U8 | U8 FS |
| 15 | sortie | code U8 | ||
| 16 | exécutif | Nom U16 | U16 Argv | |
| 17 | dup | U8 Dev | U8 ndev | |
| 18 | Mme | durée U16 | ||
| 19 | se dérouler | ID U8 | Temps U16 | |
| 20 | Gettime | ID U8 | Temps U16 | |
| 21 | setDate | Date U16 | ||
| 22 | getDate | Date U16 | ||
| 23 | carte | U16 DST | u24 src | |
| 24 | échanger | U8 Dev | U8 ndev |
Veuillez vérifier la section ci-dessous pour plus d'informations sur chacun de ces appels et leurs paramètres.
Remarque : Certains systèmes peuvent être non implémentés. Par exemple, sur les ordinateurs où les répertoires ne sont pas pris en charge, les systèmes liés aux répertoires peuvent être omis.
Afin d'effectuer un système, le numéro d'opération doit être stocké dans le registre L , les paramètres doivent être stockés en suivant ces règles:
| Nom du paramètre dans l'API | Registre Z80 |
|---|---|
| U8 Dev | H |
| U8 ndev | E |
| drapeaux U8 | H |
| CMD U8 | C |
| lettre U8 | D |
| code U8 | H |
| U8 FS | E |
| ID U8 | H |
| u8 d'où | A |
| u16 buf | DE |
| Taille U16 | BC |
| Nom U16 | BC |
| U16 DST | DE |
| U16 Arg | DE |
| chemin U16 | DE |
| U16 Argv | DE |
| durée U16 | DE |
| Temps U16 | DE |
| Date U16 | DE |
| u24 src | HBC |
| Offset U32 | BCDE |
Et enfin, le code doit effectuer une RST $08 (veuillez vérifier les vecteurs de réinitialisation).
La valeur renvoyée est placée dans A. Le sens de cette valeur est spécifique à chaque appel, veuillez vérifier la documentation des routines concernées pour plus d'informations.
Pour maximiser la compatibilité des programmes utilisateur avec le noyau du système d'exploitation zéro 8 bits, que le noyau ait été compilé en mode MMU ou sans MMU, les contraintes de paramètres syscalls sont les mêmes:
Tout tampon passé à un système ne doit pas traverser une pages virtuelles de 16 Ko
En d'autres termes, si un buf de tampon de taille n est situé dans la page virtuelle i , son dernier octet, pointé par buf + n - 1 , doit également être situé sur la même page i
Par exemple, si read du syscall est appelée avec:
DE = 0x4000 et BC = 0x1000 , les paramètres sont corrects , car le tampon pointé par DE s'inscrit dans la page 1 (de 0x4000 à 0x7FFF )DE = 0x4000 et BC = 0x4000 , les paramètres sont corrects , car le tampon pointé par DE s'inscrit dans la page 1 (de 0x4000 à 0x7FFF )DE = 0x7FFF et BC = 0x2 , les paramètres sont incorrects , car le tampon pointé par DE est entre les page 1 et la page2.exec Même si Zeal 8 bits OS est un système d'exploitation mono-tâches, il peut exécuter et conserver plusieurs programmes en mémoire. Lorsqu'un programme A exécute un programme B grâce au Syscall exec , il doit fournir un paramètre mode qui peut être EXEC_OVERRIDE_PROGRAM ou EXEC_PRESERVE_PROGRAM :
EXEC_OVERRIDE_PROGRAM : Cette option indique au noyau que le programme A n'a plus besoin d'être exécuté, de sorte que le programme B sera chargé dans le même espace d'adresse que le programme A. En d'autres termes, le programme B sera chargé dans les mêmes pages RAM que le programme A, il l'écrasera.EXEC_PRESERVE_PROGRAM : Cette option indique au noyau que le programme A doit être conservé dans RAM jusqu'à ce que le programme B termine son exécution et appelle le système exit . Pour ce faire, le noyau attribuera 3 nouvelles pages de mémoire ( 16KB * 3 = 48KB ) dans lesquelles il stocke le programme nouvellement chargé B. Une fois le programme B quitte, le noyau libère les pages précédemment allouées pour le programme B, les pages de mémoire du programme A et renvoient la main pour le programme A. Si nécessaire, A peut récupérer la valeur de sortie de B. La profondeur de l'arborescence d'exécution est définie dans le menuconfig , grâce à l'option CONFIG_KERNEL_MAX_NESTED_PROGRAMS . Il représente le nombre maximum de programmes qui peuvent être stockés dans RAM à la fois. Par exemple, si la profondeur est de 3, le programme A peut appeler le programme B, le programme B peut appeler le programme C, mais le programme C ne peut appeler aucun autre programme. Cependant, si un programme invoque exec avec EXEC_OVERRIDE_PROGRAM , la profondeur n'est pas incrémentée car le nouveau programme à charger remplacera celui actuel. En tant que tel, si nous reprenons l'exemple précédent, le programme C peut appeler un programme si et seulement s'il invoque le Syscall exec en mode EXEC_OVERRIDE_PROGRAM .
Soyez prudent, lors de l'exécution d'un sous-programme, l'ensemble de la table de périphérique ouverte (y compris les fichiers, les répertoires et les pilotes), le répertoire actuel et les registres CPU seront partagés .
Cela signifie que si le programme A ouvre un fichier avec le descripteur 3, le programme B héritera de cet index, et sera donc également en mesure de lire, d'écrire ou même de fermer ce descripteur. Réciproquement, si B ouvre un fichier, un répertoire ou un pilote et sort sans le fermer, le programme A y aura également accès. En tant que tel, la directive générale à suivre est qu'avant la sortie, un programme doit toujours fermer les descripteurs qu'il a ouverts. Le seul moment où le tableau des périphériques ouverts et du répertoire actuel est réinitialisé est lorsque le programme initial (programme A dans l'exemple précédent) sort. Dans ce cas, le noyau fermera tous les descripteurs du tableau des appareils ouverts, rouvrira l'entrée et la sortie standard et rechargez le programme initial.
Cela signifie également que lors de l'invoquer le système d' exec dans un programme d'assemblage, sur le succès, tous les registres, à l'exception de HL, doivent être considérés comme modifiés car ils seront utilisés par le sous-programme. Donc, si vous souhaitez préserver AF , BC , DE , IX ou IY , ils doivent être poussés sur la pile avant d'appeler exec .
Les systèmes sont tous documentés dans les fichiers d'en-tête fournis à la fois pour l'assemblage et C, vous trouverez ces fichiers d'en-tête dans le kernel_headers/ Directory, vérifiez son fichier Readme pour plus d'informations.
Un conducteur se compose d'une structure contenant:
SER0 , SER1 , I2C0 , etc. Les caractères non ASCII sont autorisés mais non conseillés.init , appelée lorsque le noyau est en bottes.read , où les paramètres et l'adresse de retour sont les mêmes que dans la table syscall.write , comme ci-dessus.open , comme ci-dessus.close , comme ci-dessus.seek , comme ci-dessus.ioctl , comme ci-dessus.deinit , appelée lors du déchargement du pilote.Voici l'exemple d'une simple inscription au pilote:
my_driver0_init:
; Register itself to the VFS
; Do something
xor a ; Success
ret
my_driver0_read:
; Do something
ret
my_driver0_write :
; Do something
ret
my_driver0_open :
; Do something
ret
my_driver0_close :
; Do something
ret
my_driver0_seek :
; Do something
ret
my_driver0_ioctl :
; Do something
ret
my_driver0_deinit :
; Do something
ret
SECTION DRV_VECTORS
DEFB "DRV0"
DEFW my_driver0_init
DEFW my_driver0_read
DEFW my_driver0_write
DEFW my_driver0_open
DEFW my_driver0_close
DEFW my_driver0_seek
DEFW my_driver0_ioctl
DEFW my_driver0_deinit L'enregistrement d'un conducteur consiste à mettre ces informations (structure) dans une section appelée DRV_VECTORS . La commande est très importante car toute dépendance du conducteur doit être résolue au temps de compilation. Par exemple, si le conducteur A dépend du conducteur B , la structure de B doit être placée avant A dans la section DRV_VECTORS .
Au démarrage, le composant driver parcourera l'ensemble de la section DRV_VECTORS et initialise les pilotes un par un en appelant sa routine init . Si cette routine renvoie ERR_SUCCESS , le pilote sera enregistré et que les programmes utilisateur peuvent l'ouvrir, lire, écrire, ioctl, etc ...
Un pilote peut être caché aux programmes, ceci est pratique pour les pilotes de disque qui ne doivent être accessibles que par la couche de système de fichiers du noyau. Pour ce faire, la routine init doit renvoyer ERR_DRIVER_HIDDEN .
Comme la communication entre les applications et le matériel se fait via les systèmes décrits ci-dessus, nous avons besoin d'une couche entre l'application utilisateur et le noyau qui déterminera si nous devons appeler un pilote ou un système de fichiers. Avant de montrer la hiérarchie d'une telle architecture, parlons des disques et des conducteurs.
Les différentes couches peuvent être vues comme ceci:
Organigramme TD;
App (programme utilisateur)
VFS (système de fichiers virtuel)
DSK (module de disque)
DRV (Implémentation du pilote: vidéo, clavier, série, etc ...)
FS (système de fichiers)
sysdis (Syscall Dispatcher)
HW (matériel)
HEURE (Module Date et Date)
MEM (module de mémoire)
chargeur (module de chargeur)
app - syscall / rst 8 -> sysdis;
sysdis --getDate / time -> time;
sysdis --mont -> dsk;
sysdis -> vfs;
sysdis - map -> mem;
sysdis - exec / exit -> chargeur;
VFS -> DSK & DRV;
DSK <-> fs;
fs -> drv;
DRV -> HW;
Le système d'exploitation du zèle 8 bits prend en charge jusqu'à 26 disques à la fois. Les disques sont désignés par une lettre, de A à Z. Il est de la responsabilité du conducteur de disque de décider où monter le disque du système.
Le premier lecteur, A , est spécial car c'est celui où le système recherchera des préférences ou des configurations.
Dans une application, un path peut être:
my_dir2/file1.txt/my_dir1/my_dir2/file1.txtB:/your_dir1/your_dir2/file2.txt Même si le système d'exploitation est complètement romable et n'a pas besoin de système ou de disque de fichiers pour démarrer, dès qu'il essaiera de charger le programme initial, appelé init.bin par défaut, il vérifiera le disque par défaut et demandera ce fichier. Ainsi, même le stockage le plus basique a besoin d'un système de fichiers ou de quelque chose de similaire.
Le premier "Système de fichiers", qui est déjà implémenté, est appelé "RawTable". Comme son nom l'indique, il représente la succession de fichiers, et non de répertoires, dans un périphérique de stockage, sans ordre particulier. La limite de taille du nom de fichier est la même que celle du noyau: 16 caractères, y compris la facultatif . et extension. Si nous voulons le comparer au code C, ce serait un tableau de structures définissant chaque fichier, suivi du contenu du fichier dans le même ordre. Un code source RomDisk Packer est disponible dans le packer/ à la racine de ce dépôt. Vérifiez son lecture pour plus d'informations à ce sujet.
Le deuxième système de fichiers, qui est également implémenté, est nommé Zealfs. Son objectif principal est d'être intégré à de très petits stockages, de 8 Ko à 64 Ko. Il est lisible et écrit, il prend en charge les fichiers et les répertoires. Plus d'informations à ce sujet dans le référentiel dédié.
Le troisième système de fichiers qui serait bien d'avoir sur le système d'exploitation du zèle 8 bits est FAT16. Très célèbre, déjà pris en charge par presque tous les systèmes d'exploitation de bureau, utilisables sur CompactFlash et même les cartes SD, c'est presque un incontournable. Il n'a pas encore été mis en œuvre, mais il est prévu. FAT16 n'est pas parfait, car il n'est pas adapté pour le petit stockage, c'est pourquoi un zèle est nécessaire.
Le système d'exploitation zèle 8 bits est basé sur deux composantes principales: un noyau et un code cible. Le noyau seul ne fait rien. La cible doit implémenter les pilotes, certaines macros MMU utilisées à l'intérieur du noyau et un script de liaison. Le script de linker est assez simple, il répertorie les sections de l'ordre, il doit être lié dans le binaire final par assembleur z80asm .
Le noyau utilise actuellement les sections suivantes, qui doivent être incluses dans n'importe quel script de linker:
RST_VECTORS : contient les vecteurs de réinitialisationSYSCALL_TABLE : contient un tableau où l'adresse de routine Syscall i est stockée à l'index i , doit être aligné sur 256SYSCALL_ROUTINES : contient le répartiteur syscall, appelé à partir d'un vecteur de réinitialisationKERNEL_TEXT : contient le code du noyauKERNEL_STRLIB : contient les routines liées à la chaîne utilisée dans le noyauKERNEL_DRV_VECTORS : représente un tableau de pilotes à initialiser, vérifiez la section du pilote pour plus de détails.KERNEL_BSS : contient les données utilisées par le code du noyau, doit être en RAMDRIVER_BSS : non utilisé directement par le noyau, il doit être défini et utilisé dans les pilotes. Le noyau le définira sur 0 sur le démarrage, il doit être supérieur à 2 octets Comme indiqué précédemment, la prise en charge de l'ordinateur Zeal 8 bits est toujours partielle mais suffisante pour avoir un programme de ligne de commande en cours d'exécution. Le RomDisk est créé avant les constructions du noyau, cela se fait dans le script.sh spécifié dans le target/zeal8bit/unit.mk
Ce script compilera le programme init.bin et l'intégrera dans un romdisk qui sera concaténé au binaire OS compilé. Le binaire final peut être directement flashé au Flash.
Ce qui doit encore être mis en œuvre, sans ordre particulier:
Un port rapide de l'ordinateur TRS-80 Model-I a été réalisé pour montrer comment porter et configurer le système d'exploitation du zèle à 8 bits vers des cibles qui n'ont pas de MMU.
Ce port est assez simple car il montre simplement la bannière de démarrage à l'écran, rien de plus. Pour ce faire, seul un pilote vidéo pour le mode texte est implémenté.
Pour avoir un port plus intéressant, les fonctionnalités suivantes devraient être implémentées:
init.bin / romdisk peut être en lecture seule, donc peut être stocké sur la ROMUn port de la lumière d'agonie alimentée EZ80, écrite et entretenue par Shawn Sijnstra. N'hésitez pas à utiliser cette fourche pour les bogues / demandes spécifiques à l'agon. Cela utilise le noyau non MMU et met en œuvre la plupart des fonctionnalités que prend en charge l'implémentation de l'ordinateur Zeal 8 bits.
Ce port nécessite un chargeur pour que le binaire soit stocké et exécuté à partir de l'emplacement correct. Le binaire est Osbootz, disponible ici.
Notez que le port utilise le mode terminal pour simplifier les E / S du clavier. Cela signifie également que la fonction de date n'est pas disponible.
Autres caractéristiques notables:
Pour porter le zèle 8 bits OS MMU, une autre machine, assurez-vous d'avoir un mappeur de mémoire d'abord qui divise l'espace d'adressage de 64 Ko du Z80 en 4 pages de 16 Ko pour la version MMU.
Pour porter le système d'exploitation 8 bits zèle sans MMU, assurez-vous que la RAM est disponible à partir de l'adresse virtuelle 0x4000 et supérieure. Le cas le plus idéal étant ROM est le premier 16 Ko pour le système d'exploitation et la RAM dans les 48 Ko restants pour les programmes utilisateur et le RAM du noyau.
Si votre cible est compatible, suivez les instructions:
Kconfig à la racine de ce dépôt et ajoutez une entrée aux options config TARGET et config COMPILATION_TARGET . Prenez ceux déjà présents comme exemples.target/ pour votre cible, le nom doit être le même que celui spécifié dans la nouvelle option config TARGET .unit.mk Il s'agit du fichier qui doit contenir tous les fichiers source à assembler ou ceux à inclure.unit.mk , pour ce faire, vous pouvez remplir les variables make suivantes:SRCS : liste des fichiers à assembler. En règle générale, ce sont les pilotes (obligatoires)INCLUDES : les répertoires contenant des fichiers d'en-tête qui peuvent être inclusPRECMD : une commande bash à exécuter avant que le noyau ne commence à construirePOSTCMD : une commande bash à exécuter après la fin du noyau termine le bâtimentmmu_h.asm qui sera inclus par le noyau pour configurer et utiliser le MMU. Vérifiez le fichier target/zeal8bit/include/mmu_h.asm pour voir à quoi il devrait ressembler.zos_disks_mount , contenant un fichier init.bin , chargé et exécuté par le noyau sur le démarrage.zos_vfs_set_stdout .Pour le Changelog complet, veuillez consulter la page de version.
Les contributions sont les bienvenues! N'hésitez pas à corriger tout bogue que vous pouvez voir ou rencontrer, ou implémenter toute fonctionnalité que vous trouvez importante.
Pour contribuer:
(*) Un bon message de validation est le suivant:
Module: add/fix/remove a from b
Explanation on what/how/why
Par exemple:
Disks: implement a get_default_disk routine
It is now possible to retrieve the default disk of the system.
Distribué sous la licence Apache 2.0. Voir le fichier LICENSE pour plus d'informations.
Vous êtes libre de l'utiliser pour une utilisation personnelle et commerciale, le chauffeur présent dans chaque fichier ne doit pas être supprimé.
Pour toute suggestion ou demande, vous pouvez me contacter au contact [at] zeal8bit [dot] com
Pour les demandes de fonctionnalités, vous pouvez également ouvrir un problème ou une demande de traction.
Ils ne seront pas considérés comme non volatils néanmoins. En d'autres termes, un gestionnaire d'interruption ne doit pas faire l'hypothèse que les données qu'il a écrites dans tout autre registre seront conservées jusqu'à la prochaine fois qu'elle sera appelée. ↩