Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

8 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Linux 0.11 — Zig port

A port of the classic Linux 0.11 kernel (Linus Torvalds, 1991) to the Zig programming language, targeting bare-metal 32-bit x86 (i386) — the same architecture the original ran on.

The goal is a faithful re-implementation of Linux 0.11's architecture: the same subsystems, the same algorithms, the same selector/IDT layout and the same file structure, rewritten in modern, memory-safe-ish Zig instead of C and hand-written assembly. It boots to protected mode, enables paging, runs a preemptive-timer-driven scheduler over multiple tasks, takes keyboard input and services system calls through int 0x80 — all verifiable under QEMU.

Linux 0.11 (Zig port) starting...

Multiboot info block at 0x00009500
Console initialised.
GDT loaded (132 entries).
IDT loaded, exceptions and PIC initialised.
Paging enabled; page pool has 3072 free pages (12 MiB).
get_free_page -> 0x00fff000, 0x00ffe000 (3070 free)
after free/realloc -> 0x00fff000 (3070 free)
Scheduler and 100 Hz timer initialised.
System-call gate (int 0x80) installed.
Keyboard initialised.
syscall write() via int 0x80 works!
getpid() -> 0, time() -> 0s
Spawned taskA and taskB.
Type on the keyboard; timer is ticking.

[taskA] iteration 0 (jiffies=6)
...
[swapper] 1s: jiffies=100
[swapper] 2s: jiffies=200
[swapper] 3s: jiffies=300

Building and running

Requires Zig 0.14.x and qemu-system-i386. If you don't have them, scripts/setup-env.sh installs both (Zig to /opt/zig, qemu via apt):

bash scripts/setup-env.sh   # optional: install the toolchain

Then:

zig build          # build the kernel ELF -> zig-out/bin/system
zig build run      # build and boot under QEMU (serial on stdio)
zig build debug    # boot and wait for GDB on :1234 (qemu -s -S)

The kernel is a freestanding i386 ELF with a Multiboot header, so QEMU can load it directly with -kernel. To watch it headless, the VGA console is mirrored to COM1:

qemu-system-i386 -kernel zig-out/bin/system -serial stdio -display none

What's implemented

Subsystem This port Original 0.11 source
Boot / entry src/boot/boot.zig — Multiboot header, _start, stack, kmain boot/bootsect.s, boot/setup.s, boot/head.s
GDT src/boot/head.zig — kernel code/data + per-task TSS/LDT slots boot/head.s, include/asm/system.h
IDT / exceptions / PIC src/kernel/traps.zig — comptime-generated ISR stubs, dispatcher, 8259 remap kernel/traps.c, kernel/asm.s
Descriptor helpers src/include/system.zigset_{intr,trap,system}_gate, lgdt/lidt/ltr/lldt include/asm/system.h
Port I/O src/include/io.ziginb/outb/cli/sti/hlt include/asm/io.h
VGA console src/kernel/chr_drv/console.zig — 80×25 text, scrolling, HW cursor kernel/chr_drv/console.c
Serial src/kernel/chr_drv/serial.zig — COM1 UART (debug console) kernel/chr_drv/serial.c, rs_io.s
Keyboard src/kernel/chr_drv/keyboard.zig — IRQ1, scan-code tables, shift/ctrl/caps kernel/chr_drv/keyboard.S
printk src/kernel/printk.zig — formatted kernel output kernel/printk.c, kernel/vsprintf.c
Scheduler / timer src/kernel/sched.zig — task table, 100 Hz PIT, schedule(), context switch kernel/sched.c
Paging / page alloc src/mm/memory.zig — identity map, get_free_page/free_page, mem_map[] mm/memory.c, boot/head.s
System calls src/kernel/sys.zig, src/include/unistd.zigint 0x80 gate + dispatcher kernel/system_call.s, kernel/sys.c, include/unistd.h
fork src/kernel/fork.zigcopy_process/sys_fork (task-table half) kernel/fork.c
Block device src/kernel/blk_drv/ramdisk.zig — RAM disk serving an embedded image kernel/blk_drv/{hd,floppy,ll_rw_blk}.c
Buffer cache src/fs/buffer.zig(dev,block)-keyed cache, bread/brelse fs/buffer.c
Filesystem (MINIX v1) src/fs/{fs,super,inode,namei,file,open}.zig — mount, iget, bmap, namei, file_read, descriptors fs/{super,inode,namei,file_dev,open,file_table}.c
panic src/kernel/panic.zig kernel/panic.c

The filesystem image itself is generated by tools/mkfs_minix.py (a self-contained MINIX v1 mkfs) and embedded into the kernel.

Each source file names the original file it ports and explains where it stays faithful and where it deliberately diverges.

Verified behaviour

Everything below was exercised under qemu-system-i386 during development:

  • Boots via Multiboot straight into 32-bit protected mode and prints.
  • Loads its own GDT (kernel CS=0x08, DS=0x10) and IDT.
  • Catches CPU exceptions: a deliberately triggered fault is reported with a full register dump and panics.
  • The 8253 PIT fires IRQ0 at exactly 100 Hz (jiffies = 100/200/300 per second).
  • The scheduler context-switches between kernel tasks, which run and exit cleanly, returning control to the idle swapper task.
  • The IRQ1 keyboard handler decodes keystrokes (with shift) and echoes them.
  • Paging is enabled (CR0.PG) and the kernel keeps running on the identity map; the page allocator hands out and reclaims pages.
  • write(), getpid() and time() round-trip through the real int 0x80 system-call gate.
  • The MINIX v1 root filesystem mounts off the RAM disk: / lists correctly, files (including the nested /etc/motd) read back with exact byte counts through the buffer cache / inode / bmap / namei path, and the same file reads back again via open()/read()/close() over int 0x80.

Design notes and divergences

A few places intentionally depart from a byte-for-byte port, always documented in the relevant source file:

  • Booting. The original 16-bit real-mode boot chain (bootsect.s loading setup.s, BIOS disk reads, A20, the switch to protected mode) is replaced by a Multiboot header. A Multiboot loader (QEMU -kernel, or GRUB) drops us into 32-bit protected mode with A20 already enabled, so the port can focus on the kernel proper rather than re-deriving BIOS floppy loading in Zig.
  • Tasks run in ring 0. The scheduler currently switches kernel threads with an explicit register/stack swap (switch_ctx) rather than the CPU's hardware task switch (ljmp through a per-task TSS) over per-process page tables. The GDT already reserves the TSS/LDT descriptor slots (see head.zig) so the move to ring-3 tasks is the natural next step.
  • printk formatting uses Zig's std.fmt placeholders ({d}, {x}, {s}) rather than C's %d/%x, giving compile-time-checked format strings.

Not yet ported (the roadmap)

The pieces that build on per-process address spaces are the next milestones:

  • User mode: ring-3 execution, per-task TSS/LDT switching, demand paging and copy-on-write in the int 14 (page fault) handler.
  • A real fork/execve with copy_mem, loading a binary off the filesystem and moving to user mode to run process 1.
  • The filesystem write path (bmap allocation, new_block/new_inode, buffer write-back) and real block drivers — hard disk and floppy (kernel/blk_drv/hd.c, floppy.c) in place of the RAM disk.
  • The full tty layer (line discipline, tty_io.c) feeding a shell.

The read side of the MINIX v1 filesystem and the buffer cache are already in place (see the table above).

Layout

build.zig            freestanding i386 build + qemu run/debug steps
linker.ld            link at 1 MiB with a Multiboot header
src/
  kernel.zig         root module (pulls in boot/, sets src/ as the module base)
  boot/              boot.zig (entry), head.zig (GDT + paging descriptors)
  kernel/            main.zig, sched.zig, traps.zig, sys.zig, fork.zig,
                     printk.zig, panic.zig
    chr_drv/         console.zig, serial.zig, keyboard.zig
    blk_drv/         ramdisk.zig (+ embedded disk.img)
  mm/                memory.zig (paging + page allocator)
  fs/                fs.zig, buffer.zig, super.zig, inode.zig, namei.zig,
                     file.zig, open.zig (MINIX v1 read path)
  include/           io.zig, system.zig, unistd.zig
tools/mkfs_minix.py  generator for the embedded MINIX v1 image

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages