Lab 01 — fakegpu: A GPU-Shaped Linux Character Device Driver
Phase: 04 — Linux & Drivers | Difficulty: ⭐⭐⭐⭐☆ | Time: 5–8 hours Language: C (kernel module + userspace client) | Hardware: Linux VM (snapshot it first!)
Concept primer:
../WARMUP.mdCh. 1–4. ⚠️ Kernel code runs with no safety net — use a VM (Lima/Multipass/UTM/WSL2/cloud), snapshot beforeinsmod. A panic here is a lesson, not a disaster.
Run
sudo apt install build-essential linux-headers-$(uname -r) # Debian/Ubuntu
make # builds fakegpu.ko (kernel) and client (user)
sudo insmod fakegpu.ko
dmesg | tail -3 # "fakegpu: loaded, /dev/fakegpu ready"
sudo ./client # exercises the full ioctl ABI; prints PASS/FAIL
strace -e ioctl,openat ./client 2>&1 | head -30 # compare to a real CUDA trace
sudo rmmod fakegpu
0. The mission
Implement the architectural skeleton every GPU driver shares, in ~300 honest lines:
| fakegpu concept | Real GPU equivalent |
|---|---|
per-open() context struct | CUDA context (per-process GPU state) |
ALLOC/FREE ioctls → handles | cudaMalloc → device pointers (via KMD page tables) |
SUBMIT ioctl → job queue | command-buffer submission (real HW: mapped ring + doorbell) |
WAIT ioctl → completed seqno | fence wait (cudaEventSynchronize) |
| per-context byte quota | per-context VRAM accounting |
INFO ioctl | cudaGetDeviceProperties |
The client is the test suite: it asserts context isolation (two fds can't see
each other's handles), quota enforcement (over-alloc returns ENOSPC), seqno
ordering, and input validation (bad handle → EINVAL, oversized request →
rejected — the adversarial-userspace discipline from WARMUP Ch. 3).
1. Reading order (fakegpu.c)
- The ABI header (
fakegpu_ioctl.h) — shared by kernel and client; note_IOWRencodings and fixed-size structs. ABI files are forever; this one is written like it. fakegpu_open/release— per-fd context allocation. This is the pattern: everything else hangs offfile->private_data.fakegpu_ioctl— the dispatcher:copy_from_user→ validate → act →copy_to_user. Count the validation lines vs the "work" lines (the ratio is the lesson).- The job model —
SUBMITenqueues and immediately completes jobs (incrementingcompleted_seq). Real hardware completes asynchronously; the extension adds atimer_listto simulate that.
2. The strace comparison (step 3 of the lab)
Your client:
openat(AT_FDCWD, "/dev/fakegpu", O_RDWR) = 3
ioctl(3, FAKEGPU_INFO, ...) = 0
ioctl(3, FAKEGPU_ALLOC, {size=1048576, ...}) = 0
ioctl(3, FAKEGPU_SUBMIT, ...) = 0
ioctl(3, FAKEGPU_WAIT, ...) = 0
A real CUDA init (from WARMUP Ch. 3): same shape — open device, info/version
ioctls, allocation ioctls, then mmap calls you don't have (the ring/doorbell —
because real drivers move submission out of ioctl; your extension 3 closes that
gap). Write the side-by-side into TRACE-NOTES.md — that artifact is your
"technical credibility" exhibit for this phase.
3. Extensions
- Quota tightening: per-context and global device quota; correct errno
for each (
ENOSPCvsENOMEM). Add a/sys/module/fakegpu/or sysfs stats attribute (your Phase 10 metrics on-ramp). - Async completion: a
timer_listcompletes jobs 10 ms after submit;WAITblocks on await_queue_head_tuntilcompleted_seq >= target. You've just implemented a fence. - The mmap ring (hard, the real pattern):
fakegpu_mmapexposes a page containingcompleted_seq; client polls it with zero syscalls in steady state. Measure waits/sec: ioctl-WAIT vs mmap-poll (~100× — the entire reason GPU submission works the way it does). - Adversarial client: write
evil.c— bad handles, huge sizes, ioctls on a closed-and-reopened fd, two threads racing SUBMIT. Every case must fail cleanly. (Phase 11 will call this "attack surface review"; here it's just good driver hygiene.)
4. Common pitfalls
- Dereferencing the user pointer instead of
copy_from_user— works in your VM (same address space mapping luck), then oopses elsewhere. The compiler__userannotation + sparse exists for this. - Forgetting
mutex_lockaround context state — two threads on one fd corrupt the buffer list; the racing-SUBMIT test in extension 4 catches it. - Leaking contexts on
release—rmmodthendmesgshows the leak warning the module prints; the kernel's own kmemleak would find it in real review. - Building against the wrong headers (
uname -rmismatch after a kernel update) —insmod: invalid module format. This is the DKMS lesson from WARMUP Ch. 6, self-inflicted once for learning.
5. What this lab proves about you
You can read (and write) the layer most GPU engineers only gesture at: when a
fleet incident says "Xid 31, context poisoned, ioctl returned EIO," every word
of that sentence now maps to code you've written. And nvidia's
open-gpu-kernel-modules repo is now readable to you — start with
kernel-open/nvidia/nv.c and recognize the shapes.