Architecture of the Linux Kernel: A Source-Code Roadmap
Architecture of the Linux Kernel — A Source-Code Roadmap
The Linux kernel source tree is one of the largest and most actively maintained C codebases in existence: roughly 35 million lines across `drivers/`, with the core (everything outside `drivers/`) hovering around 5 million. For a new systems programmer, the first encounter with the tree is usually disorienting because there is no obvious entry point, the directory structure looks like a taxonomy that nobody explained, and the macro-driven header architecture makes naïve grepping feel like guesswork. This chapter answers the question you actually need answered before doing anything else: where in this tree does each subsystem live, and how do you turn source code into a working, debuggable kernel image that you can step through?
We will work through the tree from the top, follow the boot path from the first instruction executed after a long-mode handoff down to the moment the first user-space process is spawned, study the Kconfig/Kbuild machinery that decides what gets compiled, install the source-navigation and tracing toolchain that the rest of this book assumes you have, and finish with a working QEMU-based debug kernel you can probe with `printk`, `objdump`, and `gdb`. By the end of this chapter you will have a mental map of the kernel and a tangible build artifact that anchors every subsequent investigation.
1.1 The Top-Level Layout: A Cartographer's View
The canonical kernel source tree (fetched via `git clone https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git` or, for a stable base, the `linux-6.1.y` long-term branch) is organized by *subsystem*, not by *file type*. This is the first thing to internalize: there is no `src/`, no `include/` in the conventional library sense at the root, and `lib/` does not contain "everything that isn't a subsystem." The organization reflects decades of consolidation decisions made by Linus Torvalds and the subsystem maintainers, and resisting that structure is the single most common mistake new kernel hackers make.
At the top level you will find approximately fifty directories. Most of them map 1:1 to a subsystem or build concern. The most important ones for the work in this book are:
- `arch/` — Architecture-specific code. Each supported CPU architecture has a subdirectory. For x86_64, almost everything we need is under `arch/x86/`. Other architectures include `arm64/`, `riscv/`, `loongarch/`, `powerpc/`, `s
`PowerPC/`, `s390/`, `mips/`, `parisc/`, `m68k/`, and so on—approximately twenty-five architectures in a modern `v6.1` tree. Inside each architecture directory you will find the entry assembly (`head_64.S` for x86_64, `head.S` for ARM), the trap/interrupt dispatch (`entry_64.S`, `entry.S`), low-level memory management (`mm/`), the signal handling primitives (`signal.c`, `signal_*.c`), and any architecture-tuned version of generic routines. The cardinal rule: anything in `arch/<name>/` is by definition *not* portable; if you find a useful-looking macro or function there, search for its generic counterpart in `include/`, `kernel/`, `mm/`, or `lib/` before assuming it is unique to that architecture.
- `block/` — The block I/O subsystem: request queues, the multi-queue `blk-mq` layer, the I/O scheduler framework, the BIO and `bio_set` infrastructure, and the device-mapper plumbing. If you are debugging a slow disk or a hung I/O request, the call graph almost always starts at `submit_bio()` in `block/blk-core.c` and descends through `blk_mq_submit_bio()` into `drivers/nvme/host/` or `drivers/scsi/`.
- `crypto/` — The kernel's native cryptographic API: `crypto/sha256_generic.c`, `crypto/aes_generic.c`, the algorithm registration machinery in `crypto/api.c`, and the asynchronous handoff to hardware accelerators in `drivers/crypto/`. The interface is split between synchronous (`crypto_shash`, `crypto_cipher`) and asynchronous (`crypto_akcipher`, `crypto_kpp`) variants; understanding which one your driver needs is half the battle.
- `Documentation/` — Often the most underused directory in the tree. It houses not only the user-facing user-space-facing kernel-doc generated reference, but also subsystem design notes (`Documentation/scheduler/sched-design-CFS.rst`, `Documentation/vm/`, `Documentation/filesystems/`), process-style "how to write a [X] driver" tutorials, and the ABI and sysfs interface definitions. When you cannot make sense of a subsystem's code, the corresponding `.rst` file in here is almost always more readable than the implementation.
- `drivers/` — The largest single consumer of source lines, containing every supported device driver, organized again by bus type or function: `drivers/net/`, `drivers/scsi/`, `drivers/gpu/drm/`, `drivers/usb/`, `drivers/pci/`, and so on. A sub-driver does not stand alone: it must implement a set of `struct file_operations` or `struct net_device_ops` methods that the framework calls into, and it links against generic helpers in its parent directory's non-driver code.
- `fs/` — Filesystem code: VFS layer in `fs/` proper (`inode.c`, `super.c`, `namei.c`, `read_write.c`, `open.c`, `exec.c`, `namespace.c`, etc.) and concrete filesystems in subdirectories (`fs/ext4/`, `fs/xfs/`, `fs/btrfs/`, `fs/nfs/`, `fs/overlayfs/`). The VFS layer is purely abstract; concrete filesystems `register_filesystem(&ext4_fs_type)` during module load, after which mount paths find it through `get_fs_type()`.
- `include/` — Public and internal headers, grouped by both function (`include/linux/`, `include/uapi/`, `include/net/`) and architecture (`include/asm-generic/`, `include/x86/`, `include/asm-prototypes/`). The split between `include/linux/` and `include/uapi/linux/` is significant: the latter is *the* stable user-space ABI and ships as `<linux/...>` headers in glibc; the former is private to kernel compilation.
- `init/` — Bootstrap code. Of all files here, `init/main.c` is far and away the most important: it is the C entry point of the kernel proper (after the architecture-specific assembly has set up enough state to enter C). We will trace through it later in this chapter.
- `ipc/` — System V IPC and POSIX IPC primitives (`msg.c`, `sem.c`, `shm.c`), plus the modern signalfd/timerfd/eventfd family via `signalfd.c`, `timerfd.c`, `eventfd.c`.
- `kernel/` — The architecture-independent core: scheduling (`sched/fair.c`, `sched/rt.c`, `sched/deadline.c`, `sched/core.c`), timekeeping (`time/timekeeping.c`, `time/hrtimer.c`, `time/timer.c`), synchronization primitives, signal delivery, the unified interrupt handling entry points (`irq/`, `softirq.c`, `tasklet.c`), the RCU implementations (`rcu/`), locking primitives (`locking/`), and `printk` and the kernel log buffer (`printk/`). When something is the "right" architectural feature, it is implemented in `kernel/`; when it is an "X-platform-optimized" detail, the fast paths are mirrored into `arch/`.
- `lib/` — Standalone helper implementations that want to live outside the subsystem that first needed them: `lib/vsprintf.c` (the kernel's safe printf engine), `lib/list_sort.c`, `lib/rbtree.c`, `lib/kobject.c`, `lib/debugobjects.c`, `lib/string.c`, `lib/iov_iter.c`. Many of these are exported as `EXPORT_SYMBOL` so that drivers may call them directly.
- `mm/` — Memory management: page allocation (`alloc_pages.c`, `page_alloc.c`), the page frame allocator and the buddy allocator (`page_alloc.c`), slab/SLUB/`kmem_cache` (`slub.c`), the virtual memory subsystem (`mm.c`, `memory.c`, `mmap.c`, `mprotect.c`, `mremap.c`, `pagewalk.c`), swap (`swap.c`, `swapfile.c`, `swap_state.c`), reclaim (`vmscan.c`), page tables and TLB management (`pgtable-generic.c`, `hugetlb.c`), NUMA policy (`mempolicy.c`, `migrate.c`), and KASAN/KCSAN/`page_poison` debug checkers under `mm/kasan/`.
- `net/` — Network protocol stack and the socket layer: the PF_INET/PF_INET6 implementations (`net/ipv4/`, `net/ipv6/`), PF_PACKET and AF_UNIX under `net/packet/` and `net/unix/`, the Netlin