No paid course needed
Step-by-step explanation
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:
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(), andalloc_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.
given to caller
free buddy
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.
PFN = N
PFN = N+1
PFN = N+2
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
folioabstraction (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
%pKspecifier hashes pointers shown in logs unless you have elevated privileges, a security hardening step worth remembering when debugging.
Real-World Use Cases
- 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_ZEROorget_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(), andalloc_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)
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.
Yes, as long as you understand basic C and have written a simple kernel module before. We explain every API in plain language.
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().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.
GFP_KERNEL inside an interrupt handler?No.
GFP_KERNEL may sleep. Use GFP_ATOMIC in interrupt or atomic contexts.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.The core buddy-system APIs remain stable, but folios, refined GFP flags, and stronger debugging tools (KASAN, KFENCE) have been added around them.
More free lectures on Linux kernel internals, device drivers, and embedded systems are available on EmbeddedPathashala.
