What Is the Linux Kernel Address Space Layout? – Free Linux Device Driver Course

Linux Kernel Segment & Virtual Address Space Layout
Free Linux Kernel Development Course — EmbeddedPathashala
Level
Intermediate
Topic
Kernel Memory
Course
Free Linux Kernel

What You Will Learn

In this free Linux kernel development course tutorial, you will understand exactly how the Linux kernel divides virtual address space between user space and kernel space. By the end, you will know:

  • What the Virtual Address Space (VAS) is and how it is split between user and kernel
  • What Kernel Virtual Addresses (KVAs) and kernel logical addresses mean and how they differ
  • How the kernel segment is structured and what regions live inside it
  • Why 32-bit and 64-bit systems behave very differently in terms of kernel memory layout
  • What the high memory problem on 32-bit systems is and how Linux solves it
  • How to write a kernel module (LKM) to inspect the kernel segment on your machine
  • Key kernel macros and variables that describe kernel segment regions
Prerequisites

Before this tutorial, you should be comfortable with:

Linux process memory layout basics What a kernel module (LKM) is Basic C programming How virtual vs physical memory differs What the MMU does

What Is the Linux Virtual Address Space?

Every process running on Linux lives in its own private bubble called the Virtual Address Space (VAS). The CPU, with help from the Memory Management Unit (MMU), translates these virtual addresses into real physical RAM addresses whenever a program reads or writes memory. The process itself never knows the physical address — it only ever sees virtual addresses.

The full range of possible virtual addresses is determined by how many bits the CPU uses for addressing. On a 32-bit CPU, you get 232 = 4 GB of virtual address space per process. On a 64-bit CPU (x86_64), the theoretical range is enormous, though in practice the Linux kernel uses 128 TB for user space and 128 TB for the kernel (with some address bits reserved).

This entire virtual address space is divided into two main zones:

Linux Virtual Address Space Split (32-bit vs 64-bit)
32-bit Linux (3:1 split)
0xFFFFFFFF
Kernel Segment
1 GB (KVAs)
0xC0000000 – 0xFFFFFFFF
User Space
3 GB
0x00000000 – 0xBFFFFFFF
0x00000000
64-bit Linux x86_64
0xFFFFFFFFFFFFFFFF
Kernel Segment
~128 TB
High canonical addresses
Non-canonical (unused hole)
User Space
~128 TB
Low canonical addresses
0x0000000000000000

The user space portion belongs to the running process — stack, heap, code, libraries, and so on all live here. The kernel segment is mapped into every process’s VAS but is only accessible when the CPU is running in privileged (kernel) mode. A user space program that tries to directly read a kernel address will receive a segmentation fault.

Kernel Logical Addresses vs Kernel Virtual Addresses (KVAs)

When you read kernel source code or kernel documentation, you will see two terms used for addresses inside the kernel segment: kernel logical addresses and kernel virtual addresses (KVAs). In practice they both refer to addresses in the kernel segment, but there is a subtle technical distinction worth understanding.

Kernel Logical Addresses

These are virtual addresses that have a fixed, predictable offset from their corresponding physical address. The offset is a constant called PAGE_OFFSET. Because the offset is fixed, you can convert between physical and virtual very cheaply: just add or subtract PAGE_OFFSET. This is the direct-mapped or lowmem region of the kernel segment.

Formula:

physical_address = kernel_logical_address - PAGE_OFFSET
kernel_logical_address = physical_address + PAGE_OFFSET
Kernel Virtual Addresses (KVAs)

These are also addresses in the kernel segment, but they do not have a fixed offset from their physical address. The mapping is done dynamically (via the page tables). The vmalloc region, ioremap region, and others use KVAs this way. You cannot simply subtract a constant to find the physical address.

In everyday usage, most kernel developers and documentation use KVA to refer to any address inside the kernel segment regardless of this distinction. The important takeaway is that the kernel segment is not one uniform region — it has sub-regions with different mapping characteristics.

What Lives Inside the Kernel Segment?

The kernel segment is divided into several distinct sub-regions. Not all of them exist on every CPU architecture, and their exact positions within the kernel segment depend on the target platform. Here are the major ones that are common across most architectures:

Linux Kernel Segment — Major Regions (Top to Bottom in VAS)
High KVA
Vector Table (ARM-32 only)
 
Fix Map Region (compile-time reserved)
 
vmalloc / ioremap Region
 
Kernel Modules Region (text + data)
 
Lowmem / Direct-Mapped Region
(Kernel image: text, data, BSS + RAM direct map)
PAGE_OFFSET
User Space begins here ↓

Let us look at each region in plain terms:

1. The Lowmem (Direct-Mapped) Region

This is the most important region for most kernel developers. Physical RAM is directly mapped into this region starting at PAGE_OFFSET. The kernel image itself (the compiled Linux kernel binary — its code, read-only data, mutable data, and BSS) lives here. The uncompressed kernel sits here along with all page tables, kernel stacks, and slab allocator objects.

On a 32-bit system with a 3:1 split, this region is only 1 GB. On a 64-bit system, it can be many terabytes, so there is no practical upper limit.

2. The vmalloc / ioremap Region

When kernel code calls vmalloc() to allocate large or virtually contiguous memory, or ioremap() to map hardware device registers into the kernel’s address space, the resulting addresses come from this region. Unlike lowmem, these pages are not physically contiguous, and the mapping is dynamic — set up on demand via the page tables.

3. The Kernel Modules Region

Loadable kernel modules (LKMs) — the .ko files you insert with insmod or modprobe — get their text (code) and data allocated from a dedicated region of the kernel segment. On 32-bit ARM, this region sits just above user space. On 64-bit x86, it sits higher up inside the kernel segment.

This dedicated placement matters because on some architectures, certain jump or call instructions have a limited reach (they cannot jump to an arbitrarily far address). Grouping kernel modules close together and close to the core kernel image avoids this range problem.

4. The Fix Map Region

Some kernel subsystems need virtual addresses that are decided at compile time rather than allocated dynamically at runtime. Examples include early page table setup, certain ioremap uses, and early console drivers. The fix map region reserves slots for these compile-time virtual addresses and then assigns physical pages to them at boot time.

5. The Vector Table (ARM-32 only)

On 32-bit ARM, the processor has a small table of function pointers at a well-known virtual address. When an exception occurs — an interrupt, a system call, a page fault, an MMU abort — the hardware automatically jumps to the appropriate entry in this table. The kernel sets this up during early boot. This concept does not exist in the same form on x86 (which uses IDT entries instead).

The High Memory Problem on 32-bit Linux

Understanding the high memory problem is crucial for anyone studying the free Linux kernel development course content that covers older or embedded 32-bit platforms. Even though 32-bit systems are becoming rare, the concepts reveal deep truths about how the kernel manages memory.

On a 32-bit Linux system using a 3:1 split, the kernel segment gets only 1 GB of virtual address space. Now imagine the machine has 2 GB of physical RAM. The kernel needs to be able to access all of this RAM, but 1 GB of kernel virtual space is not enough to directly map 2 GB of physical RAM at once. So what does it do?

High Memory Problem — 32-bit System with 2 GB RAM
Physical RAM
ZONE_HIGHMEM
RAM above 768 MB
~1.25 GB
Directly Mapped
0 – 768 MB
Total: 2 GB
↔
Kernel VAS (1 GB)
High Memory Window
Temporary mappings
of ZONE_HIGHMEM pages
vmalloc/ioremap
Lowmem
Direct map of first 768 MB
Only 1 GB available
⚠ Problem: 1 GB kernel VAS cannot directly map 2 GB of physical RAM

Linux’s solution on 32-bit systems is to divide physical RAM into two zones:

ZONE_NORMAL (lowmem)

The first chunk of physical RAM (typically up to 768 MB on IA-32 systems) is permanently and directly mapped into the kernel’s lowmem region. The kernel can always access this RAM instantly by subtracting PAGE_OFFSET. This RAM is considered the most valuable and is preferred for kernel data structures, DMA, and other critical uses.

ZONE_HIGHMEM (highmem)

The remaining physical RAM beyond the direct-map limit falls into ZONE_HIGHMEM. The kernel cannot permanently map this RAM because there is no virtual address space left. Instead, it sets aside a small window of virtual addresses inside the kernel segment (the high memory window) and creates temporary virtual-to-physical mappings when it needs to access highmem pages. After use, the mapping is torn down. This temporary mapping mechanism is called kmap().

This is a genuine performance and complexity cost. Accessing highmem requires setting up and tearing down page table entries for every access, which is slower than the direct map used for lowmem. This is why having more than ~768 MB of RAM on a 32-bit Linux system does not give you a fully proportional performance boost — a significant portion of that RAM is in highmem and costs more to use.

Why 64-bit Systems Do Not Have This Problem

On a 64-bit x86_64 system, the kernel segment is 128 TB in size. As of 2025, no system in the world has anywhere near 128 TB of RAM. Every byte of physical RAM can be permanently direct-mapped into the kernel segment with plenty of virtual space to spare. ZONE_HIGHMEM simply does not exist on 64-bit Linux. The kmap() functions still exist in the codebase for compatibility, but on 64-bit they are trivially implemented as no-ops (they just return the already-mapped address).

Viewing the Kernel Segment Layout at Boot

Modern Linux kernels deliberately hide the exact kernel virtual addresses from the kernel log for security reasons. A technique called Kernel Address Space Layout Randomization (KASLR) randomizes where the kernel is placed in memory at each boot. Additionally, printk format specifiers for pointers will show a hashed value rather than the real address unless you explicitly disable this protection.

You can temporarily disable the KASLR pointer hashing to see real addresses during development on a test machine by passing the no_hash_pointers parameter to the kernel at boot:

// Add to kernel boot parameters in /etc/default/grub:
GRUB_CMDLINE_LINUX="no_hash_pointers"

// Then update grub and reboot:
sudo update-grub
sudo reboot
⚠ Important Security Note

Never disable KASLR or pointer hashing on a production system. These protections make it much harder for attackers to exploit kernel vulnerabilities. Use these options only on isolated development VMs or hardware that is not connected to untrusted networks.

In the next tutorial, we will write an actual Linux kernel module that reads the kernel macros and variables describing these regions and prints them to the kernel log using printk(). This gives you a hands-on way to explore your specific kernel’s memory layout.

Key Takeaways

  • Every Linux process has a Virtual Address Space (VAS) split between user space and the kernel segment.
  • On 32-bit systems, the typical split is 3 GB user / 1 GB kernel. On 64-bit (x86_64), both user and kernel get roughly 128 TB.
  • Kernel logical addresses in the lowmem region have a fixed offset (PAGE_OFFSET) from physical addresses, enabling cheap direct mapping.
  • KVAs in regions like vmalloc/ioremap have dynamic, non-fixed physical mappings managed through page tables.
  • The kernel segment contains multiple sub-regions: lowmem, vmalloc/ioremap, kernel modules, fix map, and (on ARM-32) the vector table.
  • On 32-bit systems, RAM exceeding the direct-map limit falls into ZONE_HIGHMEM and requires temporary mappings via kmap().
  • On 64-bit Linux, the highmem problem is completely solved by the massive kernel segment size. ZONE_HIGHMEM does not apply.
  • Modern kernels use KASLR to randomize kernel addresses at boot, which is why the kernel log shows hashed pointer values by default.

Frequently Asked Questions

Q: Why does the Linux kernel map itself into every process’s VAS?

When a user program makes a system call, the CPU switches from user mode to kernel mode. At that point the kernel needs its code and data to be immediately accessible without switching page tables. Mapping the kernel segment into every process’s VAS means the page tables do not need to change on a system call, which is faster. Only the privilege level changes, not the mapping.

Q: Can a user space program accidentally read kernel memory?

No. The page table entries for the kernel segment have a supervisor bit set (called the User/Supervisor bit in x86 terms). The CPU hardware enforces that these pages can only be read when running in Ring 0 (kernel mode). Any attempt from user space (Ring 3) to access a kernel address triggers a page fault, and the kernel kills the offending process with SIGSEGV.

Q: What is PAGE_OFFSET and where is it defined?

PAGE_OFFSET is a kernel macro that gives the starting virtual address of the kernel segment. On 32-bit x86 with a 3:1 split it is 0xC0000000. On 64-bit x86_64 it is defined in architecture-specific headers. You can find it in arch/x86/include/asm/page_types.h and related files in the kernel source tree.

Q: Is the 3:1 split the only option on 32-bit Linux?

No. The kernel can be compiled with a 2:2 split (2 GB user / 2 GB kernel) or even a 1:3 split on some configurations. The tradeoff is between how much virtual space user programs can use versus how much RAM the kernel can direct-map. The 3:1 split is the historical default because most processes do not need more than 3 GB of virtual space and having more lowmem improves kernel performance.

Q: Does KASLR affect the relative positions of kernel segment regions?

KASLR randomizes the base address of the kernel image at each boot, but the relative layout of regions (vmalloc, modules, lowmem, etc.) within the kernel segment remains determined at compile time by the architecture and kernel configuration. So the offsets between regions stay the same; only the absolute starting address changes each boot.

Q: Why do kernel modules get their own region rather than loading into lowmem?

The modules region is architecturally separate for two reasons. First, on 32-bit ARM, the modules region is placed just above user space so that short-range branch instructions (which have a limited reach of ±32 MB) from module code can still reach the kernel core and vice versa. Second, placing modules in a dedicated region allows the kernel to apply stricter memory protections (e.g., marking module text as executable but not writable) independently from the rest of the kernel segment.

Q: What happens to ZONE_HIGHMEM pages after kmap() is done?

After the kernel finishes accessing a highmem page, it must call kunmap() to release the temporary virtual mapping. If it forgets, the mapping slots in the high memory window get exhausted, which causes the system to deadlock waiting for a free slot. This is a common source of bugs in older kernel drivers that handle highmem pages. Modern kernel code on 64-bit systems avoids this entirely since highmem does not exist.

Q: How can I check the kernel segment layout on my own 64-bit Linux machine right now?

You can read /proc/iomem for physical memory regions and /proc/vmallocinfo for vmalloc allocations. For the overall virtual memory layout, look at /proc/1/maps (for PID 1’s VAS which includes kernel mappings) or check the kernel documentation file at Documentation/x86/x86_64/mm.rst in the kernel source tree for a definitive reference on x86_64 memory layout.

cat /proc/vmallocinfo | head -20
cat /proc/iomem

Conclusion

Understanding the Linux kernel segment and virtual address space layout is one of the foundational skills for anyone taking a free Linux kernel development course or working on Linux device drivers. The split between user space and kernel space, the direct-mapped lowmem region, the vmalloc and modules regions, and the historical high memory problem on 32-bit systems all fit together into a coherent picture once you see the constraints the designers were working within.

In the next lecture, we will get hands-on and write a kernel module that uses the key kernel macros — PAGE_OFFSET, FIXADDR_START, MODULES_VADDR, and others — to inspect and print the layout of these regions on your actual running system. That practical exercise will solidify everything covered here.

Learn Linux Kernel Programming for Free

EmbeddedPathashala offers completely free courses on Linux kernel development, Linux device drivers, and embedded systems.

Visit EmbeddedPathashala

Leave a Reply

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