Linux Kernel kmap Function Guide-Free Linux Device Drivers Tutorial

« PREV_LEC  |  NEXT_LEC »

Linux Kernel kmap Function Guide

Free Linux Kernel Development Course — Kernel Memory Management, Lecture 15

Lecture 15 of 20+
Kernel 6.x Verified
Beginner Friendly

Keywords Covered In This Free Linux Kernel Development Course Lecture

linux kmap function
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() and kunmap() create a temporary mapping
  • Why kmap() is deprecated and how kmap_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

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.

Temporary High Memory Mapping Flow
struct page *page
physical page, no kernel VA yet
kmap_local_page(page)
creates a thread-local kernel VA
read / write via pointer
normal memory access
kunmap_local(vaddr)
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_HIGHMEM enabled, 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 temporarily
  • kmap_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().

« PREV_LEC  |  NEXT_LEC »

Leave a Reply

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