Tutorial sobre a criação de um sistema operacional do zero para fins de aprendizado.
- Objetivos
- Fontes de pesquisa
- Requisitos
- Conceitos
- Diferenças entre 'diretivas' e 'instruções'
- Makefile
- Código do boot - versão 1
- Dar boot na máquina
- Carregar o kernel
- BIOS Interrupt call - Wikipedia
- Interrupt Vector Table (IVT) - Wikipedia
- How to write a simple operating system - Mike Saunders (MikeOS)
Debian e derivados:
sudo apt install build-essential qemu nasm
- O BIOS carrega o primeiro setor do dispositivo na memória.
- Endereço:
0x7c00
. - O BIOS procura a assinatura de boot:
0xaa55
. - Se encontrada, inicia a execução do código do boot.
- O BIOS procura pela partição especial EFI.
- O sistema operacional deve ser compilado com um programa de EFI.
- Determinam como o montador deverá compilar o código.
- Não são traduzidas em código de máquina.
- Diferem de montador para montador.
- Determinam o que a CPU deverá executar.
- São traduzidas em código de máquina.
- Conjuntos de códigos de operação (OpCodes) da CPU.
- A CPU 8086 tinha um barramento de endereços de 20 bits.
- Endereçamento máximo 2^20 bytes: 1.048.576 bytes ≃ 1MB.
- Computadores tinha de 64kB a 128kB de memória.
- Como o limite era muito maior, decidiu-se usar um esquema de memória definido segmentos e offsets.
- Desta forma, o endereçamento era feito com um par de valores com 16 bits.
SEGMENTO OFFSET
\ /
0x1234:0x5678
- Cada segmento abrange 64kB de memória.
- Cada byte é acessado pelo valor do offset
- Os segmentos são sobrepostos com um deslocamento de 16 bytes.
0 16 32 48 ... 64kB
[segmento 0...............]
[segmento 1...............]
[segmento 2...............]
Endereço real = segmento * 16 + offset
Isso significa que há várias formas de acessar o mesmo local na memória.
Exemplo: endereço do código de boot na memória (0x7c00
)
Segmento:Offset | Cálculo (base 10) | Endereço real |
---|---|---|
0x0000:0x7c00 |
0 * 16 + 31744 = 31744 |
0x7c00 |
0x0001:0x7bf0 |
1 * 16 + 31728 = 31744 |
0x7c00 |
0x0010:0x7b00 |
16 * 16 + 31488 = 31744 |
0x7c00 |
0x00c0:0x7000 |
192 * 16 + 28672 = 31744 |
0x7c00 |
0x07c0:0x0000 |
1984 * 16 + 0 = 31744 |
0x7c00 |
- Code Segment (
CS
): segmento do código em execução (*). - Data Segment (
DS
): segmento de dados. - Stack Segment (
SS
): segmento endereço corrente da pilha. - Extra Segments (
ES
,FS
,GS
): segmentos de dados extra.
(*) O registrador do ponteiro de instruções (
IP
) registra apenas o offset.
Esquema geral:
segmento:[base + índice * escala + deslocamento]
Onde:
Campo | Registrador/Valor |
---|---|
Segmento | CS , DS (padrão), ES , FS , GS , SS (padrão se a base for BP ) |
Base | BP , BX (em 32/64 bits, qualquer registrador de propósito geral) |
Índice | SI , DI (em 32/64 bits, qualquer registrador de propósito geral) |
Escala | (Apenas em 32/64 bits) valor imediato 1, 2 4 ou 8 |
Deslocamento | Um valor imediato com sinal |
Exemplo 1:
var: dw 100
mov ax, var ; Copia o endereço no offset definido por 'var' para {ax}.
mov ax, [var] ; Implicitamente, copia o conteúdo do segmento de dados ({DS})
; no offset definido por 'var' para {ax}.
Exemplo 2:
array: dw 100, 200, 300
mov bx, array ; Copia o endereço de 'array' para {bx}.
mov si, 2 * 2 ; Copia 4 para {si} (será o offset de array[2]).
mov ax, [bx + si] ; Copia para {ax} o conteúdo no endereço base em {bx} + 4 bytes.
Importante! Antes de executar o
make
, é preciso copiar o código-fonte trabalhado para o arquivosrc/main.asm
!
ASM=nasm
SRC_DIR=src
BUILD_DIR=build
$(BUILD_DIR)/disk.img: $(BUILD_DIR)/main.bin
cp $(BUILD_DIR)/main.bin $(BUILD_DIR)/lost.img
truncate -s 1440k $(BUILD_DIR)/lost.img
$(BUILD_DIR)/main.bin: $(SRC_DIR)/main.asm
$(ASM) $(SRC_DIR)/main.asm -f bin -o $(BUILD_DIR)/main.bin
run:
@qemu-system-i386 -drive file=build/lost.img,format=raw,index=0,if=floppy
Esta versão do código apenas inicia o boot e não faz mais nada.
Arquivo src/main.asm
:
org 0x7c00 ; Onde o BIOS carregará o nosso boot na memória.
bits 16 ; Produzir código em 16 bits.
main: ; Apenas uma referência para o início do código.
hlt ; Para a execução do código.
jmp $ ; Salte para cá se a execução continuar (loop infinito).
times 510-($-$$) db 0 ; Preenche com zeros o restante do binário até 510 bytes.
dw 0xaa55 ; Escreve a palavra 0xaa55 (assinatura de boot).