Kernel Samepage Merging

Kernel Samepage Merging (KSM) is a memory-saving de-duplication feature of the Linux kernel: a background kernel thread named ksmd periodically scans regions of anonymous user memory that have been explicitly opted in, finds pages whose contents are byte-for-byte identical, and collapses them into a single write-protected shared page that every owner maps copy-on-write. The moment any owner writes, the normal copy-on-write fault makes that writer a private copy, so the sharing is transparent to correctness. KSM was originally built for KVM (where it was called “Kernel Shared Memory”) to pack more virtual machines into the same RAM by sharing the large quantities of identical pages — zeroed memory, identical guest kernels, shared libraries — that virtual machines accumulate (per the kernel KSM admin guide, CONFIG_KSM, added in 2.6.32). It is enabled by CONFIG_KSM=y and implemented in mm/ksm.c. Everything below is pinned to the Linux 6.12 LTS / 6.18 LTS source trees (6.18 released 2025-11-30) unless noted.

KSM trades CPU time for memory: scanning, hashing, and tree-walking cost cycles, and the per-page bookkeeping (rmap_item structures) costs memory, so on the wrong workload KSM can lose on both axes. It is therefore off by default and opt-in twice over — the global daemon must be told to run, and memory must be marked as a merge candidate. It is also a documented side-channel and Rowhammer attack surface, covered at the end.


Mental Model

Think of KSM as a continuous, background, content-addressed deduplicator for anonymous RAM, structured around two red-black trees. The stable tree holds pages KSM has already merged — these are write-protected, so their contents cannot change, which makes the tree stable and permanently searchable. The unstable tree is a speculative staging area for pages that look identical but are not yet write-protected, so their contents could still change underneath KSM; it is therefore thrown away and rebuilt on every full scan. A candidate page is first looked up in the stable tree (can I join an existing shared page?); failing that, in the unstable tree (did I see an identical not-yet-merged page earlier this pass?); failing that, it is inserted into the unstable tree to wait for a future twin.

flowchart TD
  CAND["Candidate anonymous page<br/>(in a MADV_MERGEABLE VMA)"]
  CKSUM{"checksum changed<br/>since last scan?"}
  STABLE{"match in<br/>STABLE tree?<br/>(already-merged, write-protected)"}
  UNSTABLE{"match in<br/>UNSTABLE tree?<br/>(speculative, rebuilt each pass)"}
  MERGE["merge into existing<br/>shared KSM page<br/>(remap COW, free duplicate)"]
  NEWSHARE["create new shared page,<br/>write-protect both,<br/>promote to stable tree"]
  INSERT["insert into unstable tree<br/>(wait for a future twin)"]
  SKIP["skip / leave volatile<br/>(too unstable to merge)"]

  CAND --> CKSUM
  CKSUM -->|"yes, volatile"| SKIP
  CKSUM -->|"no, stable enough"| STABLE
  STABLE -->|"hit"| MERGE
  STABLE -->|"miss"| UNSTABLE
  UNSTABLE -->|"hit"| NEWSHARE
  UNSTABLE -->|"miss"| INSERT

Figure: the per-page decision ksmd makes each scan. What it shows: the stable tree is consulted first because joining an existing write-protected page is the cheapest, safest merge; the unstable tree is the speculative second chance; a page whose checksum keeps changing is “volatile” and left alone. The insight: KSM never blindly merges — it filters out fast-changing pages with a cheap checksum before paying for a full memcmp-based tree walk, because merging a page that is about to be written would just trigger an immediate copy-on-write break and waste the effort.


Mechanical Walk-through

Opting memory in: MADV_MERGEABLE

KSM does not scan all of memory. An application marks a range as a merge candidate with the madvise(2) call madvise(addr, length, MADV_MERGEABLE) (since Linux 2.6.32). This sets the VM_MERGEABLE flag on the affected VMAs and registers the address space with ksmd. MADV_UNMERGEABLE reverses it and un-merges whatever KSM had merged in that range — and the docs warn this un-merge “may suddenly require more memory than is available,” possibly failing with EAGAIN or, worse, waking the OOM killer, because every shared page now needs its own private copy (ksm.rst, v6.12).

Two important constraints. First, KSM only merges anonymous (private) pages, never page-cache (file) pages — file pages backed by the same inode are already shared through the page cache, so there is nothing for KSM to add. Second, if the kernel lacks CONFIG_KSM, MADV_MERGEABLE/MADV_UNMERGEABLE fail with EINVAL; if CONFIG_KSM=y they succeed even when ksmd is not running (the range is simply registered for whenever the daemon starts), and even on ranges that contain nothing mergeable. A range split that would push the process past vm.max_map_count returns ENOMEM.

The daemon’s scan loop

ksmd runs as a kernel thread. Each wake-up it scans up to pages_to_scan pages of registered memory, then sleeps sleep_millisecs milliseconds. For each candidate page it computes a checksum; if the checksum differs from the previous scan, the page is “volatile” (changing too fast to be worth merging) and is skipped — this is the cheap pre-filter before any expensive comparison. A page whose checksum is stable is then looked up in the stable tree and, on a miss, the unstable tree, using a full memcmp of page contents as the tree comparator (the page is treated as one enormous integer and sorted accordingly, per LWN’s “KSM tries again”).

The use of memcmp-ordered red-black trees instead of a hash table is a deliberate historical design choice. The earliest KSM (2008–2009) hashed page contents (SHA-1), but that raised two problems: a hash collision could in principle let an attacker inject content into another VM’s page, and VMware held patent 6,789,156 whose claims mostly involved hashing. Replacing the hash with two memcmp-sorted rbtrees killed both birds — no collision risk, and the patent’s hash-centric claims no longer read against the code (LWN 330589).

The two trees in detail

  • Unstable tree. Holds pages KSM is speculating are mergeable but has not write-protected. Because their contents can still change, a page can drift to the “wrong” position in the tree. KSM accepts this by rebuilding the unstable tree from scratch every full scan. The cost is low because in a red-black tree, search and insert are nearly the same operation, so re-inserting everything each pass is cheap (LWN 330589). A page seen to change during a scan cycle is excluded entirely — a bad merge candidate.
  • Stable tree. Holds pages KSM has already merged and write-protected. Since their contents are immutable, they never become misplaced, so the stable tree is never rebuilt. A page stays in the stable tree until all of its users either write to it (breaking COW) or unmap it.

What a merge actually does

When ksmd finds two identical pages, it picks one physical frame to be the canonical KSM page, write-protects it, repoints the second mapping’s PTE at the canonical frame (also write-protected), frees the now-redundant frame, and records reverse-mapping information so it can later find every mapping of the shared page. From then on, any write to either mapping faults; the COW handler allocates a fresh private copy for the writer and removes it from the share. KSM’s merged pages were originally pinned in kernel memory, but modern KSM pages can be swapped out like other user pages — though “sharing is broken when they are swapped back in: ksmd must rediscover their identity and merge again” (ksm.rst).

Reverse mapping, max_page_sharing, and stable-node chains

The cost that bounds KSM is the reverse-map (rmap) walk. Operations like swapping, compaction, NUMA balancing, and page migration must, for a given physical page, find and update every virtual mapping that points at it. For a heavily-shared KSM page that is an O(N) walk over N sharers. To cap the worst case, KSM enforces a per-page sharing limit, max_page_sharing. Below the limit, the stable-tree node points at a simple list of ksm_rmap_items. When sharing crosses the limit, KSM adds a second dimension: the node becomes a “chain” linking one or more “dups,” each dup being a separate physical KSM-page copy of the same content, each carrying its own bounded rmap list (mm/ksm.rst design doc, v6.12). This spreads the linear rmap cost across multiple physical pages instead of one unbounded list, keeping the worst-case mapping traversal — which can stall swapping, compaction, NUMA balancing, and migration — bounded. Higher max_page_sharing means faster merging and a higher dedup factor but a slower worst-case rmap walk; the minimum is 2 (a fresh KSM page already has two sharers). Stale stable-node dups are pruned periodically, every stable_node_chains_prune_millisecs.


Configuration: /sys/kernel/mm/ksm/

All knobs live under /sys/kernel/mm/ksm/, world-readable but root-writable. Values and defaults below are from the v6.12 admin guide (ksm.rst).

Activation and pacing:

echo 1   > /sys/kernel/mm/ksm/run             # 0=stop but keep merges; 1=run; 2=stop AND unmerge all
echo 100 > /sys/kernel/mm/ksm/pages_to_scan   # pages scanned before ksmd sleeps (default 100)
echo 20  > /sys/kernel/mm/ksm/sleep_millisecs # sleep between passes in ms (default 20)
  • rundefault 0. KSM does nothing until you set it to 1. Setting it to 2 stops the daemon and un-merges everything currently merged (the dangerous memory-balloon case above), while leaving registered ranges in place for the next run.
  • pages_to_scan (default 100) and sleep_millisecs (default 20) jointly set throughput; both defaults are explicitly “chosen for demonstration purposes,” i.e. deliberately conservative. pages_to_scan cannot be written while the scan-time advisor (below) is enabled.

Sharing and placement policy:

  • merge_across_nodesdefault 1. When 0, KSM merges only pages physically resident on the same NUMA node, giving lower access latency on multi-node machines at the cost of less sharing. Can only be changed when there are no KSM-shared pages (set run to 2 first to unmerge).
  • max_page_sharing — the per-page dedup ceiling described above (minimum 2).
  • stable_node_chains_prune_millisecs — how often stale stable-node metadata is pruned; a no-op until at least one page hits max_page_sharing.
  • use_zero_pagesdefault 0. When 1, all-zero pages are merged with the kernel zero page rather than with each other, which can help on architectures with coloured zero pages but can hurt if non-zero pages happen to checksum like a zero page. Effective only for pages merged after the change.

smart_scan (since Linux 6.7):

cat /sys/kernel/mm/ksm/smart_scan      # 1 = enabled (default)
cat /sys/kernel/mm/ksm/pages_skipped   # how many pages smart-scan skipped

Historically ksmd re-checked every candidate every pass. smart_scan (commit 5e924ff54d08, “mm/ksm: add ‘smart’ page scanning mode,” merged for 6.7 via mm-stable 2023-11-01) tracks per-page history and skips pages that have repeatedly failed to deduplicate, skipping them more often the more times de-dup has failed; the pages_skipped metric reports its effectiveness. Enabled by default.

The scan-time advisor (since Linux 6.8):

The number of merge candidates is bursty — application startup floods KSM with candidates, then quiesces. Without help you must size pages_to_scan for the peak. The advisor (added in 6.8; commit 4e5fa4f5eff6, “mm/ksm: add ksm advisor,” merged 2023-12-18 for the 6.8 window) auto-tunes pages_to_scan from observed scan times:

echo scan-time > /sys/kernel/mm/ksm/advisor_mode   # default "none"; other value: scan-time
cat /sys/kernel/mm/ksm/advisor_max_cpu             # CPU% ceiling for ksmd (default 70)
cat /sys/kernel/mm/ksm/advisor_target_scan_time    # target seconds to scan all candidates (default 200)
cat /sys/kernel/mm/ksm/advisor_min_pages_to_scan   # lower bound on pages_to_scan (default 500)
cat /sys/kernel/mm/ksm/advisor_max_pages_to_scan   # upper bound (default 30000)

advisor_target_scan_time is the key knob: lower values scan more aggressively. With advisor_mode=scan-time, pages_to_scan is recomputed after each completed scan and becomes read-only.

Effectiveness counters (read-only): general_profit (net memory benefit, formula below), pages_scanned, pages_shared (number of distinct shared pages in use), pages_sharing (number of additional mappings sharing them — the actual savings), pages_unshared (unique pages repeatedly re-checked in vain), pages_volatile (pages changing too fast to tree), pages_skipped (smart-scan skips), full_scans (count of complete passes), stable_node_chains (pages that hit max_page_sharing), stable_node_dups, and ksm_zero_pages (still-mapped zero pages KSM produced when use_zero_pages was on). A high pages_sharing/pages_shared ratio means good sharing; a high pages_unshared/pages_sharing or pages_volatile ratio means wasted effort and a poorly-targeted MADV_MERGEABLE.

Profit accounting. KSM’s per-page rmap_item bookkeeping costs memory, so it can lose net. The system-wide estimate is general_profit ≈ ksm_saved_pages × sizeof(page) − all_rmap_items × sizeof(rmap_item), where ksm_saved_pages = pages_sharing + ksm_zero_pages and all_rmap_items = pages_sharing + pages_shared + pages_unshared + pages_volatile. An rmap_item is ~64 bytes on 64-bit (32 bytes on 32-bit) against a 4 KiB page, so if the per-process ksm_rmap_items/ksm_merging_pages ratio exceeds ~64 (64-bit) the merge policy is net-negative and should be dropped (ksm.rst profit section).

Per-process opt-in: prctl(PR_SET_MEMORY_MERGE)

Requiring every allocation to be wrapped in madvise(MADV_MERGEABLE) is awkward for an orchestrator that just wants “merge everything this process owns.” Since Linux 6.4, a process can opt its entire address space in with one prctl(2) call (PR_SET_MEMORY_MERGE = 67, PR_GET_MEMORY_MERGE = 68, confirmed present in both v6.12 and v6.18 and absent in v6.3):

#include <sys/prctl.h>
/* Opt every present and future mergeable VMA of this process into KSM. */
prctl(PR_SET_MEMORY_MERGE, 1, 0, 0, 0);
/* Query current state (returns 0 or 1). */
int on = prctl(PR_GET_MEMORY_MERGE, 0, 0, 0, 0);
/* Turn it back off. */
prctl(PR_SET_MEMORY_MERGE, 0, 0, 0, 0);

This marks all current and future anonymous VMAs of the calling process mergeable without per-mapping madvise calls — exactly what a container runtime or VM monitor wants. The setting is inherited semantics-wise across the process’s mappings (a system-wide policy lever rather than a region hint).

Uncertain

Verify: the precise fork/exec inheritance semantics of PR_SET_MEMORY_MERGE (does the flag survive fork() to children, and is it cleared on execve()?) and whether it is gated by any capability. Reason: the prctl(2) man page version fetched here does not document PR_SET_MEMORY_MERGE/PR_GET_MEMORY_MERGE at all, so inheritance/permission details were not confirmed against a primary man-page source — the constants were verified only from include/uapi/linux/prctl.h. To resolve: read kernel/sys.c / mm/ksm.c handling of PR_SET_MEMORY_MERGE at the v6.12/v6.18 tag, or an updated prctl(2) man page that documents it. uncertain

Per-process statistics: /proc/<pid>/ksm_stat

Per-process KSM accounting lives in /proc/<pid>/ksm_stat, exposing ksm_merging_pages, ksm_rmap_items, ksm_process_profit, and (when use_zero_pages is/was used) ksm_zero_pages, used by the per-process profit formula above. /proc/vmstat also carries event counters: cow_ksm increments each time writing a KSM page forces a COW copy, and ksm_swpin_copy increments when a KSM page is copied on swap-in (because do_swap_page() cannot take all the locks needed to reconstitute a cross-anon_vma KSM page).


Failure Modes and Common Misunderstandings

  • “KSM is on, why is nothing merging?” run defaults to 0 and memory must be opted in (MADV_MERGEABLE or the prctl). Both are required. A daemon running over zero registered ranges does nothing.
  • CPU burn for little gain. On workloads with few duplicates, ksmd spends cycles checksumming and walking trees while pages_volatile/pages_unshared stay high and pages_sharing stays low — pure overhead. The admin guide explicitly notes “some installations will disable KSM for that reason.” Watch general_profit and the sharing ratios.
  • The un-merge memory bomb. MADV_UNMERGEABLE or echo 2 > run must give every shared page its own copy right now. If the savings were large and free memory is tight, this can fail with EAGAIN or trigger the OOM killer. Never un-merge a large KSM deployment on a memory-pressured box without headroom.
  • Swap breaks sharing. When a KSM page is swapped out and back in, the identity is lost until ksmd rediscovers and re-merges it — a transient memory-usage spike and ksm_swpin_copy increments.
  • Write-heavy pages thrash COW. If merged pages are written soon after merging, each write breaks COW (cow_ksm climbs), so KSM merges then immediately un-merges — the smart-scan and volatility checksum exist precisely to avoid this, but a poorly-chosen MADV_MERGEABLE range defeats them.

Security: The Side-Channel and Rowhammer Risk

KSM’s correctness is transparent, but its timing is not. Because a write to a merged (shared, write-protected) page is slow — it triggers a copy-on-write fault and a page copy — while a write to an unmerged page is fast, an attacker who can write a page and measure how long the write took can infer whether the kernel had merged that page with an identical one elsewhere on the machine. That turns KSM into an oracle: an attacker controlling one process (or VM, or even JavaScript in a browser) can craft a guess page, wait for KSM to merge it with a victim’s page if their contents match, then time a write to detect the merge — leaking the victim’s secret content one guessed page at a time across an isolation boundary.

This is not theoretical. Dedup Est Machina (Bosman, Razavi, Bos, Giuffrida — IEEE S&P 2016, VUSec) chained exactly this memory-deduplication timing side channel with Rowhammer to mount an end-to-end exploit of Microsoft Edge on Windows 10 from JavaScript, relying on no software vulnerability; it won the Pwnie Award for Most Innovative Research at Black Hat USA 2016. Microsoft’s response was to disable memory deduplication by default (tracked as CVE-2016-3272), per the VUSec project page. The takeaway for Linux: KSM is safe between mutually trusting processes (the VM-density case it was built for) but is a genuine cross-tenant information-leak and fault-injection amplifier in adversarial multi-tenant settings — which is precisely why KSM is off by default and why the per-process prctl opt-in exists, so an operator can scope merging to a trust domain instead of globally.

Uncertain

Verify: the exact current default and recommended posture for KSM in public-cloud / multi-tenant Linux hypervisors (e.g. whether major cloud KVM stacks enable KSM, and any KSM-specific CVE beyond the Windows CVE-2016-3272 referenced here). Reason: the side-channel mechanism is well-established primary research (Dedup Est Machina, S&P 2016), but the current operational guidance is dated and was not pinned to a 2025-era primary source in this pass. To resolve: consult current cloud-provider hardening guides and recent LWN coverage. uncertain


Alternatives and When to Choose Them

  • Page-cache sharing. Identical file-backed pages are already shared via the page cache keyed by inode — no KSM needed. KSM exists only for anonymous memory, where there is no inode to key on.
  • Transparent / explicit huge pages. THP reduce page-table and TLB pressure but do not deduplicate; KSM and huge pages are orthogonal (and KSM works on base pages, so heavy THP use reduces KSM’s opportunity surface).
  • Compressed memory: zswap / zram. These compress reclaimed/anonymous pages instead of deduplicating identical ones — a different CPU-for-memory trade that does not leak a merge-timing oracle. On adversarial multi-tenant hosts, compression is generally the safer density lever.
  • Ballooning. Hypervisor memory ballooning reclaims guest memory cooperatively; complementary to, not a replacement for, KSM.

Choose KSM when you control all tenants (single-trust-domain VM consolidation, many identical containers) and have CPU headroom; avoid it, or scope it tightly with the prctl, on adversarial multi-tenant hosts.


Production Notes

KSM’s headline use remains VM density on KVM: many guests booting the same kernel and libraries share huge amounts of identical anonymous memory, and KSM can reclaim a large fraction of it. Userspace daemons like ksmtuned were historically used to ramp run/pages_to_scan up under memory pressure and down when idle; the in-kernel scan-time advisor (6.8+) now subsumes much of that tuning. For containerized fleets, PR_SET_MEMORY_MERGE (6.4+) lets a runtime opt entire workloads in without touching application madvise calls — but the same operator must weigh the side-channel exposure if those containers belong to different tenants. Monitor general_profit and the pages_sharing/pages_shared ratio to confirm KSM is actually winning, and cow_ksm to catch write-heavy ranges that thrash COW.


See Also