*Se os parâmetros do programa de usuário estiverem apontando para uma parte da memória na página 3 (última página), há um conflito, pois o kernel sempre mapeará sua página de RAM dentro desta página exatamente durante uma página durante um syscall. Assim, ele remapará a página 3 do usuário na página 2 (terceira página) para acessar os parâmetros do programa. Obviamente, caso os parâmetros sejam ponteiros, eles serão modificados para permitir que eles apontem para o novo endereço virtual (em outras palavras, um ponteiro será subtraído por 16kb para deixá -lo apontar para a página 2).
Para poder portar o sistema operacional de 8 bits de zelo para computadores baseados em Z80 que não possuem um MMU/Memory Mapper organizado, como mostrado acima, o kernel possui um novo modo que pode ser escolhido através do menuconfig : No-MMU.
Nesse modo, o código do SO ainda deve ser mapeado nos primeiros 16 KB da memória, de 0x0000 a 0x3FFF e o restante deve ser RAM.
Idealmente, 48kb de RAM devem ser mapeados a partir de 0x4000 e subir para 0xFFFF , mas na prática é possível configurar o kernel para esperar menos do que isso. Para fazer isso, duas entradas no menuconfig devem ser configuradas adequadamente:
KERNEL_STACK_ADDR : Isso marca o final da área do RAM do kernel e, como o nome afirma, será o fundo da pilha do kernel.KERNEL_RAM_START : Isso marca o endereço inicial do kernel RAM onde a pilha, todas as variáveis usadas pelo kernel e os drivers serão armazenados. Obviamente, deve ser grande o suficiente para armazenar todos esses dados. Para obter informações, o tamanho atual da seção BSS do kernel é de cerca de 1kb. A profundidade da pilha depende da implementação dos drivers de destino. A alocação de 1KB para a pilha deve ser mais do que suficiente, pois não houver buffers (grandes). A alocação geral de pelo menos 3KB para a RAM do kernel deve ser segura e à prova de futuro.Para resumir, aqui está um diagrama para mostrar o uso da memória:
Em relação aos programas do usuário, o endereço da pilha sempre será definido como KERNEL_RAM_START - 1 pelo kernel antes da execução. Ele também corresponde ao endereço de seu último byte disponível em seu espaço de endereço utilizável. Isso significa que um programa pode determinar o tamanho da RAM disponível executando SP - 0x4000 , que fornece, na montagem:
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.
O Z80 apresenta vários registros de uso geral, nem todos eles são usados no kernel, aqui está o escopo de cada um deles:
| Registrar | Escopo |
|---|---|
| AF, BC, DE, HL | Sistema e aplicação |
| Af ', bc', de ', hl' | Manipuladores de interrupção |
| Ix, iy | Aplicação (não utilizado no sistema operacional) |
Isso significa que o sistema operacional não alterará os registros IX e IY, para que eles possam ser usados livremente no aplicativo.
Os registros alternativos (nomes seguidos por ' ) só podem ser usados nos manipuladores de interrupção 1 . Um aplicativo não deve usar esses registros. Se, por algum motivo, você ainda precisar usá -los, considere desativar as interrupções durante o tempo em que são usadas:
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
Lembre -se de que desativar as interrupções por muito tempo pode ser prejudicial, pois o sistema não receberá nenhum sinal de hardware (temporizadores, teclado, GPIOS ...)
O Z80 fornece 8 vetores de redefinição distintos, pois o sistema deve ser sempre armazenado na primeira página virtual da memória, todos são reservados para o sistema operacional:
| Vetor | Uso |
|---|---|
| $ 00 | Redefinição de software |
| $ 08 | Syscall |
| $ 10 | Salta para o endereço no HL (pode ser usado para ligar para HL) |
| $ 18 | Não utilizado |
| $ 20 | Não utilizado |
| US $ 28 | Não utilizado |
| $ 30 | Não utilizado |
| $ 38 | Reservado para o modo de interrupção 1, utilizável pela implementação de destino |
Quando um programa de usuário é executado, o kernel aloca 3 páginas de RAM (48KB), lê o arquivo binário para executá -lo e carrega -o começando no endereço virtual 0x4000 por padrão. Este endereço virtual do ponto de entrada é configurável através do menuconfig com a opção KERNEL_INIT_EXECUTABLE_ADDR , mas lembre -se de que os programas existentes não funcionam mais sem serem recompilados porque não são realocáveis no tempo de execução.
Conforme descrito abaixo, o SySCall exec leva dois parâmetros: um nome de arquivo binário a ser executado e um parâmetro.
Este parâmetro deve ser uma sequência de terminado nulo que será copiada e transmitida ao binário para executar através dos registros DE e BC :
DE contém o endereço da string. Essa string será copiada para o espaço de memória do novo programa, geralmente no topo da pilha.BC contém o comprimento dessa corda (então, excluindo o nulo de byte). Se BC for 0, DE deverá ser descartado pelo programa do usuário. O sistema depende de syscalls para executar solicitações entre o programa do usuário e o kernel. Assim, esta será a maneira de executar operações no hardware. As operações possíveis estão listadas na tabela abaixo.
| Num | Nome | Param. 1 | Param. 2 | Param. 3 |
|---|---|---|---|---|
| 0 | ler | u8 dev | U16 BUF | tamanho U16 |
| 1 | escrever | u8 dev | U16 BUF | tamanho U16 |
| 2 | abrir | Nome U16 | U8 sinalizadores | |
| 3 | fechar | u8 dev | ||
| 4 | dstat | u8 dev | U16 DST | |
| 5 | Stat | Nome U16 | U16 DST | |
| 6 | procurar | u8 dev | Offset U32 | U8 de onde |
| 7 | ioctl | u8 dev | U8 cmd | U16 Arg |
| 8 | mkdir | Caminho U16 | ||
| 9 | chdir | Caminho U16 | ||
| 10 | Curdir | Caminho U16 | ||
| 11 | opendir | Caminho U16 | ||
| 12 | readdir | u8 dev | U16 DST | |
| 13 | rm | Caminho U16 | ||
| 14 | montar | u8 dev | letra U8 | U8 fs |
| 15 | saída | Código U8 | ||
| 16 | exec | Nome U16 | U16 Argv | |
| 17 | dup | u8 dev | U8 ndev | |
| 18 | msleep | Duração U16 | ||
| 19 | settime | u8 id | U16 tempo | |
| 20 | gettime | u8 id | U16 tempo | |
| 21 | setDate | U16 Data | ||
| 22 | getDate | U16 Data | ||
| 23 | mapa | U16 DST | U24 Src | |
| 24 | trocar | u8 dev | U8 ndev |
Verifique a seção abaixo para obter mais informações sobre cada uma dessas chamadas e seus parâmetros.
NOTA : Alguns syscalls podem não ser implementados. Por exemplo, em computadores onde os diretórios não são suportados, os syscalls relacionados aos diretórios podem ser omitidos.
Para executar um syscall, o número da operação deve ser armazenado no registro L , os parâmetros devem ser armazenados seguindo estas regras:
| Nome do parâmetro na API | Registro Z80 |
|---|---|
| u8 dev | H |
| U8 ndev | E |
| U8 sinalizadores | H |
| U8 cmd | C |
| letra U8 | D |
| Código U8 | H |
| U8 fs | E |
| u8 id | H |
| U8 de onde | A |
| U16 BUF | DE |
| tamanho U16 | BC |
| Nome U16 | BC |
| U16 DST | DE |
| U16 Arg | DE |
| Caminho U16 | DE |
| U16 Argv | DE |
| Duração U16 | DE |
| U16 tempo | DE |
| U16 Data | DE |
| U24 Src | HBC |
| Offset U32 | BCDE |
E, finalmente, o código deve executar uma instrução RST $08 (verifique os vetores de redefinição).
O valor retornado é colocado em A. O significado desse valor é específico para cada chamada, verifique a documentação das rotinas em questão para obter mais informações.
Para maximizar a compatibilidade dos programas de usuários com o kernel de OS de 8 bits zelo, independentemente de o kernel ter sido compilado no modo MMU ou sem mmu, os parâmetros de syscalls restrições são os mesmos:
Qualquer buffer passado para um syscall não deve atravessar uma página virtual de 16kb
Em outras palavras, se um buffer buf de tamanho n estiver localizado na página virtual i , seu último byte, apontado por buf + n - 1 , também deverá estar localizado na mesma página i .
Por exemplo, se o syscall read for chamado com:
DE = 0x4000 e BC = 0x1000 , os parâmetros estão corretos , porque o buffer apontado por DE Fits na página 1 (de 0x4000 a 0x7FFF )DE = 0x4000 e BC = 0x4000 , os parâmetros estão corretos , porque o buffer apontado por DE Fits na página 1 (de 0x4000 a 0x7FFF )DE = 0x7FFF e BC = 0x2 , os parâmetros estão incorretos , porque o buffer apontado por DE está entre a página 1 e a página2.exec Embora o sistema operacional zelo de 8 bits seja um sistema operacional mono-atento, ele pode executar e manter vários programas na memória. Quando um programa A executa um programa B graças ao Syscall exec , ele fornecerá um parâmetro mode que pode ser EXEC_OVERRIDE_PROGRAM ou EXEC_PRESERVE_PROGRAM :
EXEC_OVERRIDE_PROGRAM : Esta opção diz ao kernel que o programa A não precisa mais ser executado; portanto, o Programa B será carregado no mesmo espaço de endereço que o Programa A. Em outras palavras, o Programa B será carregado nas mesmas páginas Ram do Programa A, ele o substituirá.EXEC_PRESERVE_PROGRAM : Esta opção informa ao kernel que o programa A precisa ser mantido na RAM até que o Programa B termine sua execução e chama o syscall exit . Para isso, o kernel alocará três novas páginas de memória ( 16KB * 3 = 48KB ) nas quais armazena o programa recém -carregado B. Depois que o programa B saídas, o kernel libera as páginas alocadas anteriormente para o programa B, as Páginas de memória do Programa A do Programa B podem recuperar o valor da saída do Programa A. A profundidade da árvore de execução é definida no menuconfig , graças à opção CONFIG_KERNEL_MAX_NESTED_PROGRAMS . Representa o número máximo de programas que podem ser armazenados em RAM ao mesmo tempo. Por exemplo, se a profundidade for 3, o programa A pode chamar o Programa B, o Programa B poderá ligar para o Programa C, mas o Programa C não pode chamar nenhum outro programa. No entanto, se um programa invocar exec com EXEC_OVERRIDE_PROGRAM , a profundidade não será incrementada, pois o novo programa para carregar substituirá o atual. Como tal, se retomarmos o exemplo anterior, o Programa C poderá ligar para um programa se e somente se ele invocar o Syscall exec no modo EXEC_OVERRIDE_PROGRAM .
Cuidado, ao executar um subprograma, toda a tabela de dispositivos abertos (incluindo arquivos, diretórios e drivers), o diretório atual e os registros da CPU serão compartilhados .
Isso significa que, se o programa A abrir um arquivo com o descritor 3, o Programa B herdará esse índice e, portanto, também poderá ler, escrever ou até fechar esse descritor. Reciprocamente, se B abrir um arquivo, diretório ou driver e sair sem fechá -lo, o programa A também terá acesso a ele. Como tal, a diretriz geral a seguir é que, antes de sair, um programa deve sempre fechar os descritores que ele abriu. O único momento em que a tabela de dispositivos abertos e diretório atual é redefinida é quando o programa inicial (programa A no exemplo anterior) sai. Nesse caso, o kernel fechará todos os descritores da tabela de dispositivos abertos, reabrirá a entrada e saída padrão e recarregue o programa inicial.
Isso também significa que, ao invocar o Syscall exec em um programa de montagem, sobre o sucesso, todos os registros, exceto HL, devem ser considerados alterados porque serão usados pelo subprograma. Então, se você deseja preservar AF , BC , DE , IX ou IY , eles devem ser empurrados na pilha antes de invocar exec .
Os Syscalls estão todos documentados nos arquivos de cabeçalho fornecidos para Assembly e C, você encontrará esse arquivo de cabeçalho no kernel_headers/ Directory, verifique seu arquivo README para obter mais informações.
Um motorista consiste em uma estrutura contendo:
SER0 , SER1 , I2C0 , etc. Os caracteres não-ASCII são permitidos, mas não são aconselhados.init , chamada quando as botas do kernel.read , onde os parâmetros e o endereço de retorno são os mesmos da tabela syscall.write , o mesmo que acima.open , o mesmo que acima.close , igual à acima.seek , o mesmo que acima.ioctl , o mesmo que acima.deinit , chamado ao descarregar o driver.Aqui está o exemplo de um simples registro de motorista:
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 O registro de um driver consiste em colocar essas informações (estrutura) dentro de uma seção chamada DRV_VECTORS . O pedido é muito importante, pois qualquer dependência do motorista deve ser resolvida em tempo de compilação. Por exemplo, se o driver A depende do driver B , a estrutura de B deverá ser colocada antes de A na seção DRV_VECTORS .
Na inicialização, o componente driver navegará em toda a seção DRV_VECTORS e inicializará os drivers um por um chamando sua rotina init . Se essa rotina retornar ERR_SUCCESS , o driver será registrado e os programas de usuário podem abri -lo, ler, escrever, ioctl, etc ...
Um driver pode ser oculto nos programas, isso é útil para drivers de disco que devem ser acessados apenas pela camada do sistema de arquivos do kernel. Para fazer isso, a rotina init deve retornar ERR_DRIVER_HIDDEN .
Como a comunicação entre aplicativos e hardware é feita através dos syscalls descritos acima, precisamos de uma camada entre o aplicativo do usuário e o kernel que determinará se precisamos chamar um driver ou um sistema de arquivos. Antes de mostrar a hierarquia dessa arquitetura, vamos falar sobre discos e motoristas.
As diferentes camadas podem ser vistas assim:
Fluxograma TD;
aplicativo (programa de usuário)
VFS (sistema de arquivos virtual)
DSK (módulo de disco)
DRV (implementação do driver: vídeo, teclado, serial, etc ...)
FS (sistema de arquivos)
Sysdis (Syscall Dispatcher)
HW (hardware)
Hora (módulo de hora e data)
MEM (módulo de memória)
carregador (módulo carregador)
App -Syscall/RST 8 -> SYSDIS;
sysdis --getdate/time-> time;
sysdis-montagem-> dsk;
sysdis -> vfs;
sysdis-map-> mem;
sysdis -exec/saída -> carregador;
VFS -> DSK & DRV;
dsk <-> fs;
fs -> drv;
drv -> hw;
O sistema operacional de 8 bits de zelo suporta até 26 discos ao mesmo tempo. Os discos são indicados por uma carta, de A a Z. É responsabilidade do driver de disco decidir onde montar o disco no sistema.
A primeira unidade, A , é especial, pois é aquela em que o sistema procurará preferências ou configurações.
Em um aplicativo, um path pode ser:
my_dir2/file1.txt/my_dir1/my_dir2/file1.txtB:/your_dir1/your_dir2/file2.txt Embora o sistema operacional seja completamente capaz de ser abrangente e não precise de nenhum sistema de arquivos ou disco para inicializar, assim que tentar carregar o programa inicial, chamado init.bin por padrão, ele verificará o disco padrão e solicitará esse arquivo. Assim, mesmo o armazenamento mais básico precisa de um sistema de arquivos ou algo semelhante.
O primeiro "sistema de arquivos", que já foi implementado, é chamado de "RawTable". Como o nome afirma, representa a sucessão de arquivos, não diretórios, em um dispositivo de armazenamento, em nenhuma ordem específica. O limite de tamanho do nome do arquivo é o mesmo que o kernel: 16 caracteres, incluindo o opcional . e extensão. Se quisermos compará -lo com o código C, seria uma matriz de estruturas que definem cada arquivo, seguido pelo conteúdo do arquivo na mesma ordem. Um código fonte do RomDisk Packer está disponível no packer/ na raiz deste repositório. Verifique seu ReadMe para obter mais informações sobre isso.
O segundo sistema de arquivos, que também é implementado, é chamado Zealfs. Seu principal objetivo é ser incorporado em armazenamentos muito pequenos, de 8kb a 64kb. É legível e gravável, suporta arquivos e diretórios. Mais informações sobre isso no repositório dedicado.
O terceiro sistema de arquivos que seria bom ter no sistema operacional de 8 bits é o FAT16. Muito famoso, já apoiado por quase todos os sistemas operacionais de desktop, utilizável em cartões Compactflash e até SD, isso é quase um must-have. Ainda não foi implementado, mas está planejado. O FAT16 não é perfeito, pois não é adaptado para um pequeno armazenamento, é por isso que o Zealfs é necessário.
O sistema operacional zelo de 8 bits é baseado em dois componentes principais: um kernel e um código de destino. Somente o kernel não faz nada. O destino precisa implementar os drivers, algumas macros MMU usadas dentro do kernel e um script de ligação. O script do vinculador é bastante simples, ele lista as seções na ordem em que elas devem ser vinculadas no binário final pelo z80asm Assembler.
Atualmente, o kernel usa as seguintes seções, que devem ser incluídas em qualquer script de ligação:
RST_VECTORS : contém os vetores de redefiniçãoSYSCALL_TABLE : contém uma tabela onde o endereço de rotina syscall i é armazenado no índice i , deve estar alinhado em 256SYSCALL_ROUTINES : contém o despachante syscall, chamado de um vetor de redefiniçãoKERNEL_TEXT : contém o código do kernelKERNEL_STRLIB : contém as rotinas relacionadas a string usadas no kernelKERNEL_DRV_VECTORS : representa uma variedade de drivers para inicializar, verifique a seção Driver para obter mais detalhes.KERNEL_BSS : contém os dados usados pelo código do kernel, devem estar em RAMDRIVER_BSS : Não usado diretamente pelo kernel, ele deve ser definido e usado nos drivers. O kernel o definirá para 0s na inicialização, deve ser maior que 2 bytes Como dito anteriormente, o suporte ao computador de 8 bits de zelo ainda é parcial, mas o suficiente para ter um programa de linha de comando em execução. O RomDisk é criado antes da criação do kernel, isso é feito no script.sh especificado no target/zeal8bit/unit.mk .
Esse script compilará o programa init.bin e o incorporará dentro de um RomDisk que será concatenado ao binário do sistema operacional compilado. O binário final pode ser piscado diretamente para o Nor Flash.
O que ainda precisa ser implementado, em nenhuma ordem específica:
Uma rápida porta para o computador Modelo-I do TRS-80 foi feita para mostrar como portar e configurar o sistema operacional de 8 bits de zelo para alvos que não possuem um MMU.
Esta porta é bastante simples, pois simplesmente mostra o banner de inicialização na tela, nada mais. Para fazer isso, apenas um driver de vídeo para o modo de texto é implementado.
Para ter uma porta mais interessante, os seguintes recursos precisariam ser implementados:
init.bin /romdisk pode ser somente leitura, portanto pode ser armazenado na ROMUm porto para a luz de Agon alimentada por EZ80, escrita e mantida por Shawn Sijnstra. Sinta -se à vontade para usar esse garfo para bugs/solicitações específicos de Agon. Isso usa o kernel não-MMU e implementa a maioria dos recursos que a implementação de computador de 8 bits zelo suporta.
Esta porta requer um carregador para que o binário seja armazenado e executado no local correto. O binário é osbootz, disponível aqui.
Observe que a porta usa o modo de terminal para simplificar a E/S do teclado. Isso também significa que a função de data não está disponível.
Outros recursos notáveis:
Para portar o Zeal de 8 bits, a versão MMU para outra máquina, verifique se você tem um mapeador de memória primeiro que divide o espaço de endereço de 64kb do Z80 em 4 páginas de 16 KB para a versão MMU.
Para transportar o sistema operacional de 8 bits de zelo de não-MMU, verifique se a RAM está disponível no endereço virtual 0x4000 e acima. O caso mais ideal que é ter ROM é o primeiro 16 KB para o sistema operacional e RAM nos 48 KB restantes para os programas de usuário e RAM do kernel.
Se seu alvo for compatível, siga as instruções:
Kconfig na raiz deste repositório e adicione uma entrada às opções de config TARGET e config COMPILATION_TARGET . Pegue os já presentes como exemplos.target/ para o seu destino, o nome deve ser o mesmo que o especificado na nova opção config TARGET .unit.mk Este é o arquivo que deve conter todos os arquivos de origem a serem montados ou os que incluem.unit.mk , para fazer isso, você pode preencher as seguintes make :SRCS : Lista dos arquivos a serem montados. Normalmente, esses são os drivers (obrigatórios)INCLUDES : os diretórios que contêm arquivos de cabeçalho que podem ser incluídosPRECMD : um comando Bash a ser executado antes do kernel começar a construirPOSTCMD : um comando Bash a ser executado após o edifício do kernel acabarmmu_h.asm que será incluído pelo kernel para configurar e usar o MMU. Verifique o target/zeal8bit/include/mmu_h.asm para ver como ele deve ser.zos_disks_mount , contendo um arquivo init.bin , carregado e executado pelo kernel na inicialização.zos_vfs_set_stdout .Para o Changelog completo, verifique a página de lançamento.
As contribuições são bem -vindas! Sinta -se à vontade para corrigir qualquer bug que você possa ver ou encontrar ou implementar qualquer recurso que considere importante.
Para contribuir:
(*) Uma boa mensagem de compromisso é a seguinte:
Module: add/fix/remove a from b
Explanation on what/how/why
Por exemplo:
Disks: implement a get_default_disk routine
It is now possible to retrieve the default disk of the system.
Distribuído sob a licença Apache 2.0. Consulte o arquivo LICENSE para obter mais informações.
Você é livre para usá -lo para uso pessoal e comercial, o placa presente em cada arquivo não deve ser removido.
Para qualquer sugestão ou solicitação, você pode entrar em contato comigo no contato [em] zeal8bit [dot] com
Para solicitações de recursos, você também pode abrir um problema ou uma solicitação de tração.
No entanto, eles não devem ser considerados não voláteis. Em outras palavras, um manipulador de interrupção não deve assumir que os dados que escrevem dentro de qualquer registro alternativo serão mantidos até a próxima vez que for chamado. ↩