Milestone 0: The Kernel Mental Model
No code in this chapter. This is the model you will spend the rest of the curriculum making concrete. Read it, then explain it out loud to someone — or to a rubber duck, or to a text file. If any explanation stalls, that stall is exactly where your model is wrong.
The goal: after this chapter, for any behavior you observe on a Linux machine, you can say which side of the privilege boundary owns it, which context it runs in, and which directory of the tree it lives in.
1. The Kernel Is Not a Program That Runs
The first and most persistent misconception is that the kernel is a process — something with a
main() that starts, loops, and services requests. It is not, and dropping that picture is the
single biggest step in this chapter.
The kernel is a body of code and data, mapped into every address space, that runs only when something makes it run. It has no thread of its own by default. Between the moment your syscall returns and the moment the next interrupt fires, there may be no kernel code executing anywhere on the machine.
THE WRONG PICTURE THE RIGHT PICTURE
┌──────────┐ requests ┌────────┐ ┌────────────────────────────────┐
│ your app │────────────▶│ KERNEL │ │ your app's address space │
│ │◀────────────│ (a big │ │ ┌──────────────────────────┐ │
└──────────┘ responses │ loop) │ │ │ user half: your code, │ │
└────────┘ │ │ heap, stack, libraries │ │
│ ├──────────────────────────┤ │
│ │ kernel half: THE SAME │ │
│ │ physical kernel, mapped │ │
│ │ into EVERY process, │ │
│ │ unreachable at CPL 3 │ │
│ └──────────────────────────┘ │
└────────────────────────────────┘
the CPU raises its privilege and
JUMPS INTO the upper half.
Same thread. Same page tables.
Different permissions.
Two consequences worth internalizing now:
A syscall is not a message. It is your own thread, changing privilege level and jumping to a
fixed address. There is no queue, no receiver, no context switch to a "kernel process". This is why
syscalls are measured in tens of nanoseconds and not microseconds — and why getpid() is fast and
read() on a cold file is not (the work is slow; the transition is not).
"The kernel" running on CPU 0 and "the kernel" running on CPU 7 are the same code, at the same time, on different data — or worse, on the same data. Every kernel function you read must be assumed reentrant across CPUs unless something prevents it. This is where locking comes from, and it is why kernel C feels paranoid compared to application C.
2. The Privilege Boundary
The hardware provides exactly two things: a privilege level, and a controlled way to change it.
| Concept | x86-64 | arm64 |
|---|---|---|
| Unprivileged level (your program) | Ring 3 / CPL 3 | EL0 |
| Kernel level | Ring 0 | EL1 |
| Hypervisor | (VMX root, orthogonal) | EL2 |
| Secure firmware | SMM (orthogonal) | EL3 |
| Deliberate entry instruction | syscall | svc #0 |
| Entry point register | MSR_LSTAR | VBAR_EL1 (vector table) |
| Return instruction | sysretq / iretq | eret |
The privilege level is a bit in a CPU register. Everything the kernel protects — page tables, device registers, other processes' memory, the ability to halt the CPU — is protected by hardware checks against that bit. There is no software gatekeeper. When kernel code runs, the checks are simply not applied.
That is the entire security model, and it is why a bug in a sound driver can corrupt a filesystem.
3. The Four Ways In
Control enters the kernel in exactly four ways. Being able to name all four, and say which context each produces, is the point of this section.
══════════════════════════════ USER SPACE ══════════════════════════════
① SYSCALL ② EXCEPTION (nothing here)
deliberate accidental
`syscall` / `svc` page fault, divide-by-zero,
you asked illegal instruction, alignment
│ │ you did not ask
═══════│═════════════════════════│════════════════════════════════════════
▼ ▼ ▲
┌─────────────────────────────────────────┐ ③ INTERRUPT
│ KERNEL │◀──────── a device, a timer, an
│ │ IPI from another CPU,
│ ④ KERNEL THREADS were already here. │ or an NMI
│ kthreadd's children: kworker, │ nobody asked; it just
│ ksoftirqd, kcompactd, rcu_*. │ happened, on whatever
│ They have no user half at all. │ was running
└─────────────────────────────────────────┘
| # | Entry | Synchronous? | Whose task is running? | Context it produces | May it sleep? |
|---|---|---|---|---|---|
| ① | Syscall | Yes, deliberate | Yours | Process context | Yes |
| ② | Exception / fault | Yes, accidental | Yours | Process context (a page fault is handled on your behalf) | Yes — this is how demand paging can do I/O |
| ③ | Interrupt | No | Whoever was unlucky | Interrupt (atomic) context | No |
| ④ | Kernel thread | — | Its own; no user half | Process context | Yes |
Two facts from that table are worth stopping on.
A page fault is a syscall you did not write. When you touch a page that is not present, the CPU traps to the kernel, the kernel figures out what should be there (a file mapping? swap? a fresh anonymous page?), possibly performs I/O, and returns to re-execute your instruction. Your program sees nothing. Most of what "memory management" means is this handler.
Kernel threads are real tasks with no user space. Run ps aux and look at the names in square
brackets: [kworker/2:1], [ksoftirqd/0], [rcu_preempt]. They appear in ps, they are scheduled
like anything else, they can sleep — and they have no address space of their own, no user stack, and
no file descriptors in the usual sense.
ps -eo pid,ppid,stat,comm | awk '$4 ~ /^\[/ || $2 == 2' | head -20 # kthreadd's children
4. Context: The Single Most Important Idea in This Book
"Context" answers one question: may this code go to sleep?
To sleep means to call schedule() and give up the CPU until something wakes you. That requires
there to be a task whose state can be saved and later restored. In interrupt context there is no
such task — the interrupt borrowed whatever task happened to be on the CPU, and putting that
innocent task to sleep on your behalf would be a correctness disaster.
So the rule is simple to state and endless to apply: you may not sleep in atomic context.
The taxonomy
| Context | You are here when | May sleep | May take a mutex | May take a spinlock | May call kmalloc(GFP_KERNEL) |
|---|---|---|---|---|---|
| Process (task) context | In a syscall, a fault handler, a kernel thread, a workqueue item, or a threaded IRQ | ✅ | ✅ | ✅ | ✅ |
| …holding a spinlock | Between spin_lock() and spin_unlock() — still process context, but now atomic | ❌ | ❌ | ✅ (a different one, carefully) | ❌ (GFP_ATOMIC only) |
| Softirq / tasklet / timer | Bottom halves, NET_RX, timer callbacks | ❌ | ❌ | ✅ | ❌ |
| Hardware IRQ handler | The top half of an interrupt | ❌ | ❌ | ✅ (an IRQ-safe variant) | ❌ |
| NMI | Non-maskable interrupt: watchdog, perf, some machine checks | ❌ | ❌ | ❌ (almost nothing is safe) | ❌ |
Warning: Read the second row again. Taking a spinlock puts you in atomic context even inside a syscall. The most common beginner bug in the kernel is not "I wrote code in an interrupt handler"; it is "I called something that sleeps while holding a spinlock." The sleeping call is usually three functions deep and does not look like it sleeps.
The functions that sleep and do not look like it
| Call | Why it can sleep |
|---|---|
kmalloc(size, GFP_KERNEL) | May trigger reclaim, which does I/O |
copy_to_user() / copy_from_user() | May take a page fault on the user address |
mutex_lock(), down(), wait_event() | That is their entire purpose |
printk() — sometimes | Console output paths have historically been able to block; this is why printk rework is a decade-long project |
request_firmware() | Talks to user space |
Anything that ends in _interruptible or _killable | The name is telling you |
| Almost any function you have not read | Assume yes until you check |
How the kernel catches you
You are not expected to reason about this unaided. The kernel will tell you — if you turned the checks on.
| Mechanism | What it catches | Config |
|---|---|---|
might_sleep() annotations | A possibly-sleeping call reached from atomic context | CONFIG_DEBUG_ATOMIC_SLEEP |
lockdep | Lock-ordering inversions, sleeping-in-atomic, IRQ-unsafe/safe mixing — before they deadlock | CONFIG_PROVE_LOCKING |
| KASAN | Use-after-free and out-of-bounds on kernel memory | CONFIG_KASAN |
The scheduler's own __schedule_bug | An actual schedule() from an invalid context | always |
The characteristic message is one you will see many times, and it names the file and line of the offending call:
BUG: sleeping function called from invalid context at mm/page_alloc.c:...
in_atomic(): 1, irqs_disabled(): 0, non_block: 0, pid: 1234, name: my-module
Tip: Turn on
CONFIG_DEBUG_ATOMIC_SLEEPandCONFIG_PROVE_LOCKINGin every lab kernel and never turn them off. They cost performance you do not care about and catch bugs that would otherwise reach a mailing list with your name on them. The lab rig enables both.
Preemption, the other half of the same idea
Even in process context, and even with no interrupt in sight, the scheduler can take the CPU away
from you between any two instructions — unless preemption is disabled. spin_lock() disables it
(that is half the reason spinlocks exist); so does preempt_disable() directly.
Which preemption model your kernel uses changes when this can happen:
| Model | Kernel code can be preempted |
|---|---|
PREEMPT_NONE | Only at explicit reschedule points. Best throughput. |
PREEMPT_VOLUNTARY | Plus at might_sleep() points. |
PREEMPT (full) | Almost anywhere. Lower latency, more preemption bugs found. |
PREEMPT_RT | Plus: most spinlocks become sleeping locks and most IRQ handlers become threads. |
Recent kernels can also select the model at boot or at runtime (PREEMPT_DYNAMIC). Check yours
rather than assuming:
grep -E 'CONFIG_PREEMPT' /boot/config-"$(uname -r)" 2>/dev/null || zcat /proc/config.gz | grep -E 'CONFIG_PREEMPT'
grep -o 'preempt=[a-z]*' /proc/cmdline
cat /sys/kernel/debug/sched/preempt 2>/dev/null # if PREEMPT_DYNAMIC is enabled
Why this matters for correctness, not just latency: code that is correct under PREEMPT_NONE
because "nothing else can run here" is wrong, and it will be found to be wrong the day someone runs
it on a PREEMPT_RT kernel. Never reason from "the scheduler probably will not".
5. The Boundary: Who Owns What
This table is the reason this chapter exists. Memorize the middle column.
| Behavior | Owned by | Not owned by |
|---|---|---|
| Which task runs on which CPU, right now | Kernel scheduler | The process; nice/sched_setaffinity are requests |
| The value of a pointer inside your program | User space | Kernel — which is exactly why it must never trust one |
| Whether the page behind that pointer is in RAM | Kernel (page fault handler, reclaim) | The program; mmap expresses intent, not residency |
| Which physical page backs a virtual address | Kernel (and it can change it under you) | Anyone else |
Whether write() reached the disk | Filesystem + block layer. write() promises visibility to other readers, not durability; fsync() is the durability contract | The write() call |
The contents and format of /proc/meminfo | Kernel — and it is uapi, so the format is a promise | The tools that parse it |
| Which process receives a signal you send | Kernel, per the signal's target rules | The sender |
| When a device's data becomes available | The device, announced by an interrupt | Polling code, mostly |
| Which CPU handles a given interrupt | Kernel + interrupt controller, tunable via /proc/irq/N/smp_affinity | The driver |
| Whether your module's data structure is being touched by another CPU | Nothing, unless you locked it | Hope |
The lifetime of a struct file | Kernel refcounting; the fd is a handle, not the object | The fd number |
Whether a kmalloc succeeds | Kernel allocator + reclaim, per the GFP flags you chose | The size alone |
| Whether a device can DMA to a buffer you allocated | The DMA API and the IOMMU — not kmalloc | Your assumption that RAM is RAM |
| The internal signature of any kernel function | Whoever changes it next | You, if your code is out of tree |
| Anything user space can observe | Frozen forever (rule 2) | Anyone's convenience |
Tip: When you hit a confusing kernel behavior for the rest of your career, ask three questions in this order: (1) Which side of the privilege boundary is this? (2) What context am I in? (3) Who else can be touching this data right now? Those three resolve the large majority of cases.
6. What a Task Actually Is
Everything the scheduler runs is a struct task_struct. Processes and threads are the same kind of
object; what differs is what they share, decided by the flags passed to clone().
struct task_struct ── the schedulable entity ("a task")
│
├── pid, tgid tgid == "the process id" that getpid() returns
├── state RUNNING / INTERRUPTIBLE / UNINTERRUPTIBLE / STOPPED / ZOMBIE
├── mm ──────────────▶ struct mm_struct the address space
│ (shared between threads; NULL for kernel threads)
├── files ────────────▶ the fd table (shared or copied, per CLONE_FILES)
├── signal / sighand ─▶ signal state (shared or copied, per CLONE_SIGHAND)
├── cred ─────────────▶ uid/gid/capabilities
├── sched_class, se, rt, dl which scheduling policy owns this task
└── stack ────────────▶ THE KERNEL STACK 8 or 16 KB. Per task. Always allocated.
Find the real thing rather than trusting the sketch:
rg -n "struct task_struct \{" include/linux/sched.h
rg -n "define THREAD_SIZE" arch/x86/include/asm/ arch/arm64/include/asm/
rg -n "TASK_RUNNING|TASK_INTERRUPTIBLE|TASK_UNINTERRUPTIBLE" include/linux/sched.h | head
Every task has two stacks: the user stack (which can grow to megabytes) and a kernel stack of 8 or 16 KB that is allocated when the task is created and never grows. All kernel execution on behalf of that task uses it — every nested function call, every local variable, every interrupt that lands while the task is in the kernel.
That is why kernel C has rules application C does not:
- No large local variables. A 4 KB buffer on the stack is a quarter of your budget. The build
warns:
warning: the frame size of N bytes is larger than M bytes. - No deep recursion. Ever. There is no stack growth and no guard beyond a page.
- No
alloca, no variable-length arrays.
A stack overflow does not raise an exception; it corrupts whatever is adjacent. CONFIG_VMAP_STACK
puts a guard page there so it faults instead — one of many "make the failure loud" options you will
turn on.
7. The Source Tree as a Map
Roughly 80,000 files. It is navigable, because the top level is a genuine taxonomy.
ls -d */ | tr -d /
git ls-files | wc -l
for d in */; do printf "%-16s %s\n" "${d%/}" "$(git ls-files "$d" | wc -l)"; done | sort -k2 -rn
| Directory | What lives there | Roughly |
|---|---|---|
drivers/ | Every device driver. The majority of the tree. | ~60–70% of files |
arch/ | Per-architecture code: entry, page tables, atomics, boot | large |
fs/ | The VFS plus every filesystem | large |
net/ | Protocol stacks, sockets, netfilter, qdiscs, XDP core | large |
sound/ | ALSA | large |
include/ | linux/ (internal), uapi/ (the permanent contract), asm-generic/ | — |
kernel/ | The core: sched/, time/, irq/, locking/, rcu/, trace/, bpf/, cgroup/, printk/, module/, power/ | small and dense |
mm/ | Page allocator, slab, page cache, folios, reclaim, page faults, memcg | small and dense |
block/ | The block layer: bio, request, blk-mq, I/O schedulers | — |
security/ | The LSM framework and its modules: SELinux, AppArmor, Landlock, Smack, IMA | — |
crypto/ | The kernel crypto API | — |
io_uring/ | The io_uring implementation (moved out of fs/ as it grew) | — |
ipc/ | System V IPC | small |
lib/ | Generic data structures and helpers usable anywhere in the kernel | — |
virt/ | Architecture-independent KVM; the arch parts are in arch/*/kvm/ | — |
init/ | start_kernel() and early boot | tiny |
rust/ | Rust support: the abstractions and bindings layer | growing |
tools/ | Userspace programs shipped in-tree: perf, bpftool, tools/testing/selftests/ | — |
samples/ | Runnable example code for kernel APIs. Underrated. | — |
scripts/ | The build system's helpers, plus checkpatch.pl, get_maintainer.pl, faddr2line, decode_stacktrace.sh | — |
Documentation/ | The primary source. Read it before anything on the web. | — |
Three navigation habits to build now:
# 1. "Where is this defined?" — never guess a line number; ask.
rg -n "struct file_operations \{" include/linux/fs.h
rg -n --type c "\bvfs_read\b" fs/ | head
# 2. "Who owns this, and is it alive?"
./scripts/get_maintainer.pl -f mm/
git log --oneline --since="6 months ago" -- mm/ | wc -l
# 3. "How did this get to be this way?" — the commit message is the design doc.
git log -S'copy_to_user' --oneline -- kernel/ | head
git log --format='%h %s%n%n%b' -1 <some commit>
Warning: Nothing in this book — not a struct name, not a function signature, not a config symbol, not a directory — should be believed without running the command next to it. The tree merged a thousand commits while you read this page. Naming things by role and then grepping is not a stylistic preference; it is the only way to write about the kernel that stays true.
8. The Life of a Syscall, as a State Machine
The introduction traced a specific read().
Here is the same path with the example removed, because the shape is what generalizes.
USER kernel entry generic kernel hardware
──── ──────────── ────────────── ────────
set up regs
`syscall` ──────▶ switch stack
save pt_regs
(KPTI: switch CR3)
bounds-check nr
table lookup
│
▼
SYSCALL_DEFINEn body ── validate arguments
copy_from_user() any structs
│
┌───────┴────────┐
│ │
can answer now? must wait?
│ │
│ set task state
│ add to a wait queue
│ issue the request ────▶ device
│ schedule() ◀── CPU runs someone else
│ │
│ (time passes)
│ │
│ ◀───────────────────── IRQ fires
│ top half: ack, defer
│ bottom half: complete,
│ wake_up()
│ │
│ scheduler picks us again;
│ we resume inside schedule()
└───────┬────────┘
▼
copy_to_user() the result
return a value: >= 0, or -Exxx
┌───────────────────────┘
▼
EXIT-TO-USER WORK — this part surprises people:
need_resched? → schedule() (a preemption point)
pending signals? → deliver; maybe restart the syscall
task work queued? → run it
│
restore pt_regs
◀──────────────── `sysretq` / `eret`
next instruction
Three things in that diagram are load-bearing and commonly missed:
- The return value is the error channel. The kernel has no exceptions. Functions return
int:>= 0for success,-EINVAL/-ENOMEM/-EFAULTfor failure. Pointer-returning functions useERR_PTR()/IS_ERR()/PTR_ERR()to encode an error in the pointer itself.errnoin user space is libc negating that value and stashing it. - Signals are delivered on the way out, not when they are sent.
kill()sets a flag and wakes the target; the target notices at the next boundary crossing. This is why a task inTASK_UNINTERRUPTIBLE(theDstate inps) cannot be killed — it is not going to check. - The preemption point is on exit.
need_reschedset by a wakeup or the timer tick is honored here, which is one reason aPREEMPT_NONEkernel still feels responsive to ordinary programs: they cross this boundary constantly.
9. Kernel C Is a Dialect
You know C. The kernel's C has different rules, and every one of them exists for a reason you can name.
| Rule | Why |
|---|---|
No libc. No malloc, printf, strcpy with those semantics | There is no C library under you. The kernel has its own: kmalloc, printk/pr_info, strscpy, kstrtoint. |
No floating point (without kernel_fpu_begin()/_end()) | FPU state is not saved on kernel entry. Using it silently corrupts a user program's registers. |
| 8–16 KB stack, no growth | One page-order allocation per task. See section 6. |
| Errors are negative errnos, not exceptions | No unwinding mechanism exists. Every caller checks. |
ERR_PTR/IS_ERR/PTR_ERR for pointer returns | Lets one return value carry "pointer or error" without an out-parameter. |
__user, __iomem, __rcu, __percpu annotations | Type-system-adjacent markers that sparse (make C=1) checks. A __user pointer dereferenced directly is a bug sparse will find. |
Explicit endianness types: __le32, __be16 | Anything crossing to hardware or the wire has a byte order, and sparse enforces the conversions. |
No dynamic linking, no dlopen | Modules are relocated and linked by the kernel's own loader against exported symbols. |
| Everything is potentially concurrent | See section 1. |
container_of() instead of inheritance | Embedded structs plus pointer arithmetic are how the kernel does polymorphism, alongside function-pointer tables. |
rg -n "define container_of" include/linux/container_of.h include/linux/kernel.h
rg -n "define IS_ERR\b|define ERR_PTR\b" include/linux/err.h
rg -n "__user" include/linux/compiler_types.h | head
10. Common Misconceptions, Corrected
| Misconception | Reality |
|---|---|
| "The kernel is a process." | It is code mapped into every address space, entered by privilege transition. It has no thread of its own — except the kernel threads, which are ordinary tasks. |
| "A syscall context-switches to the kernel." | Same task, same page tables (mostly), higher privilege. A context switch is a much more expensive, different thing. |
| "Kernel code runs to completion." | It is preemptible, interruptible, and concurrent across CPUs. Assume all three. |
| "Interrupt handlers are just callbacks." | They run in atomic context on a borrowed stack and must not sleep. Half of driver design is deciding what to defer. |
| "A module is isolated from the kernel." | There is no isolation of any kind. insmod links code into the running kernel at full privilege. |
"kmalloc returns memory a device can use." | Physically contiguous, yes; DMA-able, not necessarily — the device may have addressing limits, and an IOMMU may be in the way. Use the DMA API. |
"vmalloc is just a bigger kmalloc." | It is virtually contiguous and physically scattered. Slower to allocate, cannot be handed to most DMA, and consumes a limited virtual range. |
"The write() returned, so the data is on disk." | It is in the page cache. Durability requires fsync(), and journaling and barriers are why that is not free. |
| "Free memory is good." | Free memory is unused memory. The page cache should be eating it. Watch MemAvailable. |
| "A spinlock is a lightweight mutex." | It is a different thing: it spins rather than sleeping, disables preemption, and is the only option in atomic context. Using one where a mutex belongs wastes CPU; using a mutex where a spinlock belongs is a bug. |
| "RCU is a lock." | It is a way to read a data structure with no synchronization on the read side, paying for it with deferred reclamation on the write side. |
| "If it compiles and boots, it works." | It works on your config, your hardware, your CPU count, and your timing. allmodconfig, KASAN, lockdep, and the 0-day bot exist because that sentence is false. |
| "I can test this on my laptop." | You can, once. Then you reboot from a rescue USB. Use a VM. |
| "Kernel documentation is out of date." | The Documentation/ in your checkout describes your code and is reviewed with it. Web tutorials are what is out of date. |
11. The Reader's Method: How to Approach Any Subsystem
You will use this in Subsystems eight times. Learn the shape now.
1. WHO OWNS IT? ./scripts/get_maintainer.pl -f <dir>
Is it Supported, Maintained, or Orphan?
2. WHAT DOES IT SAY find Documentation -ipath '*<name>*'
ABOUT ITSELF? Read the overview .rst before any .c file.
3. WHAT ARE THE rg -n "^struct [a-z_]+ \{" <dir>/*.h include/linux/<name>*.h
NOUNS? A subsystem is 3–6 core structs and the relationships between
them. Draw them before reading any function.
4. WHAT ARE THE rg -n "struct [a-z_]+_ops \{" -A 30 <dir> include/linux/
VERBS? Function-pointer tables ARE the architecture. Who fills them
in, and who calls through them?
5. WHERE DOES CONTROL Find the entry points: a syscall, a probe(), an IRQ handler,
ENTER? a softirq. Then follow ONE path all the way down.
6. WHAT IS MOVING? git log --oneline --since="6 months ago" -- <dir> | head -30
Recent churn tells you what is contentious and what is dead.
7. HOW IS IT TESTED? ls tools/testing/selftests/ | grep -i <name>
rg -l "kunit" <dir>
The tests are executable documentation of the contract.
Do not read a subsystem front to back. Find one path and follow it to the hardware, then find a second path and notice what it shares with the first.
Validation / Self-check
Milestone 0 is complete when you can answer all of these without notes. These are the same twelve questions from the introduction.
- Name every way control can enter the kernel from outside it, and for each, say what context it produces and whether that context may sleep.
- Why can kernel code not dereference a user pointer directly? Name three distinct failure modes and the function you must use instead.
- Define "context". List the kinds and, for each, whether you may sleep.
- What does
might_sleep()do, which config option makes it useful, and what is the exact message it produces? - Name three functions that can sleep but do not look like they can.
GFP_KERNELvs.GFP_ATOMIC: what does each permit the allocator to do, and what goes wrong in each direction if you choose the other?- Spinlock vs. mutex: which contexts may take each, why, and what does
spin_lock()do to preemption? - What is preemption, and why is "the scheduler probably will not preempt here" an unsound argument even when it is empirically true?
- Your interrupt handler needs 2 ms of work. Name three deferral mechanisms 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" forbid? Give one allowed and one forbidden change that look superficially similar.
- A patch is in
linux-next. Is it "in Linux"? What must happen next, and roughly when?
And three more that only this chapter can ask:
- Draw the kernel's position in a process's address space. Why is the kernel mapped into every process rather than living in one of its own?
- Explain why a task in state
D(TASK_UNINTERRUPTIBLE) cannot be killed, in terms of when signals are delivered. - Your module works perfectly on your 4-core laptop and corrupts a list on a 96-core server. Name the three most likely causes, in order.
Tip: Write these answers into
answers-m0.mdand commit it. At the capstone you re-answer the same fifteen and diff. That diff is the most direct measure of what this curriculum did for you.
Next: The Lab Rig — the tree, the config, the initramfs, QEMU, and GDB, assembled into a loop you can run in under five minutes.