About This Guide
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.
Environment Setup
Toolchain Setup
Ubuntu / Debian (recommended)
Native Linux — simplest path
| Package | Purpose |
|---|---|
nasm | Assembles boot.asm to a flat 512-byte binary |
gcc + gcc-multilib | C cross-compiler with -m32 flag (32-bit output) |
qemu-system-i386 | PC emulator — runs your disk image |
gdb | Debugger — attaches to QEMU via the remote protocol |
binutils | Provides ld, objcopy, nm, objdump |
-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
macOS
Homebrew — tested on macOS 13+
gdb via Homebrew and sign it with a code-signing certificate — see sourceware.org/gdb/wiki/PermissionsDarwin.
Windows
WSL2 — run Ubuntu inside Windows
Open PowerShell as Administrator:
-nographic and route output to the serial port — the Makefile's -serial stdio flag already does this.
Verify Installation
Confirm all tools are accessible
sudo apt install nasm. On macOS: brew install nasm.
On some systems NASM must be added to $PATH — check with which nasm.
Codebase
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
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
Compilation
Build & Run
Download & Extract
Get the starter kit
stage0/ directory in your working folderMake Targets
All build operations go through make
The final artefact is seng21213.img — a raw disk image consisting of the 512-byte boot sector followed immediately by the kernel binary.
| Make target | What it does |
|---|---|
make | Build seng21213.img (default) |
make run | Build (if needed) and launch in QEMU |
make debug | Launch QEMU paused, waiting for GDB on port 1234 |
make gdb | Attach GDB to a running make debug session |
make clean | Remove build/ and seng21213.img |
make info | Print section layout and symbol table |
QEMU opens a window showing the VGA output. You should see:
halt at the shell prompt, or press Ctrl+C in the terminal where you ran make run.
.c file you create in Stages 1–4Open Makefile and add your file to KERN_SRCS:
For NASM assembly files (e.g., boot/switch.asm) add a separate rule:
GDB + QEMU
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.
Terminal 1 — start QEMU in debug mode (CPU paused):
Terminal 2 — attach GDB (sets breakpoint at kernel_main then continues):
| Command | Description |
|---|---|
break kernel_main | Set breakpoint at function entry |
break kernel.c:55 | Set breakpoint at specific source line |
break *0x10000 | Set breakpoint at absolute address |
continue / c | Run until next breakpoint or halt |
step / s | Step into — one C source line |
next / n | Step over — one C source line |
stepi / si | Step exactly one machine instruction |
info registers | Show all CPU registers (EAX, ESP, EIP, EFLAGS…) |
print var | Print value of C variable |
x/10xw 0xB8000 | Examine 10 words at VGA buffer address |
x/10xw 0x90000 | Examine 10 words at kernel stack base |
disassemble | Disassemble current function |
layout asm | Split-screen: source + assembly panes |
layout regs | Split-screen: registers + assembly panes |
backtrace / bt | Print call stack |
delete breakpoints | Clear all breakpoints |
quit | Exit GDB (QEMU keeps running) |
[attr | char] stored little-endian as a 16-bit word.
0x0753 = attribute 0x07 (white on black) + character 0x53 ('S').
| Symptom | Likely cause | Debug 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 |
Interactive Shell
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.
| Command | Arguments | Description | Stage |
|---|---|---|---|
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.
OS Assignment
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.
- 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
v0.1-stage0 in GitLab. The kernel must boot in QEMU and all 6 built-in commands must work.
- 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
psshell command — lists all PCBs and their states
v0.2-stage1. Two user processes must run concurrently (e.g., each printing a character at a different rate), and ps must show both.
- Kernel threads sharing the process's address space —
thread_create(fn, arg) mutex_twithmutex_lock()andmutex_unlock()(blocking)- Counting
semaphore_twithsem_wait()andsem_signal() - Demonstrate the myglobal race condition with and without a mutex
- Bounded-buffer producer-consumer using three semaphores
v0.3-stage2. The producer-consumer demo must run without data corruption. The race condition demo must show the difference clearly on screen.
- 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 addresspmm_free_frame(paddr)— clears the bitmap bitmeminfoshell command — prints total / used / free MB
v0.4-stage3. meminfo must show correct totals. Allocate and free 100 frames in a loop and verify no leaks.
- 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
v0.5-stage4. Create, write to, read back, and delete at least 5 files. Verify the file system state with ls after each operation.
Bonus Marks
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.
| Extension | Stage | Lecture concept |
|---|---|---|
| Full ISO keyboard layout (Shift, CapsLock, AltGr) | 0 | L08 §3 — interrupt-driven I/O |
| VGA scrolling buffer with Page-Up / Page-Down | 0 | L07 §5 — VGA memory mapping |
| ANSI escape-code colour support in VGA driver | 0 | L07 §5 — memory-mapped I/O |
| Command history ring buffer (up/down arrow) | 0 | L02 §3 — circular buffer data structure |
| GRUB2 multiboot header (replace custom bootloader) | 0 | L07 §4 — boot protocol |
| Multi-level feedback queue (MLFQ) scheduler | 1 | L09 §4 — scheduling algorithms |
fork() — duplicate PCB and stack | 1 | L09 §2 — process creation |
sleep(ms) — sorted sleep queue on PIT ticks | 1 | L09 §3 — process state transitions |
| Priority inheritance in mutex | 2 | L10 §4 — priority inversion |
Read-write lock (rwlock_t) | 2 | L10 §5 — concurrency patterns |
| Deadlock detector (resource-allocation graph DFS) | 2 | L11 §2 — Coffman conditions |
| Buddy allocator for contiguous multi-frame requests | 3 | L11 §5 — memory management |
Slab allocator (kmalloc / kfree) | 3 | L11 §5 — memory allocation strategies |
| Single-indirect block pointer (max file 4 MB) | 4 | L12 §2 — i-node indirection |
Subdirectories (mkdir / cd / pwd) | 4 | L12 §3 — hierarchical file systems |
| Write-ahead journaling (crash recovery) | 4 | L12 §4 — file system reliability |
VFS abstraction layer (file_ops_t vtable) | 4 | L12 §3 — virtual file system design |
GitLab
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.
# 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
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.
| Stage | Tag name | Due |
|---|---|---|
| Stage 0 — Boot & Shell | v0.1-stage0 | End of Week 8 |
| Stage 1 — Scheduler | v0.2-stage1 | End of Week 9 |
| Stage 2 — Threads | v0.3-stage2 | End of Week 10 |
| Stage 3 — Memory | v0.4-stage3 | End of Week 11 |
| Stage 4 — File System | v0.5-stage4 | End of Week 12 |
make clean && makeproduces zero errors and zero warningsmake runboots 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")
.gitignoreexcludesbuild/andseng21213.imgREADME.mdis 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
Troubleshooting
FAQ
nasm: command not founderror: unrecognized command line option '-m32'Your GCC installation lacks 32-bit multilib support. Install it:
The most common cause is the kernel loading address not matching the linker script. Check:
KERNEL_LOAD_SEG EQU 0x1000inboot.asm→ physical address0x1000 × 0x10 = 0x10000. = 0x10000inlinker.ld- Both values must match. Run
make debugand step through with GDB from0x7C00.
Remote connection closed immediatelyYou 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.
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.
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.
References
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