Este não é um sistema operacional real . É apenas um sistema operacional simples criado em fins educacionais.
O principal objetivo que estou seguindo é aprender como está funcionando desde o início. A partir do próprio setor de inicialização, hardware e software interrompem, os próprios drivers.
O repositório é sufixo com o GCC porque estou planejando escrever outro sistema operacional simples com ferrugem. Espero que haja Ghaiklor-OS-GCC e Ghaiklor-Os-Rustc .
| Olá, mundo |
|---|
O Ghaiklor-OS-GCC consiste em dois arquivos em formato binário bruto: boot.bin e kernel.bin . Eles estão localizados em boot/boot.bin e kernel/kernel.bin de acordo após a compilação.
Boot.bin é compilado via NASM . Makefile pega boot/boot.asm e chama nasm boot/boot.asm -f bin -o boot/boot.bin . As alças de Nasm incluem dos próprios sub-dobradores, para que todos os arquivos de montagem sejam compilados para binários. Nada mais, simples.
O kernel.bin é compilado via GCC e LD cruzado que você deve instalar. Dê uma olhada na seção de ambiente de desenvolvimento. Após a instalação do compilador cruzado, podemos pegar fontes da CPU , drivers , incluir as pastas Kernel e Libc recursivamente. Todos os arquivos .C são compilados via gcc . Os arquivos de objeto compilados são usados para compilar o kernel.bin , via LD ld -o kernel/kernel.bin -Ttext 0x1000 <OBJ_FILES> --oformat binary .
OS-IMAGE.BIN IS compilado concatenado de boot.bin e kernel.bin . Facilmente alcançado com cat boot/boot.bin kernel/kernel.bin > os-image.bin .
Eu escrevi script bootstrap.sh , que você pode executar. Ele instalará todas as dependências necessárias para sua máquina host.
bash bootstrap.shQuando um computador é ligado ou redefinido, ele passa por uma série de diagnósticos chamados pós-teste de energia em si. Essa sequência culmina na localização de um dispositivo inicializável, como um disquete, cdrom ou um disco rígido.
Um dispositivo é inicializável se transportar um setor de inicialização com a sequência de bytes 0x55 , 0xAA nos bytes 511 e 512, respectivamente. Quando o BIOS encontra um setor de inicialização, ele é carregado na memória em 0x0000:0x7C00 .
Uma implementação simples do dispositivo inicializável:
jmp $
times 510 - ($ - $$) db 0
dw 0xAA55 $ - $$ resulta em CURRENT_POINTER - START_POINTER . Dessa forma, estamos calculando quanto tempo nosso registro de inicialização dura. Posteriormente, estamos substituindo 510 e preenchendo Zeros, obtendo o registro de inicialização de 512 bytes com a assinatura do setor de inicialização.
Por exemplo, temos $ - $$ igual a 100. Portanto, temos 510 - 100 = 410 bytes livres. Estamos preenchendo esses 410 bytes com zeros. E os dois últimos bytes 511 e 512 são assinaturas inicializáveis, que estamos preenchendo com dw 0xAA55 .
Feito! Temos nosso dispositivo inicializável e podemos substituir nosso jmp $ por qualquer código que desejar.
Implementação do setor de inicialização
No início, nosso código está em execução no modo real.
O modo real é um modo simplista de 16 bits que está presente em todos os processadores X86. O Modo Real foi o primeiro design do modo X86 e foi usado por muitos sistemas operacionais iniciais. Para fins de compatibilidade, todos os processadores X86 começam a execução no modo real.
O que é ruim e bom no modo real?
Contras
Prós
Devido às muitas limitações e problemas que o modo real tem, precisamos mudar para o modo protegido.
O modo protegido é o principal modo de operação dos processadores Intel modernos desde o 80286. Ele permite trabalhar com vários espaços de endereço virtual, cada um dos quais tem um máximo de 4 GB de memória endereçável.
Como a CPU inicializada pelo BIOS inicia no modo real, a troca para o modo protegido impede que você use a maioria das interrupções do BIOS. Antes de mudar para o modo protegido, você deve desativar as interrupções, incluindo NMI, ativar a linha A20 e carregar a tabela de descritores globais.
Algoritmo para mudar para o modo protegido:
cli
lgdt [ gdt_descriptor ]
mov eax , cr0
or eax , 0x1
mov cr0 , eax
jmp CODE_SEG:init_pmImplementação para mudar para PM
Tabela de descritor global
Mas, podemos ir mais longe ...
O que é o modo longo e por que configurá -lo?
Desde a introdução dos processadores x86-64, também foi introduzido um novo modo, o que é chamado de modo longo. O modo longo consiste basicamente em dois sub-modos, que são o modo real de 64 bits e o modo de compatibilidade (32 bits).
O que estamos interessados é simplesmente o modo de 64 bits, pois esse modo fornece muitos novos recursos, como:
Antes de mudar para o modo longo, devemos verificar se a CPU suporta esse modo. No caso, se a CPU não suportar o modo longo, precisamos falar no modo protegido.
Detectar se o modo longo suporta
Nesse caso, mude para o modo longo
Todos esses modos são ótimos, mas não podemos escrever um sistema operacional em 512 bytes. Portanto, nosso setor de inicialização deve saber como carregar nosso kernel compilado no disco rígido.
Quando estamos no modo real, podemos usar interrupções no BIOS para a leitura do disco. No nosso caso, é INT 13,2 - Read Disk Sectors .
Como usá -lo?
;; al = number of sectors to read (1 - 128)
;; ch = track/cylinder number
;; cl = sector number
;; dh = head number
;; dl = drive number
;; bx = pointer to buffer
mov ah , 0x02
mov al , 15
mov ch , 0x00
mov cl , 0x02
mov dh , 0x00
mov dl , 0
mov bx , KERNEL_OFFSET_IN_MEMORY
int 0x13 Este código resulta na leitura do disco rígido no endereço KERNEL_OFFSET_IN_MEMORY . Ele lê 15 setores começando no segundo e o armazena por endereço KERNEL_OFFSET_IN_MEMORY .
Como nossa imagem do sistema operacional compilada é uma concatenação do setor de botas e do kernel, e sabemos que nosso setor de inicialização é de 512 bytes, podemos ter certeza de que nosso kernel inicia no segundo setor.
Quando a leitura é concluída com êxito, podemos ligar para a instrução em nosso KERNEL_OFFSET_IN_MEMORY e fazer execução no kernel.
call KERNEL_OFFSET_IN_MEMORY
jmp $Implementação para leitura de disco
Podemos desenhar uma linha aqui sobre o nosso setor de inicialização. O fluxo é simples:
call ;Nesta etapa, nosso setor de botas terminou seu trabalho e começa a trabalhar com o kernel.
Você pode navegar pelas fontes de inicialização e tentar conseguir como funciona.
Quando estamos chamando instruções por endereço, podemos ter alguns problemas. Não podemos ter certeza, essa instrução por endereço é um kernel_main() . A solução é simples.
Podemos escrever uma sub-rotina anexada ao início do código do kernel. Esta sub -rotina chama função externa do nosso kernel - kernel_main() . Quando os arquivos do objeto serão vinculados, esta chamada será traduzida em chamada de nosso kernel_main() .
global _start
[bits 32]
[extern kernel_main]
_start:
call kernel_main
jmp $Implementação de entrada do kernel
Nesta etapa, temos um ponto de entrada no método kernel_main() . E esse é o nosso ponto de entrada para o kernel inteiro.
Eu acho, é chato explicar como #include funciona e o que acontece em nosso kernel_main() . Você pode facilmente seguir os métodos que estou chamando dele.
Entrada do kernel em C
Essa é a parte mais simples.
Precisamos criar imagem boot/boot.bin no formato binário bruto. Para fazer isso, chamamos nasm Assembler com bandeiras especiais.
nasm boot/boot.asm -f bin -o boot/boot.binIsso resulta em um formato binário bruto que você pode executar via qemu.
Nesta etapa, temos o setor de inicialização compilado.
Precisamos criar todas as fontes de todas as pastas recursivamente, exceto a pasta boot .
Todos os arquivos C são compilados em arquivos de objeto via gcc e arquivos de montagem via nasm :
gcc -g -ffreestanding -Wall -Wextra -fno-exceptions -m32 -std=c11 -c < SOURCE > -o < OBJ_FILE >
nasm < SOURCE > -f elf -o < OBJ_FILE > Isso resulta em todos os arquivos de objeto necessários para vincular o formato binário bruto. Tudo o que resta fazer é vinculá -los via ld :
ld -o kernel/kernel.bin -Ttext 0x1000 kernel/kernel_entry.o < OBJ_FILES > --oformat binary Observe que kernel/kernel_entry.o em primeiro lugar, pois temos um problema em chamar o kernel_main() . Dessa forma, garantimos que a primeira instrução seja chamada de nossa boot/kernel_entry.asm .
Afinal, compilamos a imagem do kernel em formato binário bruto.
Desde então, nosso setor de botas e kernel são formatos binários brutos, podemos apenas concatená -los.
cat boot/boot.bin kernel/kernel.bin > os-image.bin Agora, podemos executar os-image.bin via qemu-system-i386 . BIOS tentando localizar o setor inicializável, descubra nossa boot/boot.bin e vê a assinatura. Comece a executar nosso código de montagem em boot/boot.bin , que carrega nosso kernel/kernel.bin via Int 13,2 na memória e o executa.
É assim que tudo funciona junto. Sinta -se à vontade para navegar pelo projeto, obrigado?
A licença do MIT (MIT)
Copyright (c) 2016 Eugene Obrezkov
A permissão é concedida, gratuita, a qualquer pessoa que obtenha uma cópia deste software e arquivos de documentação associados (o "software"), para lidar com o software sem restrição, inclusive sem limitação os direitos de usar, copiar, modificar, mesclar, publicar, distribuir, mobilizar o software e/ou vender cópias do software e permitir que as pessoas a quem
O aviso de direitos autorais acima e este aviso de permissão devem ser incluídos em todas as cópias ou em partes substanciais do software.
O software é fornecido "como está", sem garantia de qualquer tipo, expresso ou implícito, incluindo, entre outros, as garantias de comercialização, aptidão para uma finalidade específica e não innoculação. Em nenhum caso os autores ou detentores de direitos autorais serão responsáveis por qualquer reclamação, danos ou outro passivo, seja em uma ação de contrato, delito ou não, decorrente de, fora ou em conexão com o software ou o uso ou outras negociações no software.