Núcleo monolítico bare-metal implementado em C11 e Assembly para a arquitetura RISC-V de 32 bits (RV32I / rv32imafdch), projetado para execução emulada sobre a plataforma virtual virt do QEMU em cooperação com firmware OpenSBI.
O sistema implementa do zero os mecanismos fundamentais de isolamento de privilégio, paginação virtual em dois níveis (Sv32), escalonamento preemptivo de processos, vetores de trap com tratamento de exceções e chamadas de sistema (system calls), além de uma aplicação de usuário (shell) embutida.
mini-kernel/
├── kernel/
│ ├── kernel.c # Ponto de entrada, paginação Sv32, escalonador e trap handler
│ ├── kernel.h # Definições de CSRs, PCB (struct process) e flags de página
│ └── kernel.ld # Linker script: mapeia seções (.text, .rodata, .data, .bss, RAM)
├── user/
│ ├── shell.c # Aplicação em user-space executando em U-mode
│ ├── user.c # Runtime de usuário: vetor de inicialização, syscalls (ecall) e exit
│ ├── user.h # Protótipos de chamadas e interface de biblioteca de usuário
│ └── user.ld # Linker script do processo de usuário (origem em 0x01000000)
├── common/
│ ├── common.c # Implementação freestanding de funções de suporte (memcpy, printf)
│ └── common.h # Tipos primitivos estritos (uint32_t, paddr_t, vaddr_t) e macros
└── Makefile # Pipeline de build determinístico via Clang, LLD e llvm-objcopy
- Machine-mode (M-Mode): O emulador QEMU inicializa o firmware OpenSBI em
0x80000000, preparando os registradores de deleção de traps (mideleg,medeleg) e saltando para o endereço de carga do kernel (0x80200000) em Supervisor-mode. - Supervisor-mode (S-Mode): A rotina
bootemkernel/kernel.cinicializa o ponteiro de pilha (sp = __stack_top), zera a seção.bss, configura o vetor de exceções (stvec = kernel_entry) e instancia as tabelas de páginas do processo sentinela (idle_proc) e da aplicação de usuário. - User-mode (U-Mode): A transição de privilégio é orquestrada pela rotina
user_entry. Através da instruçãosret, o registradorsepcé carregado comUSER_BASE (0x01000000)e o registradorsstatustem o bitSPIE(Previous Interrupt Enable) ativado, rebaixando a CPU para execução não privilegiada.
O kernel opera sob o esquema padrão RISC-V Sv32, traduzindo endereços virtuais de 32 bits em endereços físicos de 34 bits através de uma hierarquia de tabelas de dois níveis com páginas de
-
Alocador Físico de Quadros (
alloc_pages): Alocador sequencial com alinhamento estrito a fronteiras de página ($4\text{ KB}$ ), gerenciando a região física delimitada entre__free_rame__free_ram_end. -
Mapeamento de Identidade do Kernel: Mapeia a memória do kernel com permissões de leitura, escrita e execução (
PAGE_R | PAGE_W | PAGE_X | PAGE_V). -
Espaço de Endereçamento do Usuário: Cria tabelas de segundo nível sob demanda (
map_page), mapeando a imagem do executável emUSER_BASE (0x01000000)com o bit de privilégio de usuário ativo (PAGE_U). A ausência dePAGE_Uem páginas do kernel garante que qualquer tentativa de acesso a dados privilegiados a partir de U-mode resulte em Page Fault imediato.
MEMÓRIA FÍSICA (RAM) MEMÓRIA VIRTUAL (PROCESSO)
0x80000000 ┌───────────────────────────┐ 0x00000000 ┌───────────────────────────┐
│ Firmware OpenSBI (M-mode) │ │ Área Não Mapeada (Guard) │
0x80200000 ├───────────────────────────┤ 0x01000000 ├───────────────────────────┤ USER_BASE
│ Kernel (.text, .rodata) │ │ Código/Dados da Aplicação │
├───────────────────────────┤ ├───────────────────────────┤
│ Dados e BSS (.data, .bss) │ │ Pilha do Usuário │
├───────────────────────────┤ 0x80200000 ├───────────────────────────┤
│ Pilha de Boot do Kernel │ │ Mapeamento de Identidade │
__free_ram ├───────────────────────────┤ │ do Kernel (Supervisor) │
│ Quadros de Páginas │ │ [PAGE_U = 0: Protegido] │
│ Gerenciados Dinamicamente │ 0xFFFFFFFF └───────────────────────────┘
0x88000000 └───────────────────────────┘
-
Process Control Block (
struct process): Mantém o identificador do processo (pid), estado (PROC_UNUSED,PROC_RUNNABLE), pilha de execução dedicada de$8\text{ KB}$ (stack[8192]) e endereço físico da tabela de páginas raiz (page_table). -
Troca de Contexto (
switch_context): Operação atômica em Assembly que salva os registradores callee-saved da ABI RISC-V (ra,s0-s11) na pilha do processo atual e restaura o contexto do próximo processo a ser executado. -
Escalonamento Round-Robin (
yield): Itera pela tabela de processos ativos alternando a execução cooperativamente. Ao trocar de contexto, o registrador de controlesatp(Supervisor Address Translation and Protection) é atualizado com a raiz da nova tabela de páginas e a instruçãosfence.vmainvalida as entradas correspondentes no TLB (Translation Lookaside Buffer).
-
Entrada de Trap (
kernel_entry): Ao ocorrer uma exceção ou interrupção, o hardware transfere o controle para o endereço configurado no CSRstvec. A rotinakernel_entryexecuta a troca atômica do ponteiro de pilha (csrrw sp, sscratch, sp), aloca espaço para astruct trap_framee empilha os 31 registradores gerais da CPU. -
Despacho de System Calls: Exceções do tipo Environment Call from U-mode (
scause = 8) são interceptadas pelo tratadorhandle_trap. O argumento transmitido no registradora0é consumido (e.g., impressão de caractere via console SBI), e o endereço de retorno emsepcé incrementado em$+4$ bytes antes de retornar ao usuário viasret. -
Kernel Panic com Diagnóstico Completo: Traps inesperadas (falhas de página, instruções ilegais, violações de alinhamento) despejam os valores dos CSRs
scause,stvalesepcna saída padrão antes de congelar a CPU em laço seguro.
O processo de build desacopla completamente o espaço de usuário do kernel:
- O código de usuário (
user/shell.ceuser/user.c) é compilado e linkado pelo Clang/LLD usando o scriptuser/user.ld, gerando o executável ELFshell.elf. - A ferramenta
llvm-objcopyextrai a imagem binária crua (shell.bin). - Uma segunda passagem com
llvm-objcopy -Ibinary -Oelf32-littleriscvencapsula o binário em um arquivo objeto relocável (shell.bin.o), expondo os símbolos_binary_shell_bin_starte_binary_shell_bin_size. - O kernel linka diretamente esse objeto, permitindo carregar e inicializar processos de usuário a partir da memória física sem a necessidade de um sistema de arquivos externo.
- Clang / LLVM com suporte a RISC-V (alvo
riscv32-unknown-elfe linkerlld). - llvm-objcopy
- qemu-system-riscv32
Em distribuições baseadas em Debian/Ubuntu:
sudo apt-get update
sudo apt-get install clang lld llvm qemu-system-miscmakeCompila a aplicação de usuário, gera os binários intermediários, embuti o executável no objeto e gera a imagem consolidada em build/kernel.elf.
make runInicializa a máquina virtual RISC-V (-machine virt), carrega o firmware OpenSBI padrão e executa o kernel com redirecionamento de I/O para a saída do terminal (-nographic).
Para encerrar a emulação do QEMU a qualquer momento: pressione Ctrl + A seguido de X.
make clean- João Pedro Sant'Ana de Carvalho (@santcjoao) — Concepção, implementação dos subsistemas de bootstrap, paginação Sv32, escalonamento e arquitetura de build bare-metal.