Previous Lectures
Next Lectures
linux kernel architecture
user space vs kernel space
monolithic kernel explained
loadable kernel module
free embedded systems course
linux device drivers course free
ring 0 ring 3 linux
Before you can write a single line of kernel code, you need to understand where that code runs and why it is different from a regular C program. This lesson answers exactly that. We will look at how Linux organises its memory, why there are two separate worlds called user space and kernel space, what kind of kernel Linux actually is, and how Loadable Kernel Modules (LKMs) fit into all of this.
Everything here applies to Linux kernel 6.x — the version you will encounter in any modern Ubuntu, Fedora, Debian, or Raspberry Pi OS installation today.
1. Two Worlds: User Space and Kernel Space
Every modern operating system divides the memory address space into two separate regions. Linux calls them user space and kernel space. This separation is not optional — it is enforced directly by the CPU hardware and it exists to protect the operating system from badly written or malicious programs.
Think of it like a hospital. Patients (user applications) are allowed inside but they cannot walk into the surgery theatre (kernel) whenever they like. They have to go through a proper procedure — a nurse (system call) takes them where they need to go. The surgery theatre has special instruments (hardware access) that only trained staff (kernel code) can touch.
Process Scheduler · Memory Manager · VFS · Device Drivers
Network Stack · System Call Handler · LKMs
Your app · glibc / musl · bash · systemd services
Each process has its own isolated virtual address space
1.1 What lives in user space?
Every program you run as a regular user — your text editor, web browser, Python script — runs in user space. Each process gets its own virtual address space. One process cannot directly read or write another process’s memory. If a user-space program crashes, only that program goes down. The rest of the system keeps running.
User space code runs in Ring 3 on x86 processors, which is the lowest privilege level. The CPU hardware prevents Ring 3 code from executing privileged instructions directly.
1.2 What lives in kernel space?
The Linux kernel itself runs in kernel space. This includes the process scheduler, memory management subsystem, virtual file system (VFS), device drivers, the networking stack, and kernel modules. Kernel code runs in Ring 0 — the most privileged CPU mode. Code in Ring 0 can do absolutely anything the hardware allows: directly access I/O ports, configure CPU registers, manage physical memory.
A bug in user space crashes only one process. A bug in kernel space can crash the entire system (kernel panic), corrupt data, or create a security hole that gives an attacker root access. This is why kernel programming demands extra care.
1.3 How do they talk to each other? — System Calls
User-space programs cannot directly call kernel functions. Instead, they use system calls — a well-defined interface provided by the kernel. When your C program calls open(), read(), or write(), the C library (glibc) translates those into the corresponding system call. The CPU switches from Ring 3 to Ring 0, the kernel executes the request, and then returns back to Ring 3.
On x86-64 Linux, the syscall instruction triggers this switch. The kernel 6.x system call table contains over 300 such entry points. You can see them all in the kernel source under arch/x86/entry/syscalls/syscall_64.tbl.
2. What Kind of Kernel Is Linux?
Kernels can be designed in different ways. The two classic approaches are monolithic kernels and microkernels. Linux is a monolithic kernel — but with a very important twist that makes it flexible.
2.1 Monolithic Kernel
In a monolithic kernel, almost all operating system services — the scheduler, memory manager, file system, device drivers, network stack — all run together in a single binary in kernel space. They share the same address space and can call each other directly without any message passing overhead.
The advantage is speed. A function call inside the kernel is just a regular CPU jump instruction — nanoseconds. There is no inter-process communication involved.
The disadvantage is that a bug in any one component can affect the entire kernel. Linux accepts this tradeoff and compensates with rigorous code review, testing, and the kernel’s own internal protection mechanisms.
2.2 Microkernel
In a microkernel, only the most essential parts — process scheduling and inter-process communication — live in kernel space. Everything else (device drivers, file systems, network stack) runs as separate server processes in user space.
This improves fault isolation — a buggy driver cannot crash the kernel — but every interaction between components requires a message passing round-trip, which adds latency. QNX and Minix are well-known microkernel examples.
Direct function calls = fast
Safer but slower
2.3 Linux Is Monolithic — But Also Modular
Here is the key insight: Linux is architecturally monolithic, but it supports dynamic modules. You can add or remove functionality from the running kernel without rebooting. These are called Loadable Kernel Modules (LKMs).
Linux gets the performance of a monolithic kernel and the flexibility of a microkernel’s modularity — through LKMs. A compiled
.ko (kernel object) file can be inserted at runtime. If it crashes, it can corrupt the kernel, but during development you can unload and reload it without rebooting. This makes driver development much more practical.3. Major Subsystems Inside the Linux Kernel
The Linux kernel is not just one blob of code. It is logically divided into well-defined subsystems. As of kernel 6.x, the source tree has grown beyond 27 million lines of code. Here is how the main pieces are organised:
scheduler
signals
namespaces
paging
slab alloc
mmap
open/read/write
dentries/inodes
mounts
TCP/IP
netfilter
eBPF
platform bus
IRQ handling
DMA
.ko files
init/exit hooks
modprobe
Each subsystem is maintained by a dedicated group of engineers and has its own set of internal APIs. For example, device drivers use the driver model APIs to register themselves with the kernel. When you write an LKM — a loadable kernel module — your code plugs into one or more of these subsystems.
4. Why Does This Architecture Matter for LKM Programming?
When you write an LKM, you are writing code that will run in kernel space. That means:
- There is no C standard library (
printf,malloc,freedo not exist here). You use kernel equivalents likeprintk,kmalloc,kfree. - You do not have a
main()function. Your module has an init function (called on load) and an exit function (called on unload). - You share the kernel’s address space. A stray pointer or an out-of-bounds write can corrupt live kernel data and cause a panic.
- You have direct access to hardware registers and interrupt handlers — things that are impossible from user space.
- Floating point arithmetic is generally not safe to use in kernel context without special setup.
Starting from kernel 5.x, the kernel adopted better compile-time checks and static analysis tooling. In kernel 6.x, features like KCSAN (Kernel Concurrency Sanitizer), improved KASAN (Kernel Address Sanitizer), and lockdep are available to help developers catch bugs in their modules. These tools are extremely helpful when you start writing your own modules.
5. Virtual Address Space on 64-bit Linux
On a 64-bit x86 machine running kernel 6.x, the virtual address space is astronomically large — theoretically 264 bytes. In practice, only 48 bits are used (some CPUs now support 57-bit addressing with 5-level page tables enabled by default in 6.x). The address space is split like this:
KERNEL
—
USER
Every user process thinks it owns the entire user half of the address space. In reality, the kernel uses page tables to map virtual addresses to physical RAM. The kernel half is mapped identically in every process’s page table — so the kernel can be entered quickly from any process context without switching page tables (though Spectre/Meltdown mitigations in kernel 5.x+ added extra complexity here via KPTI — Kernel Page Table Isolation).
🎯 Interview Questions & Answers
syscall instruction on x86-64) to switch from Ring 3 to Ring 0. The kernel’s system call handler looks up the requested function in the system call table, executes it, and then returns to Ring 3 with the result. The entire transition takes on the order of a few hundred nanoseconds.printf() is a C standard library function and the standard library is a user-space library — it does not exist in kernel space. Inside a kernel module you use printk() instead, which writes messages to the kernel ring buffer (readable via dmesg). The kernel has its own set of APIs for memory allocation, string handling, and everything else that replaces the standard C library.This is Lesson 1 of the LKM series. In the next lesson we go hands-on — you will understand the LKM framework, write your very first Hello World kernel module, and learn the Makefile that builds it.
