Linux Kernel Programming
Chapter 4 | Part 1 of 6
Kernel Architecture: User Space & Kernel Space
📚 Beginner Friendly
⏱ ~20 min read
🔐 Kernel 6.x Updated
🔍 Topics Covered
User Space
Kernel Space
CPU Privilege Rings
ARM Execution Modes
System Call Interface
Kernel Subsystems
Monolithic Kernel
LKM Framework
🎯 What You Will Learn
Before you can write even a single line of kernel code, you need to understand where that code runs and why it is different from the code you write for regular applications. This tutorial builds that foundation clearly. By the end of this page, concepts like privilege levels, CPU rings, user space, and kernel space will feel completely natural to you.
🤔 Why Does Privilege Even Exist?
Think about what happens when your browser, music player, and file manager all run at the same time on your Linux desktop. Each of these programs is written by different people, may have bugs, and could try to do something harmful — like reading another program’s memory or directly controlling the hardware.
If all software ran with full access to everything, one buggy program could crash or corrupt the entire system. Modern CPUs solve this problem by providing privilege levels. Different code runs at different privilege levels, and the hardware enforces what each level is allowed to do.
The Linux kernel takes advantage of this hardware feature to create a clean boundary between application code and kernel code.
🔒 CPU Privilege Levels — x86 and ARM
Different CPU families implement privilege in their own way. Let us look at the two most common ones you will encounter in embedded and desktop Linux development.
x86 / x86-64 (Intel and AMD)
These processors define four privilege levels, commonly called protection rings, numbered Ring 0 through Ring 3. Ring 0 is the most privileged and Ring 3 is the least privileged.
x86 CPU Protection Rings
Ring 0
Kernel
Ring 3 — User Apps
Ring 2 — (unused by Linux)
Ring 1 — (unused by Linux)
Linux only uses Ring 0 (kernel) and Ring 3 (user apps). Rings 1 and 2 are unused.
ARM (32-bit) Execution Modes
ARM processors have a different approach. They define up to seven execution modes. Six of these are privileged (used for various exception handlers, the kernel, etc.) and one — User mode — is non-privileged. On a 64-bit ARM (AArch64), the architecture defines Exception Levels (EL0–EL3) where EL0 is user space and EL1 is the kernel.
AArch64 (ARM64) Exception Levels
EL3 — Secure Monitor (TrustZone / firmware)
EL2 — Hypervisor (virtualization)
EL1 — OS Kernel (Linux kernel runs here)
EL0 — User Applications (least privilege)
↑ Privilege increases as you go up
Key takeaway: No matter what CPU architecture you are on, Linux always uses exactly two levels — one for user applications and one for the kernel itself.
⛰ User Space and Kernel Space — The Two Worlds
Linux maps the CPU privilege levels to two distinct software environments:
- User Space: Where all regular applications run. Your browser, your Python scripts, the
ls command — all of these live in user space. Code here runs at the lowest privilege level. It cannot directly touch hardware, cannot access arbitrary memory, and cannot call kernel-internal functions.
- Kernel Space: Where the Linux kernel and all kernel modules run. Code here has full access to hardware registers, all physical memory, and every kernel data structure. A bug here can bring down the entire system — there is no safety net.
Linux: Two Worlds
🏠 USER SPACE (Ring 3 / EL0)
Browser
Shell
Python App
Database
C Program
🔒 Cannot access hardware directly | Protected memory space
↓ System Call Interface (syscall) ↓
The only legal gateway between the two worlds
⚙ KERNEL SPACE (Ring 0 / EL1)
Process Scheduler
Memory Manager
Filesystem
Network Stack
Device Drivers
LKMs
⚡ Full hardware access | One shared address space | No protection from itself
🖥 HARDWARE — CPU, RAM, Peripherals, Storage
The System Call Interface is the carefully guarded gate between user space and kernel space. When your C program calls open() to open a file, the C library (glibc) translates that into a syscall instruction. The CPU switches from Ring 3 to Ring 0, the kernel handles the request, and then control returns to user space. This context switch has a small performance cost, which is why the kernel tries to do work in batches where possible.
🏗 Inside the Linux Kernel — Major Subsystems
The Linux kernel is a monolithic kernel — meaning all core services run together in kernel space as a single large executable. This is different from a microkernel (like QNX or Minix) where services run in separate user-space processes. Linux’s monolithic approach gives it better performance because subsystems can call each other directly without crossing the user/kernel boundary.
However, Linux is not a rigid monolith. It supports Loadable Kernel Modules (LKMs), which let you extend the kernel at runtime without rebooting. More on that in Part 2.
Here are the major subsystems you need to know about:
Linux Kernel Major Subsystems (Kernel 6.x)
⏱ Process Scheduler
Decides which process/thread runs on which CPU core and for how long. Linux uses CFS (Completely Fair Scheduler) with real-time extensions.
💾 Memory Manager
Manages physical RAM, virtual memory, page tables, slab allocator, and swap. Every process gets its own virtual address space.
📁 Virtual Filesystem (VFS)
Provides a uniform file API regardless of the underlying filesystem (ext4, btrfs, tmpfs, etc.). Everything in Linux is a file.
🌐 Network Stack
Implements TCP/IP, UDP, UNIX sockets, and more. Pluggable via netfilter hooks (iptables/nftables use these).
🔌 Device Drivers
Code that talks to hardware — GPIOs, I2C, SPI, USB, PCIe, block devices. Most drivers are loadable kernel modules.
🔗 IPC & Synchronization
Signals, pipes, shared memory, semaphores, mutexes, spinlocks — mechanisms for processes and kernel threads to communicate and stay safe.
👥 Security Subsystem
Linux Security Modules (LSM) framework powering SELinux, AppArmor, and seccomp. Mandatory Access Control lives here.
🔌 LKM Framework
Lets you load/unload kernel extensions (modules) at runtime. Device drivers, filesystems, and more can be modules.
📅 Kernel Versions — What You Need to Know in 2024-25
The Linux kernel uses a version numbering scheme: major.minor.patch. For example, 6.6.30 means major version 6, minor version 6, patch 30.
- Long-Term Support (LTS) kernels receive bug and security fixes for several years. As of 2025, LTS kernels include 6.6, 6.1, 5.15, 5.10, 5.4, and 4.19. For production embedded systems, always target an LTS kernel.
- Mainline is the latest development kernel from Linus Torvalds’ tree. Good for learning the latest features.
- The kernel version matters because APIs, data structures, and subsystem behaviours change between versions. Code written for kernel 5.3 may not compile on kernel 6.6 without modifications.
To check the kernel version on your running system:
uname -r
# Example output: 6.8.0-45-generic
cat /proc/version
# Example output: Linux version 6.8.0-45-generic (buildd@lcy02-amd64-059) ...
💡 Why Does This Matter When Writing a Kernel Module?
When you write a kernel module, your code runs inside kernel space. This has very real consequences that you must always keep in mind:
⚠ No standard C library. You cannot call printf(), malloc(), or sleep(). You use kernel equivalents: printk(), kmalloc(), msleep().
⚠ A NULL pointer dereference kills the system. In user space, a bug causes a segmentation fault and your process dies. In kernel space, the same bug causes a kernel panic — the whole system stops.
⚠ No memory protection between modules. All kernel code shares the same address space. A bad pointer in your module can corrupt unrelated kernel data structures.
✅ You have full power. You can talk to hardware directly, intercept system calls, manage memory at the page level, and hook into any kernel subsystem.
🏆 Interview Questions — Kernel Architecture
Q1. What is the difference between user space and kernel space?
User space is where applications run at the lowest CPU privilege level. They have restricted access to hardware and memory. Kernel space is where the kernel runs at the highest privilege level with full hardware access. The system call interface is the controlled boundary between them.
Q2. How many protection rings does x86 have and how many does Linux use?
x86 defines four rings (Ring 0 to Ring 3). Linux uses only two: Ring 0 for the kernel and Ring 3 for user applications. Rings 1 and 2 are not used.
Q3. What is the difference between a monolithic kernel and a microkernel?
In a monolithic kernel (like Linux), all core OS services run together in a single kernel space binary. In a microkernel, only essential services like scheduling and IPC run in kernel space; everything else (filesystems, drivers) runs in user-space servers. Monolithic kernels are generally faster; microkernels are more modular but have higher IPC overhead.
Q4. What happens when a NULL pointer is dereferenced in a kernel module?
It typically causes a kernel oops or a kernel panic, halting the system. Unlike user space where only the faulting process dies, a bug in kernel code can affect the entire system because all kernel code shares the same address space.
Q5. What are the exception levels in AArch64 and what runs at each level?
EL0 is user applications (lowest privilege). EL1 is the OS kernel. EL2 is the hypervisor (for virtualization). EL3 is the secure monitor (TrustZone firmware). Linux runs at EL1 on bare metal, or at EL1 inside a VM where the hypervisor is at EL2.
Q6. Why can’t a kernel module use printf() or malloc()?
printf() and malloc() are part of the C standard library (glibc), which is a user-space library. The kernel does not link against glibc. Instead, the kernel provides its own equivalents: printk() for logging and kmalloc()/vmalloc() for memory allocation.
Up Next: Part 2 — The LKM Framework
What are Loadable Kernel Modules, why do they exist, and how does the kernel load and unload them?