Memory
Almost everything a program believes about memory is a lie the MMU tells, maintained lazily by the page-fault handler. Kernel memory is the other half: real, finite, un-swappable, and allocated through a family of allocators whose differences matter enormously and are invisible at the call site.
Six concepts in the six-part treatment: virtual memory and the page fault, the page allocator, GFP flags, slab, vmalloc, and choosing, folios and the page cache, and DMA.
Concept 1: Virtual Memory and the Page Fault
1. What problem it solves
Every process needs its own address space, more address space than there is RAM, protection from every other process, and the ability to map files and share pages. One mechanism provides all of it: the CPU translates every address through page tables the kernel controls, and traps to the kernel whenever the translation is absent or the permission is wrong.
That trap is not an error path — it is the main path. Demand paging, copy-on-write, mmap of
files, swap, and lazy allocation are all "we deliberately left the page table empty and will fill it
in when someone touches it".
2. Where it exists in the kernel
mm/, plus per-architecture page-table code in arch/*/mm/.
rg -n "handle_mm_fault|do_user_addr_fault" mm/memory.c arch/x86/mm/fault.c
rg -n "struct vm_area_struct \{" include/linux/mm_types.h
ls Documentation/mm/
find Documentation -path '*x86*' -name 'mm.rst' # the x86-64 address-space layout
3. Who owns or interacts with it
| Actor | Interaction |
|---|---|
struct mm_struct | One address space; shared between the threads of a process; NULL for kernel threads |
struct vm_area_struct (VMA) | One contiguous mapping with uniform permissions and backing |
| The MMU + TLB | Does the translation; the TLB caches it and must be invalidated on change |
| The page-fault handler | Decides what should have been there, puts it there, and returns to re-execute |
Reclaim (kswapd, direct reclaim) | Takes pages back when they are scarce |
4. The structures and paths
task_struct
└── mm_struct the address space
├── pgd the top of the page tables
└── a set of VMAs each: [start, end), prot, flags, and a
backing (a file, or anonymous)
FAULT: CPU traps → handler asks: which VMA covers this address?
│
├── none → SIGSEGV (or an oops, if the
│ address came from kernel code)
├── VMA exists, page not present
│ ├── file-backed, in page cache → MINOR fault: just map it
│ ├── file-backed, not cached → MAJOR fault: read from disk,
│ │ and this SLEEPS
│ ├── anonymous, first touch → allocate a zeroed page
│ └── swapped out → MAJOR fault: read from swap
└── VMA exists, present, but write to a read-only page
├── it is a CoW page → copy it, map writable
└── it really is read-only → SIGSEGV
Two things follow that people find surprising:
A malloc() that succeeds has not allocated memory. It extended a VMA. The pages appear on
first touch, which is why a program can succeed at allocation and be OOM-killed while writing to it.
A page fault can sleep, because a major fault does I/O. That is why copy_to_user() belongs on
the list of functions that can sleep, and why the whole of
context and atomicity applies to it.
5. Experiment
CLAIM. Allocation and residency are different events, and you can watch the gap.
METHOD.
cat > /tmp/vm.c <<'EOF'
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
static void show(const char *tag) {
char line[256]; FILE *f = fopen("/proc/self/status", "r");
printf("== %s\n", tag);
while (fgets(line, sizeof line, f))
if (!strncmp(line, "VmSize", 6) || !strncmp(line, "VmRSS", 5)) printf(" %s", line);
fclose(f);
}
int main(void) {
show("start");
char *p = malloc(256 * 1024 * 1024); /* 256 MB */
show("after malloc (untouched)");
for (size_t i = 0; i < 256UL*1024*1024; i += 4096) p[i] = 1; /* touch every page */
show("after touching every page");
return 0;
}
EOF
gcc -O0 -o /tmp/vm /tmp/vm.c && /tmp/vm
PREDICT FIRST: after malloc, does VmSize grow? Does VmRSS? By how much? And after the
touch loop?
Then count the faults themselves:
/usr/bin/time -v /tmp/vm 2>&1 | grep -E 'Maximum resident|page faults'
sudo perf stat -e page-faults,minor-faults,major-faults /tmp/vm
sudo bpftrace -e 'software:page-faults:1 /comm=="vm"/ { @ = count(); }' -c /tmp/vm
PREDICT FIRST (again): how many minor faults for 256 MB at 4 KB pages? Are you off by a factor of 512? If so, you just discovered transparent huge pages:
cat /sys/kernel/mm/transparent_hugepage/enabled
grep -E 'AnonHugePages|Hugepagesize' /proc/meminfo
6. Failure mode
| Mistake | Symptom |
|---|---|
| Assuming a successful allocation means resident memory | OOM kill during a write, long after the allocation "succeeded" |
Assuming copy_to_user cannot sleep | BUG: sleeping function called from invalid context |
| Dereferencing a bad address in kernel code | Oops — there is no VMA and no SIGSEGV to catch |
Measuring memory with VmSize | You measured address space, not memory. Use VmRSS, and read smaps_rollup for the truth. |
Concept 2: The Page Allocator
1. What problem it solves
Everything else is built on this. Physical memory has to be handed out in page-sized and larger-power-of-two-sized chunks, with low fragmentation, quickly, from any CPU, under pressure.
2. Where it exists in the kernel
mm/page_alloc.c, the buddy allocator.
rg -n "__alloc_pages\b|struct zone \{" mm/page_alloc.c include/linux/mmzone.h | head
rg -n "MAX_PAGE_ORDER|MAX_ORDER" include/linux/mmzone.h | head
cat /proc/buddyinfo
cat /proc/zoneinfo | head -30
Note: The maximum allocation order and its spelling both changed during the 6.x series (
MAX_ORDERbecame inclusive, then was renamedMAX_PAGE_ORDER). This is exactly the kind of thing not to memorize — grep it in your tree.
3. How it works
FREE LISTS, one per order, per zone, per migrate type:
order 0 [4 KB] ████████████████ many
order 1 [8 KB] ██████
order 2 [16 KB] ███
order 3 [32 KB] █
...
order N [max]
ALLOCATE order 2, none free:
split an order-3 block → two order-2 "buddies"; give one away, free the other.
FREE an order-2 block:
if its buddy is also free, merge into an order 3. Recursively.
This is why /proc/buddyinfo is a fragmentation report: lots of order-0 and
nothing at order 5+ means you cannot get a 128 KB contiguous allocation even
though there is plenty of free memory.
Zones exist because not all physical memory is equivalent to all devices:
| Zone | Why |
|---|---|
ZONE_DMA / ZONE_DMA32 | Ancient or 32-bit-limited devices can only address low memory |
ZONE_NORMAL | The ordinary case |
ZONE_MOVABLE | Pages that can be migrated, for hotplug and huge-page defragmentation |
ZONE_DEVICE | Memory that belongs to a device (persistent memory, GPU memory) |
4. The API
| Call | Returns |
|---|---|
alloc_pages(gfp, order) | A struct page * — 2^order contiguous pages |
__get_free_pages(gfp, order) | The virtual address instead |
get_zeroed_page(gfp) | One zeroed page's address |
__free_pages(page, order) / free_pages(addr, order) | Free it. The order must match. |
page_address(page) | Virtual address of a struct page |
virt_to_page(addr) | The reverse |
5. Experiment
CLAIM. High-order allocations fail long before memory runs out, because of fragmentation — and
/proc/buddyinfo predicts it.
METHOD. In a module, try increasing orders and report where it stops:
for (order = 0; order <= 10; order++) {
struct page *p = alloc_pages(GFP_KERNEL | __GFP_NOWARN, order);
pr_info("order %2d (%4lu KB): %s\n", order,
(PAGE_SIZE << order) >> 10, p ? "ok" : "FAILED");
if (p)
__free_pages(p, order);
}
PREDICT FIRST: on a freshly booted guest with plenty of free memory, at which order does it start
failing? Now run something that fragments memory first (a large find, a kernel build), re-run, and
predict again.
cat /proc/buddyinfo # before and after; the right-hand columns are the story
6. Failure mode
| Mistake | Symptom |
|---|---|
| Depending on a high-order allocation at runtime | Works at boot, fails after a week of uptime |
| Mismatched order on free | Corruption of the free lists |
Not passing __GFP_NOWARN on an allocation you handle failure for | A page of stack trace in dmesg on every miss |
Using alloc_pages where kmalloc would do | Wasting up to a page per small object |
Concept 3: GFP Flags
1. What problem it solves
The allocator needs to know what it is allowed to do to satisfy you: may it sleep? may it start I/O? may it recurse into the filesystem? may it use the emergency reserve? That is not a property of the size — it is a property of your calling context, and only you know it.
2. Where it exists in the kernel
rg -n "define GFP_KERNEL|define GFP_ATOMIC|define GFP_NOWAIT" include/linux/gfp_types.h
$EDITOR Documentation/core-api/memory-allocation.rst # READ THIS. It is short and canonical.
3. The flags that matter
| Flag | May sleep | Reclaim | Emergency reserves | Use when |
|---|---|---|---|---|
GFP_KERNEL | ✅ | ✅ full, incl. I/O and FS | ❌ | The default. Process context, no locks held. |
GFP_NOWAIT | ❌ | ❌ | ❌ | Atomic context, and you can handle failure gracefully |
GFP_ATOMIC | ❌ | ❌ | ✅ | Atomic context, and failure is genuinely not an option |
GFP_NOIO | ✅ | no I/O | ❌ | You are on the I/O path; reclaiming via I/O would recurse |
GFP_NOFS | ✅ | no FS | ❌ | You are in a filesystem holding its locks |
GFP_USER / GFP_HIGHUSER | ✅ | ✅ | ❌ | Memory that will be mapped into user space |
| Modifier | Effect |
|---|---|
__GFP_ZERO | Zero it (what kzalloc adds) |
__GFP_NOWARN | Do not dump a stack trace on failure — pair with actually handling failure |
__GFP_NORETRY | Give up quickly rather than reclaiming hard |
__GFP_RETRY_MAYFAIL | Try hard, but still return NULL rather than invoking the OOM killer |
__GFP_NOFAIL | Loop forever until it succeeds. Strongly discouraged; needs a justification in review. |
__GFP_ACCOUNT | Charge to the memory cgroup |
Warning:
GFP_NOIOandGFP_NOFSare largely superseded by scopes. Rather than threading a flag through twenty call layers, mark the region:unsigned int flags = memalloc_nofs_save(); ... /* everything in here allocates as if GFP_NOFS, including callees */ memalloc_nofs_restore(flags);Modern filesystem code does it this way, and a patch adding
GFP_NOFSto a deep helper will be asked why it did not use the scope. SeeDocumentation/core-api/gfp_mask-from-fs-io.rst.
GFP_ATOMIC is not "the atomic-context version of GFP_KERNEL". It is a request to dip into a
reserve that exists so that network receive and interrupt paths can allocate at all when memory is
exhausted. Overusing it exhausts that reserve and hurts unrelated subsystems. The order of preference
in atomic context is: restructure so you allocate outside the lock → GFP_NOWAIT with a real
failure path → GFP_ATOMIC.
4. What happens on the slow path
kmalloc(n, GFP_KERNEL)
│
├── fast path: a free object in this CPU's slab cache → done, ~20 ns
│
├── ask the page allocator for a new slab
│ └── free page available in the right zone → done
│
└── no free pages → RECLAIM (this is where GFP_KERNEL sleeps)
├── wake kswapd (background reclaim) and try again
├── DIRECT RECLAIM in the calling task:
│ drop clean page cache · write back dirty pages
│ · swap anonymous pages · shrink slab caches
│ (LRU, or MGLRU if CONFIG_LRU_GEN is on)
├── compaction, if the request is high-order
└── still nothing → the OOM killer picks a victim
Watch it:
grep -E 'pgscan|pgsteal|pgalloc|allocstall|compact_' /proc/vmstat
cat /proc/pressure/memory 2>/dev/null # PSI: how much time is lost to memory stalls
dmesg | grep -i "out of memory\|oom-kill"
5. Experiment
CLAIM. GFP_ATOMIC and GFP_KERNEL behave identically on an idle machine and differently under
pressure — so the distinction is untestable without creating pressure deliberately.
METHOD. With CONFIG_FAILSLAB (in lab-paranoid), make allocations fail on demand:
F=/sys/kernel/debug/failslab
echo 50 > $F/probability
echo -1 > $F/times
echo N > $F/ignore-gfp-wait # ALSO fail allocations that could have slept
insmod ./alloc-lab.ko
PREDICT FIRST: with a 50% injected failure rate, how many of your module's allocation sites
handle NULL correctly? Count them in the source first, then count the ones the module survives.
RESULT. Note every path that oopsed. Each one is a real bug that would have appeared, in production, on a machine under pressure, in a way that looked like something else.
6. Failure mode
| Mistake | Symptom |
|---|---|
GFP_KERNEL in atomic context | BUG: sleeping function called from invalid context, or a deadlock |
GFP_ATOMIC everywhere "to be safe" | Reserve exhaustion; unrelated subsystems fail to allocate |
Not checking for NULL | An oops on a machine under pressure, i.e. someone else's production |
__GFP_NOFAIL for convenience | An unkillable loop when memory is genuinely gone |
GFP_KERNEL on the I/O writeback path | Reclaim recurses into the filesystem that is trying to write back. Deadlock. |
Concept 4: Slab, vmalloc, and Choosing
1. What problem it solves
The page allocator's granularity is a page. Most kernel objects are 32–512 bytes and there are millions of them. The slab allocator sits on top of the page allocator and hands out small objects from per-CPU caches, with no lock in the fast path.
vmalloc solves the opposite problem: a large allocation where physical contiguity is not needed and
would be hard to get.
2. Where it exists in the kernel
ls mm/slub.c mm/vmalloc.c mm/util.c
grep -E 'CONFIG_SLUB|CONFIG_SLAB\b|CONFIG_SLOB' ~/kernel/build/.config # SLUB is the only one now
head -3 /proc/slabinfo; sudo slabtop -o | head -15
grep -E 'VmallocTotal|VmallocUsed' /proc/meminfo
3. The choice — this table is the chapter
| Allocator | Physically contiguous | Max practical size | May sleep | DMA-able | Use for |
|---|---|---|---|---|---|
kmalloc / kzalloc | Yes | A few pages (KMALLOC_MAX_CACHE_SIZE, then falls through to the page allocator) | Depends on GFP | Yes (streaming) | Almost everything |
kmalloc_array / kcalloc | Yes | Same | Depends | Yes | Arrays — overflow-checked, unlike kmalloc(n * size) |
kmem_cache_alloc | Yes | Fixed, per cache | Depends | Yes | Many objects of one type; you get a named line in slabtop |
alloc_pages | Yes | Bounded by MAX_PAGE_ORDER | Depends | Yes | Whole pages; page-table-like structures |
vmalloc / vzalloc | No | Large | Yes — never atomic | No | Big buffers where only virtual contiguity matters |
kvmalloc / kvzalloc | Maybe | Large | Yes | No — assume not | Runtime-sized buffers. Tries kmalloc, falls back to vmalloc. |
/* Arrays: use the checked helpers. kmalloc(n * sizeof(x)) can overflow
* with an attacker-controlled n, and that is a classic heap overflow. */
p = kmalloc_array(n, sizeof(*p), GFP_KERNEL); /* not kmalloc(n * sizeof(*p)) */
p = kcalloc(n, sizeof(*p), GFP_KERNEL); /* ...and zeroed */
/* A struct with a trailing flexible array: */
s = kzalloc(struct_size(s, items, n), GFP_KERNEL); /* overflow-checked */
/* A large, runtime-sized buffer with no DMA and no contiguity requirement: */
buf = kvmalloc(len, GFP_KERNEL);
...
kvfree(buf);
/* Many objects of one type — and now `slabtop` shows YOUR cache by name: */
cache = kmem_cache_create("mylab_node", sizeof(struct node), 0,
SLAB_HWCACHE_ALIGN, NULL);
node = kmem_cache_alloc(cache, GFP_KERNEL);
Why vmalloc is not simply "kmalloc for big things":
| Property | kmalloc | vmalloc |
|---|---|---|
| Physical layout | Contiguous | Scattered pages |
| Allocation cost | Very low (per-CPU free list) | High: page-by-page, plus page-table setup |
| Access cost | Uses the huge-page direct map | Extra TLB pressure |
| Free cost | Low | Requires a TLB flush, sometimes cross-CPU |
| Can it be handed to a device | Yes | No — see the DMA section |
| Available in atomic context | Yes, with GFP_ATOMIC | No |
| Fails when | Memory is fragmented | Rarely; only virtual space runs out (a real limit on 32-bit) |
4. Experiment
CLAIM. vmalloc memory is not physically contiguous, and you can prove it.
METHOD.
size_t sz = 64 * 1024;
void *k = kmalloc(sz, GFP_KERNEL);
void *v = vmalloc(sz);
int i;
pr_info("kmalloc: virt=%p\n", k);
for (i = 0; i < 4; i++)
pr_info(" page %d: phys=%pa\n", i,
&(phys_addr_t){ virt_to_phys(k + i * PAGE_SIZE) });
pr_info("vmalloc: virt=%p\n", v);
for (i = 0; i < 4; i++) {
phys_addr_t pa = page_to_phys(vmalloc_to_page(v + i * PAGE_SIZE));
pr_info(" page %d: phys=%pa\n", i, &pa);
}
PREDICT FIRST: for kmalloc, are the four physical addresses 4096 apart? For vmalloc? Write
both predictions down.
Then check the timing:
t0 = ktime_get_ns(); for (i = 0; i < 1000; i++) kfree(kmalloc(4096, GFP_KERNEL));
pr_info("kmalloc/kfree x1000: %llu ns\n", ktime_get_ns() - t0);
t0 = ktime_get_ns(); for (i = 0; i < 1000; i++) vfree(vmalloc(4096));
pr_info("vmalloc/vfree x1000: %llu ns\n", ktime_get_ns() - t0);
PREDICT FIRST: the ratio. Most people guess 3×.
5. Failure mode
| Mistake | Symptom |
|---|---|
kmalloc(n * size) with attacker-controlled n | Integer overflow → a heap buffer far smaller than expected → overflow |
vmalloc in atomic context | It sleeps. BUG. |
vmalloc for a small object | A whole page plus page-table entries for 64 bytes |
kfree on a vmalloc pointer (or vice versa) | Corruption. kvfree exists precisely so kvmalloc callers cannot get this wrong. |
Handing vmalloc memory to a device | Silent corruption — see the next section |
| Freeing twice, or using after free | KASAN report if you are lucky; anything at all if you are not |
Concept 5: Folios and the Page Cache
1. What problem it solves
The kernel caches file contents in memory. That cache is most of your RAM (Cached in
/proc/meminfo), and it is why the second cat of a file in
the warm-up was so much faster than
the first.
Folios solve a second, internal problem: struct page was used to mean both "one 4 KB page" and
"the head of a compound multi-page allocation", and a great deal of code could not tell which it had.
A struct folio is by construction the head — a set of physically contiguous pages treated as one
unit — which makes whole classes of head/tail bugs unrepresentable.
2. Where it exists in the kernel
rg -n "struct folio \{" include/linux/mm_types.h
rg -n "struct address_space \{" include/linux/fs.h
ls Documentation/mm/
git log --oneline --grep="folio" -- mm/ | head -20 # the conversion, still in progress
3. The structures
struct file ──▶ struct inode ──▶ struct address_space
│ (an XArray, indexed by file offset)
▼
folios: the cached contents
flags: uptodate? dirty? writeback? locked?
read(): look up the folio at this offset
present and uptodate → copy_to_user. MINOR. Microseconds.
absent → allocate, submit I/O, SLEEP, wake, copy. MAJOR.
write(): find or create the folio, copy into it, mark it DIRTY, return.
The data is NOT on disk. Writeback happens later, or on fsync().
Note: This is why
write()returning does not mean the data is durable, and whyfsync()exists and is slow. It is also why a filesystem's crash-consistency story is complicated: the page cache decides when things reach the disk, and journaling exists to make "some of it did" a recoverable state.
4. Experiment
CLAIM. The page cache is visible, controllable, and dominant.
METHOD.
dd if=/dev/urandom of=/tmp/f bs=1M count=200 status=none
sync; sudo sh -c 'echo 3 > /proc/sys/vm/drop_caches'
grep -E '^Cached' /proc/meminfo
sudo perf stat -e major-faults,minor-faults -- cat /tmp/f > /dev/null # cold
grep -E '^Cached' /proc/meminfo
sudo perf stat -e major-faults,minor-faults -- cat /tmp/f > /dev/null # warm
# Which pages of a file are resident right now:
python3 - <<'EOF'
import mmap, os, ctypes
f = open('/tmp/f','rb'); sz = os.fstat(f.fileno()).st_size
m = mmap.mmap(f.fileno(), 0, prot=mmap.PROT_READ)
vec = (ctypes.c_ubyte * ((sz + 4095)//4096))()
libc = ctypes.CDLL(None)
libc.mincore(ctypes.c_void_p(ctypes.addressof(ctypes.c_char.from_buffer(m))),
ctypes.c_size_t(sz), vec)
print("resident pages:", sum(v & 1 for v in vec), "of", len(vec))
EOF
PREDICT FIRST: the ratio of cold to warm time, and the major-fault count in each case.
5. Failure mode
| Mistake | Symptom |
|---|---|
| Benchmarking without dropping caches | You measured the page cache, not the storage |
Assuming write() is durable | Data loss on power failure; the classic application bug |
| Treating "free memory is low" as a problem | The page cache is doing its job; MemAvailable is the number that matters |
Writing new code against struct page where a folio API exists | A review comment, and a conversion patch someone else has to write |
Concept 6: DMA
1. What problem it solves
A device writes into RAM without the CPU. For that to work, three things must be true that are not guaranteed by "I allocated some memory":
1. THE DEVICE MUST BE ABLE TO ADDRESS IT.
A 32-bit device cannot reach a buffer above 4 GB. An IOMMU may
remap; a bounce buffer (SWIOTLB) may copy. Either way, the address
the DEVICE uses is not the address the CPU uses.
2. THE MEMORY MUST BE PHYSICALLY CONTIGUOUS (for a single mapping).
The device follows physical addresses. It knows nothing of page tables.
3. THE CACHES MUST AGREE.
On a non-cache-coherent architecture, the CPU's cached copy and what
the device wrote to RAM are different. Somebody must invalidate or
flush, at the right moment, in the right direction.
The DMA API exists so drivers express intent and the platform handles all three.
2. Where it exists in the kernel
ls kernel/dma/ include/linux/dma-mapping.h
$EDITOR Documentation/core-api/dma-api-howto.rst # READ THIS before writing a driver
$EDITOR Documentation/core-api/dma-api.rst
grep -E 'CONFIG_DMA_API_DEBUG' ~/kernel/build/.config
3. The two kinds of mapping
| Coherent (consistent) | Streaming | |
|---|---|---|
| Call | dma_alloc_coherent() / dma_free_coherent() | dma_map_single() / dma_unmap_single(), dma_map_sg() |
| Allocates memory | Yes — it gives you the buffer | No — you provide an existing buffer |
| Lifetime | Long-lived | One transfer |
| Cache handling | Automatic and always valid | You must dma_sync_* if you touch it mid-transfer |
| Cost | May be uncached memory; slow for the CPU to access | Cheap, plus a sync |
| Use for | Descriptor rings, mailboxes, anything both sides poll | Packet buffers, I/O data |
/* Declare what the device can address. Do this in probe, BEFORE any mapping. */
ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(64));
if (ret)
return ret;
/* Coherent: a descriptor ring both the CPU and the device read continuously. */
ring = dma_alloc_coherent(dev, RING_BYTES, &ring_dma, GFP_KERNEL);
/* ^ CPU virtual address ^ the DEVICE's address — NOT a physical
* address, and not interchangeable */
/* Streaming: one buffer, one transfer, one direction. */
dma_addr_t handle = dma_map_single(dev, buf, len, DMA_TO_DEVICE);
if (dma_mapping_error(dev, handle)) /* ALWAYS check */
return -ENOMEM;
start_transfer(dev, handle, len);
/* ...on completion: */
dma_unmap_single(dev, handle, len, DMA_TO_DEVICE);
4. The rules, and why kmalloc memory is not automatically safe
| Rule | Why |
|---|---|
| Never DMA to or from the stack | The stack may be vmalloced (CONFIG_VMAP_STACK), so it is not physically contiguous — and it shares cache lines with unrelated locals |
Never DMA to vmalloc/kvmalloc memory with a single mapping | Not physically contiguous. Use dma_map_sg over its pages, or dma_alloc_coherent. |
| Never let a DMA buffer share a cache line with other data | On a non-coherent architecture the sync operates on whole cache lines and will destroy the neighbour |
Always check dma_mapping_error() | Mapping can fail: no IOMMU space, no bounce buffer |
| Always unmap | An IOMMU mapping left in place is a leak and an open window for the device to write |
| Do not touch a streaming buffer while it is mapped | Unless you dma_sync_single_for_cpu() first, and _for_device() after |
Use dma_addr_t, never phys_addr_t, for a device address | They are different things and the type says so |
So: kmalloc memory is usable for streaming DMA — it is physically contiguous and suitably aligned
— but that is not sufficient. The device's addressing mask may exclude where it landed, in which case
the DMA API silently bounces it through a low buffer (SWIOTLB), which costs a copy. And on a
non-coherent platform, a kmalloc of 8 bytes next to other live data is a cache-line hazard. The
correctness comes from using the API, not from the allocator.
5. Experiment
CLAIM. CONFIG_DMA_API_DEBUG catches DMA misuse that is otherwise invisible on x86 and
catastrophic elsewhere.
METHOD. Build a lab-paranoid kernel with CONFIG_DMA_API_DEBUG=y and write a module that
deliberately maps a stack buffer:
static int __init dma_lab_init(void)
{
char stackbuf[64]; /* WRONG ON PURPOSE */
dma_addr_t h = dma_map_single(&pdev->dev, stackbuf, sizeof(stackbuf),
DMA_TO_DEVICE);
...
}
PREDICT FIRST: does this (a) fail to compile, (b) warn at runtime, (c) work on x86 and corrupt data on arm64, or (d) all of the above depending on config? Then run it and read:
WARNING: ... at kernel/dma/debug.c:...
DMA-API: device driver maps memory from stack [addr=...]
Also try forgetting the dma_unmap_single and see what the checker says at driver removal.
6. Failure mode
| Mistake | Symptom |
|---|---|
| DMA to a stack buffer | Works on x86 with a coherent device; corrupts memory on arm64 |
DMA to vmalloc memory | The device writes to whatever physical pages happen to follow — arbitrary corruption |
Missing dma_mapping_error check | A NULL-ish dma_addr_t handed to hardware |
Forgetting dma_unmap | IOMMU space exhaustion, and a device that can still write into freed memory |
Confusing dma_addr_t with a physical address | Works with no IOMMU, breaks with one — i.e. works in your VM |
| Touching a mapped streaming buffer without a sync | Stale data, on non-coherent platforms only |
Not calling dma_set_mask_and_coherent | Silent bounce-buffering, or a device that cannot reach its buffers at all |
The Decision Table
| You need | Use |
|---|---|
| A small object, any context | kmalloc / kzalloc with the right GFP |
| An array | kmalloc_array / kcalloc (overflow-checked) |
| A struct with a trailing flexible array | kzalloc(struct_size(...)) |
| Many objects of one type | kmem_cache_create + kmem_cache_alloc |
| A large, runtime-sized buffer, no DMA | kvmalloc / kvfree |
| Whole pages | alloc_pages |
| A buffer a device will DMA into, long-lived | dma_alloc_coherent |
| A buffer a device will DMA into, one transfer | kmalloc + dma_map_single |
| Memory to map into user space | GFP_USER / GFP_HIGHUSER, or vm_insert_page machinery |
| Something in atomic context | GFP_NOWAIT first, GFP_ATOMIC only if failure is unacceptable |
| Something on the writeback path | memalloc_noio_save() / memalloc_nofs_save() scopes |
Validation / Self-check
- Why is a page fault the main path rather than an error path? Name four normal features built on it.
malloc()returned successfully. How much memory has been allocated? Explain.- Which kinds of page fault can sleep, and what does that imply about
copy_to_user? - Explain the buddy allocator's split and merge, and say what
/proc/buddyinfotells you. - Why do memory zones exist? Give one device-driven reason.
- Compare
GFP_KERNEL,GFP_NOWAIT, andGFP_ATOMICalong three axes. - What is the correct order of preference for allocating in atomic context, and why is
GFP_ATOMIClast? - What are
memalloc_nofs_save()/memalloc_noio_save()for, and what problem withGFP_NOFSdo they solve? - Trace what
kmalloc(64, GFP_KERNEL)does when no memory is free. Where does it sleep? - Give four differences between
kmallocandvmallocthat would change your choice. - What is
kvmallocfor, and what may you never do with its result? - Why is
kmalloc(n * sizeof(*p))a security bug andkmalloc_array(n, sizeof(*p))not? - What is a folio and which class of bug did it eliminate?
- Why does
write()returning not mean the data is on disk? - Name the three things that must be true for a device to DMA into a buffer, and the API call that handles each.
- Why may you not DMA to or from the stack? Give two independent reasons.
kmallocmemory is physically contiguous, so why is it still not automatically safe for DMA?- What is
dma_addr_t, and why is it not a physical address?
Next: Deferred Work — the interrupt takes 2 µs and the work takes 2 ms. Now what?