Overview

The OS assignment runs across five stages (Stage 0–4), each corresponding to the OS lectures L08–L12. You begin with a fully working Stage 0 starter kit — a 512-byte MBR bootloader, a VGA driver, a PS/2 keyboard driver, and a minimal shell — and progressively add a scheduler, threads, a memory manager, and a file system.

The kernel is written in C (C99, freestanding) and x86-32 NASM assembly. It runs inside QEMU, which emulates a real PC at the ISA level. No operating system features are available — you build them yourself from scratch.

This guide covers everything from installing the toolchain to tagging your final submission in GitLab.

Start here Download the Stage 0 starter kit, confirm QEMU boots it, then begin extending. Everything compiles on Ubuntu, Debian, macOS, or Windows (WSL2).

Toolchain Setup

A

Ubuntu / Debian (recommended)

Native Linux — simplest path

Install required packages
One command installs everything needed for Stages 0–4
1
$ sudo apt update $ sudo apt install -y \ nasm \ gcc \ gcc-multilib \ make \ qemu-system-i386 \ gdb \ binutils \ git
PackagePurpose
nasmAssembles boot.asm to a flat 512-byte binary
gcc + gcc-multilibC cross-compiler with -m32 flag (32-bit output)
qemu-system-i386PC emulator — runs your disk image
gdbDebugger — attaches to QEMU via the remote protocol
binutilsProvides ld, objcopy, nm, objdump
Cross-compiler (optional but recommended) For a cleaner build without -m32, install a dedicated i686-elf cross-compiler. The Makefile auto-detects it and falls back to host gcc if absent.
See the OSDev Wiki: wiki.osdev.org/GCC_Cross-Compiler
B

macOS

Homebrew — tested on macOS 13+

Install via Homebrew
Install Homebrew first if not present: brew.sh
1
$ brew install nasm qemu gdb $ brew install i686-elf-gcc # cross-compiler (preferred)
GDB on macOS Apple's LLDB is installed by default but does not support the remote protocol used by QEMU. Install gdb via Homebrew and sign it with a code-signing certificate — see sourceware.org/gdb/wiki/PermissionsDarwin.
C

Windows

WSL2 — run Ubuntu inside Windows

Enable WSL2 and install Ubuntu
Then follow the Ubuntu instructions above
1

Open PowerShell as Administrator:

PS> wsl --install # Restart your machine when prompted. # Then open Ubuntu from the Start menu and run the Ubuntu apt commands above.
QEMU display on WSL2 QEMU's graphical window requires an X server on Windows (e.g., VcXsrv or the built-in WSLg on Windows 11). Alternatively, use -nographic and route output to the serial port — the Makefile's -serial stdio flag already does this.
D

Verify Installation

Confirm all tools are accessible

Version check
Run these before attempting to build
2
$ nasm --version NASM version 2.15.05 compiled on ... $ gcc --version gcc (Ubuntu 11.4.0) 11.4.0 ... $ qemu-system-i386 --version QEMU emulator version 8.0.4 ... $ gdb --version GNU gdb (Ubuntu 12.1) 12.1 ...
If nasm is not found On Ubuntu: sudo apt install nasm. On macOS: brew install nasm. On some systems NASM must be added to $PATH — check with which nasm.

Project Structure

The starter kit unpacks to a single directory. Every file you need for Stage 0 is provided. For Stages 1–4 you create new files under kernel/ and add entries to the Makefile.

stage0/
                ├── boot/
                │   └── boot.asm              # 512-byte MBR (NASM) — do not modify for Stage 0
                ├── kernel/
                │   ├── kernel.c              # kernel_main() — C entry point, add Stage 1–4 inits here
                │   ├── shell.c               # Shell loop + command table — add new commands here
                │   └── string.c              # memset, strlen, strcmp etc. (no libc available)
                ├── drivers/
                │   ├── vga/
                │   │   └── vga.c             # VGA text driver — writes to 0xB8000
                │   └── keyboard/
                │       └── keyboard.c        # PS/2 keyboard driver (polling)
                ├── include/
                │   ├── kernel.h              # kernel_main / kernel_halt declarations
                │   ├── vga.h                 # VGA public API + colour enum
                │   ├── keyboard.h            # Keyboard public API
                │   ├── io.h                  # Inline x86 IN/OUT port helpers
                │   ├── shell.h               # shell_run() declaration
                │   └── string.h              # Minimal string library
                ├── linker.ld                 # Linker script — places kernel at 0x10000
                ├── Makefile                  # Full build system
                └── run.sh                    # Convenience QEMU launch script
Files to add for each stage Stage 1: kernel/process.c, kernel/scheduler.c, boot/switch.asm  ·  Stage 2: kernel/thread.c, kernel/mutex.c, kernel/semaphore.c  ·  Stage 3: kernel/pmm.c  ·  Stage 4: kernel/ramdisk.c, kernel/fs.c

Build & Run

1

Download & Extract

Get the starter kit

Unpack the starter kit
Creates a stage0/ directory in your working folder
1
$ tar -xzf SENG21213_OS_Stage0.tar.gz $ cd stage0 $ ls Makefile README.md boot/ drivers/ include/ kernel/ linker.ld run.sh
2

Make Targets

All build operations go through make

Build the disk image
Assembles the bootloader, compiles the kernel, links and concatenates
2
$ make AS boot/boot.asm Boot sector: 512 bytes (must be 512) CC kernel/kernel.c CC kernel/shell.c CC kernel/string.c CC drivers/vga/vga.c CC drivers/keyboard/keyboard.c LD build/kernel.elf BIN build/kernel.bin IMG seng21213.img Disk image: 33K

The final artefact is seng21213.img — a raw disk image consisting of the 512-byte boot sector followed immediately by the kernel binary.

Make targetWhat it does
makeBuild seng21213.img (default)
make runBuild (if needed) and launch in QEMU
make debugLaunch QEMU paused, waiting for GDB on port 1234
make gdbAttach GDB to a running make debug session
make cleanRemove build/ and seng21213.img
make infoPrint section layout and symbol table
Run in QEMU
The kernel boots and the shell appears in the QEMU window
3
$ make run # or simply: $ ./run.sh

QEMU opens a window showing the VGA output. You should see:

SENG21213 Stage 0 — Loading kernel... Kernel loaded. Switching to Protected Mode. [ OK ] VGA driver initialised [ OK ] PS/2 keyboard driver initialised +=================================================+ | SENG 21213 - Stage 0 Kernel Shell | | University of Kelaniya | | Type 'help' for available commands | +=================================================+ kernel> _
Close QEMU Type halt at the shell prompt, or press Ctrl+C in the terminal where you ran make run.
Adding new source files
Required for every new .c file you create in Stages 1–4
4

Open Makefile and add your file to KERN_SRCS:

# Before (Stage 0) KERN_SRCS := \ $(KERN_DIR)/kernel.c \ $(KERN_DIR)/shell.c \ $(KERN_DIR)/string.c # After (Stage 1 — add process.c and scheduler.c) KERN_SRCS := \ $(KERN_DIR)/kernel.c \ $(KERN_DIR)/shell.c \ $(KERN_DIR)/string.c \ $(KERN_DIR)/process.c \ $(KERN_DIR)/scheduler.c

For NASM assembly files (e.g., boot/switch.asm) add a separate rule:

BOOT_OBJS += $(BUILD)/boot/switch.o $(BUILD)/boot/switch.o: boot/switch.asm | $(BUILD) @echo " AS $<" @$(AS) $(ASFLAGS) $< -o $@

Debugging with GDB

QEMU has a built-in GDB stub. When launched with -s -S it listens on localhost:1234 and pauses the CPU before executing the first instruction. GDB can then connect, load the kernel's ELF symbol table, set breakpoints, and single-step through code — even inside interrupt handlers and NASM stubs.

Start a debug session
Two terminals required — one for QEMU, one for GDB
1

Terminal 1 — start QEMU in debug mode (CPU paused):

$ make debug QEMU [debug mode] — waiting for GDB on :1234

Terminal 2 — attach GDB (sets breakpoint at kernel_main then continues):

$ make gdb GNU gdb (Ubuntu 12.1) 12.1 Reading symbols from build/kernel.elf... Remote debugging using localhost:1234 0x0000fff0 in ?? () Breakpoint 1 at 0x10000: file kernel/kernel.c, line 47. Continuing. Breakpoint 1, kernel_main () at kernel/kernel.c:47 47 vga_init(); (gdb) _
Essential GDB commands
For kernel and bare-metal debugging
2
CommandDescription
break kernel_mainSet breakpoint at function entry
break kernel.c:55Set breakpoint at specific source line
break *0x10000Set breakpoint at absolute address
continue / cRun until next breakpoint or halt
step / sStep into — one C source line
next / nStep over — one C source line
stepi / siStep exactly one machine instruction
info registersShow all CPU registers (EAX, ESP, EIP, EFLAGS…)
print varPrint value of C variable
x/10xw 0xB8000Examine 10 words at VGA buffer address
x/10xw 0x90000Examine 10 words at kernel stack base
disassembleDisassemble current function
layout asmSplit-screen: source + assembly panes
layout regsSplit-screen: registers + assembly panes
backtrace / btPrint call stack
delete breakpointsClear all breakpoints
quitExit GDB (QEMU keeps running)
Inspecting the VGA buffer
Verify characters are being written correctly to 0xB8000
3
(gdb) x/8xh 0xB8000 0xb8000: 0x0753 0x0745 0x074e 0x0747 0x0720 0x0732 0x0731 0x0732 # High byte (0x07) = attribute: white on black # Low byte = ASCII: 0x53='S', 0x45='E', 0x4E='N', 0x47='G' ...
Memory layout reminder Each VGA cell is 2 bytes: [attr | char] stored little-endian as a 16-bit word. 0x0753 = attribute 0x07 (white on black) + character 0x53 ('S').
Common crash scenarios
What to do when QEMU resets or freezes
4
SymptomLikely causeDebug approach
QEMU reboots in a loop Triple fault — CPU reset caused by invalid GDT, IDT, or stack Run make debug, break at *0x7C00, step through boot.asm
Blank screen VGA driver not writing to 0xB8000, or wrong attribute byte Break at vga_init, inspect x/4xh 0xB8000
Keyboard not responding PS/2 controller not ready, or status bit polling incorrect Break at keyboard_getchar, check inb(0x64) return
Garbage characters Wrong load address in Makefile vs. linker.ld Check KERNEL_LOAD_SEG in boot.asm matches linker.ld . = 0x10000
Stack overflow Deep recursion or large stack-allocated arrays Inspect ESP: info registers, check it is below 0x90000

Shell Commands

The Stage 0 shell reads a line from the PS/2 keyboard, tokenises on whitespace, and dispatches to a command handler via a command table in kernel/shell.c. To add a new command, add a row to the commands[] array and implement the handler function.

CommandArgumentsDescriptionStage
help List all available commands with descriptions 0
clear Fill screen with spaces and reset cursor to (0,0) 0
echo<text> Print all arguments back to the screen 0
version Print kernel name and version string 0
colour<fg> <bg> Change text colour (0–15 for each) 0
halt Disable interrupts and halt the CPU 0
ps List all processes (PID, state, name) 1
kill<pid> Terminate a process by PID 1
meminfo Show total / used / free physical frames 3
ls List files on the RAM disk 4
touch<name> Create an empty file 4
cat<name> Print file contents to screen 4
write<name> <text> Append text to a file 4
rm<name> Remove a file 4

Greyed rows are not part of Stage 0 — implement them as you complete later stages.

Assignment Stages

Each stage builds directly on the previous one. Do not skip stages — the scheduler depends on the context-switch stub you write in Stage 1, the thread system depends on the scheduler, and so on.

Stage 0 — Boot, VGA & Shell Stage 0
Lecture L07–L08 · Starter kit provided
0
  • 512-byte MBR in NASM loads the kernel via INT 0x13
  • 3-entry GDT and CR0 Protected Mode switch
  • VGA text-mode driver writing directly to physical 0xB8000
  • PS/2 keyboard driver (polling mode, Set-1 scancodes)
  • Interactive shell with command table dispatch
Deliverable Tag v0.1-stage0 in GitLab. The kernel must boot in QEMU and all 6 built-in commands must work.
Stage 1 — Process Table & Round-Robin Scheduler Stage 1
Lecture L09 · Create: process.c, scheduler.c, switch.asm
1
  • Define pcb_t — at minimum: PID, state, stack pointer, entry point
  • Implement create_process(entry_fn) — allocates a 4 KB stack
  • Program the i8253 PIT to fire IRQ0 at 100 Hz (10 ms tick)
  • Write switch.asm — PUSHAD/POPAD context switch stub
  • Implement the Round-Robin scheduler in the IRQ0 handler
  • Add ps shell command — lists all PCBs and their states
Deliverable Tag v0.2-stage1. Two user processes must run concurrently (e.g., each printing a character at a different rate), and ps must show both.
Stage 2 — Threads, Mutex & Semaphore Stage 2
Lecture L10 · Create: thread.c, mutex.c, semaphore.c
2
  • Kernel threads sharing the process's address space — thread_create(fn, arg)
  • mutex_t with mutex_lock() and mutex_unlock() (blocking)
  • Counting semaphore_t with sem_wait() and sem_signal()
  • Demonstrate the myglobal race condition with and without a mutex
  • Bounded-buffer producer-consumer using three semaphores
Deliverable Tag v0.3-stage2. The producer-consumer demo must run without data corruption. The race condition demo must show the difference clearly on screen.
Stage 3 — Physical Memory Manager Stage 3
Lecture L11 · Create: pmm.c
3
  • Parse the BIOS E820 memory map stored by the bootloader at a known address
  • Build a bitmap — 1 bit per 4 KB physical page frame
  • pmm_alloc_frame() — first-fit scan, marks frame used, returns physical address
  • pmm_free_frame(paddr) — clears the bitmap bit
  • meminfo shell command — prints total / used / free MB
Deliverable Tag v0.4-stage3. meminfo must show correct totals. Allocate and free 100 frames in a loop and verify no leaks.
Stage 4 — RAM Disk File System Stage 4
Lecture L12 · Create: ramdisk.c, fs.c
4
  • 1 MB RAM disk — a fixed-size byte array in BSS
  • Superblock at block 0 — magic number, block/inode counts
  • Block bitmap and inode bitmap at fixed offsets
  • inode_t — size, direct block pointers (8 × 4 KB = 32 KB max)
  • Flat directory — array of (name[28], inode) entries in block 1
  • POSIX-style API: fs_open, fs_read, fs_write, fs_close, fs_unlink
  • Shell: ls, touch, cat, write, rm
Deliverable Tag v0.5-stage4. Create, write to, read back, and delete at least 5 files. Verify the file system state with ls after each operation.

Extension Ideas

Extensions are optional but can earn bonus marks. Each extension must be clearly documented in your README — explain what it does, how to test it, and which lecture concept it demonstrates. More ambitious extensions that are thoroughly tested and commented earn more marks than trivial ones that are broken.

ExtensionStageLecture concept
Full ISO keyboard layout (Shift, CapsLock, AltGr)0L08 §3 — interrupt-driven I/O
VGA scrolling buffer with Page-Up / Page-Down0L07 §5 — VGA memory mapping
ANSI escape-code colour support in VGA driver0L07 §5 — memory-mapped I/O
Command history ring buffer (up/down arrow)0L02 §3 — circular buffer data structure
GRUB2 multiboot header (replace custom bootloader)0L07 §4 — boot protocol
Multi-level feedback queue (MLFQ) scheduler1L09 §4 — scheduling algorithms
fork() — duplicate PCB and stack1L09 §2 — process creation
sleep(ms) — sorted sleep queue on PIT ticks1L09 §3 — process state transitions
Priority inheritance in mutex2L10 §4 — priority inversion
Read-write lock (rwlock_t)2L10 §5 — concurrency patterns
Deadlock detector (resource-allocation graph DFS)2L11 §2 — Coffman conditions
Buddy allocator for contiguous multi-frame requests3L11 §5 — memory management
Slab allocator (kmalloc / kfree)3L11 §5 — memory allocation strategies
Single-indirect block pointer (max file 4 MB)4L12 §2 — i-node indirection
Subdirectories (mkdir / cd / pwd)4L12 §3 — hierarchical file systems
Write-ahead journaling (crash recovery)4L12 §4 — file system reliability
VFS abstraction layer (file_ops_t vtable)4L12 §3 — virtual file system design

Submission

Each stage is submitted as a tagged release in your private GitLab repository. The teaching team will clone your repo, check out the tag, run make run, and test the required functionality. Ensure your code builds cleanly with zero warnings on a standard Ubuntu 22.04 installation.

Repository setup
Do this once before Stage 0 submission
1
# 1. Create a private repository on your personal GitHub account
                #    Go to https://github.com/new — set visibility to Private
                #    Repository name: seng21213-os
                
                # 2. Clone it and copy the starter kit in
                $ git clone https://github.com/<your-username>/seng21213-os.git
                $ cp -r stage0/* seng21213-os/
                $ cd seng21213-os
                
                # 3. Add a .gitignore to exclude build artefacts
                $ echo "build/
                seng21213.img
                *.o
                *.bin
                *.elf" > .gitignore
                
                # 4. Initial commit and push
                $ git add .
                $ git commit -m "Initial commit: Stage 0 starter kit"
                $ git push origin main
                
                # 5. Add the lecturer as a collaborator so your work can be reviewed
                #    GitHub → Settings → Collaborators → Add: tiroshanm
Use your personal GitHub account. Create the repository at github.com/new and set visibility to Private. Add tiroshanm as a collaborator — this is how your submission will be reviewed. Do not commit binary artefacts: the build/ directory and seng21213.img must stay in .gitignore. Only source files (.c, .asm, .h), the Makefile, linker.ld, and README.md should be committed.
Tagging a stage for submission
Repeat for each stage; push tags to remote
2
# After completing a stage — clean build passes, QEMU runs correctly $ make clean && make # Confirm clean build $ git add . $ git commit -m "Stage 0 complete: boot, VGA, keyboard, shell" $ git tag v0.1-stage0 $ git push origin main --tags
StageTag nameDue
Stage 0 — Boot & Shellv0.1-stage0End of Week 8
Stage 1 — Schedulerv0.2-stage1End of Week 9
Stage 2 — Threadsv0.3-stage2End of Week 10
Stage 3 — Memoryv0.4-stage3End of Week 11
Stage 4 — File Systemv0.5-stage4End of Week 12
Submission checklist
Verify before pushing each tag
3
  • make clean && make produces zero errors and zero warnings
  • make run boots in QEMU without crashing or rebooting
  • All required commands for this stage work correctly
  • Code is commented with references to lecture sections (e.g., "L09 §3")
  • .gitignore excludes build/ and seng21213.img
  • README.md is updated — describes your extensions and how to test them
  • Extension(s) documented in README with test instructions (bonus marks)
  • Tag pushed to GitLab: git push origin main --tags

FAQ

nasm: command not found
Q
$ sudo apt install nasm # Ubuntu/Debian $ brew install nasm # macOS
error: unrecognized command line option '-m32'
Q

Your GCC installation lacks 32-bit multilib support. Install it:

$ sudo apt install gcc-multilib
QEMU window opens but screen is blank / QEMU reboots immediately
Q

The most common cause is the kernel loading address not matching the linker script. Check:

  • KERNEL_LOAD_SEG EQU 0x1000 in boot.asm → physical address 0x1000 × 0x10 = 0x10000
  • . = 0x10000 in linker.ld
  • Both values must match. Run make debug and step through with GDB from 0x7C00.
GDB says Remote connection closed immediately
Q

You ran make gdb without a running make debug session. Start make debug first in a separate terminal, wait for the message "waiting for GDB on :1234", then run make gdb.

Keyboard input not appearing in QEMU window
Q

Click inside the QEMU window to capture keyboard focus. To release focus press Ctrl+Alt+G. If running headless (-nographic), type directly in the terminal — the PS/2 driver will still receive scancodes via QEMU's emulated keyboard.

Can I use C++ instead of C?
Q

Technically yes, but it is strongly discouraged for bare-metal work at this level. C++ requires additional runtime support (__cxa_atexit, global constructors, vtable infrastructure) that adds complexity without benefit. All lecture examples use C99 — stay with C.

Additional Resources

📘

Stallings COA

Computer Organization and Architecture, 10th Ed. — primary textbook for all lectures

🌐

OSDev Wiki

Comprehensive bare-metal x86 reference — GDT, IDT, PIT, PS/2, VGA, paging

📖

NASM Manual

Complete reference for NASM syntax, directives, and output formats

Intel SDM

Intel 64 and IA-32 Architectures Software Developer's Manual (Vol. 1–3)

🛠

QEMU Docs

QEMU user and developer documentation including GDB remote protocol

🎯

Course Home

Lecture slides, notes, and OS implementation blog articles for all 12 lectures