Memory Management
Almost everything a program believes about memory is a lie the MMU tells, maintained lazily by the
page-fault handler. mm/ is the code that maintains the lie: it decides what is really in RAM, what
gets evicted when RAM runs out, and who gets killed when eviction is not enough.
It is small — a few dozen files — and it is the second-hardest place in the kernel to get a patch merged, for the same reason as the scheduler: everything here is performance-sensitive, the effects are workload-dependent, and a mistake corrupts data rather than merely slowing things down.
Orient Yourself First
cd ~/kernel/linux
./scripts/get_maintainer.pl --scm --status -f mm/
git log --oneline --since="6 months ago" -- mm/ | head -30
git log --since="1 year ago" --format='%cN' -- mm/ | sort | uniq -c | sort -rn | head
ls mm/ | head -40
wc -l mm/*.c | sort -n | tail -12
ls Documentation/mm/ Documentation/admin-guide/mm/
Predict first: how many .c files are in mm/? How does that compare with drivers/gpu/drm/?
The ratio of importance to size is the highest in the tree.
Why It Matters
| Because | Consequence |
|---|---|
| Every allocation in every subsystem ends here | A change here affects everything, always |
| It decides what is resident and what is evicted | Which is what "the machine is slow" usually means |
| It is the boundary between "you asked for memory" and "there is no memory" | The OOM killer is a policy decision with no good answer |
| Devices see physical memory; programs see virtual | Every DMA bug is a disagreement about which |
| It is where the page cache lives | Which is most of your RAM, and most of your I/O performance |
Where the Code Is
ls mm/
| File / area | What lives there |
|---|---|
memory.c | The fault handler. handle_mm_fault() — start here. |
mmap.c, vma.c | The address space: VMAs, mmap, munmap, mprotect |
page_alloc.c | The buddy allocator — the bottom of everything |
slub.c | The slab allocator: kmalloc and named caches |
vmalloc.c | Virtually-contiguous allocation |
filemap.c | The page cache: reads, writes, and the folio lookup |
vmscan.c | Reclaim. LRU, MGLRU, kswapd, direct reclaim, shrinkers |
oom_kill.c | When reclaim is not enough |
memcontrol.c | Memory cgroups |
compaction.c, migrate.c | Making high-order allocations possible again |
huge_memory.c, hugetlb.c | Transparent and explicit huge pages |
rmap.c | Reverse mapping: given a page, who maps it? |
swap*.c, page_io.c | Swap |
mmu_gather.c, tlb* | TLB invalidation batching |
$EDITOR Documentation/mm/index.rst
$EDITOR Documentation/core-api/memory-allocation.rst # read this one twice
ls Documentation/admin-guide/mm/ # the operator's view
The Structures
struct mm_struct ONE ADDRESS SPACE
├── pgd the top of the page tables
├── mm_mt a MAPLE TREE of VMAs (was an rbtree)
├── mmap_lock the big per-mm lock
├── rss_stat resident set accounting
└── owner, mm_users, mm_count lifetime
struct vm_area_struct (VMA) ONE CONTIGUOUS MAPPING
├── vm_start, vm_end [start, end)
├── vm_flags READ/WRITE/EXEC/SHARED/...
├── vm_file, vm_pgoff file-backed? which file, what offset?
├── vm_ops ── fault(), map_pages(), ... ← the ops table
└── anon_vma for reverse mapping of anonymous pages
struct folio ONE OR MORE CONTIGUOUS PAGES, AS A UNIT
├── flags uptodate, dirty, locked, writeback, lru...
├── mapping ──▶ struct address_space which file (or anon) owns it
├── index offset within that mapping
└── _refcount, _mapcount who holds it; how many page tables map it
struct address_space THE PAGE CACHE FOR ONE FILE
├── i_pages an XArray: file offset -> folio
├── a_ops ── read_folio(), writepages(), ... ← ops
└── host ──▶ struct inode
rg -n "struct mm_struct \{" -A 40 include/linux/mm_types.h
rg -n "struct vm_area_struct \{" -A 40 include/linux/mm_types.h
rg -n "struct folio \{" -A 30 include/linux/mm_types.h
rg -n "struct vm_operations_struct \{" -A 30 include/linux/mm.h
rg -n "struct address_space_operations \{" -A 40 include/linux/fs.h
Note:
struct pageis being progressively replaced bystruct folioand by type-specific descriptors. The conversion has been running for several years and is not finished, so you will find both in the tree and code that converts between them. New code should use folio APIs; if you are unsure which is current,git log --oneline -20 -- mm/filemap.cwill show you what the maintainers are doing this month.
The Concepts
| Chapter | Answers |
|---|---|
| Page Tables and Faults | How does a virtual address become a physical one, and what happens when it cannot? |
| Allocators and Folios | Where does memory come from, and what is the page cache? |
| Reclaim, cgroups, and OOM | What happens when there is not enough? |
Then Lab 11 traces one page fault from the instruction that caused it to the page that satisfied it.
How to Read It
1. Documentation/core-api/memory-allocation.rst -- which allocator, which
flags. Short, canonical, and it settles most questions.
2. The structures above. Draw mm_struct -> VMA -> folio -> address_space
before opening a .c file.
3. ONE PATH: handle_mm_fault() in mm/memory.c.
- the VMA lookup
- the page-table walk: pgd -> p4d -> pud -> pmd -> pte
- the four cases: anonymous, file-backed, swap, copy-on-write
Follow it in GDB. It is the single most instructive path in mm/.
4. THE SECOND PATH: filemap_read() in mm/filemap.c -- the page cache
lookup, and what happens on a miss.
5. Only then: vmscan.c. Reclaim is where the policy lives, and it will not
make sense until you know what it is reclaiming.
Observing It
# The whole-system view
grep -E '^(MemTotal|MemFree|MemAvailable|Cached|Dirty|Writeback|Slab|AnonPages|Mapped)' /proc/meminfo
cat /proc/vmstat | grep -E 'pgfault|pgmajfault|pgalloc|pgsteal|pgscan|allocstall|compact'
cat /proc/buddyinfo # fragmentation, by order
sudo slabtop -o | head -20
cat /proc/pressure/memory 2>/dev/null # PSI: time lost to memory stalls
# One process
cat /proc/self/maps
cat /proc/self/smaps_rollup # the honest per-process number
grep -E 'VmRSS|VmSwap|RssAnon|RssFile' /proc/self/status
# Tracing
ls /sys/kernel/tracing/events/ | grep -E 'kmem|vmscan|filemap|compaction|oom'
sudo bpftrace -e 'kprobe:handle_mm_fault { @[comm] = count(); }'
sudo bpftrace -e 'tracepoint:vmscan:mm_vmscan_direct_reclaim_begin { @[comm] = count(); }'
sudo perf stat -e page-faults,minor-faults,major-faults -- <workload>
Tip:
mm_vmscan_direct_reclaim_beginis one of the highest-value tracepoints in the kernel. Direct reclaim means an allocating task had to stop and free memory itself, synchronously, before its allocation could proceed. If that counter is nonzero on a latency-sensitive workload, you have found the problem.
What Is Moving
git log --oneline --since="6 months ago" -- mm/ | head -40
git log --oneline --grep="folio" -- mm/ | head -20
| Change | Roughly | Why it matters |
|---|---|---|
| Folios | 5.16 onward, ongoing | Disambiguates head/tail pages; the API surface is still converting |
| MGLRU (multi-generational LRU) | 6.1 | A different reclaim algorithm, selectable at runtime |
| Maple tree for VMAs | 6.1 | Replaced the rbtree + cached-vma arrangement |
| Per-VMA locks for faults | 6.4 | Fault handling without the per-mm mmap_lock in the common case |
| SLAB removed; SLUB only | 6.8 | One allocator. CONFIG_SLAB is gone. |
| DAMON, memory tiering, CXL | ongoing | Where "which memory" becomes a question with more than one answer |
What a Good First Contribution Looks Like
mm/ has an extremely high review bar. Realistic entry points, in ascending difficulty:
| Target | Why it is plausible |
|---|---|
Documentation in Documentation/mm/ and admin-guide/mm/ | The folio and MGLRU work left real staleness; verify a claim, find it wrong, patch it |
Selftests in tools/testing/selftests/mm/ | Actively wanted, and writing one requires understanding rather than invention |
A kmemleak/KASAN-found bug elsewhere in the tree | You use mm tooling to fix someone else's subsystem — much easier than patching mm itself |
| Folio conversions in a consumer | A filesystem or driver still using deprecated page APIs. Mechanical, reviewable, wanted. |
| A tracepoint or a debugfs counter | Additive and low risk |
| Anything in the allocator or reclaim | Expect months, several maintainers, and a demand for measurements across many workloads |
Warning: The characteristic
mm/rejection is not "this is wrong". It is "what workload does this help, by how much, and what does it cost the workloads it does not help?" Reclaim and allocator heuristics are tuned against a decade of accumulated real-world cases, and a change that helps yours usually hurts one of those. Come with measurements from several workloads or do not come.
Common Misconceptions
| Misconception | Reality |
|---|---|
| "Free memory should be high" | Free memory is wasted memory. The page cache should be eating it. MemAvailable is the number that matters. |
"malloc allocated memory" | It extended a VMA. Pages appear on first touch — which is why a program can be OOM-killed while writing to memory it "already has". |
| "RSS is how much memory a process uses" | Shared pages are counted in every process that maps them. smaps_rollup's PSS is the honest number. |
| "The OOM killer picks the biggest process" | It scores by oom_score, adjustable via oom_score_adj, and a memcg OOM only considers that cgroup |
| "Swap is for when you run out of RAM" | Swap lets the kernel evict cold anonymous pages so hot ones can stay. A machine with no swap must evict page cache instead — often the worse trade. |
"drop_caches frees memory" | It discards clean cache the kernel would have dropped anyway. It is a benchmarking tool, not a fix. |
"vmalloc is just a bigger kmalloc" | Virtually contiguous, physically scattered, slower, and not DMA-able |
| "Huge pages are always faster" | Fewer TLB misses, but more internal fragmentation and expensive compaction to obtain them |
| "The kernel can move any page" | Pinned, mlocked, and non-migratable pages are why high-order allocations fail on a machine with plenty of free memory |
Validation / Self-check
- Name the four core structures and how they reference each other.
- What replaced the rbtree of VMAs, and roughly when?
- What is a folio, and which class of bug did it eliminate?
- Which single function is the most instructive path in
mm/, and what are its four cases? - Which tracepoint tells you an allocating task had to reclaim synchronously, and why does that matter more than most counters?
- Why is
MemAvailablea better number thanMemFree? - Why is RSS a misleading measure of a process's memory use, and what is better?
- Why can a program be OOM-killed while writing to memory
mallocalready returned? - What does swap actually buy you on a machine that is not out of RAM?
- Why do high-order allocations fail on a machine with plenty of free memory?
- What is the characteristic reason an
mm/patch is rejected, and what would you need to bring? - Give three plausible first contributions here, and say why each is more plausible than patching
page_alloc.c.
Next: Page Tables and Faults — how a virtual address becomes a physical one, and what happens when it cannot.