What Is the Kernel Segment in Linux? – Free Linux Device Driver Course

 

 

Linux Kernel Virtual Address Space
Understanding Kernel Segment Layout in 32-bit and 64-bit Systems
Free
Course
~45 min
Read Time
Kernel 6.x
Updated

🎓 What You Will Learn

  • How the Linux kernel virtual address space is split between kernel and user space
  • What the kernel segment actually contains on 32-bit and 64-bit systems
  • The role of key kernel VAS regions: vmalloc, lowmem, fixmap, module, and vector table
  • How the kernel-user boundary works and why it differs across architectures
  • How to read the kernel’s own memory layout documentation for your architecture
  • How the modern Linux 6.x kernel handles virtual address space layout
Topics Covered:
Linux Kernel Virtual Address Space Kernel Segment VM Split VAS Layout lowmem vmalloc Region Kernel Module Region x86_64 Memory AArch64 Memory Layout Free Linux Kernel Course

⚠ Prerequisites

  • Basic understanding of virtual memory and paging
  • Familiarity with how the MMU translates virtual to physical addresses
  • Some exposure to the Linux process model (user space processes, system calls)
  • Optional but helpful: having read our earlier tutorial on Linux Virtual Address Space Overview

Linux Kernel Virtual Address Space – Kernel Segment Deep Dive

Every process running on a Linux system has its own virtual address space. Part of that address space belongs to the process itself — the user segment — and part of it is shared across all processes and belongs to the kernel — the kernel segment. Together, these two regions make up the complete Linux kernel virtual address space for any given process.

In this tutorial, we take a detailed look at the kernel segment specifically. We will understand what regions exist inside it, how their addresses are determined, why the layout differs between 32-bit and 64-bit systems, and how the modern Linux 6.x kernel handles all of this. This is one of the most important topics in any free Linux kernel development course because without knowing the layout of the VAS, writing kernel code safely is very difficult.

Why Does the Kernel Get Its Own Region of the VAS?

When a process runs, the CPU can be in one of two privilege modes: user mode or kernel mode. In user mode, the process runs its own application code. When it needs an OS service — like reading a file or allocating memory — it issues a system call, and the CPU switches to kernel mode.

In kernel mode, the kernel code needs to be accessible in the current virtual address space without switching page tables. Switching page tables (changing the CR3 register on x86, or TTBR0/TTBR1 on ARM) is expensive. So the Linux kernel keeps itself permanently mapped into every process’s virtual address space in the upper portion. This is the kernel segment.

💡 Key Point: The kernel segment occupies the upper portion of every process’s virtual address space. It is never visible to user-space code — any attempt to access it from user mode causes a fault — but it is always present in the address space mapping.

Starting from Linux kernel 5.4, this changed somewhat on x86_64 with KPTI (Kernel Page Table Isolation), which was introduced as a mitigation for Meltdown. With KPTI enabled, the kernel unmaps most of its pages from the user-mode page tables. But the fundamental concept of having a kernel region in the VAS still holds.

The VM Split: How Address Space is Divided

The total virtual address space is divided between user and kernel. This ratio is called the VM split. On a 32-bit system, the total virtual address space is 4 GB (232 bytes). On a 64-bit system, the hardware supports far more virtual address bits, typically 48 bits, giving 256 TB of addressable space.

VM Split on 32-bit Linux (Classic 3:1 Split on ARM32)
0xFFFF FFFF
Kernel Segment (1 GB)
← Kernel VAS
─────────── Kernel / User Boundary ───────────
0xC000 0000
User Segment (3 GB)
← User VAS
0x0000 0000
NULL trap page (unmapped)
 

In the classic 3:1 split, the bottom 3 GB are for user space and the top 1 GB is for the kernel. However, Linux also supports 2:2 and 1:3 splits on 32-bit ARM, selectable at kernel build time via CONFIG_VMSPLIT_3G, CONFIG_VMSPLIT_2G, and so on.

VM Split on 64-bit Linux (x86_64 and AArch64)
0xFFFF FFFF FFFF FFFF
Kernel Segment (~128 TB on x86_64)
← Kernel VAS
0xFFFF 8000 0000 0000
Non-canonical sparse region (~16,384 PB)
← Huge gap!
0x0000 7FFF FFFF FFFF
User Segment (~128 TB on x86_64)
← User VAS
0x0000 0000 0000 0000
NULL trap page (unmapped)
 

On a 64-bit system like x86_64 with 4-level paging (48-bit addresses), the user segment occupies the lower canonical half and the kernel segment occupies the upper canonical half. Between them sits a massive non-canonical region — addresses whose upper bits do not satisfy the canonical form requirement. On x86_64 with 4-level paging, this non-canonical gap is approximately 16,383.75 petabytes. Any access to this region immediately causes a general protection fault.

Architecture Total VAS User VAS Kernel VAS Paging Levels
ARM32 (3:1) 4 GB 3 GB 1 GB 2-level
ARM32 (2:2) 4 GB 2 GB 2 GB 2-level
x86_64 (4-level) 256 TB canonical ~128 TB ~128 TB 4-level
x86_64 (5-level) 128 PB canonical ~64 PB ~64 PB 5-level
AArch64 256 TB canonical ~128 TB ~128 TB 4-level
5-Level Paging: Starting with Linux 5.5 and Intel Ice Lake CPUs, Linux supports 5-level paging (LA57). This extends the canonical address space to 128 PB per half, giving enormous room for both user and kernel mappings. The kernel Kconfig option is CONFIG_X86_5LEVEL.

What Is Inside the Kernel Segment?

The kernel segment is not a monolithic blob of kernel code. It contains several distinct regions, each serving a specific purpose. The exact layout differs between architectures, but the logical structure is consistent across all of them. Let us walk through each region.

1. Kernel Code and Data (lowmem / direct-mapped region)

The most fundamental region in the kernel segment is the direct-mapped physical memory region, commonly called lowmem on 32-bit systems. Here, physical RAM is mapped directly into the kernel’s virtual address space with a fixed offset. This means that on a 32-bit ARM system with the 3:1 split, the physical address 0x00000000 maps to virtual address 0xC0000000, physical 0x00001000 maps to 0xC0001000, and so on.

This is where the kernel’s own executable code lives, along with its global data, the kernel stack for each running process, and page tables. On 32-bit systems, only a limited amount of physical RAM can be directly mapped this way because the kernel segment is only 1 GB. This is why 32-bit Linux has the concept of highmem — physical RAM beyond the directly-mapped window.

✅ On 64-bit systems this limitation disappears. With 128 TB of kernel virtual address space, the direct-mapped region can cover all practical amounts of physical RAM. The kernel uses a direct map or physmap region that covers all of physical memory, mapped with large pages (2 MB or 1 GB hugepages) for efficiency.

2. vmalloc Region

Not all kernel memory needs to be physically contiguous. The vmalloc() function allocates memory that is virtually contiguous but not necessarily physically contiguous. This is useful for large allocations where finding a large contiguous physical chunk would be difficult or impossible.

The vmalloc region is a dedicated area of the kernel VAS reserved for these virtually-contiguous mappings. On a 32-bit ARM system with the 3:1 split, the vmalloc region sits above the directly-mapped lowmem and below the fixmap region. On 64-bit systems, it is a large region of the kernel VAS that can handle enormous allocations.

The kernel exposes the vmalloc region boundaries through /proc/meminfo and through the VmallocTotal and VmallocUsed fields. You can also see individual vmalloc allocations in /proc/vmallocinfo.

# View vmalloc usage on your system
cat /proc/meminfo | grep -i vmalloc

# See individual vmalloc allocations (requires root)
sudo cat /proc/vmallocinfo | head -30

3. Kernel Module Region

Loadable kernel modules (LKMs) are loaded at runtime and need to be placed in virtual memory where the kernel can call their functions directly. On many architectures, branch and call instructions have limited range — they cannot jump to an address that is too far away without using indirect addressing.

To solve this, Linux reserves a dedicated kernel module region within the kernel VAS, close enough to the core kernel code that direct calls work without trampolines. On 32-bit ARM (3:1 split), the module region is typically 16 MB sitting just below PAGE_OFFSET (which is 0x80000000 for a 1:3 split or 0xC0000000 for a 3:1 split). On x86_64, modules are placed near the kernel text in a region around the kernel’s own load address.

/* Reading the module region boundaries in kernel code */
#include <linux/module.h>
#include <linux/kernel.h>

static int __init mymod_init(void)
{
    pr_info("Module loaded at: 0x%px\n", mymod_init);
    pr_info("MODULES_VADDR: 0x%lx\n", MODULES_VADDR);
    pr_info("MODULES_END:   0x%lx\n", MODULES_END);
    return 0;
}

static void __exit mymod_exit(void)
{
    pr_info("Module unloaded\n");
}

module_init(mymod_init);
module_exit(mymod_exit);
MODULE_LICENSE("GPL");
⚠ Architecture Dependency: The macros MODULES_VADDR and MODULES_END are architecture-specific. They are defined in the arch-specific headers. Always check the header for your target architecture.

4. fixmap Region

The fixmap region is a small but important area of the kernel VAS used for compile-time fixed virtual addresses. Unlike normal dynamic mappings, fixmap entries are defined at kernel compile time and their virtual addresses never change. This makes them useful for hardware registers or memory regions that must be accessible very early during boot before the normal memory allocator is ready.

For example, on ARM systems, the fixmap region contains the fixed mapping for the CPU’s local timer and interrupt controller registers so the kernel can access them before setting up the full virtual memory subsystem.

5. Vector Table (ARM-specific)

On 32-bit ARM processors, the exception vector table must reside at a specific address — either 0x00000000 (low vectors) or 0xFFFF0000 (high vectors). The high vector location (0xFFFF0000) sits at the very top of the virtual address space and falls inside the kernel segment. The kernel maps its exception handler jump table there so that when a CPU exception occurs, the processor jumps to the correct location automatically.

On AArch64 (ARM64), the vector table works differently — the VBAR_EL1 register points to the base of the exception vector table, and it can be placed anywhere that is aligned. The fixed 0xFFFF0000 address is no longer needed.

Visual Map: The Full Linux Kernel Segment (32-bit ARM, 3:1 Split)

Kernel Segment Memory Layout – 32-bit ARM (3:1 Split)
0xFFFF FFFF
Vector Table (4KB)
High vectors
0xFFC0 0000
fixmap Region (3 MB)
Fixed VA mappings
0xFF80 0000
vmalloc Region (~1088 MB)
Virtual alloc area
0xBF00 0000
Kernel Module Region (16 MB)
LKM load area
─────────── PAGE_OFFSET (start_kva) ───────────
0xC000 0000
lowmem – Direct-mapped Physical RAM
(Kernel code + data, Page tables, Stacks)
PA=VA−0xC0000000

Kernel VAS Layout on 64-bit x86_64

On 64-bit systems the picture is quite different because there is far more virtual address space available. The Linux kernel documentation file Documentation/x86/x86_64/mm.txt (or Documentation/x86/x86_64/mm.rst in newer kernels) lists the exact boundaries. Let us walk through the major regions from top to bottom.

Kernel Segment Layout – x86_64 (4-level paging, Kernel 6.x)
0xFFFF FFFF FFFF FFFF
Fixmap, vsyscall, EFI mappings
Top of kernel VAS
0xFFFF E000 0000 0000
vmalloc / ioremap area (~32 TB)
Dynamic kernel mappings
0xFFFF C000 0000 0000
Kernel modules (~1.5 GB)
LKM load area
0xFFFF BFFF FFFF FFFF
Kernel text mapping (kernel code, 1 GB)
Kernel .text section
0xFFFF 8880 0000 0000
Direct physical memory map (~64 TB)
physmap – all RAM mapped here
0xFFFF 8000 0000 0000
Guard / unused / kasan shadow
 

The direct physical memory map (physmap) on x86_64 is huge. It maps all of installed physical RAM into the kernel’s virtual address space at a fixed offset. On a machine with 64 GB of RAM, the physmap region uses 64 GB of virtual kernel address space, but since there are tens of terabytes available, this is trivial.

Finding Kernel Memory Layout Boundaries in Code

The Linux kernel exposes several macros and variables that let kernel module code query the current VAS layout. These are critical when writing portable kernel code that runs across architectures.

/* Useful kernel VAS boundary macros and variables */
#include <linux/mm.h>
#include <asm/pgtable.h>

/* PAGE_OFFSET: start of kernel segment (= start of lowmem on 32-bit) */
pr_info("PAGE_OFFSET   = 0x%lx\n", PAGE_OFFSET);

/* TASK_SIZE: top of user VAS (= start of kernel from user's perspective) */
pr_info("TASK_SIZE     = 0x%lx\n", TASK_SIZE);

/* high_memory: top of directly-mapped lowmem */
pr_info("high_memory   = 0x%px\n", high_memory);

/* On x86_64: VMALLOC_START / VMALLOC_END */
#ifdef CONFIG_X86_64
pr_info("VMALLOC_START = 0x%lx\n", VMALLOC_START);
pr_info("VMALLOC_END   = 0x%lx\n", VMALLOC_END);
#endif

You can also check the kernel’s own runtime output through /proc/iomem and, most usefully, through /proc/meminfo. For a visual picture of the full process VAS including both user and kernel regions, the /proc/<pid>/maps file is your best friend:

# See the kernel-mapped regions for any process
# The vvar and vsyscall regions are kernel mappings visible in user maps
cat /proc/self/maps | tail -10

# On x86_64 you will see:
# ffffffffff600000-ffffffffff601000 --xp vsyscall
# (this is a kernel region mapped into user space for fast syscalls)

Where to Find Official Kernel Memory Layout Documentation

The Linux kernel ships detailed memory layout documentation for each architecture. These files are maintained by the architecture maintainers and are kept up to date with each kernel release. They are the authoritative reference for exact address ranges.

Architecture Documentation File in Kernel Source
ARM32 Documentation/arm/memory.rst
AArch64 (ARM64) Documentation/arm64/memory.rst
x86_64 Documentation/x86/x86_64/mm.rst
RISC-V Documentation/riscv/vm-layout.rst
All architectures (online) https://www.kernel.org/doc/html/latest/
✅ Practical tip: On your development machine, run find /usr/src/linux-headers-$(uname -r)/Documentation -name "mm.rst" -o -name "memory.rst" 2>/dev/null to find the documentation files for your running kernel’s architecture.

How KASLR Affects Kernel VAS Layout

Kernel Address Space Layout Randomization (KASLR) was introduced as a security mitigation. When KASLR is enabled (which is the default in most distributions), the kernel is loaded at a random offset within its allowed region each boot. This makes it harder for an attacker to predict the exact virtual address of kernel code or data structures.

# Check if KASLR is active on your system
cat /proc/cmdline | grep -o nokaslr
# If nokaslr appears, KASLR is disabled

# The kernel's load address with KASLR can be seen in:
sudo cat /proc/kallsyms | grep " _text"
# Output varies each boot when KASLR is active

KASLR randomizes the base address of the kernel code region, the physmap base, the vmalloc region base, and the module region base. Each of these is randomized independently. The randomization happens within the allowed range for each region, so the regions do not overlap even after randomization. The randomization entropy is typically around 6 bits for the physmap on x86_64, giving 64 possible locations.

⚠ KASLR and Kernel Development: When writing kernel modules, never hard-code virtual addresses. Always use the exported symbols and macros. Hard-coded addresses will be wrong with KASLR enabled and may crash the system.

Common Mistakes When Working with Kernel VAS

Here are mistakes that embedded and kernel developers commonly make when first working with the Linux kernel virtual address space:

  • Assuming 32-bit layout on 64-bit systems: The boundary addresses are completely different. PAGE_OFFSET is 0xC0000000 on 32-bit ARM but 0xFFFF888000000000 on x86_64.
  • Using physical addresses directly in kernel code: You must always convert physical addresses to virtual before using them in C. Use phys_to_virt() and virt_to_phys().
  • Accessing user-space pointers directly in the kernel: Always use copy_from_user() and copy_to_user(). Directly dereferencing a user pointer in the kernel is a security bug and causes faults.
  • Ignoring KASLR: Never rely on fixed virtual addresses. Use kernel symbols.
  • Confusing lowmem and highmem on 32-bit: On 32-bit with limited kernel VAS, not all physical RAM is directly accessible from the kernel. highmem pages must be temporarily mapped with kmap() before use.

Summary and Key Takeaways

Key Takeaways from This Tutorial:
  • The Linux kernel virtual address space is split into a user segment (lower) and a kernel segment (upper).
  • The VM split ratio is configurable on 32-bit systems (3:1, 2:2, 1:3) and is fixed at 50:50 canonical halves on 64-bit.
  • The kernel segment contains: lowmem/physmap, vmalloc region, module region, fixmap region, and arch-specific regions like the ARM vector table.
  • On 64-bit systems, the kernel segment is enormous (~128 TB on x86_64) and easily maps all practical amounts of physical RAM directly.
  • KASLR randomizes the base addresses of kernel regions each boot for security. Never hard-code kernel virtual addresses.
  • The authoritative source for exact address ranges is the kernel documentation in Documentation/<arch>/memory.rst or mm.rst.

FAQ: Linux Kernel Virtual Address Space

Q: What is PAGE_OFFSET in the Linux kernel?

PAGE_OFFSET is the virtual address where the kernel segment begins. It is the boundary between user space and kernel space in the VAS. On 32-bit ARM with the 3:1 split it is 0xC0000000. On x86_64 it is architecture-defined and refers to the start of the direct physical memory map.

Q: Can a user-space process read kernel memory?

No. Kernel memory is mapped with kernel-only permissions. Any attempt by a user-space process to read or write kernel virtual addresses results in a segmentation fault (SIGSEGV). The CPU’s MMU enforces this by checking privilege level against page table entries.

Q: What is the difference between vmalloc and kmalloc?

kmalloc() allocates physically contiguous memory, which is required for DMA and certain hardware operations. vmalloc() allocates virtually contiguous but not necessarily physically contiguous memory, which is easier to satisfy for large allocations. kmalloc allocates from the lowmem/direct-map region; vmalloc uses the dedicated vmalloc region of the kernel VAS.

Q: Why does KASLR exist and how does it protect the kernel?

KASLR randomizes the load address of the kernel and its subsystems each boot. Without KASLR, an attacker who knows the kernel version also knows the exact virtual address of every kernel function and data structure. With KASLR, they must first leak the randomized base address, which is harder. KASLR is a defense-in-depth measure, not a complete solution by itself.

Q: What is the non-canonical region on x86_64?

On x86_64 with 4-level paging, valid virtual addresses must have bits 48-63 all equal to bit 47. Addresses that violate this are called non-canonical. The non-canonical region between user and kernel canonical halves is a massive hole (~16,384 PB) that no software can access. Any access causes an immediate general protection fault.

Q: How do I find the kernel segment layout on my running system?

You can use /proc/iomem for physical memory layout, /proc/meminfo for memory statistics, and for kernel VAS boundaries you can write a small kernel module that prints the values of PAGE_OFFSET, VMALLOC_START, MODULES_VADDR, and similar macros using pr_info().

Q: Does the kernel segment layout change between kernel versions?

Yes, it can change. Major changes have included the introduction of KASLR (3.14+), 5-level paging support (5.5+), KPTI for Meltdown mitigation (4.15+), and ongoing refinements to region sizes. Always refer to the documentation in the kernel source tree for the version you are targeting.

Outbound References

Continue Your Free Linux Kernel Journey

EmbeddedPathashala offers a completely free Linux kernel development course, Linux device drivers course, and embedded systems course. No fees, no paywalls.

Browse All Courses

Leave a Reply

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