How Does Linux Allocate Physical Pages? – Linux Device Drivers Course Online

Linux Kernel Page Allocator Explained (Free Linux Kernel Development Course)
Learn how the kernel’s low-level page allocator (buddy system) hands out physical memory — with simple language, original diagrams, and modern kernel notes.
100% Free
No paid course needed
Beginner Friendly
Step-by-step explanation
Hands-On
Original code samples

If you are searching for a free Linux kernel development course, this lecture is a great place to start understanding physical memory management. This is part of EmbeddedPathashala’s free Linux device drivers course and free embedded systems course series, where we explain core Linux kernel internals in plain language — no prior memory-management background required.

In this lecture you will learn how the kernel’s page allocator (often called the “buddy system allocator” or BSA) works, which kernel APIs are used to request physical pages, and how modern kernels (6.x series and later) have refined this subsystem.

Topics covered in this free Linux kernel development course lecture:

Page Allocator
Buddy System
GFP Flags
Page Frame Number
Physical Contiguity
Kernel Memory Allocation

What You Will Learn

  • What the page allocator does and why kernel code uses it
  • The difference between __get_free_page(), __get_free_pages(), get_zeroed_page(), alloc_page(), and alloc_pages()
  • How GFP (Get Free Page) flags control allocation behaviour
  • How to verify physical contiguity using page frame numbers (PFN)
  • How the page allocator has evolved in modern (6.x+) kernels, including folios
  • Common mistakes and best practices when allocating kernel memory

Prerequisites

Before this lecture in our free Linux kernel development course, you should be comfortable with:

  • Writing and loading a basic Linux Kernel Module (LKM)
  • Basic C programming (pointers, structs)
  • Using a Linux VM or a spare machine for safe kernel testing

If you haven’t covered LKM basics yet, please check the previous lecture in this free Linux device drivers course series (link above).

What Is the Linux Kernel Page Allocator?

The Linux kernel manages physical RAM in fixed-size chunks called pages (typically 4 KB on most architectures). Whenever kernel code — like a device driver — needs raw physical memory, it does not ask for “bytes” the way user-space malloc() does. Instead, it asks the page allocator for whole pages.

The page allocator is built on an algorithm called the buddy system. It keeps free pages organized into power-of-two sized blocks (1, 2, 4, 8, 16 pages and so on). When you request memory, the buddy system finds the smallest available block that satisfies your request, splitting larger blocks if needed, and merging (“buddying”) blocks back together when memory is freed. This design keeps allocation fast and helps reduce external fragmentation.

How the Buddy System Splits a Block
8-page free block
↓ request for 2 pages arrives ↓
2 pages
given to caller
2 pages
free buddy
4 pages
free buddy

Page Allocator APIs You Should Know

The kernel exposes several wrapper functions around the buddy system. Each one is suited to a slightly different use case.

API Purpose Returns
__get_free_page(gfp_mask) Allocate exactly one page Kernel virtual (logical) address
__get_free_pages(gfp_mask, order) Allocate 2^order contiguous pages Kernel virtual (logical) address
get_zeroed_page(gfp_mask) Allocate one page, pre-zeroed Kernel virtual (logical) address
alloc_page(gfp_mask) Allocate one page, get the descriptor struct page *
alloc_pages(gfp_mask, order) Allocate 2^order pages, get the descriptor struct page *

A point that trips up many newcomers: alloc_page() and alloc_pages() do not hand you a usable pointer directly — they return a pointer to a struct page, the kernel’s bookkeeping structure describing that physical page. To get a usable kernel virtual address, you must convert it using page_address().

A Simple, Original Example Module

Below is a small, original demo (not copied from any book) showing how these APIs are typically combined inside an LKM’s init function. Replace my_module_name with your own module’s tag for log messages.

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/gfp.h>

#define LOGTAG "pagealloc_demo"

static void *single_page;
static void *multi_page_block;
static struct page *page_descriptor;

static int __init pagealloc_demo_init(void)
{
    /* Request a single page */
    single_page = (void *)__get_free_page(GFP_KERNEL);
    if (!single_page)
        return -ENOMEM;
    pr_info("%s: single page allocated at %pK\n", LOGTAG, single_page);

    /* Request 4 contiguous pages (order = 2 -> 2^2 = 4) */
    multi_page_block = (void *)__get_free_pages(GFP_KERNEL | __GFP_ZERO, 2);
    if (!multi_page_block) {
        free_page((unsigned long)single_page);
        return -ENOMEM;
    }
    pr_info("%s: 4-page block allocated at %pK\n", LOGTAG, multi_page_block);

    /* Request one page via the descriptor-based API */
    page_descriptor = alloc_page(GFP_KERNEL);
    if (page_descriptor)
        pr_info("%s: page_address() -> %pK\n", LOGTAG, page_address(page_descriptor));

    return 0;
}

static void __exit pagealloc_demo_exit(void)
{
    if (single_page)
        free_page((unsigned long)single_page);
    if (multi_page_block)
        free_pages((unsigned long)multi_page_block, 2);
    if (page_descriptor)
        __free_page(page_descriptor);
    pr_info("%s: module unloaded, all pages freed\n", LOGTAG);
}

module_init(pagealloc_demo_init);
module_exit(pagealloc_demo_exit);
MODULE_LICENSE("GPL");

Important: always free what you allocate, and always free it in the reverse order of allocation in your cleanup path. Forgetting this is one of the most common sources of kernel memory leaks in custom drivers.

Understanding GFP Flags in This Free Linux Kernel Course

Every page allocator call takes a GFP (Get Free Page) mask that tells the kernel how aggressively, and in what context, it is allowed to search for free memory.

Flag Meaning
GFP_KERNEL Normal allocation; the call may sleep while waiting for memory. Safe for most driver code running in process context.
GFP_ATOMIC Allocation must not sleep — used inside interrupt handlers or while holding a spinlock.
__GFP_ZERO Combine with another flag to get zero-filled pages, useful for security and predictability.
GFP_DMA Restrict allocation to memory usable by older DMA-limited hardware.

Checking Physical Contiguity with Page Frame Numbers

A multi-page block returned by __get_free_pages() or alloc_pages() is guaranteed to be physically contiguous, not just virtually contiguous. You can prove this yourself by converting each page’s kernel virtual address to a physical address with virt_to_phys(), then dividing by the page size to get the Page Frame Number (PFN). Consecutive pages in a contiguous block will have consecutive PFNs.

Physical Contiguity via Page Frame Numbers
Page 0
PFN = N
Page 1
PFN = N+1
Page 2
PFN = N+2
Page 3
PFN = N+3

Consecutive PFNs confirm the block is physically contiguous.

unsigned long pfn;
phys_addr_t phys;

phys = virt_to_phys((void *)kernel_vaddr);
pfn  = PHYS_PFN(phys);
pr_info("vaddr=%pK phys=%pa pfn=%lu\n", (void *)kernel_vaddr, &phys, pfn);

Caution: virt_to_phys() only works correctly for direct-mapped (“lowmem”) kernel memory — the kind returned by the page allocator. It does not work for vmalloc() memory, I/O remapped memory, or highmem on 32-bit systems. Treating it as a general-purpose conversion is a common beginner mistake.

Page Allocator Changes in Modern Kernels (6.x and Later)

Since this free Linux kernel development course aims to stay current, it is worth knowing how the page allocator has evolved beyond older textbooks:

  • Folios: Newer kernels increasingly use the folio abstraction (a wrapper around one or more physically contiguous pages) for page-cache and file-backed memory, reducing bugs related to compound pages. Driver writers allocating raw memory still mostly use the classic APIs shown above.
  • GFP flag cleanups: Several legacy flags from older kernels have been removed or merged over time as the reclaim logic was simplified; always check your kernel’s <linux/gfp.h> rather than relying on old code samples.
  • Stronger debug tooling: Tools like KASAN, KFENCE, and page_owner tracking make it much easier to catch use-after-free and leak bugs in allocator-based code than in older kernel versions.
  • Address hashing by default: The %pK specifier hashes pointers shown in logs unless you have elevated privileges, a security hardening step worth remembering when debugging.

Real-World Use Cases

Where the Page Allocator Matters in Practice
  • DMA buffer setup in network and storage drivers
  • Building custom memory pools for high-performance subsystems
  • Backing memory for kernel-level ring buffers
  • Low-level building block beneath kmalloc() itself

Common Mistakes and Troubleshooting

Mistake Fix
Using GFP_KERNEL inside an interrupt handler Use GFP_ATOMIC instead — never sleep in atomic context
Forgetting the allocation order on free Always pass the same order to free_pages() that you used in __get_free_pages()
Using virt_to_phys() on vmalloc() memory Use vmalloc_to_pfn() / vmalloc_to_page() instead
Treating large multi-page requests as always reliable Higher orders can fail under fragmentation; check return values and have a fallback

Best Practices

  • Prefer the smallest order that satisfies your need — large contiguous requests are more likely to fail.
  • Zero memory with __GFP_ZERO or get_zeroed_page() when the content matters for security or correctness.
  • Avoid working with physical addresses unless you truly need DMA or low-level translation — most drivers never should.
  • Always check the return value of every allocation API; never assume success.
  • Pair every allocation with a matching free call in your module’s cleanup path.

Security Considerations

Avoid printing raw kernel virtual or physical addresses with unrestricted format specifiers in production builds; use %pK so addresses are hashed for unprivileged users. Also avoid leaving allocated kernel pages un-zeroed if they will ever be exposed to user space or hardware, since stale data could leak previous kernel memory contents.

Performance Considerations

Requesting very large contiguous blocks (high order values) is more expensive and more likely to fail as system uptime increases and memory fragments. Where possible, prefer multiple smaller allocations or, for very large buffers, look into vmalloc() (virtually contiguous, physically scattered) as an alternative when physical contiguity is not required.

Summary / Key Takeaways

  • The page allocator is the kernel’s lowest-level memory allocator, based on the buddy system.
  • __get_free_page(s), get_zeroed_page(), alloc_page(), and alloc_pages() are the main entry points.
  • GFP flags control sleeping behaviour, zeroing, and DMA suitability.
  • PFNs let you verify physical contiguity, but physical-address tricks should stay rare in driver code.
  • Modern kernels add folios and stronger debug tooling on top of the same core allocator.

Conclusion

Understanding the page allocator is a foundational step in any serious free Linux kernel development course or free Linux device drivers course journey. Once you are comfortable with these APIs, the behaviour of higher-level allocators like kmalloc() and vmalloc() will make a lot more sense, since they all ultimately rest on the same buddy-system foundation. Keep practicing with small, original kernel modules of your own, and move on to the next lecture in this free embedded systems course series when you’re ready.

Frequently Asked Questions (FAQ)

Q1. What is the Linux kernel page allocator?
It is the low-level subsystem (based on the buddy algorithm) that hands out physical memory in page-sized or power-of-two-page blocks to the rest of the kernel.
Q2. Is this free Linux kernel development course suitable for absolute beginners?
Yes, as long as you understand basic C and have written a simple kernel module before. We explain every API in plain language.
Q3. What’s the difference between alloc_page() and __get_free_page()?
__get_free_page() returns a usable kernel virtual address directly. alloc_page() returns a struct page * descriptor, which you must convert with page_address().
Q4. Why does my allocation fail with a high order value?
Higher-order (larger) contiguous requests are harder to satisfy as physical memory becomes fragmented over time. Always check return values and prefer smaller orders when possible.
Q5. Can I use GFP_KERNEL inside an interrupt handler?
No. GFP_KERNEL may sleep. Use GFP_ATOMIC in interrupt or atomic contexts.
Q6. Does virt_to_phys() work on all kernel memory?
No, it only works reliably on direct-mapped (lowmem) memory returned by the page allocator, not on vmalloc() regions.
Q7. What changed in modern (6.x+) kernels regarding page allocation?
The core buddy-system APIs remain stable, but folios, refined GFP flags, and stronger debugging tools (KASAN, KFENCE) have been added around them.

Continue Your Free Linux Kernel Development Course

More free lectures on Linux kernel internals, device drivers, and embedded systems are available on EmbeddedPathashala.

Visit EmbeddedPathashala

Leave a Reply

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