What Do Buffers and Cache Actually Mean in /proc/meminfo? — The Hidden Semantics of Linux Memory Stats

What Do Buffers and Cache Actually Mean in /proc/meminfo? — The Hidden Semantics of Linux Memory Stats
Every Linux administrator has stared at /proc/meminfo and wondered: what is the difference between Buffers and Cache? The names sound similar. Both grow when you read files. Both shrink when applications need memory. Surely they are both "cache" in some sense?
They are not. Buffers and Cache track fundamentally different structures, maintained by different kernel subsystems, with different reclaim semantics. And the formula for Cached is not what most people assume: it is page cache minus swapcache minus buffers.
This article walks through the fs/proc/meminfo.c and mm/page_alloc.c source to decode every field in /proc/meminfo. By the end, you will understand why a server with 2 GB "free" and 40 GB "available" is healthy, why Buffers is usually small on modern systems, and why MemAvailable (not MemFree) is the metric that actually matters.
Key Takeaways
- Buffers ≠ Cache: Buffers count
buffer_headblock device metadata. Cached counts file content in page cache minus swapcache and buffers.- The real formula:
Cached = NR_FILE_PAGES - total_swapcache_pages() - Buffers. It is derived, not directly counted.MemAvailable(kernel 3.14+) estimates reclaimable memory. It is the correct "how much can I use" metric, notMemFree.- "Free memory is wasted memory": Linux aggressively uses RAM for page cache. Low MemFree with high Cached is healthy.
- AnonPages vs Mapped: AnonPages = anonymous pages mapped to page tables. Mapped = file pages mapped to page tables. They track different things.
The Myth: "Cached Is Just File Cache, Buffer Is Also Cache"
The confusion between Buffers and Cache is understandable — both names contain "cache" and both grow when the system reads from disk. But they serve completely different purposes:
| Aspect | Buffers | Cached |
|---|---|---|
| What it counts | buffer_head block device metadata |
File content in page cache |
| Source subsystem | Block layer (block/bdev.c) |
VM subsystem (mm/filemap.c) |
| Typical size | Small (MB to low GB) | Large (can be tens of GB) |
| Reclaim method | Rarely reclaimed directly | First to be reclaimed under pressure |
| Grows when | Raw block I/O, metadata operations | Reading files |
The key insight: Buffers are about block device mapping (which blocks belong to which file), while Cached are about file content (the actual data you read from a file). They are tracked by different subsystems for different reasons.
The /proc/meminfo Source: Where Numbers Come From
Every number in /proc/meminfo traces to a specific source function. Understanding the source reveals what each field actually measures.
si_meminfo() — The Main Function
The primary memory info function lives in fs/proc/meminfo.c:
// fs/proc/meminfo.c
static int show_meminfo(struct seq_file *m, void *v)
{
struct sysinfo i;
si_meminfo(&i);
...
seq_printf(m, "MemTotal: %8lu kB\n", i.totalram);
seq_printf(m, "MemFree: %8lu kB\n", i.freeram);
seq_printf(m, "MemAvailable: %8lu kB\n", si_memavailable());
seq_printf(m, "Buffers: %8lu kB\n", i.bufferram);
...
}si_meminfo() itself is simple — it collects basic counters:
// fs/proc/meminfo.c
void si_meminfo(struct sysinfo *val)
{
val->totalram = totalram_pages();
val->freeram = global_zone_page_state(NR_FREE_PAGES);
val->bufferram = nr_blockdev_pages();
...
}The Cached Calculation
Cached is not a direct counter. It is computed by subtraction:
// fs/proc/meminfo.c
cached = global_node_page_state(NR_FILE_PAGES)
- total_swapcache_pages()
- i.bufferram;
if (cached < 0) cached = 0;This formula reveals three important facts:
- NR_FILE_PAGES counts ALL file-backed pages (including those also in swap)
- total_swapcache_pages() subtracts pages that are both in swap and memory
- bufferram subtracts pages already counted as Buffers
The result: Cached counts file content that is ONLY in page cache (not in swap, not counted as Buffers).
┌─────────────────────────────────────────────────────────────────────┐
│ NR_FILE_PAGES (all file-backed pages) │
│ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ Cached (what /proc/meminfo reports) │ │
│ │ │ │
│ │ = NR_FILE_PAGES - swapcache - buffers │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────┐ ┌──────────────────┐ │
│ │ SwapCached │ │ Buffers │ │
│ │ (pages in both │ │ (block device │ │
│ │ swap AND memory)│ │ metadata) │ │
│ └──────────────────┘ └──────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
Buffers: Block Device Metadata
Buffers count buffer_head structures — the kernel's legacy mechanism for tracking block device I/O.
What Buffers Actually Count
buffer_headstructures for raw block I/O operations- Inode pages from the inode cache (when accessed via block layer)
- Dentry cache entries (when accessed via block layer)
- Superblock buffers
- Block device mapping metadata
The Source: nr_blockdev_pages()
// block/bdev.c
long nr_blockdev_pages(void)
{
struct block_device *bdev;
long ret = 0;
spin_lock(&all_bdevs_lock);
list_for_each_entry(bdev, &all_bdevs, bd_list)
ret += bdev->bd_inode->i_mapping->nrpages;
spin_unlock(&all_bdevs_lock);
return ret;
}This function iterates all block devices and counts pages in their inode mappings. These are the buffer_head pages used for block-level I/O.
Why Buffers Are Usually Small
On modern systems, Buffers is typically small (megabytes to low gigabytes) because:
- Page cache dominates: Most file I/O goes through page cache, not buffer cache
- Direct I/O bypasses buffers:
O_DIRECTand raw block I/O skip the buffer cache - Slab caches metadata: Dentry and inode caches use slab allocators, not buffer_head
Buffers grows significantly only when:
- Doing raw block I/O (e.g.,
dd if=/dev/sda) - Accessing files on filesystems without page cache support
- Heavy metadata operations on block devices
Cached: Page Cache Minus Swapcache
Cached counts file content in the page cache, excluding pages also in swap and pages counted as Buffers.
What Cached Actually Counts
- File-backed pages in page cache (executables, libraries, data files)
- The content you get when you read a file (first read populates, subsequent reads hit)
- Minus: pages that are also in swap (counted under SwapCached)
- Minus: pages already counted as Buffers
NR_FILE_PAGES — The Global Counter
// include/linux/mm.h
enum node_stat_item {
NR_FILE_PAGES, // total file-backed pages in page cache
NR_ANON_MAPPED, // anonymous pages mapped to page tables
NR_PAGETABLE, // page table pages
NR_SLAB_RECLAIMABLE_B,
NR_SLAB_UNRECLAIMABLE_B,
...
};NR_FILE_PAGES is a global counter maintained by the VM subsystem. It increments when a file page is added to page cache and decrements when removed.
The Counterintuitive Behavior
Because Cached is derived by subtraction, it can decrease even when actual file caching increases. This happens when:
- Pages are swapped out and back in → swapcache grows → Cached shraps
- Block device metadata grows → Buffers grows → Cached shraps
This is why "Cached" is not always a reliable indicator of "how much file content is cached." For that, you want NR_FILE_PAGES directly (available via /proc/vmstat).
MemAvailable: The Metric That Actually Matters
MemAvailable was added in kernel 3.14 to answer the question: "How much memory can a new application use without swapping?"
Why MemFree Is Misleading
MemFree counts completely unused pages. But Linux uses almost all available RAM for page cache. A server with 64 GB RAM might show:
MemTotal: 65536000 kB
MemFree: 524288 kB ← looks scary: only 0.8% free!
MemAvailable: 45875200 kB ← reality: 70% available
The 524 MB "free" is the tiny fraction not used for anything. The 45 GB "available" is the page cache that can be instantly reclaimed.
The si_memavailable() Implementation
// mm/page_alloc.c — si_memavailable()
u64 si_memavailable(void)
{
unsigned long available;
available = global_zone_page_state(NR_FREE_PAGES);
available -= totalreserve_pages; // subtract low watermark reserves
available += global_node_page_state(NR_INACTIVE_FILE);
available += global_node_page_state(NR_ACTIVE_FILE);
available -= min(available / 2, total_swapcache_pages());
available += global_node_page_state(NR_SLAB_RECLAIMABLE_B) / 2;
available -= min(available / 2, global_node_page_state(NR_PAGETABLE));
available += global_node_page_state(NR_INACTIVE_ANON);
if (available < 0)
available = 0;
return available;
}What MemAvailable Includes
┌─────────────────────────────────────────────────────────────────────┐
│ MemAvailable │
│ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ NR_FREE_PAGES (completely free) │ │
│ │ + NR_INACTIVE_FILE (cold file cache — instant reclaim) │ │
│ │ + NR_ACTIVE_FILE (hot file cache — reclaimable with work) │ │
│ │ + NR_INACTIVE_ANON (cold anon — reclaimable via swap) │ │
│ │ + NR_SLAB_RECLAIMABLE_B / 2 (reclaimable kernel caches) │ │
│ │ - totalreserve_pages (low watermark reserves) │ │
│ │ - swapcache adjustment (pages already counted in swap) │ │
│ │ - pagetable adjustment (page table overhead) │ │
│ └─────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
The /2 fractions on slab and pagetable are conservative estimates — not all reclaimable slab can actually be reclaimed, and page tables are needed for any new process.
The Full /proc/meminfo Field Map
Every field in /proc/meminfo has a specific source. Here is the complete map:
| Field | Source | Meaning |
|---|---|---|
MemFree |
NR_FREE_PAGES |
Completely unused pages |
MemAvailable |
si_memavailable() |
Estimated reclaimable memory |
Buffers |
nr_blockdev_pages() |
Block device metadata (buffer_head) |
Cached |
NR_FILE_PAGES - swapcache - buffers |
File page cache (derived) |
SwapCached |
total_swapcache_pages() |
Pages in both swap AND memory |
Active(anon) |
NR_ACTIVE_ANON |
Hot anonymous pages |
Inactive(anon) |
NR_INACTIVE_ANON |
Cold anonymous pages |
Active(file) |
NR_ACTIVE_FILE |
Hot file-backed pages |
Inactive(file) |
NR_INACTIVE_FILE |
Cold file-backed pages |
Dirty |
NR_FILE_DIRTY |
Pages needing writeback |
Writeback |
NR_WRITEBACK |
Pages under writeback |
AnonPages |
NR_ANON_MAPPED |
Anonymous pages mapped to pagetables |
Mapped |
NR_FILE_MAPPED |
File pages mapped to pagetables |
Shmem |
NR_SHMEM |
Shared memory (tmpfs, SYSV SHM) |
Slab |
SReclaimable + SUnreclaim |
Kernel slab caches |
SReclaimable |
NR_SLAB_RECLAIMABLE_B |
Reclaimable slab (dcache, inode_cache) |
SUnreclaim |
NR_SLAB_UNRECLAIMABLE_B |
Non-reclaimable slab |
PageTables |
NR_PAGETABLE |
Page table pages |
NFS_Unstable |
0 (legacy) | Always 0 |
AnonPages vs Mapped
These two fields cause frequent confusion:
-
AnonPages (
NR_ANON_MAPPED): Anonymous pages (heap, stack, mmap'd anonymous memory) that are mapped into page tables. These pages are NOT backed by files and must be swapped to reclaim. -
Mapped (
NR_FILE_MAPPED): File-backed pages that are mapped into page tables (executables, libraries, mmap'd files). These pages can be reclaimed by dropping from page cache (if clean) or writing back (if dirty).
The key difference: Mapped pages have a backing file and can be re-read. AnonPages have no backing file and must go to swap.
"Free Memory Is Wasted Memory"
Linux's design philosophy: unused RAM is wasted RAM. The kernel aggressively caches files to improve performance.
The Page Cache Lifecycle
read() reaccess memory pressure
│ │ │
▼ ▼ ▼
┌─────────┐ populate ┌─────────┐ reclaim ┌─────────┐
│ Disk │ ───────────► │ Cache │ ──────────► │ Free │
└─────────┘ └─────────┘ └─────────┘
│
│ write()
▼
┌─────────┐
│ Dirty │ ──► writeback ──► Disk
└─────────┘
- First read: Page fetched from disk, stored in page cache (Cached++)
- Subsequent reads: Hit page cache (no disk I/O)
- Memory pressure: Clean pages dropped from cache (Cached--)
- Dirty pages: Written back to disk, then dropped
Why "Low Free Memory" Is Normal
- Page cache grows to fill available RAM
- Cached pages are the first to be reclaimed under memory pressure
- Clean file pages: instant reclaim (just drop from cache)
- The system performs BETTER with high cache, not worse
The Monitoring Fallacy
Alerting on "low free memory" is counterproductive. A healthy system has:
- Low
MemFree(most RAM is used for cache) - High
Cached(file content cached for fast access) - High
MemAvailable(cache can be reclaimed when needed) - Low swap I/O (no thrashing)
Better alerting metrics:
MemAvailable< 5-10% of total → memory pressure- Rising
si/soinvmstat→ swap thrashing - Rising
pgmajfault→ disk I/O from page faults - High
kswapdCPU → reclaim overhead
How to Read /proc/meminfo in Practice
Quick Memory Health Check
# The one-liner that matters
awk '/MemTotal|MemFree|MemAvailable|Cached|Buffers|SwapCached|Dirty|AnonPages|Shmem/ {
printf "%-15s %s kB\n", $1, $2
}' /proc/meminfoUnderstanding Memory Pressure
# Check page fault rate (minor = cache miss, major = disk I/O)
vmstat 1 5 | awk '{print "minor_faults:", $10, "major_faults:", $11}'
# Check page cache activity
cat /proc/vmstat | grep -E "pgpgin|pgpgout|pgfault|pgmajfault"
# Check kswapd activity
cat /proc/vmstat | grep -E "kswapd|direct_reclaim"Per-Process Memory Breakdown
# Detailed memory map for a process
cat /proc/<pid>/status | grep -E "VmSize|VmRSS|VmSwap|VmData|VmStk|VmExe|VmLib"
# Smaps for granular breakdown
cat /proc/<pid>/smaps | grep -E "^(Rss|Shared|Private|Swap|Pss):"
# Check which files a process has mmap'd
cat /proc/<pid>/maps | head -20Calculating Real Cache Hit Ratio
# Before and after workload, compare:
# pgpgin (pages read from disk) vs pgfault (total page faults)
# Hit ratio ≈ 1 - (pgpgin / pgfault)
cat /proc/vmstat | grep -E "pgpgin|pgfault"Frequently Asked Questions
Why is MemFree so low on my server?
This is normal and desirable. Linux uses available RAM for page cache. Low MemFree with high Cached means the kernel is effectively using memory to cache file I/O. Check MemAvailable for the real "how much can I use" number. A healthy server often has MemFree < 5% of total.
Should I worry about high Cached?
No. High Cached means the kernel is effectively caching file content. These pages are the first to be reclaimed when applications need memory. High Cached is a sign of healthy memory utilization — the kernel is doing its job.
What is the difference between Cached and SwapCached?
Cached = file content in page cache that is NOT in swap. SwapCached = pages that are BOTH in swap AND in memory (recently swapped in, swap copy kept as backup). They are mutually exclusive in the accounting. If a page is in both swap and memory, it counts toward SwapCached, not Cached.
Why does Buffers matter?
On modern systems, Buffers is usually small (most metadata uses slab caches, not buffer_head). But on systems with raw block I/O or many small files, Buffers can be significant. It tracks block device metadata (buffer_head), not file content.
What is Slab and why does it grow?
Slab is the kernel object cache (dcache, inode_cache, task_struct, etc.). SReclaimable can be shrunk under memory pressure (e.g., shrinking dentry cache). SUnreclaim cannot — it holds kernel metadata that must stay in memory. Slab grows as the kernel creates more objects (files opened, processes started).
How do I know if my system is running out of memory?
Watch MemAvailable (not MemFree). If MemAvailable drops below 5-10% of total RAM, the system is under memory pressure. Also watch for: rising swap I/O (si/so in vmstat), increasing major page faults (pgmajfault), and high kswapd CPU usage.
What is the difference between AnonPages and Mapped?
AnonPages counts anonymous pages (heap, stack, anonymous mmap) mapped into page tables. Mapped counts file-backed pages (executables, libraries, file mmap) mapped into page tables. AnonPages must be swapped to reclaim; Mapped can be re-read from disk.
Conclusion
/proc/meminfo is subtle. Buffers and Cache are not the same — they track different structures from different subsystems. Cached is a derived value (NR_FILE_PAGES minus swapcache minus buffers), not a direct counter. MemAvailable, not MemFree, is the metric that tells you how much memory your applications can actually use.
The "free memory is wasted memory" philosophy explains why Linux servers show low MemFree and high Cached. This is not a problem — it is the kernel doing its job. The page cache makes file I/O faster, and the kernel instantly reclaims cached pages when applications need memory.
For production monitoring, the practical takeaways are: alert on MemAvailable (not MemFree), watch for swap I/O (not swap usage), and understand that high Cached is a feature, not a bug.
Sources
- Linux kernel source,
fs/proc/meminfo.c,si_meminfo()and Cached calculation - Linux kernel source,
mm/page_alloc.c,si_memavailable() - Linux kernel source,
block/bdev.c,nr_blockdev_pages() - Linux kernel source,
mm/vmstat.c,global_node_page_state() - Linux kernel source,
mm/swap_state.c,total_swapcache_pages() - Linux kernel Documentation, admin-guide/mm/page_cache, https://www.kernel.org/doc/html/latest/admin-guide/mm/page_cache.html