The Teaching Method
This chapter describes the shape of every concept chapter and every lab in this curriculum. Read it once so the structure of what follows is not a surprise, and come back to it when you are designing your own experiments — because by the end, designing your own is the actual skill.
The Phase Template
Every unit of work here — every concept chapter, every lab — follows this order. The order is not decorative; each step exists because skipping it produces a specific failure.
| # | Step | Why it is here | What goes wrong if you skip it |
|---|---|---|---|
| 1 | Explain the model before writing code | You cannot debug a system whose shape you do not know | You write code that works by coincidence and cannot fix it when a different CPU count breaks it |
| 2 | Draw the boundary — user/kernel, and which context | Half of all kernel confusion is a misplaced boundary | You try to fix a "sleeping in atomic context" bug by adding a lock |
| 3 | Name the data structures before the functions | A subsystem is nouns; the verbs are how they relate | You read 400 lines of a function and retain nothing |
| 4 | Follow one control path all the way down | Depth beats breadth on the first pass | You have a vague map and cannot answer any specific question |
| 5 | Implement the smallest thing that runs | Small enough to hold in your head, big enough to prove something | You build three plausible-looking layers you have never actually loaded |
| 6 | Add instrumentation | You cannot verify what you cannot see, and in the kernel you cannot single-step most things | Every future bug costs an hour of re-instrumenting |
| 7 | Design an experiment | An experiment is a claim plus a way to be wrong | You confirm your assumptions instead of testing them |
| 8 | Add a test that survives | The internal API will change under you; the test is what tells you | Your module silently stops being correct three releases from now |
| 9 | Turn on the checkers and run it again | KASAN, lockdep, and PROVE_LOCKING find what review does not | You ship a use-after-free that reproduces once a month on someone else's hardware |
| 10 | Explain the failure mode | Most kernel bugs are the same bugs | You rediscover sleeping-under-a-spinlock the hard way, at 2 a.m. |
The Six-Part Concept Treatment
Every concept chapter in this curriculum presents each concept in exactly six parts. When you see this shape, it is deliberate; when you write your own notes on a new subsystem, copy it.
┌─────────────────────────────────────────────────────────────────────────┐
│ 1. WHAT PROBLEM IT SOLVES │
│ The thing that would be impossible or wrong without it. │
│ Not "what it is" — what it is FOR. │
│ │
│ 2. WHERE IT LIVES IN THE KERNEL │
│ Which directory, which layer, which side of the boundary. │
│ Always with the `rg` / `find` command that shows you. │
│ │
│ 3. WHO OWNS OR INTERACTS WITH IT │
│ Which subsystem owns it, who calls in, who it calls out to, │
│ what refcounts or lifetimes are involved. Usually a table. │
│ │
│ 4. THE STRUCTURES, SYSCALLS, AND CODE PATHS │
│ The actual nouns and verbs: struct names, ops tables, the │
│ function that is the entry point, the flags that change behavior. │
│ │
│ 5. AN EXPERIMENT THAT DEMONSTRATES IT │
│ Something you run on YOUR kernel that makes the concept visible: │
│ ftrace, /proc, /sys, bpftrace, a printk, GDB. With a prediction. │
│ │
│ 6. THE FAILURE MODE WHEN IT IS DONE WRONG │
│ The symptom, the message, the class of bug. Usually a table of │
│ "mistake → what you will see". This is the part you will reread. │
└─────────────────────────────────────────────────────────────────────────┘
Part 6 is the one that pays off years later. When you hit
BUG: scheduling while atomic or a lockdep splat at work, what you will retrieve is not the
explanation — it is the failure-mode table.
Note: Part 5 is non-negotiable and it is why this curriculum insists on the lab rig. Every assertion about kernel behavior in this book is paired with a way to observe it. If a chapter ever tells you something is true without telling you how to see it, that is a defect in the chapter.
The Eleven-Part Lab Template
Every lab has these eleven parts, in this order. When a lab omits one, it is because the step genuinely has none — not as a shortcut.
┌──────────────────────────────────────────────────────────────────────────┐
│ 1. Background what this is and why it exists │
│ 2. Why This Lab Matters the contributor-facing reason │
│ 3. Prerequisites what must already work │
│ 4. Predict First written predictions, BEFORE step 5 │
│ 5. Step-by-Step Tasks real commands and real code, with a │
│ target-architecture diagram and a │
│ deliverables checklist │
│ 6. Expected Output so you know whether it worked │
│ 7. Debugging Steps symptom → cause, for when it did not │
│ 8. One Experiment a claim you could disprove │
│ 9. One Test KUnit, kselftest, or a reproducer │
│ 10. One Challenge Extension past the lesson │
│ 11. Validation / Self-check gating questions, answered unaided │
└──────────────────────────────────────────────────────────────────────────┘
Two properties of that ordering matter more than the list itself.
Part 4 comes before part 5. You predict before you build, not after. The gap is the lesson.
Parts 8, 9, and 10 are not optional extras. A lab you finished through part 7 is a lab where you typed something in. The experiment is where you find out whether your model is right; the test is what keeps it right; the challenge is where the depth compounds.
Warning: Avoid large unexplained code dumps — including your own. If a code block in this curriculum runs past about 80 lines, it is followed by a walkthrough of the parts that carry meaning. If you find yourself copying a block you cannot annotate line by line, stop and annotate it. The annotation is the exercise. In kernel code this is not a study tip: an unannotated block is where the missing
spin_unlockon the error path lives.
The Predict-First Protocol
Throughout the curriculum you are asked to predict behavior before revealing the result. This is not a gimmick. Prediction converts a passive read into a test of your model, and the gap between prediction and observation is where learning happens. A confirmed prediction teaches you almost nothing; a wrong one teaches you exactly which belief was false.
The protocol:
- Read the question.
- Write your prediction down — in a file, in a comment, on paper. Writing is required. A prediction you kept in your head will silently rewrite itself when you see the answer. This is hindsight bias and you are not immune to it.
- Include your confidence: high / medium / guessing.
- Run the experiment.
- If you were wrong, write one sentence naming the false belief. Not "I forgot" — the actual belief.
Keep these in predictions.md in your workspace. At the capstone you review it.
Example questions you will be asked
- Your module calls
kmalloc(GFP_KERNEL)while holding a spinlock. What happens — always, sometimes, or never? - You dereference a user pointer directly. What happens for a valid pointer, for
NULL, and for a kernel address? - Your driver works on your 4-CPU VM and corrupts a list on a 96-CPU server. Which three causes are most likely, in order?
- A use-after-free runs ten times without KASAN. How many of those ten show any symptom at all?
- You add a field to the middle of a struct in a uapi header and rebuild only the kernel. What does the old userspace binary do?
- You set a breakpoint in the network receive path and leave the guest stopped for two minutes. What happens when you continue?
- Your patch builds with
defconfig. Name two configurations under which it will not.
Warning: The four predictions most people get wrong on first encounter are: which functions can sleep, what a use-after-free looks like without KASAN, how much of a stack trace to trust, and how large your measurement noise is. If you predict all four correctly, you may move faster through Foundations and Engineering.
What "Instrumentation" Means Here
Instrumentation is not printk scattered in a loop. It is a deliberate, switchable view of one
layer, and the kernel gives you an unusually good set of them.
| Layer | Instrument | Turn it on with |
|---|---|---|
| One line of your own code | pr_debug() | Dynamic debug: echo 'file mymod.c +p' > /sys/kernel/debug/dynamic_debug/control |
| Your module's call graph | ftrace function_graph | echo my_func > set_graph_function |
| Any function's arguments | kprobe / bpftrace kprobe: | bpftrace -e 'kprobe:my_func { printf(...) }' |
| A stable, permanent hook | A tracepoint you add | TRACE_EVENT() in include/trace/events/ |
| Where the time goes | perf record -g | perf report, flame graphs |
| Memory errors | KASAN, KFENCE, kmemleak | Config, then read dmesg |
| Lock ordering | lockdep | CONFIG_PROVE_LOCKING, then read the splat |
| Whole-machine state | GDB + lx- scripts | -s on QEMU |
| Anything, aggregated | eBPF via bpftrace | One line, no rebuild |
Design rules for kernel instrumentation, learned the hard way:
printkis not free and it is not neutral. It can be slow, it serializes on the console, and at high rates it changes the timing of the bug you are chasing — sometimes making it disappear. Usepr_debug(off by default, switchable at runtime) or a tracepoint. Useprintk_ratelimited()for anything in a path that can repeat.- Never
printkfrom a path the console itself uses. You will deadlock or recurse, and the output that would have told you why is the thing that is broken. - It must be switchable without a rebuild. A rebuild is two minutes you spend fifty times. Dynamic debug, ftrace filters, and bpftrace all cost zero rebuilds.
- Prefer tracepoints for anything permanent. They are zero-cost when off, they have a stable format that tooling can consume, and adding one is a legitimate upstream contribution.
- Ask the tooling before you instrument. KASAN, lockdep, and
dmesgmay already know the answer. The instinct to reach forprintkfirst is the instinct this curriculum is trying to replace.
What "One Experiment" Means Here
An experiment has four parts, and it is not an experiment without all four:
CLAIM A falsifiable statement about the system.
"A spinlock held across kmalloc(GFP_KERNEL) is detected by
CONFIG_DEBUG_ATOMIC_SLEEP before it deadlocks."
METHOD The exact commands, in order, that would show it.
"Build lab-fast. Load a module that takes a spinlock and then
calls kmalloc(GFP_KERNEL). Read dmesg."
PREDICTION What you expect to observe, written before you run it.
Include: will it BUG, WARN, hang, or appear to work?
RESULT What you observed, and — if it differs — which belief was wrong.
Every experiment in this book is written in that shape. When you invent your own, keep it. The habit transfers: this is how you will debug a kernel bug in production five years from now, and it is also what a maintainer means when they ask "how did you test this?"
Tip: Kernel experiments have one extra requirement that userspace experiments do not: state the configuration. "It worked" is meaningless without the config, the CPU count, the preemption model, and whether the debug options were on. Record
uname -a,nproc, and theCONFIG_symbols that matter, next to the result. Every result in this curriculum that you cannot reproduce later will be missing one of those four.
What "One Test" Means Here
The kernel has two test frameworks and they are for different things. Choosing wrongly is a common review comment.
| Framework | Tests | Runs | Use it for |
|---|---|---|---|
| KUnit | In-kernel logic, in isolation | Inside a kernel built for the purpose (tools/testing/kunit/kunit.py run) | A parser, a state machine, an allocator's arithmetic, anything pure |
| kselftest | The kernel from the outside | On a booted kernel, as a userspace program (tools/testing/selftests/) | Syscall behavior, /proc and /sys formats, device semantics, ABI |
The four properties every test in this curriculum has:
- It names what it is testing in a comment, including the rule. Six months later
assert(ret == -EINVAL)is unreadable;/* unknown flag bits must be rejected so the flags field stays extensible */is not. - It asserts one thing. A test that checks the return value and the side effect and the refcount tells you nothing when it fails.
- Its failure message states the rule, not just the numbers.
- It fails when you reintroduce the bug. Verify this. A test you have never seen fail is a test you have not written.
For every kernel interface you implement, this book asks for six things:
- The happy path works.
- Every error path returns the right errno — and you exercised each one.
- The behavior under concurrency: two callers at once, with
PROVE_LOCKINGon. - The behavior under allocation failure: use
CONFIG_FAILSLAB/ fault injection. - The behavior at the boundaries: zero, one, maximum, off-by-one,
NULL. - What happens when the module is unloaded while in use.
Item 6 is the one people skip, and it is the one that produces a use-after-free in a real user's kernel a year later.
What "One Challenge Extension" Means Here
Each lab ends with something past the lesson: a feature the lab did not need, a correctness edge the happy path avoided, or the thing a maintainer would ask about. Challenges are optional; they are also where the depth compounds. A representative sample:
- Make your char device's
read()correct when two processes read concurrently at different offsets. - Convert your module's manual cleanup to
devresand explain what changed about the failure paths. - Add a tracepoint to your driver and consume it with
bpftrace. - Make your module survive
rmmodwhile a userspace process has the device open. (This is genuinely hard. That is the point.) - Run your module under
CONFIG_FAILSLABwith a 10% failure rate and fix everything that breaks. - Build your module for a different architecture and find the endianness or alignment assumption you did not know you had made.
Why This Book Never Cites a Line Number
You will not find kernel/sched/fair.c:4212 anywhere in this curriculum. This is a rule, not an
oversight, and it has a reason you should adopt for your own notes.
The tree merges on the order of 1,500 commits a week. A line number is wrong within days. A function name is wrong within releases. What is stable is the role a thing plays — "the function that picks the next task in the fair class" — and the command that finds it today.
So every reference in this book has this shape:
# NOT: "see pick_next_task_fair() at kernel/sched/fair.c:8123"
# BUT:
rg -n "pick_next_task_fair" kernel/sched/ # where is it now?
git log --oneline -S'pick_next_task_fair' | head # when did it change, and why?
Adopt this in your own notes, your own commit messages, and your own review comments. A review comment that says "line 412" is unreadable in v2; one that quotes the line is readable forever. This is also why kernel review happens by quoting inline: the quoted text is the anchor, because line numbers move.
Warning: The corollary applies to this book. Any struct name, function signature, config symbol, or directory named here may have changed. Run the command next to it. If the command finds nothing, the tree moved — use
git log -Sto find out where it went, and treat that as a free exercise in the skill you are here to learn.
Common Failure Modes of Learners (Not of Code)
| Failure mode | Symptom | Correction |
|---|---|---|
| Debugging by editing | Random changes, rebuild, hope | Add instrumentation to the layer you suspect. If you cannot name the layer, that is the actual problem. |
| Testing on one CPU | "It works" | -smp 8, always. A single-CPU guest hides every race you are here to learn about. |
| Debug options off | Nondeterministic corruption you cannot pin down | lab-paranoid. KASAN turns a mystery into a report with a stack trace. |
| Trusting a stack trace completely | Chasing a frame with a ? in front of it | The ? means the unwinder is guessing. Confirm with a second signal before you commit to it. |
| Reading a subsystem front to back | Weeks of reading, no retained model | Nouns first, then one path all the way down. Breadth after depth, not before. |
| Copying a driver from the web | It does not compile, and the fix is not obvious | The API changed. git log -S'<the function>' and read the commit. That is the lesson. |
| Sending a patch too early | Silence, or a blunt reply | The list is a real place. Do Lab 7's checklist completely, including the mail-to-yourself round trip. |
| Skipping the experiments | "I understood it from the text" | You did not. The text is the hypothesis; ftrace is the evidence. |
| Claiming a performance win | A number with no variance and no control | Five runs, reported spread, a named noise source. Otherwise it is not a result. |
| Optimizing before measuring | A clever change that helps nothing | perf record first. Every time. |
How to Ask Yourself a Debugging Question
When something is wrong, ask in this order. This ordering is the single most valuable transferable skill in the curriculum.
1. WHAT DOES THE KERNEL ALREADY KNOW?
dmesg first. A KASAN report, a lockdep splat, or a WARN has already
done the analysis for you — and people skip it constantly.
→ dmesg -T --level=err,warn | tail -50
2. WHICH CONTEXT AM I IN?
Process? Softirq? Holding a spinlock? Almost every "impossible" kernel
bug is a context violation.
→ in_task(), in_atomic(), preempt_count(); CONFIG_DEBUG_ATOMIC_SLEEP
3. WHICH SIDE OF THE BOUNDARY?
Is the bad value coming from user space? Then it is untrusted, and the
bug is a missing validation, not a wrong computation.
→ check every copy_from_user return; check every bound
4. WHO ELSE CAN TOUCH THIS DATA RIGHT NOW?
Another CPU? An interrupt? A timer? A workqueue? If the answer is
"nothing", prove it — do not assume it.
→ lockdep; -smp 8; CONFIG_DEBUG_SPINLOCK
5. IS IT DETERMINISTIC?
Reproduces every time → logic. Read the code.
Reproduces sometimes → concurrency, timing, or memory corruption.
Reach for KASAN and lockdep, not printk.
6. IS IT MY BUG, OR MY CONFIG?
→ Try the other config. Try defconfig. Try a released tag.
A bug that vanishes under defconfig is telling you something.
7. HAS SOMEONE ELSE ALREADY FIXED IT?
→ git log --oneline --since="1 year ago" -- <the file>
lore.kernel.org search for the function name
This is the step that saves whole days, and it costs two minutes.
Validation / Self-check
- Name the six parts of the concept treatment, and say which one you will still be using in five years.
- Name the eleven parts of the lab template. Which three come after the code, and why does the ordering matter?
- What are the four parts of an experiment, and which one do people skip?
- Why must a prediction be written rather than held in your head?
- What extra thing must a kernel experiment record that a userspace experiment need not, and name its four components.
- When should a test be a KUnit test and when a kselftest? Give one example of each from your own work.
- Name the six things this book asks for with every kernel interface you implement. Which one do people skip, and what does it cause a year later?
- Give five rules for kernel instrumentation, and say which one
printkin an interrupt handler violates. - Why does this book never cite line numbers? Give the rule you should apply in your own review comments as a result.
- Give the seven-question debugging order. Now apply it to: "my module works on my laptop and corrupts a list on the CI machine."
Next: Foundations — the floor. Seven concepts and six labs, and by the end of it you can write, load, debug, and test kernel code.