Minimal multi-arch bare-metal kernel (x86 / RISC-V / ARM) — boots on QEMU and real Allwinner D1 & Zynq-7020 silicon
Boots under QEMU on all three ISAs from one clang build: - shared C core (kmain, console) calls a small per-arch contract (arch.h) - per-arch asm entry + trap/vector table + UART driver - parallel Makefile (make -j), 'make run-<arch>', 'make sizes' M1 = boot, UART up, trap vector installed, banner from kmain(). |
||
|---|---|---|
| arch | ||
| common | ||
| include | ||
| .gitignore | ||
| Makefile | ||
| README.md | ||
minos — a minimal multi-arch kernel
One tiny OS core, three ISAs: x86 (i386), RISC-V (rv64), ARM
(Cortex-A9 / Zynq-7000). Built by a single clang in parallel so you can
compare how the same C behaves across architectures.
Milestone M1 (this state): each target boots, brings up its UART, sets its
interrupt/trap vector, and prints a banner from shared C (kmain).
Design
The only boundary is a small per-arch contract (include/arch.h):
arch_early_init() bring up the UART
arch_uart_putc(c) emit one byte
arch_set_trap_vector() install IDT / mtvec / VBAR
arch_name() banner string
arch_halt() hlt / wfi
Everything in common/ is portable C and calls only that contract. Each
arch/<name>/ supplies the irreducible assembly (boot.S, trap/vector table)
plus a C file implementing the contract and a linker script.
common/ main.c (kmain), console.c (putc/puts/puthex)
include/ arch.h (the contract), console.h
arch/x86/ boot.S (multiboot) arch.c (COM1 0x3F8, IDT) link.ld (1 MiB)
arch/riscv/ boot.S (_start) trap.S (mtvec) arch.c (UART 0x10000000)
arch/arm/ boot.S (_start) vectors.S (VBAR) arch.c (Cadence UART 0xE0000000)
Build & run
make -j # build all three in parallel
make run-x86 # boot under QEMU (Ctrl-A X to quit)
make run-riscv
make run-arm
make sizes # compare stripped binary size
make clean
The interesting comparison (M1)
| Aspect | x86 (i386) | RISC-V (rv64) | ARM (Cortex-A9) |
|---|---|---|---|
| Entry protocol | Multiboot1 header | _start @ 0x80000000 |
_start @ DDR |
| Console | port I/O outb 0x3F8 |
MMIO 16550 0x10000000 | MMIO Cadence 0xE0000000 |
| Trap vector | IDT via lidt |
mtvec CSR |
VBAR (CP15) |
| Multi-core gate | (single by default) | mhartid check |
MPIDR check |
| Halt | hlt |
wfi |
wfi |
| Idle byte size | ~890 | ~816 | ~800 |
Real hardware (next)
- RISC-V: the only change is the UART base —
0x10000000(QEMU virt) →0x02500000(Allwinner D1 / Lichee RV). Loadable viaxfel. - ARM/Zynq: same Cadence UART base as the real PS; boot via U-Boot/JTAG.
- x86: boot the multiboot ELF from GRUB.
Roadmap
- M2: real trap handlers + a timer tick per arch.
- M3: expose a tiny syscall-style interface (the "own interface").