DAMON: Data Access Monitoring
A lightweight, accurate, and scalable kernel framework for observing how memory is accessed — and acting on that knowledge
What Is DAMON?
DAMON (Data Access MONitoring) is a kernel subsystem that tracks how frequently each memory region is accessed at runtime. It is not a reclaim policy on its own. Instead, it is a monitoring framework that produces access-frequency data which other subsystems — most importantly DAMOS (DAMON-based Operation Schemes) — can act on.
The key design goals are:
- Accuracy: access frequency is tracked per-region, not per-page, but remains close enough to reality to drive useful decisions
- Scalability: works across address spaces of arbitrary size without O(n) page-scan cost
- Adaptivity: regions grow and shrink automatically to focus monitoring effort where it matters
Without DAMON: With DAMON:
┌──────────────────────────────┐
Kernel reclaims pages based on │ Kernel observes access │
LRU age alone — no knowledge of │ patterns over time, then │
which regions are truly cold. │ targets cold regions │
│ precisely via DAMOS │
└──────────────────────────────┘
DAMON vs. DAMOS
DAMON collects data. DAMOS uses that data to trigger actions (page out cold memory, promote hot memory, etc.). You configure both together via the sysfs interface.
Core Concepts
Regions: The Unit of Monitoring
DAMON divides a monitoring target's address space into a list of struct damon_region objects. Each region represents a contiguous address range [ar.start, ar.end) and carries:
nr_accesses— how many sampling intervals detected an access in the current aggregation windownr_accesses_bp— a basis-point (1/10,000) moving-average version updated every sample interval, useful when you cannot wait for a full aggregation to elapseage— how many aggregation intervals the region's access frequency has stayed roughly the same; resets to zero when access frequency changes significantlysampling_addr— the specific address within the region that will be checked for access next interval
For virtual address monitoring (DAMON_OPS_VADDR), DAMON initialises regions from the process's VMAs. For physical address monitoring (DAMON_OPS_PADDR), regions are initialised to cover the target physical address range.
Sampling: Catching Accesses
Every sample_interval microseconds (field damon_attrs.sample_interval), the kdamond thread:
- Checks whether
sampling_addrwithin each region was accessed since the last sample (by testing and clearing the accessed/young bit in the page table entry) - Increments
nr_accessesif an access was found - Picks a new
sampling_addrrandomly within the region for the next interval
The default sample_interval is 5,000 µs (5 ms).
Aggregation: Building the Picture
Every aggr_interval microseconds (damon_attrs.aggr_interval), DAMON:
- Reports (or acts on) the accumulated
nr_accessescounts for each region - Resets
nr_accessesto zero - Updates each region's
age - Runs the adaptive region sizing logic (split/merge)
The default aggr_interval is 100,000 µs (100 ms), meaning each reported nr_accesses value represents accesses observed over the last 20 sampling intervals (100 ms / 5 ms).
Adaptive Region Sizing
DAMON maintains damon_attrs.min_nr_regions and damon_attrs.max_nr_regions bounds (defaults: 10 and 1,000). After each aggregation it adjusts regions:
- Split: a region that has been accessed unevenly (some sub-ranges hot, others cold) is split into smaller regions to capture the gradient
- Merge: adjacent regions with similar access frequencies are merged to reduce overhead
This keeps the total region count within bounds while concentrating resolution where access patterns vary.
Minimum region size
DAMON_MIN_REGION_SZ is PAGE_SIZE. No region can be smaller than one page. The field damon_ctx.min_region_sz stores this lower bound.
Intervals Auto-tuning
DAMON can automatically tune sample_interval and aggr_interval together. The goal is expressed as a struct damon_intervals_goal:
| Field | Meaning |
|---|---|
access_bp |
Target ratio of observed access events to theoretical maximum, in basis points (1/10,000) |
aggrs |
Number of aggregation windows over which to achieve the ratio |
min_sample_us |
Lower bound on the resulting sample interval |
max_sample_us |
Upper bound on the resulting sample interval |
If aggrs is zero, auto-tuning is disabled. Via sysfs, these fields appear under monitoring_attrs/intervals/intervals_goal/.
Architecture
graph TD
A[User space / kernel module] -->|sysfs write / API call| B[damon_ctx]
B --> C[kdamond thread\ntask_struct]
C --> D{ops_id}
D -->|DAMON_OPS_VADDR| E[vaddr.c\nVMA-based ops]
D -->|DAMON_OPS_FVADDR| F[vaddr.c\nFixed-range vaddr ops]
D -->|DAMON_OPS_PADDR| G[paddr.c\nPhysical address ops]
E & F & G --> H[ops-common.c\ndamon_get_folio\ndamon_ptep_mkold\ndamon_folio_mkold]
C --> I[damon_target list\nadaptive_targets]
I --> J[damon_region list\nregions_list]
J --> K[nr_accesses\nnr_accesses_bp\nage]
C --> L[damos list\nschemes]
L --> M[DAMOS engine\ncore.c]
M -->|action| N[DAMOS_PAGEOUT\nDAMOS_LRU_PRIO\nDAMOS_MIGRATE_COLD\n...]
The damon_ctx Structure
Every monitoring session is anchored by a struct damon_ctx (include/linux/damon.h):
struct damon_ctx {
struct damon_attrs attrs; /* sample/aggr/update intervals, nr_regions */
struct damon_operations ops; /* pluggable ops set (vaddr/paddr) */
unsigned long addr_unit; /* scale factor for core-to-ops address conversion */
unsigned long min_region_sz; /* minimum region size (DAMON_MIN_REGION_SZ) */
struct list_head adaptive_targets;/* list of damon_target */
struct list_head schemes; /* list of damos */
struct task_struct *kdamond; /* the monitoring thread */
/* ... private fields ... */
};
The kdamond Thread
Each damon_ctx gets exactly one kernel thread — kdamond. Its main loop (in mm/damon/core.c) runs as:
while (!should_stop):
ops.prepare_access_checks(ctx) # mark pages as old (clear accessed bits)
sleep(sample_interval)
ops.check_accesses(ctx) # re-read accessed bits → nr_accesses
if aggregation_interval_elapsed:
report / apply DAMOS schemes
adaptive_merge_regions()
adaptive_split_regions()
reset nr_accesses
if ops_update_interval_elapsed:
ops.update(ctx) # refresh VMAs, etc.
The thread is started by damon_start() and stopped by damon_stop(). While running, all external access to damon_ctx internals must go through damon_call() or damos_walk() to serialize with the kdamond loop.
Operations Sets (damon_operations)
DAMON separates the monitoring algorithm from address-space specifics via struct damon_operations:
struct damon_operations {
enum damon_ops_id id; /* DAMON_OPS_VADDR / _FVADDR / _PADDR */
void (*init)(struct damon_ctx *context);
void (*update)(struct damon_ctx *context);
void (*prepare_access_checks)(struct damon_ctx *context);
unsigned int (*check_accesses)(struct damon_ctx *context);
int (*get_scheme_score)(struct damon_ctx *context,
struct damon_target *t,
struct damon_region *r,
struct damos *scheme);
unsigned long (*apply_scheme)(struct damon_ctx *context,
struct damon_target *t,
struct damon_region *r,
struct damos *scheme,
unsigned long *sz_filter_passed);
bool (*target_valid)(struct damon_target *t);
void (*cleanup_target)(struct damon_target *t);
};
damon_ops_id |
sysfs name | Description |
|---|---|---|
DAMON_OPS_VADDR |
vaddr |
Monitors a process's virtual address space; initialises regions from VMAs; requires CONFIG_DAMON_VADDR |
DAMON_OPS_FVADDR |
fvaddr |
Like vaddr but monitors only user-specified fixed address ranges, not the full VMA set |
DAMON_OPS_PADDR |
paddr |
Monitors the physical address space system-wide; requires CONFIG_DAMON_PADDR; used by DAMON_RECLAIM and DAMON_LRU_SORT |
Both vaddr and paddr use the page idle flag (PAGE_IDLE_FLAG) to detect accesses without modifying the page table's dirty bit.
DAMOS: Data Access Monitoring-based Operation Schemes
DAMOS is the policy engine layered on top of DAMON's monitoring data. A DAMOS scheme (struct damos) specifies:
- Which regions to act on (access pattern filter)
- What action to take
- How aggressively to act (quota)
- Under what system conditions to activate (watermarks)
Access Pattern
struct damos_access_pattern selects regions whose metrics fall within all of these ranges:
| Field | Meaning |
|---|---|
min_sz_region / max_sz_region |
Region byte size range |
min_nr_accesses / max_nr_accesses |
Observed accesses in the last aggregation window |
min_age_region / max_age_region |
Number of aggregation intervals the pattern has been stable |
A cold region that has never been accessed since DAMON started would have nr_accesses == 0 and a large age. A frequently-accessed hot region has high nr_accesses and resets its age often.
Actions
The enum damos_action values and their effects:
| Action | Effect |
|---|---|
DAMOS_WILLNEED |
madvise(MADV_WILLNEED) — prefetch the region |
DAMOS_COLD |
madvise(MADV_COLD) — deactivate pages (move toward LRU tail) |
DAMOS_PAGEOUT |
Reclaim (page out) the region |
DAMOS_HUGEPAGE |
madvise(MADV_HUGEPAGE) — promote to THP |
DAMOS_NOHUGEPAGE |
madvise(MADV_NOHUGEPAGE) — demote from THP |
DAMOS_LRU_PRIO |
Prioritize region on its LRU lists (move to active) |
DAMOS_LRU_DEPRIO |
Deprioritize region on its LRU lists (move to inactive) |
DAMOS_MIGRATE_HOT |
Migrate region to a faster NUMA node (warmer regions first) |
DAMOS_MIGRATE_COLD |
Migrate region to a slower NUMA node (colder regions first) |
DAMOS_STAT |
No action — only update statistics counters |
DAMOS_PAGEOUT does not demote
DAMOS_PAGEOUT reclaims pages to swap. It does not demote to a slower NUMA tier. For tiered-memory demotion use DAMOS_MIGRATE_COLD.
Quotas
Without limits, DAMOS could apply its action to gigabytes of memory per second. struct damos_quota rate-limits it:
| Field | Meaning |
|---|---|
reset_interval |
Window for quota accounting, in milliseconds |
ms |
Maximum CPU time (milliseconds) the scheme may spend per reset_interval |
sz |
Maximum bytes the action may be applied to per reset_interval |
esz |
Effective size quota (computed internally from ms, sz, and any goals) |
weight_sz |
Prioritization weight for region size |
weight_nr_accesses |
Prioritization weight for access frequency |
weight_age |
Prioritization weight for region age |
When a quota is reached, DAMON stops applying the action for the remainder of the reset_interval and increments damos_stat.qt_exceeds.
Quota Auto-tuning with Goals
Instead of (or in addition to) hard ms/sz caps, DAMOS can auto-tune the effective quota using a feedback loop. Goals are expressed as struct damos_quota_goal objects. Available goal metrics (enum damos_quota_goal_metric):
| sysfs name | Meaning |
|---|---|
user_input |
Manually supplied feedback value (user writes current_value) |
some_mem_psi_us |
System-level "some" memory PSI in µs per reset interval |
node_mem_used_bp |
Memory used ratio of a NUMA node in basis points |
node_mem_free_bp |
Memory free ratio of a NUMA node in basis points |
node_memcg_used_bp |
Memory used ratio of a NUMA node for a specific cgroup |
node_memcg_free_bp |
Memory free ratio of a NUMA node for a specific cgroup |
active_mem_bp |
Active-to-total LRU memory ratio in basis points |
inactive_mem_bp |
Inactive-to-total LRU memory ratio in basis points |
DAMOS increases or decreases the effective quota so that the observed metric value converges toward target_value.
Watermarks
struct damos_watermarks controls whether a scheme is active based on a system-level metric:
| Field | Meaning |
|---|---|
metric |
none (always active) or free_mem_rate |
interval |
How often to check the metric, in µs |
high |
Deactivate scheme when metric is above this value |
mid |
Activate scheme when metric is between mid and low |
low |
Deactivate scheme when metric falls below this value |
When all schemes of a context are inactive (watermarks not met), kdamond stops monitoring and only polls the watermarks — greatly reducing overhead.
DAMOS Filters
Before applying an action to a region, DAMOS can test individual pages within the region against filters (struct damos_filter). Filter types (enum damos_filter_type):
| sysfs name | Type | Scope |
|---|---|---|
anon |
Anonymous pages | ops layer |
active |
Active LRU pages | core layer |
memcg |
Pages belonging to a specific cgroup | ops layer |
young |
Recently accessed (young) pages | core layer |
hugepage_size |
Pages that are part of a huge page | core layer |
unmapped |
Unmapped pages | core layer |
addr |
Specific address range | core layer |
target |
Specific DAMON target index | core layer |
Each filter has a matching bool (apply to matching pages vs. non-matching) and an allow bool (include or exclude the matched pages).
Filter handling layers
Filters in the "ops layer" (anon, memcg) are handled by the damon_operations implementation and are counted toward sz_tried statistics. Core-layer filters are evaluated before sz_tried is incremented.
The sysfs Interface
CONFIG_DAMON_SYSFS exposes the full DAMON API under /sys/kernel/mm/damon/admin/. All monitoring is controlled by reading and writing files in this hierarchy.
Directory Tree
/sys/kernel/mm/damon/admin/
└── kdamonds/
├── nr_kdamonds # write N to create N kdamond directories
└── 0/
├── state # write: on|off|commit|update_schemes_stats|...
├── pid # read: PID of the kdamond thread
├── refresh_ms # auto-refresh interval for sysfs reads
└── contexts/
├── nr_contexts # currently only 1 context per kdamond
└── 0/
├── avail_operations # read: available ops (vaddr fvaddr paddr)
├── operations # write: vaddr | fvaddr | paddr
├── addr_unit # scale factor for address values
├── monitoring_attrs/
│ ├── intervals/
│ │ ├── sample_us # sampling interval in µs (default 5000)
│ │ ├── aggr_us # aggregation interval in µs (default 100000)
│ │ ├── update_us # ops update interval in µs (default 60000000)
│ │ └── intervals_goal/
│ │ ├── access_bp
│ │ ├── aggrs
│ │ ├── min_sample_us
│ │ └── max_sample_us
│ └── nr_regions/
│ ├── min # minimum number of regions (default 10)
│ └── max # maximum number of regions (default 1000)
├── targets/
│ ├── nr_targets
│ └── 0/
│ ├── pid_target # PID to monitor (vaddr/fvaddr only)
│ ├── obsolete_target
│ └── regions/
│ ├── nr_regions # for fvaddr: number of fixed regions
│ └── 0/
│ ├── start # region start address
│ └── end # region end address
└── schemes/
├── nr_schemes
└── 0/
├── action # willneed|cold|pageout|hugepage|nohugepage|
│ # lru_prio|lru_deprio|migrate_hot|migrate_cold|stat
├── target_nid # destination NUMA node for migrate actions
├── apply_interval_us # override aggr_interval for this scheme
├── access_pattern/
│ ├── sz/
│ │ ├── min
│ │ └── max
│ ├── nr_accesses/
│ │ ├── min
│ │ └── max
│ └── age/
│ ├── min
│ └── max
├── quotas/
│ ├── ms
│ ├── bytes
│ ├── reset_interval_ms
│ ├── effective_bytes # read-only: current effective quota
│ ├── weights/
│ │ ├── sz_permil
│ │ ├── nr_accesses_permil
│ │ └── age_permil
│ └── goals/
│ ├── nr_goals
│ └── 0/
│ ├── target_metric
│ ├── target_value
│ ├── current_value
│ ├── nid
│ └── path
├── watermarks/
│ ├── metric # none | free_mem_rate
│ ├── interval_us
│ ├── high
│ ├── mid
│ └── low
├── filters/ # core + ops filters combined
│ ├── nr_filters
│ └── 0/
│ ├── type # anon|active|memcg|young|hugepage_size|
│ │ # unmapped|addr|target
│ ├── matching
│ ├── allow
│ ├── memcg_path
│ ├── addr_start
│ ├── addr_end
│ ├── min # for hugepage_size filter
│ ├── max
│ └── damon_target_idx
├── dests/ # for migrate_hot/cold actions
│ ├── nr_dests
│ └── 0/
│ ├── id # destination NUMA node id
│ └── weight
├── stats/ # read-only counters
│ ├── nr_tried
│ ├── sz_tried
│ ├── nr_applied
│ ├── sz_applied
│ ├── sz_ops_filter_passed
│ ├── qt_exceeds
│ ├── nr_snapshots
│ └── max_nr_snapshots
└── tried_regions/ # regions that matched this scheme
├── total_bytes
└── 0/
├── start
├── end
├── nr_accesses
├── age
└── sz_filter_passed
kdamond State Commands
Write one of these strings to kdamonds/0/state:
| Command | Effect |
|---|---|
on |
Start the kdamond thread |
off |
Stop the kdamond thread |
commit |
Apply updated parameters to a running kdamond |
commit_schemes_quota_goals |
Push updated quota goal current_value fields to the kernel |
update_schemes_stats |
Refresh the stats/ files from kernel state |
update_schemes_tried_bytes |
Refresh tried_regions/total_bytes |
update_schemes_tried_regions |
Populate tried_regions/ with current matching regions |
clear_schemes_tried_regions |
Remove all entries from tried_regions/ |
update_schemes_effective_quotas |
Refresh quotas/effective_bytes |
update_tuned_intervals |
Refresh intervals/ with auto-tuned values |
Practical Examples
1. Proactive Reclaim: Page Out Cold Pages
This example sets up a physical-address DAMOS scheme that pages out memory regions not accessed for approximately 120 seconds. It mirrors what CONFIG_DAMON_RECLAIM does internally.
#!/bin/bash
ADMIN=/sys/kernel/mm/damon/admin
# Create one kdamond with one context
echo 1 > $ADMIN/kdamonds/nr_kdamonds
echo 1 > $ADMIN/kdamonds/0/contexts/nr_contexts
# Use the physical address space ops (system-wide, no PID needed)
echo paddr > $ADMIN/kdamonds/0/contexts/0/operations
# Tune monitoring intervals
echo 5000 > $ADMIN/kdamonds/0/contexts/0/monitoring_attrs/intervals/sample_us
echo 100000 > $ADMIN/kdamonds/0/contexts/0/monitoring_attrs/intervals/aggr_us
# One target (paddr needs exactly one target; no pid_target needed)
echo 1 > $ADMIN/kdamonds/0/contexts/0/targets/nr_targets
# Create one DAMOS scheme
echo 1 > $ADMIN/kdamonds/0/contexts/0/schemes/nr_schemes
SCHEME=$ADMIN/kdamonds/0/contexts/0/schemes/0
# Action: page out
echo pageout > $SCHEME/action
# Access pattern: size [4KB, unlimited], nr_accesses [0,0], age [24000,max]
# age 24000 = 24000 aggr intervals × 100ms = 2,400,000ms ≈ 2400s ... too long
# Better: age in aggr intervals. 120s / 100ms = 1200 aggregation intervals
echo 4096 > $SCHEME/access_pattern/sz/min
echo 18446744073709551615 > $SCHEME/access_pattern/sz/max
echo 0 > $SCHEME/access_pattern/nr_accesses/min
echo 0 > $SCHEME/access_pattern/nr_accesses/max
echo 1200 > $SCHEME/access_pattern/age/min
echo 18446744073709551615 > $SCHEME/access_pattern/age/max
# Quota: use at most 10ms CPU, page out at most 128MiB per second
echo 10 > $SCHEME/quotas/ms
echo 134217728 > $SCHEME/quotas/bytes # 128 MiB
echo 1000 > $SCHEME/quotas/reset_interval_ms
# Prioritize older (colder) regions first
echo 0 > $SCHEME/quotas/weights/sz_permil
echo 0 > $SCHEME/quotas/weights/nr_accesses_permil
echo 1000 > $SCHEME/quotas/weights/age_permil
# Watermarks: only activate when free memory is between 20% and 50%
echo free_mem_rate > $SCHEME/watermarks/metric
echo 5000000 > $SCHEME/watermarks/interval_us # check every 5s
echo 500 > $SCHEME/watermarks/high # deactivate above 50%
echo 400 > $SCHEME/watermarks/mid # activate at 40%
echo 200 > $SCHEME/watermarks/low # deactivate below 20%
# Start monitoring
echo on > $ADMIN/kdamonds/0/state
After a few seconds, check what has happened:
# Trigger stats refresh
echo update_schemes_stats > $ADMIN/kdamonds/0/state
STATS=$ADMIN/kdamonds/0/contexts/0/schemes/0/stats
echo "Regions tried: $(cat $STATS/nr_tried)"
echo "Bytes tried: $(cat $STATS/sz_tried)"
echo "Regions applied: $(cat $STATS/nr_applied)"
echo "Bytes applied: $(cat $STATS/sz_applied)"
echo "Quota exceeded: $(cat $STATS/qt_exceeds)"
2. Working Set Estimation: Count Accesses by Region
Use DAMOS_STAT to observe which regions are hot without taking any action:
ADMIN=/sys/kernel/mm/damon/admin
echo 1 > $ADMIN/kdamonds/nr_kdamonds
echo 1 > $ADMIN/kdamonds/0/contexts/nr_contexts
echo vaddr > $ADMIN/kdamonds/0/contexts/0/operations
# Monitor a specific process
echo 1 > $ADMIN/kdamonds/0/contexts/0/targets/nr_targets
echo $(pidof myapp) > $ADMIN/kdamonds/0/contexts/0/targets/0/pid_target
# Scheme: stat action, capture all regions
echo 1 > $ADMIN/kdamonds/0/contexts/0/schemes/nr_schemes
SCHEME=$ADMIN/kdamonds/0/contexts/0/schemes/0
echo stat > $SCHEME/action
echo 0 > $SCHEME/access_pattern/sz/min
echo 18446744073709551615 > $SCHEME/access_pattern/sz/max
echo 0 > $SCHEME/access_pattern/nr_accesses/min
echo 18446744073709551615 > $SCHEME/access_pattern/nr_accesses/max
echo 0 > $SCHEME/access_pattern/age/min
echo 18446744073709551615 > $SCHEME/access_pattern/age/max
echo on > $ADMIN/kdamonds/0/state
# After a monitoring window, snapshot matched regions
echo update_schemes_tried_regions > $ADMIN/kdamonds/0/state
# Read which regions matched
for dir in $SCHEME/tried_regions/[0-9]*/; do
start=$(cat $dir/start)
end=$(cat $dir/end)
acc=$(cat $dir/nr_accesses)
age=$(cat $dir/age)
printf "0x%x - 0x%x accesses=%d age=%d\n" $start $end $acc $age
done
3. Memory Tiering: Demote Cold Pages to Slow NUMA Memory
For systems with CXL-attached memory or slow NUMA nodes, use DAMOS_MIGRATE_COLD to demote cold pages:
ADMIN=/sys/kernel/mm/damon/admin
echo 1 > $ADMIN/kdamonds/nr_kdamonds
echo 1 > $ADMIN/kdamonds/0/contexts/nr_contexts
echo paddr > $ADMIN/kdamonds/0/contexts/0/operations
echo 1 > $ADMIN/kdamonds/0/contexts/0/targets/nr_targets
echo 1 > $ADMIN/kdamonds/0/contexts/0/schemes/nr_schemes
SCHEME=$ADMIN/kdamonds/0/contexts/0/schemes/0
# Demote cold, large regions to NUMA node 2 (slow tier)
echo migrate_cold > $SCHEME/action
echo 2 > $SCHEME/target_nid
# Pattern: any size, zero accesses, aged at least 60 aggr intervals (6s at 100ms)
echo 0 > $SCHEME/access_pattern/sz/min
echo 18446744073709551615 > $SCHEME/access_pattern/sz/max
echo 0 > $SCHEME/access_pattern/nr_accesses/min
echo 0 > $SCHEME/access_pattern/nr_accesses/max
echo 60 > $SCHEME/access_pattern/age/min
echo 18446744073709551615 > $SCHEME/access_pattern/age/max
# Demote at most 256MiB per second
echo 0 > $SCHEME/quotas/ms
echo 268435456 > $SCHEME/quotas/bytes
echo 1000 > $SCHEME/quotas/reset_interval_ms
echo on > $ADMIN/kdamonds/0/state
Multiple migration destinations
For workloads that benefit from weighted distribution across multiple tiers, use the dests/ subdirectory of the scheme instead of target_nid. Create nr_dests entries, each with an id (NUMA node) and a weight.
Built-in DAMOS Modules
CONFIG_DAMON_RECLAIM
DAMON_RECLAIM (mm/damon/reclaim.c) is a prebuilt kernel module that runs a DAMOS_PAGEOUT scheme against the physical address space. It is designed for proactive, lightweight reclaim under mild memory pressure, complementing kswapd's reactive scanning.
Kernel config: CONFIG_DAMON_RECLAIM (depends on CONFIG_DAMON_PADDR)
Module parameters (readable/writable via /sys/module/damon_reclaim/parameters/):
| Parameter | Default | Description |
|---|---|---|
enabled |
N |
Enable/disable DAMON_RECLAIM at runtime |
commit_inputs |
N |
Set to Y to re-read all parameters without restarting |
min_age |
120,000,000 µs (120 s) | Minimum age of a cold region before reclaim |
quota_mem_pressure_us |
0 (disabled) | Auto-tune quota to achieve this PSI level (µs per reset interval) |
quota_autotune_feedback |
0 (disabled) | Manual feedback value for auto-tuning (target: 10,000) |
skip_anon |
N |
If Y, skip anonymous pages (file-backed only) |
monitor_region_start |
0 | Start of physical address range to monitor |
monitor_region_end |
0 | End of physical address range (0 = largest System RAM region) |
Default quota: 10 ms CPU time and 128 MiB bytes per 1-second window. Default watermarks: activate when free memory is between 20% and 50%.
# Enable DAMON_RECLAIM with more aggressive settings
echo Y > /sys/module/damon_reclaim/parameters/enabled
# Reclaim pages cold for 60 seconds instead of 120
echo 60000000 > /sys/module/damon_reclaim/parameters/min_age
# Apply changes without restarting
echo Y > /sys/module/damon_reclaim/parameters/commit_inputs
When to use DAMON_RECLAIM vs. direct sysfs configuration
DAMON_RECLAIM is a coarse-grained knob for system-wide proactive reclaim. If you need fine-grained control — per-cgroup targeting, custom watermarks, multiple schemes, or migration rather than swap — configure DAMON directly via the sysfs interface.
CONFIG_DAMON_LRU_SORT
DAMON_LRU_SORT (mm/damon/lru_sort.c) uses two DAMOS schemes to influence the LRU lists before kswapd sees them: hot pages are prioritized (DAMOS_LRU_PRIO) and cold pages are deprioritized (DAMOS_LRU_DEPRIO). This makes subsequent kswapd reclaim more precise without bypassing the LRU machinery.
Kernel config: CONFIG_DAMON_LRU_SORT (depends on CONFIG_DAMON_PADDR)
Key module parameters (/sys/module/damon_lru_sort/parameters/):
| Parameter | Default | Description |
|---|---|---|
enabled |
N |
Enable/disable |
active_mem_bp |
0 (disabled) | Auto-tune to achieve this active/total LRU ratio |
autotune_monitoring_intervals |
N |
Automatically tune sample/aggr intervals |
CONFIG_DAMON_STAT
DAMON_STAT (mm/damon/stat.c) runs DAMON against the physical address space and exposes aggregated access statistics as simple metrics, useful for observability without taking any memory management action.
Kernel config: CONFIG_DAMON_STAT
CONFIG_DAMON_STAT_ENABLED_DEFAULT controls whether it starts automatically at boot.
DAMON and MGLRU
DAMON and Multi-Generational LRU (MGLRU) both track memory access patterns, but at different granularities and for different purposes:
| DAMON | MGLRU | |
|---|---|---|
| Granularity | damon_region (pages to megabytes, adaptively sized) |
Individual page (folio) generation |
| Mechanism | Samples a random address per region per interval | Walks page tables, updates folio generations |
| Output | nr_accesses, age per region |
Generation number per folio (0=oldest) |
| Actionable via | DAMOS schemes (policy-agnostic) | kswapd generation-based eviction |
| Overhead | Configurable via intervals; sub-1% typical | Amortized across page table walks |
| Use case | Custom policies, tiering, prefetch, working set sizing | General-purpose reclaim ordering |
They complement each other: MGLRU efficiently orders pages for reclaim within the LRU, while DAMON provides region-level access data that can drive actions MGLRU cannot express — proactive demotion to CXL memory, THP promotion decisions, or working set reports for capacity planning.
No conflict
DAMON and MGLRU can run concurrently. DAMON_LRU_SORT specifically cooperates with MGLRU by manipulating LRU priority before generation-based eviction runs.
Performance Overhead
DAMON's overhead is primarily controlled by three parameters:
| Parameter | Effect on overhead | Effect on accuracy |
|---|---|---|
sample_interval (↑) |
Reduces CPU time in kdamond | Fewer samples → may miss short-lived accesses |
aggr_interval (↑) |
No direct overhead effect | Larger window smooths out transient spikes |
max_nr_regions (↑) |
More regions → more page table lookups per sample | Finer spatial resolution |
Typical configurations achieve well under 1% CPU overhead. The DAMON_RECLAIM defaults (5ms sample, 100ms aggr, 1000 max regions) are designed for near-zero impact on production workloads.
Overhead tuning guidance
- For background observability (working set sizing), prefer longer intervals:
sample_us=50000,aggr_us=1000000 - For reactive policies (proactive reclaim under pressure), use the defaults or enable
intervals_goalauto-tuning to let DAMON adjust dynamically
Kernel Configuration
| Config option | Description | Depends on |
|---|---|---|
CONFIG_DAMON |
Core DAMON framework | — |
CONFIG_DAMON_VADDR |
Virtual address space ops (vaddr, fvaddr) |
DAMON, MMU, selects PAGE_IDLE_FLAG |
CONFIG_DAMON_PADDR |
Physical address space ops (paddr) |
DAMON, MMU, selects PAGE_IDLE_FLAG |
CONFIG_DAMON_SYSFS |
sysfs interface under /sys/kernel/mm/damon/ |
DAMON, SYSFS |
CONFIG_DAMON_RECLAIM |
Built-in proactive reclaim module | DAMON_PADDR |
CONFIG_DAMON_LRU_SORT |
Built-in LRU sorting module | DAMON_PADDR |
CONFIG_DAMON_STAT |
Built-in access statistics module | DAMON_PADDR |
CONFIG_DAMON_STAT_ENABLED_DEFAULT |
Enable DAMON_STAT at boot |
DAMON_STAT |
CONFIG_DAMON_KUNIT_TEST |
KUnit tests for DAMON core | DAMON, KUNIT=y |
CONFIG_DAMON_VADDR_KUNIT_TEST |
KUnit tests for vaddr ops | DAMON_VADDR, KUNIT=y |
CONFIG_DAMON_SYSFS_KUNIT_TEST |
KUnit tests for sysfs interface | DAMON_SYSFS, KUNIT=y |
Key Source Files
| File | Role |
|---|---|
include/linux/damon.h |
All public structs and enums: damon_region, damon_target, damon_ctx, damon_attrs, damon_operations, damos, damos_access_pattern, damos_quota, damos_watermarks, damos_filter, damos_stat |
mm/damon/core.c |
kdamond main loop, region split/merge, DAMOS engine, damon_new_ctx(), damon_start(), damon_stop(), damon_register_ops(), damon_select_ops() |
mm/damon/vaddr.c |
DAMON_OPS_VADDR and DAMON_OPS_FVADDR implementation: VMA-based region init, page table walk for access checks |
mm/damon/paddr.c |
DAMON_OPS_PADDR implementation: physical address region management, folio-level access checks via damon_pa_mkold() |
mm/damon/ops-common.c |
Shared helpers: damon_get_folio(), damon_ptep_mkold(), damon_folio_mkold() |
mm/damon/sysfs.c |
sysfs directory tree: kdamond, contexts, targets, regions, monitoring_attrs |
mm/damon/sysfs-schemes.c |
sysfs for schemes: access_pattern, quotas, watermarks, filters, stats, tried_regions |
mm/damon/sysfs-common.c |
Shared sysfs helpers: damon_sysfs_ul_range for min/max pairs |
mm/damon/reclaim.c |
DAMON_RECLAIM built-in module: module parameters, DAMOS_PAGEOUT scheme configuration |
mm/damon/lru_sort.c |
DAMON_LRU_SORT built-in module: DAMOS_LRU_PRIO and DAMOS_LRU_DEPRIO schemes |
mm/damon/stat.c |
DAMON_STAT built-in module: observability-only DAMOS_STAT scheme |
mm/damon/modules-common.c |
Shared helpers for the built-in modules |
mm/damon/Kconfig |
All CONFIG_DAMON_* options |
Further reading
Kernel documentation
Documentation/admin-guide/mm/damon/— administrator guide covering DAMON concepts, the sysfs interface, DAMOS scheme configuration, and the built-in modules (DAMON_RECLAIM,DAMON_LRU_SORT,DAMON_STAT)Documentation/mm/damon/design.rst— design document describing the adaptive region-based sampling algorithm, the monitoring operations abstraction, and the DAMOS engine- mm/damon/ — full DAMON implementation: core sampling loop, virtual and physical address ops, sysfs interface, and built-in scheme modules
LWN articles
- DAMON: Data Access MONitor — overview of DAMON's design and its introduction to the mainline in Linux 5.15 (2021)
- DAMON-based proactive reclaim — how
DAMON_RECLAIMwas developed and evaluated againstkswapd-only reclaim
Related docs
- reclaim.md — page reclaim; DAMON_RECLAIM targets cold pages identified by DAMON rather than relying solely on LRU age
- psi.md — pressure stall information; DAMON_RECLAIM can auto-tune its quota using PSI memory pressure as feedback
- mglru.md — Multi-Generational LRU; complements DAMON at page granularity for reclaim ordering within the LRU lists