The Linux Kernel: Build It, Read It, and Get a Patch Merged
Welcome to the Linux Kernel Curriculum — a project-based engineering apprenticeship in which you build and boot your own kernel, write drivers and modules by hand, debug them under QEMU with GDB attached, learn to read any subsystem cold, and then send a patch to a mailing list and see it merged into mainline.
The primary goal is not the patch. The primary goal is that you can open any directory of the
Linux source tree — mm/, net/core/, drivers/gpu/drm/, security/landlock/ — and read it
without hitting a layer you do not understand, then change it in a way a maintainer will take.
What This Curriculum Is
This is the book's first build-it and contribute curriculum, and that dual nature is not an accident of scope. The other curricula pick a side:
| Curriculum | Shape |
|---|---|
| Apache Tez, OpenSearch, Firecracker | Contribute to one existing project |
| Terminals, PTYs & Multiplexers | Build a system from scratch to learn a domain |
| This one | Both, because the kernel gives you no choice |
You must build by hand, because there is no way to understand what GFP_ATOMIC means by reading
about it — you learn it the first time your module deadlocks in an interrupt handler. And you must
master a contribution workflow that exists nowhere else in open source: no pull requests, no web UI,
no CI badge on a green PR. Plain-text email, to a mailing list, threaded correctly, reviewed by a
person who has said no to a thousand patches before yours.
Neither half works alone. A person who can write a char device but cannot send a patch has a hobby. A person who can format a patch but cannot explain why their change is safe in atomic context will get one trivial fix merged and never a second.
You will finish with:
- A kernel tree you build, boot, break, and debug daily.
- A set of out-of-tree modules — a char device, a sysfs device, a ramdisk block driver, a netdev, a toy LSM, a fake accelerator — each written from a skeleton with a failing test.
- A
~/kernel-labsworkspace with a QEMU rig, a GDB setup, and scripts you actually use. - Working knowledge of eight subsystems, and depth in one.
- At least one patch merged into mainline Linux, with your name in
git log.
Note: That last line is the only deliverable in this book whose completion date is not under your control. A kernel release cycle is roughly nine to ten weeks. A patch sent today might land in
linux-nextin three weeks and in a tagged release two months after that. Plan for it — see the weekly plan.
Learning Priorities
These are the outcomes, in priority order. Everything in this curriculum exists to serve one of them. When you must choose between finishing a feature and understanding a layer, choose understanding — in kernel work, the code you cannot explain is the code that corrupts memory on someone else's machine six months later.
| # | Outcome |
|---|---|
| 1 | Build, boot, and debug a kernel you compiled yourself, under a debugger, on demand. |
| 2 | Explain the user/kernel boundary exactly: what crosses it, how, and what is forbidden on each side. |
| 3 | Know which context you are in — process, softirq, hardirq, NMI, atomic — and what you may do in each. |
| 4 | Reason correctly about concurrency: spinlocks vs. mutexes, RCU, memory barriers, per-CPU data, and lockdep. |
| 5 | Reason correctly about memory: virtual vs. physical, GFP flags, slab vs. page allocator vs. vmalloc, DMA. |
| 6 | Read an unfamiliar subsystem cold, using only the tree, Documentation/, and git log. |
| 7 | Instrument a running kernel — ftrace, kprobes, perf, eBPF, KASAN, lockdep — to answer a question you have. |
| 8 | Produce a patch that is one logical change, bisectable, correctly tagged, and passes every automated check. |
| 9 | Navigate the community: MAINTAINERS, subsystem trees, linux-next, the merge window, the -rc cycle. |
| 10 | Handle review well: revise, defend, concede, re-post, and know what silence means. |
| 11 | Understand the two rules that govern everything: no stable internal API, and we do not break userspace. |
| 12 | Explain any change you make in terms of who it can hurt and how you would detect that. |
Do not optimize for the merged patch alone. Optimize for the ability to inspect any layer of a running kernel and explain what you see.
1. The Complete Stack
Here is the whole system. Every box is a chapter of this curriculum. The doubled line is the user space / kernel space boundary — the single most important line in the diagram, because almost every kernel misconception comes from putting a behavior on the wrong side of it.
┌─────────────────────────────────────────────────────────────────────────────┐
│ YOUR PROGRAM fd = open("/data/f", O_RDONLY); n = read(fd, buf, 4096); │
│ libc (glibc/musl) wraps it — and sometimes does NOT cross the line at all │
│ (clock_gettime, getcpu → the vDSO, mapped into your proc) │
└─────────────────────────────────────────────────────────────────────────────┘
│ x86-64: `syscall` arm64: `svc #0`
│ THE ONLY DOOR. Registers carry the number and the args.
═══════════════════│═════════════════════════════════════ USER / KERNEL ═══════
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ ARCHITECTURE ENTRY arch/x86/entry/ · arch/arm64/kernel/entry.S │
│ switch stacks · save registers into `struct pt_regs` · look up the number │
│ in the syscall table · call the handler defined by SYSCALL_DEFINEn() │
└─────────────────────────────────────────────────────────────────────────────┘
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ GENERIC KERNEL CODE │
│ │
│ fs/ VFS: struct file, inode, dentry, address_space │
│ mm/ page faults, the page cache, folios, the allocators, reclaim │
│ kernel/ scheduler, signals, timers, workqueues, RCU, tracing, cgroups│
│ net/ sockets, sk_buff, protocol stacks, netfilter, qdiscs, XDP │
│ block/ bio, request, blk-mq, the I/O schedulers │
│ security/ LSM hooks: SELinux, AppArmor, Landlock, Smack, BPF-LSM │
│ ipc/ lib/ crypto/ io_uring/ virt/ init/ rust/ │
└─────────────────────────────────────────────────────────────────────────────┘
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ DRIVERS AND BUSES drivers/ · sound/ · ~70% of the whole tree │
│ the device model: bus → driver → device · probe() · devres · DT / ACPI │
│ drivers/gpu/drm drivers/net drivers/nvme drivers/accel drivers/firmware │
└─────────────────────────────────────────────────────────────────────────────┘
▼
═══════════════════════════════════════════════════════ KERNEL / HARDWARE ═════
┌─────────────────────────────────────────────────────────────────────────────┐
│ HARDWARE MMU · caches · interrupt controller · timers · DMA engines │
│ PCIe · USB · I²C · SPI · NVMe · NIC · GPU · NPU │
│ FIRMWARE UEFI / device tree / ACPI / PSCI — what runs before you do │
└─────────────────────────────────────────────────────────────────────────────┘
Two arrows are missing from that picture, and they are the ones that cause trouble:
INTERRUPTS come UP from the hardware at an arbitrary moment, on any CPU,
possibly while the code you are reading holds a lock.
hardware IRQ ──▶ handler (atomic, cannot sleep)
└─▶ softirq / tasklet (atomic, cannot sleep)
└─▶ threaded IRQ / workqueue (process ctx, MAY sleep)
PREEMPTION can take the CPU away from your task between any two instructions,
unless you have disabled it — which spin_lock() does for you, and which is
half of the reason spin_lock() exists at all.
Note: Write those two paragraphs somewhere you will see them. Roughly half of all beginner kernel bugs are one of: sleeping in atomic context, or touching shared data without synchronization because you did not notice an interrupt or another CPU could reach it. Context and Atomicity and Concurrency exist entirely for this.
2. The Words, Defined Against Each Other
These terms get used interchangeably, including by people who should know better. They are different things, and blurring them is how you end up asking a question on a mailing list that reveals you have not read the tree.
The code
| Word | Precise meaning | Not to be confused with |
|---|---|---|
| The kernel | One program, running in the privileged CPU mode, that owns all hardware and mediates all access to it. It is not a process, has no main() you can single-step from userspace, and never "returns". | The OS (kernel + userland + package manager + all of it) |
vmlinux | The full, unstripped ELF image with symbols and debug info. This is what you give GDB. | bzImage/Image — the compressed, self-decompressing image the bootloader loads. It has no symbols. |
| A subsystem | A functional area with its own maintainer, mailing list, git tree, and MAINTAINERS entry. Roughly: a row of ./scripts/get_maintainer.pl output. | A directory. Some subsystems span several; some directories host several subsystems. |
| A module | Kernel code compiled separately (.ko) and loaded at runtime. It runs at exactly the same privilege as everything else — "module" is a packaging property, not a safety boundary. | A sandbox. There is none. A module bug is a kernel bug. |
| A driver | Code that owns a device. Usually a module, but can be built in (=y). | A module. Most drivers are modules; most modules are drivers; neither implies the other. |
| uapi | Everything under include/uapi/ plus syscall numbers and behavior: the contract with user space. It is permanent. | Internal headers under include/linux/, which change whenever someone has a better idea. |
The trees
There is no single "the Linux source". There are thousands of trees, and knowing which one you are looking at is a prerequisite for asking a coherent question.
| Tree | What it is | Who runs it |
|---|---|---|
| mainline | Linus Torvalds' tree. v6.x tags come from here. The definition of "merged". | Linus |
| a subsystem tree | Where patches actually land first. net-next, tip, drm-misc, mm, for-next branches. Its MAINTAINERS T: line names it. | The subsystem maintainer |
| linux-next | A daily integration of ~200 subsystem trees. Exists to find merge conflicts and build breakage before the merge window. Never used in production; never has a stable history. | Stephen Rothwell |
| stable / LTS | linux-6.x.y. Backported fixes only, no features. LTS trees live for years. | Greg Kroah-Hartman, Sasha Levin |
| a distro kernel | Debian/Fedora/Ubuntu/RHEL kernels: an upstream base plus hundreds of backports and configuration choices. uname -r on your laptop is almost certainly this. | The distribution |
| a vendor tree | An SoC or GPU vendor's fork, often years behind, often with code that will never be upstreamed. | The vendor |
Three sentences that clear up most confusion:
- Your patch is not "in Linux" until it is in Linus's tree. Being in
linux-nextmeans it will probably be in the next release. Probably. - The kernel has no stable internal API on purpose. Function signatures change between releases because refactoring the whole tree at once is cheaper than freezing a bad interface forever. This is an argued position, not an accident.
- The kernel has an absolutely stable external API. "We do not break userspace" is enforced by Linus personally, with reverts. The asymmetry between rules 2 and 3 is the kernel's design philosophy.
Checkpoint question (answer before you continue): A patch adds a field to
struct fileand changes an exported function's signature. Is that allowed? Now: a patch changes the value returned by an existing syscall in a case that previously returned-EINVAL. Is that allowed? Write both answers down. You will be asked again in the mental model.
3. What Happens When You Call read()
This is the trace the whole curriculum is built around. Read it once now — it will not fully land. Read it again after Lab 1, when you have stepped through part of it in a debugger, and again after Lab 3, when you have written the bottom of it yourself.
The example: a program calls read(fd, buf, 4096) on a file on an NVMe SSD, and the data is not in
the page cache.
Phase A — crossing the boundary
1. Your program puts the syscall number and arguments in registers and executes
`syscall` (x86-64) or `svc #0` (arm64). The CPU switches privilege level and
jumps to a fixed address the kernel installed at boot.
x86-64: MSR_LSTAR points at the entry stub in arch/x86/entry/
arm64: VBAR_EL1 points at the exception vector table
2. The entry stub does the unglamorous, architecture-specific work:
- switch to the kernel stack for this task (never trust the user stack)
- on x86, `swapgs` so per-CPU data is reachable
- if KPTI is on, switch page tables (this is the Meltdown mitigation)
- save every user register into a `struct pt_regs` on the kernel stack
3. Generic C code takes over: bounds-check the syscall number, look it up in the
syscall table, call the handler. Handlers are declared with SYSCALL_DEFINE3(read, ...)
— a macro, which is why grepping for "sys_read" finds less than you expect.
Find all of that on your own tree, rather than believing this page:
rg -n "SYSCALL_DEFINE3\(read" fs/
rg -n "do_syscall_64|el0_svc" arch/x86/entry/ arch/arm64/kernel/
ls arch/x86/entry/syscalls/ # the .tbl files the table is generated from
Phase B — the VFS, and the first place it can sleep
4. fdget(fd) → the `struct file`. Wrong fd → -EBADF, and you are already done.
5. vfs_read() checks permissions and the file's mode, then dispatches through
`struct file_operations`: file->f_op->read_iter(...)
^ THIS is the indirection every filesystem and every char driver plugs into.
It is also exactly what you implement in Lab 3.
6. For a regular file on ext4/xfs/btrfs, read_iter lands in the generic page-cache
read path. It asks: is this data already in the page cache?
HIT → copy_to_user() and return. Microseconds. No I/O at all.
MISS → we must go to the device, which means we must SLEEP.
Warning: Step 6 is the first hard rule of this curriculum.
read()on a cache miss blocks, which means the calling task is put to sleep and the scheduler runs something else. Any code path that can reach a sleep is forbidden in atomic context — and "can reach a sleep" includeskmalloc(..., GFP_KERNEL),copy_to_user(), andmutex_lock(). Learning to see this in code you did not write is Context and Atomicity.
Phase C — down to the block layer and the hardware
7. The filesystem maps the file offset to physical blocks (extent lookup) and builds
one or more `struct bio` — the block layer's "read these sectors into these pages".
8. The bio goes to blk-mq: a per-CPU software queue, then a hardware queue that maps
to a real NVMe submission queue.
9. The NVMe driver writes a command into a submission queue in host memory and rings
a doorbell register. Now the CPU has nothing to do.
10. The task is marked TASK_UNINTERRUPTIBLE and schedule() is called. Another task runs.
Your process is now one entry on a wait queue. It does not exist to the CPU.
Phase D — the interrupt, and why "bottom halves" exist
11. Microseconds later the SSD finishes and raises an MSI-X interrupt.
12. The CPU takes the interrupt, on whatever it was doing, in ATOMIC context:
- cannot sleep, cannot call anything that might sleep
- other interrupts may be masked; every microsecond here adds latency everywhere
So the handler does the minimum: acknowledge, note completion, and defer.
13. The deferred half (softirq / threaded IRQ / workqueue, depending on the driver)
completes the bio, marks the page cache pages Uptodate, and wakes the sleeping task.
14. wake_up() moves the task to a runqueue and possibly asks for a reschedule.
Phase E — coming back out
15. The scheduler eventually picks your task. It resumes inside schedule(), exactly
where it stopped, with its kernel stack intact. This is why every task has one.
16. copy_to_user(buf, page_data, n) — the ONLY legal way to touch a user pointer.
It is not a memcpy: it handles page faults, checks the address range, and can
itself sleep. A raw dereference of a user pointer is a bug and often an exploit.
17. Update the file position, return n.
18. On the way out, before returning to user mode, the kernel checks the task's flags:
need_resched? → schedule() (this is the preemption point)
pending signals? → deliver them, possibly restarting or aborting the syscall
then restore registers from pt_regs and execute `sysretq` / `eret`.
19. Your program's next instruction runs. It has no idea any of this happened.
That is the whole system. Every remaining page of this curriculum is one of those numbered steps in detail, with code you write and an experiment that proves it.
Tip: You can watch most of this right now, before you build anything:
sudo perf trace -e read -- head -c 1 /etc/hostname sudo bpftrace -e 'kprobe:vfs_read { @[comm] = count(); }' # Ctrl-C to printThe Warm-Up is an evening of exactly this.
4. The Eight Domains
Section 3 covers eight subsystems. You will read all eight and go deep in one — depth in one subsystem is what makes you useful; breadth across eight is what lets you tell which one.
| Domain | Where it lives | The idea that unlocks it |
|---|---|---|
| CPU / scheduler | kernel/sched/ | Scheduling classes are a priority-ordered chain of policies, and the fair class is only one link. |
| Memory management | mm/ | Almost everything is a lie the MMU tells, maintained lazily by the page-fault handler. |
| Storage | block/, fs/, drivers/nvme/ | VFS is a set of function-pointer tables; a filesystem is a thing that fills them in. |
| Networking | net/, drivers/net/ | One sk_buff travels the whole stack; every layer manipulates its head/tail pointers rather than copying. |
| Graphics | drivers/gpu/drm/ | The kernel's job is memory management and modesetting. Rendering is Mesa's job, in user space. |
| Security | security/ | LSMs do not implement policy; they are hook points where a policy module can say no. |
| AI / accelerators | drivers/accel/, drivers/gpu/drm/ | An NPU is a GPU with the display parts removed — which is why it lives under DRM's infrastructure. |
| Firmware / platform | drivers/firmware/, drivers/acpi/, drivers/of/ | Device tree and ACPI answer the same question — what hardware is here? — for two different worlds. |
Every subsystem chapter opens by making you run these two commands, because a table in a book goes stale and your tree does not:
git log --oneline --since="6 months ago" -- mm/ | head -30
./scripts/get_maintainer.pl -f mm/
5. The First Three Milestones
The full fifteen-milestone sequence with completion criteria is in the roadmap. Here are the first three.
| Milestone | Goal | Done when |
|---|---|---|
| M0 — Kernel mental model | Explain the boundary, the contexts, and the tree without code. | You can answer all twelve questions in section 7 below, out loud, without notes. |
| M1 — Build and boot | Compile a kernel from source and boot it under QEMU with GDB attached. | uname -a inside the VM shows your build string, and you can set a breakpoint on do_sys_openat2 and hit it. |
| M2 — First module | An out-of-tree module that loads, logs, takes parameters, and unloads cleanly. | insmod → your message in dmesg; modinfo shows your parameters; rmmod leaves no trace and lsmod agrees. |
Warning: M1 is the gate for everything. Do not proceed on the theory that you will "set up QEMU later". A kernel you cannot rebuild and reboot in under five minutes is a kernel you will not experiment with, and this curriculum is entirely experiments. Budget a full evening; it will take two.
6. The First Hands-On Exercise
Start here: Lab 1 — Build, Boot, and Attach a Debugger.
You will clone the tree, configure it, build it, boot it in QEMU against a tiny initramfs you also
build, and attach GDB with the kernel's own lx- helper commands loaded. Then you will set a
breakpoint in the syscall path and watch your own ls stop the entire machine.
It is not a small lab and it is not supposed to be. It is the difference between reading about the kernel and having one on the bench.
Before running it, you will be asked to predict several things — including how long the build takes and how big the resulting image is. Do the prediction in writing. The gap between your prediction and the result is the actual lesson.
7. Questions to Answer Before You Write Kernel Code
You are ready to move from M0 to M1 when you can answer all of these without looking anything up. They are re-asked as the validation gate at the end of the mental model.
- Name every way control can enter the kernel from outside it. (There are more than one; "syscall" is an incomplete answer.)
- Why can kernel code not simply dereference a pointer that came from user space? Name three distinct things that can go wrong, and the function you must use instead.
- What is "context" in kernel terms? List the kinds, and for each, state whether you may sleep.
- What does
might_sleep()do, and what class of bug does it exist to catch early? - Name three functions that can sleep but do not look like they can.
- What is the difference between
GFP_KERNELandGFP_ATOMIC, and what happens if you get it backwards in each direction? - What is the difference between a spinlock and a mutex — not "one spins" but which one may be taken in which context, and why?
- What is preemption, what does
spin_lock()do about it, and why does that matter for correctness rather than just performance? - Your driver's interrupt handler needs to do something that takes 2 ms. Name three mechanisms for deferring it, and the tradeoff between them.
- Why does the kernel deliberately have no stable internal API, and what would be worse if it did?
- What does "we do not break userspace" actually forbid? Give one example of a change it allows and one it forbids that look superficially similar.
- A patch is in
linux-next. Is it "in Linux"? What has to happen next, and roughly when?
Tip: Write your answers into
answers-m0.mdin your workspace and commit it. At the end of the curriculum you will re-answer them and diff. That diff is your progress report, and the capstone asks you for it.
Who This Is For
This curriculum is designed for engineers who:
- Are fluent in C. Pointers, pointer arithmetic, function pointers,
structlayout, macros, the preprocessor, undefined behavior, and reading a linker error. The kernel is C with unusual rules, and this book teaches the unusual rules — not C. Rust in the kernel is discussed (rust/) but no lab requires it. - Have solid systems fundamentals: processes, virtual memory, interrupts, caches, what an MMU is for. If "TLB" or "DMA" are fuzzy, you will still make it, but slow down in Foundations.
- Are comfortable at a Linux command line and can drive
gitbeyondcommitandpush—rebase -i,format-patch,bisect, andrange-diffare working tools here, not trivia. - Have a Linux machine or a Linux VM they can reboot, crash, and reinstall without consequence. See Overview & Prerequisites — this is a hard requirement, and macOS users need a specific setup.
- Want to be able to explain a system, not just change it.
You do not need prior kernel experience, driver experience, or any knowledge of the mailing-list workflow. That is what you are here to build.
Restrictions (Read These; They Are the Discipline)
These constraints are what make the curriculum work. They are enforced by the ordering of the material, and breaking them produces a person with a merged patch who cannot write a second one.
- Do not start by trying to find a bug in mainline. Build first, boot first, break your own code first.
- Do not test kernel changes on your daily-driver machine. Everything happens in a VM until Lab 1 is behind you and you have said out loud why that rule exists.
- Do not use a distro's prebuilt kernel headers as a substitute for a source tree you built. You
need
vmlinuxwith debug info, and you need to be able to change any line in it. - Do not copy a driver from Stack Overflow. The kernel API it uses is probably from 2014, and the reason it does not compile is the lesson.
- Do not send a patch before Lab 7. Not one. The list is a real place with real people, and your first impression is a real thing.
- Do not send a patch that
./scripts/checkpatch.plcomplains about, and do not "fix" checkpatch complaints you do not understand. - Do not skip the experiments. In this domain the text is a hypothesis and
ftraceis the evidence. - Do not believe any line number, function signature, or config symbol printed in this book
— including in this paragraph. Run the
rg/git logcommand next to it. The tree moved while this page was being written. - Do not move to the next milestone until the current behavior can be inspected and explained.
Convenience layers — devm_*, module_platform_driver(), b4, virtme-ng — are introduced, but
every time one is, this book first shows you what it does by hand. That ordering is never reversed.
What You Will Be Able to Do
| Capability | Description |
|---|---|
| Build and boot a kernel | Configure with Kconfig, cross-compile, boot under QEMU with a rootfs you made, in a loop that takes minutes |
| Debug a kernel | GDB on a live QEMU guest, lx- scripts, ftrace, kprobes, KASAN, UBSAN, lockdep, KFENCE, decoding an oops by hand |
| Write a driver | file_operations, ioctl and its ABI rules, sysfs attributes, a block driver, a netdev, probe/remove, devres, DT/ACPI matching |
| Reason about concurrency | Choose the right primitive and defend it; read RCU code; interpret a lockdep splat |
| Reason about memory | GFP flags, slab vs. buddy vs. vmalloc, folios, DMA mappings, and why kmalloc memory is not always what a device can reach |
| Add a syscall | And explain why you almost certainly should not |
| Test like the kernel does | KUnit, kselftest, allmodconfig, sparse, smatch, coccinelle, and what the 0-day bot will send you |
| Read any subsystem | A repeatable method: MAINTAINERS → Documentation/ → the data structures → git log → the tests |
| Work the mailing list | git send-email, b4, threading, cover letters, versioned reposts, Fixes:, Cc: stable |
| Survive review | Respond to a maintainer, disagree without losing, and know what to do after two weeks of silence |
| Bisect a regression | git bisect run against a real reproducer, and report it the way the community expects |
How This Curriculum Is Organized
| Part | What it covers |
|---|---|
| Overview | Prerequisites and the setup gate, the history, the warm-up, the mental model (Milestone 0), the lab rig, the roadmap, the weekly plan, the teaching method |
| Foundations | The floor: the boundary, kernel C, context, concurrency, memory, deferred work, the device model. Seven concept chapters, six labs. Milestones 1–6. |
| Contribution | The workflow that actually blocks people: MAINTAINERS and trees, patch craft, email, review, and the automated checks. Five chapters, three labs. Milestones 7–10. |
| Subsystems | The eight domains, each with an orientation chapter, concept chapters, and labs. Milestone 11. |
| Engineering | Cross-cutting: performance measurement, regressions and bisection, stable/backports, and the API/ABI rules. Milestone 12. |
| Capstone | Get a patch merged into mainline, end to end, with an evaluation rubric. Milestones 13–14. |
| Capstone Portfolio | Eight larger, self-directed projects — most of them plausible upstream contributions |
| Appendices | Glossary, the subsystem map, the debugging and build cheat sheets, and primary sources |
The concept chapters are not optional background — they are where the depth lives. A lab says "take the lock before touching the list"; the concurrency chapter is where you learn why the same code is correct in a workqueue and a deadlock in a timer callback. Treat the labs as the spine and the concept chapters as the muscle.
Begin with Overview & Prerequisites and clear the setup gate. Then read The Hitchhiker's Guide for why the kernel and its community are the way they are, spend an evening on The Warm-Up with the kernel you are already running, and work through The Kernel Mental Model — that is Milestone 0, and it contains no code — before starting Foundations. The lab rig you will build, and the companion workspace that comes with this book, are specified in The Lab Rig.