*Si los parámetros del programa del usuario apuntan a una parte de la memoria en la página 3 (última página), hay un conflicto ya que el núcleo siempre mapeará su página RAM dentro de esta misma página durante un SYSCALL. Por lo tanto, remapará la página 3 del usuario en la página 2 (tercera página) para acceder a los parámetros del programa. Por supuesto, en caso de que los parámetros sean punteros, serán modificados para dejar que apunten a la nueva dirección virtual (en otras palabras, un puntero se restará en 16kb para dejar que apunte a la página 2).
Para poder portar el sistema operativo de 8 bits de celo a las computadoras basadas en Z80 que no tienen un mmu/mapper organizado como se muestra arriba, el núcleo tiene un nuevo modo que se puede elegir a través del menuconfig : no-mmu.
En este modo, se espera que el código del sistema operativo se mapee en los primeros 16 kb de la memoria, de 0x0000 a 0x3FFF y se espera que el resto sea RAM.
Idealmente, 48 kb de RAM deben asignarse a partir de 0x4000 y subirían a 0xFFFF , pero en la práctica, es posible configurar el núcleo para esperar menos que eso. Para hacerlo, dos entradas en menuconfig deben configurarse adecuadamente:
KERNEL_STACK_ADDR : Esto marca el final del área de RAM del kernel y, como su nombre, será la parte inferior de la pila del núcleo.KERNEL_RAM_START : esto marca la dirección de inicio de la RAM del kernel donde se almacenarán las variables utilizadas por el núcleo y los controladores. Por supuesto, debe ser lo suficientemente grande como para almacenar todos estos datos. Para obtener información, el tamaño actual de la sección BSS del núcleo es de alrededor de 1 kb. La profundidad de la pila depende de la implementación de los controladores de destino. La asignación de 1KB para la pila debe ser más que suficiente siempre que no se almacenen tampones (grandes). La asignación general de al menos 3 kb para el RAM del núcleo debe ser seguro y a prueba de futuro.En resumen, aquí hay un diagrama para mostrar el uso de la memoria:
Con respecto a los programas de usuario, la dirección de pila siempre se establecerá en KERNEL_RAM_START - 1 por el núcleo antes de la ejecución. También corresponde a la dirección de su último byte disponible en su espacio de direcciones utilizable. Esto significa que un programa puede determinar el tamaño de la RAM disponible realizando SP - 0x4000 , lo que da, en el ensamblaje:
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 presenta múltiples registros de propósito general, no todos se usan en el núcleo, aquí está el alcance de cada uno de ellos:
| Registro | Alcance |
|---|---|
| AF, BC, DE, HL | Sistema y aplicación |
| AF ', BC', de ', HL' | Manejadores de interrupción |
| Ix, iy | Aplicación (sin usar en el sistema operativo) |
Esto significa que el sistema operativo no alterará los registros IX e IY, por lo que se pueden usar libremente en la aplicación.
Los registros alternativos (nombres seguidos de ' ) solo se pueden usar en los manejadores de interrupción 1 . Una aplicación no debe usar estos registros. Si por alguna razón, aún tiene que usarlos, considere deshabilitar las interrupciones durante el tiempo que se usan:
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
Tenga en cuenta que deshabilitar las interrupciones durante demasiado tiempo puede ser dañino ya que el sistema no recibirá ninguna señal de hardware (temporizadores, teclado, gpios ...)
El Z80 proporciona 8 vectores de reinicio distintos, ya que el sistema debe almacenarse siempre en la primera página virtual de la memoria, todos están reservados para el sistema operativo:
| Vector | Uso |
|---|---|
| $ 00 | Restablecer el software |
| $ 08 | Syscall |
| $ 10 | Salta a la dirección en HL (se puede usar para llamar a HL) |
| $ 18 | No usado |
| $ 20 | No usado |
| $ 28 | No usado |
| $ 30 | No usado |
| $ 38 | Reservado para el modo de interrupción 1, utilizable por la implementación de destino |
Cuando se ejecuta un programa de usuario, el kernel asigna 3 páginas de RAM (48 kb), lee el archivo binario para ejecutarlo y lo carga a partir de la dirección virtual 0x4000 de forma predeterminada. Esta dirección virtual del punto de entrada es configurable a través del menuconfig con opción KERNEL_INIT_EXECUTABLE_ADDR , pero tenga en cuenta que los programas existentes ya no funcionarán sin ser recompilados porque no son reubicables en tiempo de ejecución.
Como se describe a continuación, el exec SYSCall toma dos parámetros: un nombre de archivo binario para ejecutar y un parámetro.
Este parámetro debe ser una cadena terminada nula que se copiará y transmitirá al binario para ejecutar a través de los registros DE y BC :
DE Contiene la dirección de la cadena. Esta cadena se copiará en el espacio de memoria del nuevo programa, generalmente en la parte superior de la pila.BC contiene la longitud de esa cadena (por lo tanto, excluyendo el byte nulo). Si BC es 0, DE programa de usuario debe descartarse. El sistema se basa en SYSCalls para realizar solicitudes entre el programa de usuario y el kernel. Por lo tanto, esta será la forma de realizar operaciones en el hardware. Las posibles operaciones se enumeran en la tabla a continuación.
| Numer | Nombre | Param. 1 | Param. 2 | Param. 3 |
|---|---|---|---|---|
| 0 | leer | u8 dev | U16 buf | Tamaño U16 |
| 1 | escribir | u8 dev | U16 buf | Tamaño U16 |
| 2 | abierto | Nombre U16 | banderas U8 | |
| 3 | cerca | u8 dev | ||
| 4 | dstat | u8 dev | U16 DST | |
| 5 | estadística | Nombre U16 | U16 DST | |
| 6 | buscar | u8 dev | compensación u32 | u8 de donde |
| 7 | ioctl | u8 dev | U8 CMD | U16 Arg |
| 8 | mkdir | ruta u16 | ||
| 9 | chdir | ruta u16 | ||
| 10 | cuajado | ruta u16 | ||
| 11 | opendir | ruta u16 | ||
| 12 | readdir | u8 dev | U16 DST | |
| 13 | RM | ruta u16 | ||
| 14 | montar | u8 dev | Carta U8 | U8 FS |
| 15 | salida | código U8 | ||
| 16 | ejecución | Nombre U16 | U16 argv | |
| 17 | hacer | u8 dev | U8 NDEV | |
| 18 | pasar por alto | Duración U16 | ||
| 19 | régimen | ID de U8 | U16 TIEMPO | |
| 20 | GetTime | ID de U8 | U16 TIEMPO | |
| 21 | setdate | Fecha de U16 | ||
| 22 | getdate | Fecha de U16 | ||
| 23 | mapa | U16 DST | U24 SRC | |
| 24 | intercambio | u8 dev | U8 NDEV |
Consulte la sección a continuación para obtener más información sobre cada una de estas llamadas y sus parámetros.
NOTA : Algunas syscalls pueden estar sin implementar. Por ejemplo, en las computadoras donde los directorios no son compatibles, se pueden omitir Syscalls relacionados con los directorios.
Para realizar un SYSCALL, el número de operación debe almacenarse en el Registro L , los parámetros deben almacenarse siguiendo estas reglas:
| Nombre del parámetro en API | Registro Z80 |
|---|---|
| u8 dev | H |
| U8 NDEV | E |
| banderas U8 | H |
| U8 CMD | C |
| Carta U8 | D |
| código U8 | H |
| U8 FS | E |
| ID de U8 | H |
| u8 de donde | A |
| U16 buf | DE |
| Tamaño U16 | BC |
| Nombre U16 | BC |
| U16 DST | DE |
| U16 Arg | DE |
| ruta u16 | DE |
| U16 argv | DE |
| Duración U16 | DE |
| U16 TIEMPO | DE |
| Fecha de U16 | DE |
| U24 SRC | HBC |
| compensación u32 | BCDE |
Y finalmente, el código debe realizar una instrucción RST $08 (verifique los vectores de reinicio).
El valor devuelto se coloca en A. El significado de ese valor es específico para cada llamada, verifique la documentación de las rutinas en cuestión para obtener más información.
Para maximizar la compatibilidad de los programas de usuario con el núcleo del sistema operativo Zeal de 8 bits, independientemente de si el núcleo se compiló en el modo MMU o NO-MMU, las restricciones de los parámetros SYSCalls son las mismas:
Cualquier amortiguación que se pase a un Syscall no cruzará una páginas virtuales de 16 kb
En otras palabras, si un buffer buf de tamaño n se encuentra en la página I virtual i , su último byte, señalado por buf + n - 1 , también debe ubicarse en la misma página i .
Por ejemplo, si se read Syscall con:
DE = 0x4000 y BC = 0x1000 , los parámetros son correctos , porque el búfer apuntado por DE encaja en la página 1 (de 0x4000 a 0x7FFF )DE = 0x4000 y BC = 0x4000 , los parámetros son correctos , porque el búfer apuntado por DE encaja en la página 1 (de 0x4000 a 0x7FFF )DE = 0x7FFF y BC = 0x2 , los parámetros son incorrectos , porque el búfer señalado por DE está entre la página 1 y la página2.exec Aunque Zeal OS de 8 bits es un sistema operativo mono-tareas, puede ejecutar y mantener varios programas en la memoria. Cuando un programa A ejecute un programa B gracias al SYSCALL exec , debe proporcionar un parámetro mode que puede ser EXEC_OVERRIDE_PROGRAM o EXEC_PRESERVE_PROGRAM :
EXEC_OVERRIDE_PROGRAM : esta opción le dice al kernel que el programa A no es necesario ejecutar más, por lo que el programa B se cargará en el mismo espacio de direcciones que el Programa A. En otras palabras, el programa B se cargará dentro de las mismas páginas RAM que el Programa A, lo sobrescribirá.EXEC_PRESERVE_PROGRAM : esta opción le dice al kernel que el programa A debe mantenerse en RAM hasta que el Programa B termine su ejecución y llama a Syscall exit . Para hacerlo, el núcleo asignará 3 nuevas páginas de memoria ( 16KB * 3 = 48KB ) en la que almacena el programa recién cargado B. Una vez que el programa B sale, el kernel libera las páginas previamente asignadas para el programa B, remapsas las páginas de memoria del programa A, y devuelve la mano al programa A. Si es necesario, A puede recuperar el valor de salida de B puede recuperar. La profundidad del árbol de ejecución se define en menuconfig , gracias a la opción CONFIG_KERNEL_MAX_NESTED_PROGRAMS . Representa el número máximo de programas que se pueden almacenar en RAM al mismo tiempo. Por ejemplo, si la profundidad es 3, el Programa A puede llamar al Programa B, el Programa B puede llamar al Programa C, pero el Programa C no puede llamar a ningún otro programa. Sin embargo, si un programa invoca exec con EXEC_OVERRIDE_PROGRAM , la profundidad no se incrementa ya que el nuevo programa para cargar anulará el actual. Como tal, si recuperamos el ejemplo anterior, el Programa C puede llamar a un programa si y solo si invoca el SYSCALL exec en el modo EXEC_OVERRIDE_PROGRAM .
Tenga cuidado, al ejecutar un subprograma, se compartirá toda la tabla de dispositivos abierto (incluidos archivos, directorios y controladores), el directorio actual y los registros de CPU.
Esto significa que si el programa A abre un archivo con el descriptor 3, el programa B heredará este índice y, por lo tanto, también podrá leer, escribir o incluso cerrar ese descriptor. Recíprocamente, si B abre un archivo, directorio o controlador y sale sin cerrarlo, el programa A también tendrá acceso a él. Como tal, la guía general a seguir es que antes de salir, un programa siempre debe cerrar los descriptores que abrió. El único momento en que se restablecen la tabla de dispositivos abiertos y directorio actual es cuando sale el programa inicial (programa A en el ejemplo anterior). En ese caso, el núcleo cerrará todos los descriptores en la tabla de dispositivos abiertos, reabrirá la entrada y salida estándar, y recargará el programa inicial.
Esto también significa que al invocar el SYSCALL exec en un programa de ensamblaje, sobre el éxito, todos los registros, excepto HL, deben considerarse alterados porque el subprograma los usará. Entonces, si desea preservar AF , BC , DE , IX o IY , deben ser empujados en la pila antes de invocar exec .
Todos los Syscalls están documentados en los archivos de encabezado proporcionados tanto para el ensamblaje como para C, encontrará estos archivos de encabezado en el directorio kernel_headers/ directorio, verifique su archivo ReadMe para obtener más información.
Un controlador consiste en una estructura que contiene:
SER0 , SER1 , I2C0 , etc. Los caracteres no ASCII están permitidos pero no se aconsejan.init , llamada cuando el núcleo bota.read , donde los parámetros y la dirección de retorno son las mismas que en la tabla SYSCall.write , igual que la anterior.open , igual que arriba.close , igual que la anterior.seek , igual que la anterior.ioctl , igual que la anterior.deinit , llamada al descargar el controlador.Aquí está el ejemplo de un simple registro de conducir:
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 El registro de un controlador consiste en colocar esta información (estructura) dentro de una sección llamada DRV_VECTORS . El pedido es muy importante ya que cualquier dependencia del conductor se resolverá en tiempo de compilación. Por ejemplo, si el controlador A depende del controlador B , entonces la estructura de B debe ponerse antes de A en la sección DRV_VECTORS .
En el arranque, el componente driver navegará por toda la sección DRV_VECTORS e inicializará los controladores uno por uno llamando a su rutina init . Si esta rutina devuelve ERR_SUCCESS , el controlador se registrará y los programas de usuario pueden abrirlo, leer, escribir, ioctl, etc ...
Se puede ocultar un controlador a los programas, esto es útil para los controladores de disco a los que solo debe acceder la capa del sistema de archivos del kernel. Para hacerlo, la rutina init debe devolver ERR_DRIVER_HIDDEN .
Como la comunicación entre aplicaciones y hardware se realiza a través de los SYSCalls descritos anteriormente, necesitamos una capa entre la aplicación del usuario y el kernel que determinará si necesitamos llamar a un controlador o un sistema de archivos. Antes de mostrar la jerarquía de tal arquitectura, hablemos de discos y conductores.
Las diferentes capas se pueden ver así:
diagrama de flujo TD;
aplicación (programa de usuario)
VFS (sistema de archivos virtuales)
DSK (módulo de disco)
DRV (Implementación del controlador: video, teclado, serie, etc.)
FS (sistema de archivos)
Sysdis (despachador de syscall)
HW (hardware)
tiempo (módulo de hora y fecha)
MEM (módulo de memoria)
cargador (módulo de cargador)
aplicación -syscall/rst 8 -> sysdis;
Sysdis --getDate/Time-> Time;
sysdis--mount-> dsk;
sysdis -> vfs;
Sysdis-map-> mem;
sysdis -ejecut/salida -> cargador;
VFS -> DSK & DRV;
dsk <-> fs;
FS -> DRV;
DRV -> HW;
El sistema operativo Zeal de 8 bits admite hasta 26 discos a la vez. Los discos se denotan una carta, de A a Z. Es responsabilidad del conductor de disco decidir dónde montar el disco en el sistema.
La primera unidad, A , es especial, ya que es la que el sistema buscará preferencias o configuraciones.
En una aplicación, una path puede ser:
my_dir2/file1.txt/my_dir1/my_dir2/file1.txtB:/your_dir1/your_dir2/file2.txt A pesar de que el sistema operativo es completamente complejo y no necesita ningún sistema de archivo o disco para arrancar, tan pronto como intentará cargar el programa inicial, llamado init.bin de forma predeterminada, verificará el disco predeterminado y solicitará ese archivo. Por lo tanto, incluso el almacenamiento más básico necesita un sistema de archivos, o algo similar.
El primer "sistema de archivos", que ya está implementado, se llama "TTABLE RAW". Como afirma su nombre, representa la sucesión de archivos, no los directorios, en un dispositivo de almacenamiento, sin ningún orden particular. El límite del tamaño del nombre del archivo es el mismo que el de los núcleos: 16 caracteres, incluido el opcional . y extensión. Si queremos compararlo con el código C, sería una variedad de estructuras que definen cada archivo, seguido del contenido del archivo en el mismo orden. Un código fuente RomDisk Packer está disponible en el packer/ en la raíz de este repositorio. Consulte su lectura para obtener más información al respecto.
El segundo sistema de archivos, que también se implementa, se llama ZealFS. Su objetivo principal es estar integrado en almacenes muy pequeños, desde 8 kb hasta 64 kb. Es legible y escritos, admite archivos y directorios. Más información al respecto en el repositorio dedicado.
El tercer sistema de archivos que sería bueno tener en el sistema operativo Zeal de 8 bits es FAT16. Muy famoso, ya compatible con casi todos los sistemas operativos de escritorio, utilizables en compactflash e incluso tarjetas SD, esto es casi imprescindible. Todavía no se ha implementado, pero está planeado. Sin embargo, FAT16 no es perfecto, ya que no está adaptado para un almacenamiento pequeño, es por eso que se necesita ZealFS.
El sistema operativo Zeal de 8 bits se basa en dos componentes principales: un núcleo y un código objetivo. El núcleo solo no hace nada. El objetivo necesita implementar los controladores, algunas macros MMU utilizadas dentro del núcleo y un script de enlazador. El script de enlazador es bastante simple, enumera las secciones en el orden en que deben estar vinculadas en el binario final por el ensamblador z80asm .
El kernel actualmente utiliza las siguientes secciones, que deben incluirse en cualquier script de enlazador:
RST_VECTORS : contiene los vectores de reinicioSYSCALL_TABLE : contiene una tabla donde la dirección de rutina Syscall i se almacena en el índice i , debe estar alineada en 256SYSCALL_ROUTINES : contiene el despachador syscall, llamado desde un vector de reinicioKERNEL_TEXT : contiene el código del kernelKERNEL_STRLIB : contiene las rutinas relacionadas con la cadena utilizadas en el kernelKERNEL_DRV_VECTORS : representa una matriz de controladores para inicializar, verifique la sección del controlador para obtener más detalles.KERNEL_BSS : contiene los datos utilizados por el código del núcleo, deben estar en RAMDRIVER_BSS : no utilizado directamente por el núcleo, se definirá y usará en los controladores. El núcleo lo establecerá en 0s en el arranque, debe ser más grande que 2 bytes Como se dijo anteriormente, el soporte informático Zeal de 8 bits sigue siendo parcial pero suficiente para que se ejecute un programa de línea de comandos. El RomDisk se crea antes de que se desarrolle el núcleo, esto se hace en el script.sh especificado en el target/zeal8bit/unit.mk .
Ese script compilará el programa init.bin e incrustará dentro de un disco rom que se concatenará al binario compilado del sistema operativo. El binario final se puede parpadear directamente al NOR Flash.
Lo que aún debe implementarse, sin ningún orden en particular:
Se ha realizado un puerto rápido a la computadora TRS-80 Model-I para mostrar cómo portar y configurar el sistema operativo Zeal de 8 bits en objetivos que no tienen una MMU.
Este puerto es bastante simple, ya que simplemente muestra el banner de arranque en la pantalla, nada más. Para hacerlo, solo se implementa un controlador de video para el modo de texto.
Para tener un puerto más interesante, las siguientes características tendrían que implementarse:
init.bin /romDisk, puede ser de solo lectura, por lo que se puede almacenar en la ROMUn puerto a la luz agon de EZ80, escrita y mantenida por Shawn Sijnstra. Siéntase libre de usar esa bifurcación para errores/solicitudes específicos de AGON. Esto utiliza el núcleo no MMU e implementa la mayoría de las características que admite la implementación de la computadora Zeal de 8 bits.
Este puerto requiere un cargador para que el binario se almacene y ejecute desde la ubicación correcta. El binario es Osbootz, disponible aquí.
Tenga en cuenta que el puerto usa el modo terminal para simplificar la E/S del teclado. Esto también significa que la función de fecha no está disponible.
Otras características notables:
Para portar la versión MMU de OS de 8 bits en celo a otra máquina, asegúrese de tener un mapeador de memoria primero que divide el espacio de direcciones de 64 kb del Z80 en 4 páginas de 16 kb para la versión MMU.
Para puerto, el sistema operativo de 8 bits Zeal de 8 bits, asegúrese de que RAM esté disponible en la dirección virtual 0x4000 y superior. El caso más ideal que tiene ROM son los primeros 16 kb para el sistema operativo y la RAM en los 48 kb restantes para los programas de usuario y la RAM del kernel.
Si su objetivo es compatible, siga las instrucciones:
Kconfig en la raíz de este repositorio y agregue una entrada a las opciones de config TARGET y config COMPILATION_TARGET . Tome los que ya están presentes como ejemplos.target/ para su objetivo, el nombre debe ser el mismo que el especificado en la nueva opción de config TARGET .unit.mk Este es el archivo que contendrá todos los archivos de origen para ensamblar o los que se incluirán.unit.mk , para hacerlo, puede completar las siguientes make :SRCS : Lista de los archivos a ensamblar. Por lo general, estos son los controladores (obligatorios)INCLUDES : los directorios que contienen archivos de encabezado que se pueden incluirPRECMD : un comando bash que se ejecutará antes de que el núcleo comience a construirPOSTCMD : un comando bash que se ejecutará después de que el núcleo termina el edificiommu_h.asm que el núcleo incluirá para configurar y usar la MMU. Verifique el archivo target/zeal8bit/include/mmu_h.asm para ver cómo debería verse.zos_disks_mount , que contiene un archivo init.bin , cargado y ejecutado por el núcleo en el arranque.zos_vfs_set_stdout .Para ver el CangeLog completo, consulte la página de lanzamiento.
¡Las contribuciones son bienvenidas! Siéntase libre de solucionar cualquier error que pueda ver o encontrar, o implementar cualquier característica que considere importante.
Para contribuir:
(*) Un buen mensaje de confirmación es el siguiente:
Module: add/fix/remove a from b
Explanation on what/how/why
Por ejemplo:
Disks: implement a get_default_disk routine
It is now possible to retrieve the default disk of the system.
Distribuido bajo la licencia Apache 2.0. Consulte el archivo LICENSE para obtener más información.
Es libre de usarlo para uso personal y comercial, no se debe eliminar la calderera presente en cada archivo.
Para cualquier sugerencia o solicitud, puede contactarme en Contact [at] Zeal8bit [dot] com
Para las solicitudes de funciones, también puede abrir un problema o una solicitud de extracción.
No serán considerados como no volátiles, sin embargo. En otras palabras, un controlador de interrupción no supondrá que los datos que escribieron dentro de cualquier registro alternativo se mantendrán hasta la próxima vez que se llame. ↩