Linux Kernel Architecture-Free Linux kernel development course

 

Previous Lectures
Next Lectures

Linux Kernel Architecture
Chapter 4 · LKMs Part 1 · Lesson 1 of 3
Understanding User Space, Kernel Space & the Modular Design
🎯 Beginner Friendly
⏱ 20 min read
🐧 Kernel 6.x
💡 Interview Q&A

📌 Keywords — Rank for Free Linux Kernel Course
free linux kernel development course
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.

Linux Memory Layout — User Space vs Kernel Space
KERNEL SPACE — Ring 0 (Privileged)
Process Scheduler · Memory Manager · VFS · Device Drivers
Network Stack · System Call Handler · LKMs
⬆ System Call (switch to kernel mode)  |  ⬇ Return to user mode
USER SPACE — Ring 3 (Unprivileged)
Your app · glibc / musl · bash · systemd services
Each process has its own isolated virtual address space
Hardware — CPU · RAM · Storage · Network interfaces

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.

⚠️ Kernel Bugs Are Serious
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.

System Call Flow — How User Space Talks to the Kernel
User program calls write(fd, buf, n) in C code
↓
glibc wrapper sets syscall number in RAX, args in registers
↓
syscall instruction → CPU switches to Ring 0
↓
Kernel’s sys_write() handler executes in kernel space
↓
sysret → CPU returns to Ring 3, result in RAX
↓
User program continues, checks return value

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.

Monolithic Kernel vs Microkernel — Side by Side
MONOLITHIC (Linux)
KERNEL SPACE
Scheduler
Memory Manager
Device Drivers
File Systems
Network Stack
All in one address space
Direct function calls = fast
MICROKERNEL (QNX/Minix)
USER SPACE SERVERS
Driver Server
FS Server
Net Server
KERNEL SPACE
Scheduler + IPC only
IPC needed for calls
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).

💡 The Best of Both Worlds
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:

Linux Kernel 6.x — Major Subsystems
USER APPLICATIONS
System Call Interface (syscall)
Process
fork/exec
scheduler
signals
namespaces
Memory
virtual mem
paging
slab alloc
mmap
VFS
ext4/xfs/btrfs
open/read/write
dentries/inodes
mounts
Network
sockets
TCP/IP
netfilter
eBPF
Device I/O
char/block
platform bus
IRQ handling
DMA
LKMs
insmod/rmmod
.ko files
init/exit hooks
modprobe
Hardware Abstraction Layer → CPU · RAM · Storage · Network · Peripherals

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, free do not exist here). You use kernel equivalents like printk, 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.
📝 Kernel 6.x Note
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:

x86-64 Virtual Address Space Layout (Kernel 6.x, 48-bit addressing)
0xFFFF800000000000 – 0xFFFFFFFFFFFFFFFF
KERNEL
non-canonical gap (unusable)
—
0x0000000000000000 – 0x00007FFFFFFFFFFF
USER
Each user process sees the full user range as its own (virtual memory)

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

Q1. What is the difference between user space and kernel space in Linux?
User space is where application programs run. Each process has its own isolated virtual address space and runs at Ring 3 (unprivileged mode). Kernel space is where the OS kernel runs, at Ring 0 (privileged mode), with direct access to all hardware. Applications cannot access kernel memory directly — they must use system calls to request services from the kernel.
Q2. Is Linux a monolithic kernel or a microkernel?
Linux is architecturally a monolithic kernel — all core OS services (scheduler, memory manager, device drivers, file systems, network stack) run together in kernel space in a single address space. However, Linux also supports dynamically loadable kernel modules (LKMs), which makes it modular at runtime without requiring a reboot. This gives Linux the performance of a monolithic design with the flexibility of modularity.
Q3. What are the CPU privilege rings and which ones does Linux use?
x86 CPUs support four privilege levels (rings) numbered 0 to 3. Ring 0 is the most privileged and Ring 3 is the least. Linux uses only two: Ring 0 for kernel code and Ring 3 for user-space applications. Rings 1 and 2 are not used by Linux.
Q4. What happens during a system call?
When a user-space program makes a system call (e.g., read or write), the CPU is instructed (via the 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.
Q5. Why can’t you use printf() inside a Linux kernel module?
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.
Q6. What is KPTI and why was it introduced in the Linux kernel?
KPTI (Kernel Page Table Isolation) was introduced in Linux 4.15 in response to the Meltdown CPU vulnerability. Before KPTI, kernel address space mappings were present in every process’s page table, making a speculative execution attack (Meltdown) possible. KPTI separates user and kernel page tables — when user code is running, the kernel mapping is removed from the page table. This adds a small overhead to every system call but prevents the Meltdown class of attacks. It remains active in kernel 6.x on affected hardware.
Q7. What is the difference between a kernel module and a device driver?
A kernel module (LKM) is any piece of code that can be dynamically loaded into and unloaded from the running kernel. A device driver is a specific type of kernel module whose job is to control a hardware device. All device drivers are kernel modules, but not all kernel modules are device drivers — for example, a module that adds a new filesystem type or a network protocol is also a kernel module but not a device driver.

📚 EmbeddedPathashala — Free Linux Kernel Development Course

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.

Previous Lectures
Next Lectures

Leave a Reply

Your email address will not be published. Required fields are marked *