Skip to main content
The Linux kernel is a monolithic kernel — all core services run in a single privileged address space. Unlike microkernels, which isolate services into user-space servers, Linux executes the process scheduler, memory manager, device drivers, networking stack, and virtual filesystem layer together in kernel space. This design maximises throughput and minimises inter-component latency at the cost of a larger trusted computing base. Despite being monolithic, Linux supports loadable kernel modules (LKMs). Modules can be inserted and removed at runtime without rebooting, allowing drivers and optional subsystems to be shipped separately from the base kernel image.
The source tree lives at kernel/ for core logic, with subsystem directories such as mm/, net/, fs/, and drivers/ each owned by their respective maintainers.

Kernel space vs user space

The processor runs in one of two privilege levels. User space code executes with restricted privileges; it cannot directly access hardware or kernel memory. Kernel space code runs with full hardware access and can execute privileged instructions.
┌─────────────────────────────────────┐
│          User Space                 │
│  Applications, Libraries (glibc)    │
├─────────────────────────────────────┤
│       System Call Interface         │  ← privilege boundary
├─────────────────────────────────────┤
│          Kernel Space               │
│  Process Mgmt │ MM │ VFS │ Net │ …  │
│          Device Drivers             │
│          Hardware Abstraction       │
└─────────────────────────────────────┘
│              Hardware               │
└─────────────────────────────────────┘
When user code needs a kernel service it issues a system call, transitioning into kernel mode via a controlled entry point (SYSCALL/sysenter on x86, svc on ARM64). The kernel performs the requested operation and returns to user mode.

System call interface

System calls are numbered and dispatched through a per-architecture syscall table. On x86-64, the SYSCALL instruction saves the user register state and jumps to entry_SYSCALL_64 in arch/x86/entry/entry_64.S, which routes to the handler via sys_call_table.
// Example: open(2) wrapper in fs/open.c
SYSCALL_DEFINE3(open, const char __user *, filename, int, flags, umode_t, mode)
{
    if (force_o_largefile())
        flags |= O_LARGEFILE;
    return do_sys_open(AT_FDCWD, filename, flags, mode);
}
Use strace(1) to observe which system calls a process issues, and ausyscall --dump to list all syscall numbers for your architecture.

Key subsystems

Process Management

Task creation (fork/clone/exec), scheduling, signals, inter-process communication, and process namespaces. Core code lives in kernel/ and kernel/sched/.

Memory Management

Physical page allocation (buddy allocator), slab/SLUB object caches, virtual memory areas (VMAs), page tables, demand paging, swap, and the OOM killer. Source in mm/.

Virtual File System

A filesystem-agnostic abstraction layer exposing a unified open/read/write/close API to user space. Concrete filesystems (ext4, btrfs, xfs, tmpfs) register with the VFS. Source in fs/.

Networking Stack

A layered implementation of L2–L4 protocols. sk_buff (socket buffer) carries packets through the stack. Netfilter hooks allow packet filtering and NAT. Source in net/.

Device Drivers

Character, block, and network device drivers abstract hardware behind uniform kernel interfaces. Most live in drivers/.

IPC

Pipes, FIFOs, UNIX domain sockets, System V and POSIX message queues, semaphores, and shared memory. Source in ipc/.

Supported architectures

The arch/ directory contains one subdirectory per supported instruction set architecture (ISA). Each architecture port provides platform-specific implementations of context switching, page table management, interrupt handling, and the system call entry path.
DirectoryArchitecture
arch/x86/x86 and x86-64 (IA-32, AMD64)
arch/arm/ARM 32-bit (ARMv6/v7)
arch/arm64/AArch64 (ARMv8/v9, including Apple Silicon)
arch/powerpc/IBM POWER and PowerPC
arch/s390/IBM Z (mainframe)
arch/mips/MIPS32 and MIPS64
arch/riscv/RISC-V (RV32 and RV64)
arch/sparc/SPARC and SPARC64
DirectoryArchitecture
arch/alpha/DEC Alpha
arch/arc/Synopsys ARC
arch/csky/C-SKY
arch/hexagon/Qualcomm Hexagon DSP
arch/loongarch/LoongArch (Loongson)
arch/m68k/Motorola 68000
arch/microblaze/Xilinx MicroBlaze
arch/nios2/Altera Nios II
arch/openrisc/OpenRISC 1000
arch/parisc/HP PA-RISC
arch/sh/SuperH
arch/um/User Mode Linux (UML)
arch/xtensa/Tensilica Xtensa

Loadable kernel modules

Modules extend the kernel at runtime. A module is an ELF object that the kernel links into its address space on load.
1

Write the module

Declare the entry and exit points with module_init() and module_exit(), add a MODULE_LICENSE() tag, and build with make M=path/to/module modules.
#include <linux/module.h>
#include <linux/kernel.h>

static int __init hello_init(void)
{
    pr_info("Hello, kernel\n");
    return 0;
}

static void __exit hello_exit(void)
{
    pr_info("Goodbye, kernel\n");
}

module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
2

Load the module

insmod hello.ko        # load by file path
modprobe hello         # load by module name, resolves dependencies
3

Inspect loaded modules

lsmod                  # list loaded modules
modinfo hello          # display metadata
cat /proc/modules      # raw module list with addresses
4

Remove the module

rmmod hello
modprobe -r hello      # also removes unused dependencies

How subsystems interact

Subsystems communicate through well-defined internal kernel APIs rather than system calls. The following shows a typical read(2) path:
read(fd, buf, len)            ← user space
  └─ sys_read()               ← VFS entry point (fs/read_write.c)
       └─ vfs_read()          ← VFS dispatch
            └─ file->f_op->read_iter()  ← filesystem method
                 └─ page cache lookup   ← mm/ involvement
                      └─ block I/O      ← bio submitted to driver
                           └─ driver interrupt handler
The page cache sits between the VFS and block layer, caching file data in memory pages managed by the MM subsystem. When data is not cached, the filesystem issues a struct bio request to the block layer, which routes it to the appropriate driver.
Code running in kernel space has no memory protection from itself. A null pointer dereference or stack overflow in a driver can panic the entire system. Always enable CONFIG_KASAN, CONFIG_UBSAN, and lockdep during development.

Build docs developers (and LLMs) love