Linux Kernel Mmap Device Memory
Free Linux Kernel Development Course — Kernel Memory Management, Lecture 16
Keywords Covered In This Free Linux Kernel Development Course Lecture
remap_pfn_range
free linux device drivers course
free linux kernel development course
free embedded systems course
Every time an application memory-maps a device file instead of calling read()/write() in a loop, a kernel driver is doing linux kernel mmap device memory work behind the scenes. This free linux kernel development course lecture walks through remap_pfn_range(), its I/O-memory sibling io_remap_pfn_range(), and the file_operations.mmap hook that ties them together — with an original driver you can build and test today.
What You Will Learn
- Why mapping memory into user space beats copying it with read/write
- How
remap_pfn_range()installs page table entries into a process’s address space - The difference between
remap_pfn_range()andio_remap_pfn_range() - How the
file_operations.mmapcallback fits into themmap(2)system call path - How to write, build, and test an original character device driver that supports
mmap()
Prerequisites
- Completion of the previous lecture on the linux kmap function
- Basic character device driver experience (open/read/write/release)
- A Linux VM or board running kernel 6.x with headers installed
Why Map Memory Instead Of Copying It
A normal read() or write() call on a device file crosses the user/kernel boundary and copies data byte-for-byte between a user buffer and a kernel buffer. That copy costs CPU time and, for large or frequently accessed regions, adds up quickly. Memory mapping takes a different approach: instead of copying, the kernel updates the process’s own page table entries so that a range of the process’s virtual address space points straight at the underlying physical memory. After that, the application reads and writes through ordinary pointer dereferences, with no further kernel involvement and no per-access copy.
virtual address range from mmap()
installed by remap_pfn_range()
no per-access copy needed
The mmap(2) System Call And file_operations.mmap
When user space calls mmap() on an open device file descriptor, the kernel eventually invokes the driver’s own mmap hook from struct file_operations:
int (*mmap) (struct file *filp, struct vm_area_struct *vma);
The kernel has already done most of the bookkeeping by this point: it picked a virtual address range, built a fresh struct vm_area_struct describing that range, and filled in fields such as vma->vm_start, vma->vm_end, vma->vm_pgoff (the offset into the mapped object, in pages) and vma->vm_page_prot (the requested page protection bits). The driver’s job inside mmap is simply to connect that VMA to the correct physical pages.
| VMA Field | Meaning |
|---|---|
vm_start / vm_end |
Start and end of the mapped virtual range, always page-aligned |
vm_pgoff |
Offset into the mapped object, expressed in pages |
vm_page_prot |
Base protection bits the driver ORs its own flags into |
vm_flags |
Mapping behavior flags such as VM_IO, VM_DONTCOPY, VM_DONTEXPAND, VM_DONTDUMP |
remap_pfn_range(): Mapping RAM Into User Space
int remap_pfn_range(struct vm_area_struct *vma, unsigned long addr,
unsigned long pfn, unsigned long size,
pgprot_t prot);
| Parameter | Meaning |
|---|---|
vma |
The VMA handed to the driver’s mmap() callback |
addr |
User virtual address where the mapping should start, normally vma->vm_start |
pfn |
Page frame number of the physical memory to map (physical address right-shifted by PAGE_SHIFT) |
size |
Size of the region in bytes |
prot |
Protection flags, normally derived from vma->vm_page_prot |
It returns 0 on success and a negative error code on failure. Most drivers call it exactly once, from inside their mmap callback, covering the entire requested range in one shot.
io_remap_pfn_range(): Mapping I/O Memory Into User Space
When the memory being mapped is device I/O memory rather than ordinary RAM, use io_remap_pfn_range() instead. Its signature takes the same arguments as remap_pfn_range():
int io_remap_pfn_range(struct vm_area_struct *vma, unsigned long addr,
unsigned long pfn, unsigned long size,
pgprot_t prot);
Modernization note: older references to this function sometimes show the pre-2005 name io_remap_page_range() with a physical address argument instead of a PFN. That name was retired well over a decade ago in favor of the current, portable io_remap_pfn_range() shown above, which behaves identically across architectures. Never use ioremap() here — ioremap() creates a mapping for the kernel’s own use, while io_remap_pfn_range() exposes physical I/O memory directly into a user process’s address space.
| Function | Use When |
|---|---|
remap_pfn_range() |
The PFN refers to ordinary RAM |
io_remap_pfn_range() |
The PFN refers to device I/O memory (MMIO) |
Modernization Note: mmap_prepare() On The Horizon
Recent mainline kernel development (from kernel 6.17 onward) has begun introducing a newer hook, file_operations.mmap_prepare(), which runs earlier in the mapping process and hands the driver a struct vm_area_desc instead of a live VMA, so a failure is cheaper to unwind. This transition is still in progress across the kernel tree at the time of writing, and the classic file_operations.mmap hook shown in this lecture remains fully supported and is what you will encounter in the overwhelming majority of existing drivers. Treat mmap_prepare() as a direction to watch, not something you need to adopt today.
Original Driver: ep_mmap_demo
This original character device driver allocates a kernel buffer with kzalloc(), exposes it through mmap() using remap_pfn_range(), and lets a user-space program write into the buffer and read the same bytes straight back — no read()/write() syscalls involved after the mapping is set up.
// ep_mmap_demo.c
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/mm.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/miscdevice.h>
#define EP_BUF_SIZE PAGE_SIZE
static char *ep_kbuf;
static int ep_mmap_demo_mmap(struct file *filp, struct vm_area_struct *vma)
{
unsigned long size = vma->vm_end - vma->vm_start;
unsigned long pfn = virt_to_phys(ep_kbuf) >> PAGE_SHIFT;
if (size > EP_BUF_SIZE) {
pr_err("ep_mmap_demo: requested size too large\n");
return -EINVAL;
}
if (remap_pfn_range(vma, vma->vm_start, pfn, size,
vma->vm_page_prot)) {
pr_err("ep_mmap_demo: remap_pfn_range failed\n");
return -EAGAIN;
}
pr_info("ep_mmap_demo: buffer mapped into user space, size=%lu\n", size);
return 0;
}
static const struct file_operations ep_mmap_demo_fops = {
.owner = THIS_MODULE,
.mmap = ep_mmap_demo_mmap,
};
static struct miscdevice ep_mmap_demo_dev = {
.minor = MISC_DYNAMIC_MINOR,
.name = "ep_mmap_demo",
.fops = &ep_mmap_demo_fops,
};
static int __init ep_mmap_demo_init(void)
{
int ret;
ep_kbuf = kzalloc(EP_BUF_SIZE, GFP_KERNEL);
if (!ep_kbuf)
return -ENOMEM;
ret = misc_register(&ep_mmap_demo_dev);
if (ret) {
kfree(ep_kbuf);
return ret;
}
pr_info("ep_mmap_demo: loaded, /dev/ep_mmap_demo ready\n");
return 0;
}
static void __exit ep_mmap_demo_exit(void)
{
misc_deregister(&ep_mmap_demo_dev);
kfree(ep_kbuf);
pr_info("ep_mmap_demo: unloaded\n");
}
module_init(ep_mmap_demo_init);
module_exit(ep_mmap_demo_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("EmbeddedPathashala");
MODULE_DESCRIPTION("remap_pfn_range mmap demo for free linux kernel development course");
User-Space Test Program: ep_mmap_test.c
// ep_mmap_test.c
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <string.h>
#define BUF_SIZE 4096
int main(void)
{
int fd = open("/dev/ep_mmap_demo", O_RDWR);
if (fd < 0) {
perror("open");
return 1;
}
char *addr = mmap(NULL, BUF_SIZE, PROT_READ | PROT_WRITE,
MAP_SHARED, fd, 0);
if (addr == MAP_FAILED) {
perror("mmap");
close(fd);
return 1;
}
strcpy(addr, "hello from user space via mmap");
printf("Read back: %s\n", addr);
munmap(addr, BUF_SIZE);
close(fd);
return 0;
}
Build And Run Steps
# Makefile for the kernel module
obj-m += ep_mmap_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_mmap_demo.ko
$ dmesg | tail -2
$ gcc -o ep_mmap_test ep_mmap_test.c
$ ./ep_mmap_test
$ dmesg | tail -1
$ sudo rmmod ep_mmap_demo
Expected Output
[ 512.001100] ep_mmap_demo: loaded, /dev/ep_mmap_demo ready
[ 520.221310] ep_mmap_demo: buffer mapped into user space, size=4096
Read back: hello from user space via mmap
Real-World Use Cases
- Frame buffer drivers exposing display memory directly to a graphics application
- High-throughput DMA buffers shared between kernel and user space (V4L2, audio drivers)
- Zero-copy shared memory regions between a driver and a user-space daemon
Common Mistakes
| Mistake | Why It Hurts |
|---|---|
Using remap_pfn_range() on I/O memory |
Use io_remap_pfn_range() for MMIO instead |
Ignoring vma->vm_pgoff |
Breaks mappings that request a non-zero offset into the buffer |
| Mapping a size larger than the backing buffer | Lets user space read or corrupt memory outside the intended region |
Forgetting to check the remap_pfn_range() return value |
A partially failed mapping can be silently ignored |
Best Practices
- Always validate the requested mapping size against the actual buffer size
- Round buffer allocations up to
PAGE_SIZE, since mappings are always page-granular - Set
VM_DONTEXPANDandVM_DONTDUMPonvma->vm_flagswhere appropriate for device memory
Performance And Security Considerations
Because a mapped region avoids a copy on every access, it is the right tool whenever an application touches a buffer frequently or the buffer is large. Security-wise, a driver’s mmap callback must never let user space map more memory, or a different physical range, than what it explicitly requested and is entitled to — always re-derive the PFN and size from trusted kernel-side state rather than user-supplied values.
Summary
- Memory mapping avoids the per-access copy cost of read()/write()
remap_pfn_range()installs page table entries for RAM into a process’s VMAio_remap_pfn_range()does the same for device I/O memory- The driver’s
file_operations.mmapcallback ties both into themmap(2)system call
Conclusion
Linux kernel mmap device memory support is what turns a device file from something you laboriously read and write into something an application can touch directly, at native memory speed. With remap_pfn_range(), io_remap_pfn_range(), and the mmap file operation covered here, this free linux kernel development course closes out the kernel memory management chapter’s coverage of user-space memory mapping.
FAQ
What does remap_pfn_range() actually do?
It installs page table entries in a process’s virtual memory area so that a range of user virtual addresses points directly at specific physical pages.
When should I use io_remap_pfn_range() instead of remap_pfn_range()?
Use io_remap_pfn_range() when the PFN refers to device I/O memory rather than ordinary RAM.
Do I need to call ioremap() before io_remap_pfn_range()?
No. ioremap() maps memory for the kernel’s own use; io_remap_pfn_range() maps physical I/O memory directly into a user process, so the two are not combined.
What is vma->vm_pgoff used for?
It carries the offset, in pages, into the mapped object where the mapping should begin, taken from the offset argument passed to mmap(2) in user space.
Is the classic file_operations.mmap hook still valid in kernel 6.x?
Yes. It remains the standard interface used by almost all drivers, even as a newer mmap_prepare() hook is gradually introduced in mainline development.
Why must mapped memory sizes be page-aligned?
The MMU manages memory in fixed-size pages, so the kernel always rounds a VMA’s size up to a multiple of PAGE_SIZE.
What happens if remap_pfn_range() fails?
It returns a negative error code, and the driver’s mmap callback should propagate that error back to the mmap(2) caller in user space.
Can remap_pfn_range() be used to share memory between two processes?
Yes, indirectly: each process opens the device and calls mmap() with MAP_SHARED, and both mappings point at the same underlying physical buffer.
Continue The Free Linux Kernel Development Course
You have completed the kernel memory management chapter’s coverage of user-space mapping.
