Skip to content

Latest commit

 

History

153 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

mboot

Minimal x86 (i386) operating system that boots from BIOS, enters 32-bit protected mode, and runs ELF userspace programs from a FAT16 partition. Includes preemptive scheduling, paging, a VFS, PS/2 + VGA + serial I/O, ATA disk, PCI/e1000 networking (IPv4/UDP/ICMP), and a ported Lua 5.5 userspace.

Sources and references: see SOURCES.md for datasheets, specs, and tutorials used (Intel SDM, OSDev Wiki, FAT/ELF specs, RFCs, etc.).

Table of Contents

Architecture

BIOS (16-bit real mode)
  └─ stage1/boot.asm (MBR @ 0x7C00) ── BIOS int 0x13 load ─┐
                                                           ▼
                                          0x10000: kernel.bin (stage2)
                                          ┌──────────────────────────┐
                                          │ loader.c: loader_start() │
                                          │  PIC remap, IDT, PIT,    │
                                          │  FPU, serial, E820 mem,  │
                                          │  paging (4 MiB identity),│
                                          │  PCI, net, GDT/TSS,      │
                                          │  scheduler, FAT16, init  │
                                          └──────────┬───────────────┘
                                                     │ spawn_elf("/init.elf")
                                                     ▼
                                          userspace (ring 0, paged)
                                          init → getty ×4 → Lua / ping / UDP echo
  • Target: i386-elf, 32-bit protected mode, identity-paged with 4 MiB pages (paging.c), per-process 4 KiB page tables on exec/fork.
  • No long mode / no SMP. All code runs at CPL 0; isolation is via paging (PAGE_USER) and TSS.esp0.
  • Syscalls via int 0x80 (idt.c:177), dispatched in syscall_dispatch (stage2/cpu/interrupts/idt.c:14).

Boot Flow

  1. MBR (stage1/boot.asm:1): BIOS loads sector 0 to 0x7C00. Saves DL boot drive, clears screen (int 0x10 AH=0x00), detects memory via int 0x15 EAX=0xE820 (stage1/boot.asm:19), loads kernel.bin sectors after the MBR via int 0x13 AH=0x02 (stage1/boot.asm:84), enables A20 (port 0x92), loads GDT (stage1/gdt.asm) and enters protected mode (CR0.PE).
  2. Trampoline (stage2/start_loader.asm): sets flat segments 0x08/0x10, stack at 0x90000, calls loader_start.
  3. Kernel init (stage2/loader.c:32): masks PIC, pic_remap to 0x20/0x28, idt_init, exception ISRs, pit_init (divisor 1193 → ~1000 Hz), enable_fpu, keyboard/serial, cpuid_init, init_alloc from E820 map at 0x71000/0x70FF0, tty_init, paging_init, pci_enumerate, net_init, gdt_init + ltr, scheduler_init, fat16_init, then spawn_elf("/init.elf") and hlt idle loop.

Disk image (image.sh): boot.bin written to LBA 0, MBR partition table at 0x1BE (P1 boot stub + P2 type 0x06 FAT16), mkfs.fat -F16 on P2 and mcopy of build/*.elf and test files.

Kernel Subsystems

Subsystem Files Notes
CPU / GDT / TSS stage2/cpu/gdt.c:19, inc/cpu/gdt.h Flat 4 GiB segments 0x9A/0x92, TSS for esp0 on interrupt
Interrupts stage2/cpu/interrupts/idt.c:158, isr.asm, irq.asm, isr.c, irq.c 256-entry IDT, gate 0x80:DPL3=0xEF, EOI via PIC
PIC 8259A stage2/cpu/pic/pic.c:31 Remap 0x20/0x28, mask per IRQ, cascade on IRQ2
PIT 8253 stage2/cpu/pit/pit.c:4 Mode 2, channel 0, divisor 1193
Memory stage2/mem.c:73, inc/mem.h E820 biggest usable region → bump allocator + free-list (kmalloc/kfree with coalesce/split), 8-byte align
Paging stage2/paging.c:40 CR4.PSE, 1024×4 MiB identity; paging_create_directory/paging_map_page/paging_clone_directory for processes
ATA PIO stage2/dev/disk.c:40 LBA28 PIO, ports 0x1F0/0x3F6, insw, per-disk mutex via scheduler_block
MBR / FAT16 inc/mbr.h:7, stage2/fat16.c:196, stage2/vfs.c MBR parse, BPB, FAT chain 0xFFF8 EOC, LFN, root + data clusters, VFS driver
ELF32 stage2/elf.c:12, inc/elf.h PT_LOAD only, filters vaddr<0x01000000 artifact, spawn_elf/exec_elf
Scheduler stage2/scheduler.c:226, stage2/sched_switch.asm Round-robin, TCB linked list, TASK_READY/RUNNING/BLOCKED/EXITED, fork_current clones page tables, exec_replace builds new address space
VGA / TTY / Keyboard / Serial / RTC stage2/dev/vga.c:8, tty.c, keyboard.c:45, serial.c, rtc.c VGA text 0xB8000, cursor 0x3D4/0x3D5, scancode set 1, 16550 UART, 4 TTYs
PCI / e1000 stage2/dev/pci.c, stage2/dev/e1k.c:369 BAR decode (I/O vs 32/64-bit MMIO), bus mastering, 82540EM TX/RX rings
Net stack stage2/net/eth.c, arp.c, ipv4.c, icmp.c, udp.c, socket.c:88, net.c:7 Ethernet II, ARP, IPv4 10.0.2.15/24 gw 10.0.2.2, ICMP echo, UDP, AF_INET SOCK_DGRAM/SOCK_RAW

Userspace

  • Toolchain: clang -target i386-elf -ffreestanding -fno-pic, ld.lld -m elf_i386, nasm (Makefile:1, link.ld:1). User ELFs linked at 0x40000000+ (LIBC_LDFLAGS, USER_LDFLAGS).
  • libc (user/src/libc.c:1, user/include/): thin int 0x80 wrappers (exit=1, fork=2, read=3, write=4, open=5, close=6, waitpid=7, exec=11, ioctl=54, dup2=63, socket=300…), minimal string.h/stdio.h/stdlib.h/math.h (bump malloc 64 KiB, no free), setjmp/longjmp via inline asm.
  • Programs: user/src/init.c:5 (spawns getty on TTY 1-4; init stays on TTY1), user/src/getty.c:3 (→ execv("/lua.elf")), user/lua/ (Lua 5.5.0 supported, code in user/lua is the original tar from https://www.lua.org/ftp/lua-5.5.0.tar.gz, built via user/lua/Makefile), test_files/ping.c (raw ICMP), test_files/udp_lua.c/socket_echo.c (UDP echo), test_files/fork_test.c.
  • Syscalls: fork (scheduler.c:471 copies user pages + int 0x80 frame), execv (elf.c:81scheduler_exec_replace), waitpid, read/write/open/close/dup2 via VFS, socket/bind/sendto/recvfrom, ioctl(TIOCSCTTY).

Networking Demo

Kernel configures 10.0.2.15/24 via 10.0.2.2 at boot (stage2/net/net.c:7). init starts getty.elf on each tty, which in turn prints a short login string and launches lua.elf.

QEMU with forwarding (host 10007 → guest 7):

qemu-system-x86_64 -m 4G -drive file=image.img,format=raw \
  -serial stdio \
  -device e1000,netdev=n0 -netdev user,id=n0,hostfwd=udp::10007-:7 \
  -object filter-dump,id=f1,netdev=n0,file=netdump.pcap

From host:

printf 'hello from host\n' | nc -u -w 1 127.0.0.1 10008

Expected serial log: NET:/IPV4: bring-up, ping: ICMP round-trips, RX a.b.c.d:src -> 10.0.2.15:7 for UDP.

Prerequisites

  • Build: clang + ld.lld + llvm-objcopy (LLVM ≥ 16), nasm, make, mtools (mcopy), mkfs.fat (dosfstools).
  • Run: qemu-system-x86_64 with e1000 support.
  • macOS: brew install llvm nasm qemu mtools dosfstools (ensure clang is LLVM clang, not Apple clang wrapper, or set CC=/opt/homebrew/opt/llvm/bin/clang).

Building

make                 # → kernel.elf / kernel.bin / boot.bin / build/*.elf / image.img
make DEBUG=1         # keep debug symbols in kernel.elf → kernel.sym
make clean

Artifacts: kernel.elf (linked at 0x10000 via link.ld:6), kernel.bin (flat binary incbin'd in stage1/boot.asm:146), boot.bin (512-byte MBR + stage2), image.img (MBR + FAT16).

Running

# headless / serial only
qemu-system-x86_64 -m 4G -drive file=image.img,format=raw -serial stdio -display none

# with networking (no forward)
qemu-system-x86_64 -m 4G -drive file=image.img,format=raw -serial stdio \
  -device e1000,netdev=n0 -netdev user,id=n0

# with UDP forward + pcap (recommended)
qemu-system-x86_64 -m 4G -drive file=image.img,format=raw -serial stdio \
  -device e1000,netdev=n0 -netdev user,id=n0,hostfwd=udp::10007-:7 \
  -object filter-dump,id=f1,netdev=n0,file=netdump.pcap

In QEMU: Alt+1..4 (via TTY handler keyboard.c:78) switches TTYs if forwarded through the emulated keyboard; serial stdio shows kernel INFO logs.

Project Layout

stage1/          BIOS MBR + GDT, disk load, switch to protected mode
stage2/          kernel: loader, cpu/{gdt,cpuid,interrupts,pic,pit}, mem, paging,
                 dev/{disk,serial,vga,tty,keyboard,pci,e1k,rtc},
                 net/{eth,arp,ipv4,icmp,udp,socket,net}, fat16, vfs, elf, scheduler
inc/             kernel headers
user/src/        libc, crt0.s, init, getty
user/lua/        Lua 5.5.0 (vendored)
user/include/    userspace libc headers
test_files/      ping, udp_lua, socket_echo, fork_test, demo programs
tools/           helper scripts
link.ld          kernel link at 0x10000
image.sh         MBR + FAT16 image creation

Screenshots

2025-10-20 — cube + time demo:

mboot cube

2026-08-06 — init + getty + Lua on TTYs:

mboot init

Roadmap

Completed in this capstone:

  • 16→32-bit protected mode + GDT/TSS (stage1/gdt.asm, stage2/cpu/gdt.c:19)
  • CPU exceptions + IDT + int 0x80 syscalls (stage2/cpu/interrupts/)
  • 8259 PIC remap + per-IRQ handling (stage2/cpu/pic/pic.c:31)
  • 8253 PIT tick (stage2/cpu/pit/pit.c:4)
  • PS/2 keyboard scancode set 1 (stage2/dev/keyboard.c:45)
  • VGA text mode + TTY layer (stage2/dev/vga.c:8, tty.c)
  • 16550 serial / RS232 (stage2/dev/serial.c)
  • ATA PIO LBA28 disk (stage2/dev/disk.c:40)
  • MBR partitioning (inc/mbr.h:7, image.sh:72)
  • FAT16 + VFS + LFN (stage2/fat16.c:196)
  • x87 FPU enable (loader.c:41)
  • E820 memory map + bump/free-list allocator (stage2/mem.c:73)
  • 4 MiB identity paging + per-process paging (stage2/paging.c:40)
  • ELF32 loader (stage2/elf.c:12)
  • Preemptive scheduler + fork/exec/waitpid (stage2/scheduler.c:226)
  • PCI enumeration + Intel e1000 driver (stage2/dev/e1k.c:369)
  • IPv4/ARP/ICMP/UDP + BSD-like sockets (stage2/net/)
  • Userspace: init/getty/Lua/ping/UDP echo demos (user/src/init.c:5)

Partial / known gaps:

  • libcuser/src/libc.c:1 is a minimal freestanding subset: bump malloc (64 KiB, no free/realloc correctness), no fseek/ftell, stub time/mktime, no pthread/errno thread-safety. Suitable for Lua + demos, not POSIX-complete.
  • File I/O — FAT16 read-only (fat16.c:374 write=-1), no directories beyond root, no unlink/rename.

Planned / out-of-scope for this capstone:

  • filesystem write support
  • Proper sbrk/mmap + free in kernel and userspace

Known Limitations

  • All tasks run at ring 0; process isolation relies only on paging and TSS.esp0 — no user-mode privilege separation.
  • Single-core, no APIC; PIC only. Timer is PIT, not LAPIC timer.
  • Network is guest 10.0.2.15/24 via QEMU user-mode NAT; no DHCP client, address is hardcoded (stage2/net/net.c:7).
  • Disk is PIO without DMA or interrupts; busy-waits on 0x1F7.

References

Full bibliography with URLs and spec chapters: SOURCES.md.

About

No description, website, or topics provided.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages