Linux Kernel kmap Function Guide
Free Linux Kernel Development Course — Kernel Memory Management, Lecture 15
Keywords Covered In This Free Linux Kernel Development Course Lecture
free linux kernel development course
free linux device drivers course
free embedded systems course
free embedded linux course
The linux kmap function is how a Linux kernel module reaches a high memory page that has no permanent kernel address of its own. If you have ever wondered why a driver cannot just dereference a struct page * pointer directly, this free Linux kernel development course lecture explains it with a working driver, an inline diagram, and the current kernel 6.x replacement API.
What You Will Learn
- Why high memory pages do not have a ready-made kernel virtual address
- How
kmap()andkunmap()create a temporary mapping - Why
kmap()is deprecated and howkmap_local_page()/kunmap_local()replace it in kernel 6.x - How to write and test an original kernel module that maps a high memory page
- Common mistakes, performance costs, and security notes around temporary mappings
Prerequisites
- Completion of the earlier lecture on low memory vs high memory and kernel zones
- Comfort writing and loading a basic loadable kernel module
- A Linux VM or board running kernel 6.x with headers installed
Why The Kernel Cannot Just Touch Every Page
Earlier in this free linux kernel development course we saw that on 32-bit systems the kernel only gets a fixed slice of virtual address space (commonly around 1 GB) to map all of physical RAM permanently. When RAM is larger than that slice, the extra pages — called high memory — simply do not get a standing kernel virtual address. On modern 64-bit x86_64 and ARM64 systems this problem mostly disappears because the virtual address space is huge, but the same API is still used for pages that are deliberately left unmapped, such as pages sitting in a page cache or highmem-capable allocation, and the API remains part of every kernel driver developer’s toolkit.
So the kernel needs a way to say: “give me a temporary address for this one page, let me read or write it, and then take the address back.” That is exactly what the kmap family of functions does.
physical page, no kernel VA yet
creates a thread-local kernel VA
normal memory access
mapping released immediately
The Classic kmap() And kunmap() Pair
The traditional API, still readable in older drivers, looks like this:
void *kmap(struct page *page);
void kunmap(struct page *page);
kmap() is smart about low memory pages: if the page already lives in low memory it simply returns the existing permanent address through page_address(), with no extra mapping work. Only when the page is genuinely high memory does it borrow a slot from a small, globally shared pool of kernel page table entries reserved for this purpose. Because that pool is shared across the whole system and protected by a lock, two problems show up in practice:
- The pool can run out under heavy pressure, and a caller may block waiting for a free slot.
- Because the mapping is visible kernel-wide, unmapping one page can force a full TLB flush, which is expensive on every CPU.
The rule that comes with the classic API is simple and strict: every kmap() must be paired with a kunmap() on the same page as soon as the access is done. Holding a kmap mapping longer than necessary starves other callers.
Modernization Note: kmap() Is Deprecated
The current kernel documentation and mainline commit history are explicit about this: kmap() and kmap_atomic() are deprecated in favor of kmap_local_page() paired with kunmap_local(). Hundreds of in-tree drivers and filesystems (gfs2, ecryptfs, fs/aio, drm/i915, and more) have already been converted upstream. The reasons the kernel documentation gives are worth knowing:
- Local mappings are per-thread and per-CPU rather than global, so there is no shared lock and no system-wide TLB flush on unmap.
- They can be taken from almost any context, including interrupts, without the special “atomic” restrictions that
kmap_atomic()used to impose. - They are faster on kernels built with
CONFIG_HIGHMEMenabled, and behave the same whether or not high memory exists on the system.
The new pair has the same shape as the old one, just renamed:
void *kmap_local_page(struct page *page);
void kunmap_local(const void *vaddr);
Note: a straight find-and-replace from kmap()/kunmap() to kmap_local_page()/kunmap_local() is not always safe. Local mappings follow a strict stack (last mapped, first unmapped) order within a thread, and the returned address must not be handed off to a different thread. Keep the mapping short-lived and local to the function that created it.
kmap vs kmap_atomic vs kmap_local_page
| Function | Scope | Context | Status in kernel 6.x |
|---|---|---|---|
kmap() |
Global, shared pool | Process context, can sleep | Deprecated |
kmap_atomic() |
Per-CPU, disables preemption | Any context, cannot sleep | Deprecated |
kmap_local_page() |
Per-thread, per-CPU | Almost any context, can take page faults | Current, recommended |
Original Driver: ep_kmap_demo
This original module allocates one page that is explicitly requested from the high memory zone whenever the platform has one, maps it with kmap_local_page(), writes a message into it, reads the message back, and then unmaps it — all logged to dmesg so you can see each step happen.
// ep_kmap_demo.c
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/gfp.h>
#include <linux/highmem.h>
#include <linux/mm.h>
#include <linux/string.h>
static struct page *ep_page;
static int __init ep_kmap_demo_init(void)
{
char *vaddr;
const char *msg = "hello from ep_kmap_demo";
/* GFP_HIGHUSER allows the allocator to hand us a high memory
* page on platforms that have one; on 64-bit systems it will
* usually still come from normal memory, and that is fine. */
ep_page = alloc_page(GFP_HIGHUSER);
if (!ep_page) {
pr_err("ep_kmap_demo: alloc_page failed\n");
return -ENOMEM;
}
vaddr = kmap_local_page(ep_page);
strscpy(vaddr, msg, PAGE_SIZE);
pr_info("ep_kmap_demo: wrote -> %s\n", msg);
pr_info("ep_kmap_demo: mapped at kernel VA %p\n", vaddr);
pr_info("ep_kmap_demo: read back -> %s\n", vaddr);
kunmap_local(vaddr);
pr_info("ep_kmap_demo: mapping released, module loaded\n");
return 0;
}
static void __exit ep_kmap_demo_exit(void)
{
if (ep_page)
__free_page(ep_page);
pr_info("ep_kmap_demo: page freed, module unloaded\n");
}
module_init(ep_kmap_demo_init);
module_exit(ep_kmap_demo_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("EmbeddedPathashala");
MODULE_DESCRIPTION("kmap_local_page demo for free linux kernel development course");
Build And Run Steps
# Makefile
obj-m += ep_kmap_demo.o
all:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
$ make
$ sudo insmod ep_kmap_demo.ko
$ dmesg | tail -6
$ sudo rmmod ep_kmap_demo
Expected dmesg Output
[ 1234.001122] ep_kmap_demo: wrote -> hello from ep_kmap_demo
[ 1234.001125] ep_kmap_demo: mapped at kernel VA 000000004f3a9c21
[ 1234.001127] ep_kmap_demo: read back -> hello from ep_kmap_demo
[ 1234.001130] ep_kmap_demo: mapping released, module loaded
[ 1240.552211] ep_kmap_demo: page freed, module unloaded
Note: the printed address is a hashed/obfuscated pointer (kernel pointer printing masks real addresses with %p by default) — that is expected and is a security feature, not a bug.
Real-World Use Cases
- Filesystems copying data between a page cache page and a kernel buffer
- Block and network drivers that need to touch scatter-gather pages briefly
- Graphics drivers reading back framebuffer pages for a short operation
Common Mistakes
| Mistake | Why It Hurts |
|---|---|
| Forgetting to unmap | Leaks a mapping slot; on the old API this can exhaust the shared pool |
| Unmapping out of order | Local mappings are stack-based; unmap in reverse of map order |
| Passing the mapped address to another thread | Local mappings are only valid on the mapping thread/CPU |
| Holding the mapping across a sleep | Increases contention and defeats the purpose of a short-lived mapping |
Best Practices
- Prefer
kmap_local_page()/kunmap_local()in all new kernel 6.x code - Keep the mapped region and the code between map and unmap as short as possible
- Use helper wrappers like
memcpy_from_page()/memcpy_to_page()when you only need a copy, since they handle the mapping internally
Performance And Security Considerations
Because local mappings avoid a global lock and a system-wide TLB flush, they scale far better on multi-core systems than the old kmap(). From a security angle, temporary mappings also reduce the window during which a kernel address is valid, which is generally good practice — map late, unmap early.
Summary
- High memory pages have no permanent kernel virtual address
kmap()/kunmap()is the classic, now-deprecated way to borrow one temporarilykmap_local_page()/kunmap_local()is the current kernel 6.x replacement, and is faster and safer- Always unmap promptly and in the correct order
Conclusion
The linux kmap function is a small API with an outsized effect on driver correctness and performance. Understanding why it exists — and using the modern kmap_local_page() form — keeps your kernel 6.x drivers correct on both constrained 32-bit boards and today’s large-memory 64-bit systems. In the next lecture of this free linux kernel development course we move from mapping kernel memory for kernel use to mapping it out to user space with remap_pfn_range().
FAQ
What is the linux kmap function used for?
It creates a temporary kernel virtual address for a physical page, most importantly for high memory pages that have no standing kernel address.
Is kmap() still available in kernel 6.x?
Yes, it still compiles, but it is marked deprecated. New code should use kmap_local_page() instead.
Do 64-bit systems need kmap at all?
Rarely for high memory itself, since 64-bit address space is huge, but the same API is still used any time a page is deliberately left unmapped, such as certain page cache pages.
What replaces kmap_atomic() in modern kernels?
kmap_local_page() replaces both kmap() and kmap_atomic().
Can kmap_local_page() be used inside an interrupt handler?
Yes, unlike the old kmap(), local mappings can be acquired from most contexts including interrupts.
What happens if I forget to call kunmap_local()?
The mapping slot is never released, which wastes resources and can cause warnings or exhaustion under repeated use.
Can I share a kmap_local_page() address with another CPU or thread?
No. Local mappings are only valid on the thread and CPU that created them.
Why does the printed kernel address look scrambled in dmesg?
The kernel hashes pointers printed with %p by default as a security measure against address leaks.
Continue The Free Linux Kernel Development Course
Next up: mapping kernel and device memory into user space with remap_pfn_range().
