Need Help or More Information?
Questions about this free C programming course, embedded C training or career guidance? Get in touch.
Kernel Space
CPU Privilege Rings
ARM Execution Modes
System Call Interface
Kernel Subsystems
Monolithic Kernel
LKM Framework
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.
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.
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.
Kernel
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.
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.
Linux maps the CPU privilege levels to two distinct software environments:
- User Space: Where all regular applications run. Your browser, your Python scripts, the
lscommand — 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.
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.
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:
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) ...
When you write a kernel module, your code runs inside kernel space. This has very real consequences that you must always keep in mind:
printf(), malloc(), or sleep(). You use kernel equivalents: printk(), kmalloc(), msleep().Need Help or More Information?
Questions about this free C programming course, embedded C training or career guidance? Get in touch.
