Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
+124
View File
@@ -0,0 +1,124 @@
# SPDX-License-Identifier: GPL-2.0-only
menu "Data Access Monitoring"
config DAMON
bool "DAMON: Data Access Monitoring Framework"
help
This builds a framework that allows kernel subsystems to monitor
access frequency of each memory region. The information can be useful
for performance-centric DRAM level memory management.
See https://www.kernel.org/doc/html/latest/mm/damon/index.html for
more information.
config DAMON_DEBUG_SANITY
bool "Check sanity of DAMON code"
depends on DAMON
help
This enables additional DAMON debugging-purpose sanity checks in
DAMON code. This can be useful for finding bugs, but impose
additional overhead. This is therefore recommended to be enabled on
only development and test setups.
If unsure, say N.
config DAMON_KUNIT_TEST
bool "Test for damon" if !KUNIT_ALL_TESTS
depends on DAMON && KUNIT=y
default KUNIT_ALL_TESTS
help
This builds the DAMON Kunit test suite.
For more information on KUnit and unit tests in general, please refer
to the KUnit documentation.
If unsure, say N.
config DAMON_VADDR
bool "Data access monitoring operations for virtual address spaces"
depends on DAMON && MMU
select PAGE_IDLE_FLAG
default DAMON
help
This builds the default data access monitoring operations for DAMON
that work for virtual address spaces.
config DAMON_PADDR
bool "Data access monitoring operations for the physical address space"
depends on DAMON && MMU
select PAGE_IDLE_FLAG
default DAMON
help
This builds the default data access monitoring operations for DAMON
that works for the physical address space.
config DAMON_VADDR_KUNIT_TEST
bool "Test for DAMON operations" if !KUNIT_ALL_TESTS
depends on DAMON_VADDR && KUNIT=y
default KUNIT_ALL_TESTS
help
This builds the DAMON virtual addresses operations Kunit test suite.
For more information on KUnit and unit tests in general, please refer
to the KUnit documentation.
If unsure, say N.
config DAMON_SYSFS
bool "DAMON sysfs interface"
depends on DAMON && SYSFS
default DAMON
help
This builds the sysfs interface for DAMON. The user space can use
the interface for arbitrary data access monitoring.
config DAMON_SYSFS_KUNIT_TEST
bool "Test for damon sysfs interface" if !KUNIT_ALL_TESTS
depends on DAMON_SYSFS && KUNIT=y
default KUNIT_ALL_TESTS
help
This builds the DAMON sysfs interface Kunit test suite.
For more information on KUnit and unit tests in general, please refer
to the KUnit documentation.
If unsure, say N.
config DAMON_RECLAIM
bool "Build DAMON-based reclaim (DAMON_RECLAIM)"
depends on DAMON_PADDR
help
This builds the DAMON-based reclamation subsystem. It finds pages
that not accessed for a long time (cold) using DAMON and reclaim
those.
This is suggested to be used as a proactive and lightweight
reclamation under light memory pressure, while the traditional page
scanning-based reclamation is used for heavy pressure.
config DAMON_LRU_SORT
bool "Build DAMON-based LRU-lists sorting (DAMON_LRU_SORT)"
depends on DAMON_PADDR
help
This builds the DAMON-based LRU-lists sorting subsystem. It tries to
protect frequently accessed (hot) pages while rarely accessed (cold)
pages reclaimed first under memory pressure.
config DAMON_STAT
bool "Build data access monitoring stat (DAMON_STAT)"
depends on DAMON_PADDR
help
This builds the DAMON-based access monitoring statistics subsystem.
It runs DAMON and expose access monitoring results in simple stat
metrics.
config DAMON_STAT_ENABLED_DEFAULT
bool "Enable DAMON_STAT by default"
depends on DAMON_STAT
default DAMON_STAT
help
Whether to enable DAMON_STAT by default. Users can disable it in
boot or runtime using its 'enabled' parameter.
endmenu
+9
View File
@@ -0,0 +1,9 @@
# SPDX-License-Identifier: GPL-2.0
obj-y := core.o
obj-$(CONFIG_DAMON_VADDR) += ops-common.o vaddr.o
obj-$(CONFIG_DAMON_PADDR) += ops-common.o paddr.o
obj-$(CONFIG_DAMON_SYSFS) += sysfs-common.o sysfs-schemes.o sysfs.o
obj-$(CONFIG_DAMON_RECLAIM) += modules-common.o reclaim.o
obj-$(CONFIG_DAMON_LRU_SORT) += modules-common.o lru_sort.o
obj-$(CONFIG_DAMON_STAT) += modules-common.o stat.o
File diff suppressed because it is too large Load Diff
+562
View File
@@ -0,0 +1,562 @@
// SPDX-License-Identifier: GPL-2.0
/*
* DAMON-based LRU-lists Sorting
*/
#define pr_fmt(fmt) "damon-lru-sort: " fmt
#include <linux/damon.h>
#include <linux/kstrtox.h>
#include <linux/module.h>
#include "modules-common.h"
#ifdef MODULE_PARAM_PREFIX
#undef MODULE_PARAM_PREFIX
#endif
#define MODULE_PARAM_PREFIX "damon_lru_sort."
/*
* Enable or disable DAMON_LRU_SORT.
*
* You can enable DAMON_LRU_SORT by setting the value of this parameter as
* ``Y``. Setting it as ``N`` disables DAMON_LRU_SORT. Note that
* DAMON_LRU_SORT could do no real monitoring and LRU-lists sorting due to the
* watermarks-based activation condition. Refer to below descriptions for the
* watermarks parameter for this.
*/
static bool enabled __read_mostly;
/*
* Make DAMON_LRU_SORT reads the input parameters again, except ``enabled``.
*
* Input parameters that updated while DAMON_LRU_SORT is running are not
* applied by default. Once this parameter is set as ``Y``, DAMON_LRU_SORT
* reads values of parameters except ``enabled`` again. Once the re-reading is
* done, this parameter is set as ``N``. If invalid parameters are found while
* the re-reading, DAMON_LRU_SORT will be disabled.
*/
static bool commit_inputs __read_mostly;
/*
* Desired active to [in]active memory ratio in bp (1/10,000).
*
* While keeping the caps that set by other quotas, DAMON_LRU_SORT
* automatically increases and decreases the effective level of the quota
* aiming the LRU [de]prioritizations of the hot and cold memory resulting in
* this active to [in]active memory ratio. Value zero means disabling this
* auto-tuning feature.
*
* Disabled by default.
*/
static unsigned long active_mem_bp __read_mostly;
module_param(active_mem_bp, ulong, 0600);
/*
* Auto-tune monitoring intervals.
*
* If this parameter is set as ``Y``, DAMON_LRU_SORT automatically tunes
* DAMON's sampling and aggregation intervals. The auto-tuning aims to capture
* meaningful amount of access events in each DAMON-snapshot, while keeping the
* sampling interval 5 milliseconds in minimum, and 10 seconds in maximum.
* Setting this as ``N`` disables the auto-tuning.
*
* Disabled by default.
*/
static bool autotune_monitoring_intervals __read_mostly;
module_param(autotune_monitoring_intervals, bool, 0600);
/*
* Filter [non-]young pages accordingly for LRU [de]prioritizations.
*
* If this is set, check page level access (youngness) once again before each
* LRU [de]prioritization operation. LRU prioritization operation is skipped
* if the page has not accessed since the last check (not young). LRU
* deprioritization operation is skipped if the page has accessed since the
* last check (young). The feature is enabled or disabled if this parameter is
* set as ``Y`` or ``N``, respectively.
*
* Disabled by default.
*/
static bool filter_young_pages __read_mostly;
module_param(filter_young_pages, bool, 0600);
/*
* Access frequency threshold for hot memory regions identification in permil.
*
* If a memory region is accessed in frequency of this or higher,
* DAMON_LRU_SORT identifies the region as hot, and mark it as accessed on the
* LRU list, so that it could not be reclaimed under memory pressure. 50% by
* default.
*/
static unsigned long hot_thres_access_freq = 500;
module_param(hot_thres_access_freq, ulong, 0600);
/*
* Time threshold for cold memory regions identification in microseconds.
*
* If a memory region is not accessed for this or longer time, DAMON_LRU_SORT
* identifies the region as cold, and mark it as unaccessed on the LRU list, so
* that it could be reclaimed first under memory pressure. 120 seconds by
* default.
*/
static unsigned long cold_min_age __read_mostly = 120000000;
module_param(cold_min_age, ulong, 0600);
static struct damos_quota damon_lru_sort_quota = {
/* Use up to 10 ms per 1 sec, by default */
.ms = 10,
.sz = 0,
.reset_interval = 1000,
/* Within the quota, mark hotter regions accessed first. */
.weight_sz = 0,
.weight_nr_accesses = 1,
.weight_age = 1,
};
DEFINE_DAMON_MODULES_DAMOS_TIME_QUOTA(damon_lru_sort_quota);
static struct damos_watermarks damon_lru_sort_wmarks = {
.metric = DAMOS_WMARK_FREE_MEM_RATE,
.interval = 5000000, /* 5 seconds */
.high = 200, /* 20 percent */
.mid = 150, /* 15 percent */
.low = 50, /* 5 percent */
};
DEFINE_DAMON_MODULES_WMARKS_PARAMS(damon_lru_sort_wmarks);
static struct damon_attrs damon_lru_sort_mon_attrs = {
.sample_interval = 5000, /* 5 ms */
.aggr_interval = 100000, /* 100 ms */
.ops_update_interval = 0,
.min_nr_regions = 10,
.max_nr_regions = 1000,
};
DEFINE_DAMON_MODULES_MON_ATTRS_PARAMS(damon_lru_sort_mon_attrs);
/*
* Start of the target memory region in physical address.
*
* The start physical address of memory region that DAMON_LRU_SORT will do work
* against. By default, the system's entire physical memory is used as the
* region.
*/
static unsigned long monitor_region_start __read_mostly;
module_param(monitor_region_start, ulong, 0600);
/*
* End of the target memory region in physical address.
*
* The end physical address of memory region that DAMON_LRU_SORT will do work
* against. By default, the system's entire physical memory is used as the
* region.
*/
static unsigned long monitor_region_end __read_mostly;
module_param(monitor_region_end, ulong, 0600);
/*
* Scale factor for DAMON_LRU_SORT to ops address conversion.
*
* This parameter must not be set to 0.
*/
static unsigned long addr_unit __read_mostly = 1;
static struct damos_stat damon_lru_sort_hot_stat;
DEFINE_DAMON_MODULES_DAMOS_STATS_PARAMS(damon_lru_sort_hot_stat,
lru_sort_tried_hot_regions, lru_sorted_hot_regions,
hot_quota_exceeds);
static struct damos_stat damon_lru_sort_cold_stat;
DEFINE_DAMON_MODULES_DAMOS_STATS_PARAMS(damon_lru_sort_cold_stat,
lru_sort_tried_cold_regions, lru_sorted_cold_regions,
cold_quota_exceeds);
static struct damos_access_pattern damon_lru_sort_stub_pattern = {
/* Find regions having PAGE_SIZE or larger size */
.min_sz_region = PAGE_SIZE,
.max_sz_region = ULONG_MAX,
/* no matter its access frequency */
.min_nr_accesses = 0,
.max_nr_accesses = UINT_MAX,
/* no matter its age */
.min_age_region = 0,
.max_age_region = UINT_MAX,
};
static struct damon_ctx *ctx;
static struct damon_target *target;
static struct damos *damon_lru_sort_new_scheme(
struct damos_access_pattern *pattern, enum damos_action action)
{
struct damos_quota quota = damon_lru_sort_quota;
/* Use half of total quota for hot/cold pages sorting */
quota.ms = quota.ms / 2;
return damon_new_scheme(
/* find the pattern, and */
pattern,
/* (de)prioritize on LRU-lists */
action,
/* for each aggregation interval */
0,
/* under the quota. */
&quota,
/* (De)activate this according to the watermarks. */
&damon_lru_sort_wmarks,
NUMA_NO_NODE);
}
/* Create a DAMON-based operation scheme for hot memory regions */
static struct damos *damon_lru_sort_new_hot_scheme(unsigned int hot_thres)
{
struct damos_access_pattern pattern = damon_lru_sort_stub_pattern;
pattern.min_nr_accesses = hot_thres;
return damon_lru_sort_new_scheme(&pattern, DAMOS_LRU_PRIO);
}
/* Create a DAMON-based operation scheme for cold memory regions */
static struct damos *damon_lru_sort_new_cold_scheme(unsigned int cold_thres)
{
struct damos_access_pattern pattern = damon_lru_sort_stub_pattern;
pattern.max_nr_accesses = 0;
pattern.min_age_region = cold_thres;
return damon_lru_sort_new_scheme(&pattern, DAMOS_LRU_DEPRIO);
}
static int damon_lru_sort_add_quota_goals(struct damos *hot_scheme,
struct damos *cold_scheme)
{
struct damos_quota_goal *goal;
if (!active_mem_bp)
return 0;
if (10000 < active_mem_bp)
return -EINVAL;
goal = damos_new_quota_goal(DAMOS_QUOTA_ACTIVE_MEM_BP, active_mem_bp);
if (!goal)
return -ENOMEM;
damos_add_quota_goal(&hot_scheme->quota, goal);
/* aim 0.2 % goal conflict, to keep little ping pong */
goal = damos_new_quota_goal(DAMOS_QUOTA_INACTIVE_MEM_BP,
10000 - active_mem_bp + 2);
if (!goal)
return -ENOMEM;
damos_add_quota_goal(&cold_scheme->quota, goal);
return 0;
}
static int damon_lru_sort_add_filters(struct damos *hot_scheme,
struct damos *cold_scheme)
{
struct damos_filter *filter;
if (!filter_young_pages)
return 0;
/* disallow prioritizing not-young pages */
filter = damos_new_filter(DAMOS_FILTER_TYPE_YOUNG, false, false);
if (!filter)
return -ENOMEM;
damos_add_filter(hot_scheme, filter);
/* disabllow de-prioritizing young pages */
filter = damos_new_filter(DAMOS_FILTER_TYPE_YOUNG, true, false);
if (!filter)
return -ENOMEM;
damos_add_filter(cold_scheme, filter);
return 0;
}
static int damon_lru_sort_apply_parameters(void)
{
struct damon_ctx *param_ctx;
struct damon_target *param_target;
struct damon_attrs attrs;
struct damos *hot_scheme, *cold_scheme;
unsigned int hot_thres, cold_thres;
int err;
err = damon_modules_new_paddr_ctx_target(&param_ctx, &param_target);
if (err)
return err;
param_ctx->addr_unit = addr_unit;
param_ctx->min_region_sz = max(DAMON_MIN_REGION_SZ / addr_unit, 1);
if (!is_power_of_2(param_ctx->min_region_sz)) {
err = -EINVAL;
goto out;
}
if (!damon_lru_sort_mon_attrs.sample_interval) {
err = -EINVAL;
goto out;
}
attrs = damon_lru_sort_mon_attrs;
if (autotune_monitoring_intervals) {
attrs.sample_interval = 5000;
attrs.aggr_interval = 100000;
attrs.intervals_goal.access_bp = 40;
attrs.intervals_goal.aggrs = 3;
attrs.intervals_goal.min_sample_us = 5000;
attrs.intervals_goal.max_sample_us = 10 * 1000 * 1000;
}
err = damon_set_attrs(param_ctx, &attrs);
if (err)
goto out;
err = -ENOMEM;
hot_thres = damon_max_nr_accesses(&attrs) *
hot_thres_access_freq / 1000;
hot_scheme = damon_lru_sort_new_hot_scheme(hot_thres);
if (!hot_scheme)
goto out;
cold_thres = cold_min_age / attrs.aggr_interval;
cold_scheme = damon_lru_sort_new_cold_scheme(cold_thres);
if (!cold_scheme) {
damon_destroy_scheme(hot_scheme);
goto out;
}
damon_set_schemes(param_ctx, &hot_scheme, 1);
damon_add_scheme(param_ctx, cold_scheme);
err = damon_lru_sort_add_quota_goals(hot_scheme, cold_scheme);
if (err)
goto out;
err = damon_lru_sort_add_filters(hot_scheme, cold_scheme);
if (err)
goto out;
err = damon_set_region_system_rams_default(param_target,
&monitor_region_start,
&monitor_region_end,
param_ctx->addr_unit,
param_ctx->min_region_sz);
if (err)
goto out;
err = damon_commit_ctx(ctx, param_ctx);
out:
damon_destroy_ctx(param_ctx);
return err;
}
static int damon_lru_sort_commit_inputs_fn(void *arg)
{
return damon_lru_sort_apply_parameters();
}
static bool damon_lru_sort_damon_has_started;
static int damon_lru_sort_commit_inputs_store(const char *val,
const struct kernel_param *kp)
{
bool commit_inputs_request;
int err;
struct damon_call_control control = {
.fn = damon_lru_sort_commit_inputs_fn,
};
if (!val) {
commit_inputs_request = true;
} else {
err = kstrtobool(val, &commit_inputs_request);
if (err)
return err;
}
if (!commit_inputs_request)
return 0;
/* Skip damon_call() if ctx has not successfully started. */
if (!damon_lru_sort_damon_has_started)
return -EINVAL;
err = damon_call(ctx, &control);
return err ? err : control.return_code;
}
static const struct kernel_param_ops commit_inputs_param_ops = {
.flags = KERNEL_PARAM_OPS_FL_NOARG,
.set = damon_lru_sort_commit_inputs_store,
.get = param_get_bool,
};
module_param_cb(commit_inputs, &commit_inputs_param_ops, &commit_inputs, 0600);
static int damon_lru_sort_damon_call_fn(void *arg)
{
struct damon_ctx *c = arg;
struct damos *s;
/* update the stats parameter */
damon_for_each_scheme(s, c) {
if (s->action == DAMOS_LRU_PRIO)
damon_lru_sort_hot_stat = s->stat;
else if (s->action == DAMOS_LRU_DEPRIO)
damon_lru_sort_cold_stat = s->stat;
}
return 0;
}
static struct damon_call_control call_control = {
.fn = damon_lru_sort_damon_call_fn,
.repeat = true,
};
static int damon_lru_sort_turn(bool on)
{
int err;
if (!on)
return damon_stop(&ctx, 1);
err = damon_lru_sort_apply_parameters();
if (err)
return err;
err = damon_start(&ctx, 1, true);
if (err)
return err;
if (!damon_lru_sort_damon_has_started)
damon_lru_sort_damon_has_started = true;
return damon_call(ctx, &call_control);
}
static int damon_lru_sort_addr_unit_store(const char *val,
const struct kernel_param *kp)
{
unsigned long input_addr_unit;
int err = kstrtoul(val, 0, &input_addr_unit);
if (err)
return err;
if (!input_addr_unit)
return -EINVAL;
addr_unit = input_addr_unit;
return 0;
}
static const struct kernel_param_ops addr_unit_param_ops = {
.set = damon_lru_sort_addr_unit_store,
.get = param_get_ulong,
};
module_param_cb(addr_unit, &addr_unit_param_ops, &addr_unit, 0600);
MODULE_PARM_DESC(addr_unit,
"Scale factor for DAMON_LRU_SORT to ops address conversion (default: 1)");
static bool damon_lru_sort_enabled(void)
{
if (!ctx)
return false;
return damon_is_running(ctx);
}
static int damon_lru_sort_enabled_store(const char *val,
const struct kernel_param *kp)
{
int err;
err = kstrtobool(val, &enabled);
if (err)
return err;
if (damon_lru_sort_enabled() == enabled)
return 0;
/* Called before init function. The function will handle this. */
if (!damon_initialized())
return 0;
/* damon_modules_new_paddr_ctx_target() in the init function failed. */
if (!ctx)
return -ENOMEM;
return damon_lru_sort_turn(enabled);
}
static int damon_lru_sort_enabled_load(char *buffer,
const struct kernel_param *kp)
{
return sprintf(buffer, "%c\n", damon_lru_sort_enabled() ? 'Y' : 'N');
}
static const struct kernel_param_ops enabled_param_ops = {
.set = damon_lru_sort_enabled_store,
.get = damon_lru_sort_enabled_load,
};
module_param_cb(enabled, &enabled_param_ops, &enabled, 0600);
MODULE_PARM_DESC(enabled,
"Enable or disable DAMON_LRU_SORT (default: disabled)");
static int damon_lru_sort_kdamond_pid_store(const char *val,
const struct kernel_param *kp)
{
/*
* kdamond_pid is read-only, but kernel command line could write it.
* Do nothing here.
*/
return 0;
}
static int damon_lru_sort_kdamond_pid_load(char *buffer,
const struct kernel_param *kp)
{
int kdamond_pid = -1;
if (ctx) {
kdamond_pid = damon_kdamond_pid(ctx);
if (kdamond_pid < 0)
kdamond_pid = -1;
}
return sprintf(buffer, "%d\n", kdamond_pid);
}
static const struct kernel_param_ops kdamond_pid_param_ops = {
.set = damon_lru_sort_kdamond_pid_store,
.get = damon_lru_sort_kdamond_pid_load,
};
/*
* PID of the DAMON thread
*
* If DAMON_LRU_SORT is enabled, this becomes the PID of the worker thread.
* Else, -1.
*/
module_param_cb(kdamond_pid, &kdamond_pid_param_ops, NULL, 0400);
static int __init damon_lru_sort_init(void)
{
int err;
if (!damon_initialized()) {
err = -ENOMEM;
goto out;
}
err = damon_modules_new_paddr_ctx_target(&ctx, &target);
if (err)
goto out;
call_control.data = ctx;
/* 'enabled' has set before this function, probably via command line */
if (enabled)
err = damon_lru_sort_turn(true);
out:
if (err && enabled)
enabled = false;
return err;
}
module_init(damon_lru_sort_init);
+40
View File
@@ -0,0 +1,40 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Common Code for DAMON Modules
*/
#include <linux/damon.h>
#include "modules-common.h"
/*
* Allocate, set, and return a DAMON context for the physical address space.
* @ctxp: Pointer to save the point to the newly created context
* @targetp: Pointer to save the point to the newly created target
*/
int damon_modules_new_paddr_ctx_target(struct damon_ctx **ctxp,
struct damon_target **targetp)
{
struct damon_ctx *ctx;
struct damon_target *target;
ctx = damon_new_ctx();
if (!ctx)
return -ENOMEM;
if (damon_select_ops(ctx, DAMON_OPS_PADDR)) {
damon_destroy_ctx(ctx);
return -EINVAL;
}
target = damon_new_target();
if (!target) {
damon_destroy_ctx(ctx);
return -ENOMEM;
}
damon_add_target(ctx, target);
*ctxp = ctx;
*targetp = target;
return 0;
}
+47
View File
@@ -0,0 +1,47 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Common Code for DAMON Modules
*/
#include <linux/moduleparam.h>
#define DEFINE_DAMON_MODULES_MON_ATTRS_PARAMS(attrs) \
module_param_named(sample_interval, attrs.sample_interval, \
ulong, 0600); \
module_param_named(aggr_interval, attrs.aggr_interval, ulong, \
0600); \
module_param_named(min_nr_regions, attrs.min_nr_regions, ulong, \
0600); \
module_param_named(max_nr_regions, attrs.max_nr_regions, ulong, \
0600);
#define DEFINE_DAMON_MODULES_DAMOS_TIME_QUOTA(quota) \
module_param_named(quota_ms, quota.ms, ulong, 0600); \
module_param_named(quota_reset_interval_ms, \
quota.reset_interval, ulong, 0600);
#define DEFINE_DAMON_MODULES_DAMOS_QUOTAS(quota) \
DEFINE_DAMON_MODULES_DAMOS_TIME_QUOTA(quota) \
module_param_named(quota_sz, quota.sz, ulong, 0600);
#define DEFINE_DAMON_MODULES_WMARKS_PARAMS(wmarks) \
module_param_named(wmarks_interval, wmarks.interval, ulong, \
0600); \
module_param_named(wmarks_high, wmarks.high, ulong, 0600); \
module_param_named(wmarks_mid, wmarks.mid, ulong, 0600); \
module_param_named(wmarks_low, wmarks.low, ulong, 0600);
#define DEFINE_DAMON_MODULES_DAMOS_STATS_PARAMS(stat, try_name, \
succ_name, qt_exceed_name) \
module_param_named(nr_##try_name, stat.nr_tried, ulong, 0400); \
module_param_named(bytes_##try_name, stat.sz_tried, ulong, \
0400); \
module_param_named(nr_##succ_name, stat.nr_applied, ulong, \
0400); \
module_param_named(bytes_##succ_name, stat.sz_applied, ulong, \
0400); \
module_param_named(nr_##qt_exceed_name, stat.qt_exceeds, ulong, \
0400);
int damon_modules_new_paddr_ctx_target(struct damon_ctx **ctxp,
struct damon_target **targetp);
+442
View File
@@ -0,0 +1,442 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Common Code for Data Access Monitoring
*/
#include <linux/migrate.h>
#include <linux/mmu_notifier.h>
#include <linux/page_idle.h>
#include <linux/pagemap.h>
#include <linux/rmap.h>
#include <linux/swap.h>
#include <linux/leafops.h>
#include "../internal.h"
#include "ops-common.h"
/*
* Get an online page for a pfn if it's in the LRU list. Otherwise, returns
* NULL.
*
* The body of this function is stolen from the 'page_idle_get_folio()'. We
* steal rather than reuse it because the code is quite simple.
*/
struct folio *damon_get_folio(unsigned long pfn)
{
struct page *page = pfn_to_online_page(pfn);
struct folio *folio;
if (!page)
return NULL;
folio = page_folio(page);
if (!folio_try_get(folio))
return NULL;
if (unlikely(page_folio(page) != folio) || !folio_test_lru(folio)) {
folio_put(folio);
folio = NULL;
}
return folio;
}
void damon_ptep_mkold(pte_t *pte, struct vm_area_struct *vma, unsigned long addr)
{
pte_t pteval = ptep_get(pte);
struct folio *folio;
bool young = false;
unsigned long pfn;
if (likely(pte_present(pteval)))
pfn = pte_pfn(pteval);
else
pfn = softleaf_to_pfn(softleaf_from_pte(pteval));
folio = damon_get_folio(pfn);
if (!folio)
return;
/*
* PFN swap PTEs, such as device-exclusive ones, that actually map pages
* are "old" from a CPU perspective. The MMU notifier takes care of any
* device aspects.
*/
if (likely(pte_present(pteval)))
young |= ptep_test_and_clear_young(vma, addr, pte);
young |= mmu_notifier_clear_young(vma->vm_mm, addr, addr + PAGE_SIZE);
if (young)
folio_set_young(folio);
folio_set_idle(folio);
folio_put(folio);
}
void damon_pmdp_mkold(pmd_t *pmd, struct vm_area_struct *vma, unsigned long addr)
{
#ifdef CONFIG_TRANSPARENT_HUGEPAGE
pmd_t pmdval = pmdp_get(pmd);
struct folio *folio;
bool young = false;
unsigned long pfn;
if (likely(pmd_present(pmdval)))
pfn = pmd_pfn(pmdval);
else
pfn = softleaf_to_pfn(softleaf_from_pmd(pmdval));
folio = damon_get_folio(pfn);
if (!folio)
return;
if (likely(pmd_present(pmdval)))
young |= pmdp_test_and_clear_young(vma, addr, pmd);
young |= mmu_notifier_clear_young(vma->vm_mm, addr, addr + HPAGE_PMD_SIZE);
if (young)
folio_set_young(folio);
folio_set_idle(folio);
folio_put(folio);
#endif /* CONFIG_TRANSPARENT_HUGEPAGE */
}
#define DAMON_MAX_SUBSCORE (100)
#define DAMON_MAX_AGE_IN_LOG (32)
int damon_hot_score(struct damon_ctx *c, struct damon_region *r,
struct damos *s)
{
int freq_subscore;
unsigned int age_in_sec;
int age_in_log, age_subscore;
unsigned int freq_weight = s->quota.weight_nr_accesses;
unsigned int age_weight = s->quota.weight_age;
int hotness;
freq_subscore = r->nr_accesses * DAMON_MAX_SUBSCORE /
damon_max_nr_accesses(&c->attrs);
age_in_sec = (unsigned long)r->age * c->attrs.aggr_interval / 1000000;
if (age_in_sec)
age_in_log = min_t(int, ilog2(age_in_sec) + 1,
DAMON_MAX_AGE_IN_LOG);
else
age_in_log = 0;
/* If frequency is 0, higher age means it's colder */
if (freq_subscore == 0)
age_in_log *= -1;
/*
* Now age_in_log is in [-DAMON_MAX_AGE_IN_LOG, DAMON_MAX_AGE_IN_LOG].
* Scale it to be in [0, 100] and set it as age subscore.
*/
age_in_log += DAMON_MAX_AGE_IN_LOG;
age_subscore = age_in_log * DAMON_MAX_SUBSCORE /
DAMON_MAX_AGE_IN_LOG / 2;
hotness = (freq_weight * freq_subscore + age_weight * age_subscore);
if (freq_weight + age_weight)
hotness /= freq_weight + age_weight;
/*
* Transform it to fit in [0, DAMOS_MAX_SCORE]
*/
hotness = hotness * DAMOS_MAX_SCORE / DAMON_MAX_SUBSCORE;
hotness = max(min(hotness, DAMOS_MAX_SCORE), 0);
return hotness;
}
int damon_cold_score(struct damon_ctx *c, struct damon_region *r,
struct damos *s)
{
int hotness = damon_hot_score(c, r, s);
/* Return coldness of the region */
return DAMOS_MAX_SCORE - hotness;
}
static bool damon_folio_mkold_one(struct folio *folio,
struct vm_area_struct *vma, unsigned long addr, void *arg)
{
DEFINE_FOLIO_VMA_WALK(pvmw, folio, vma, addr, 0);
while (page_vma_mapped_walk(&pvmw)) {
addr = pvmw.address;
if (pvmw.pte)
damon_ptep_mkold(pvmw.pte, vma, addr);
else
damon_pmdp_mkold(pvmw.pmd, vma, addr);
}
return true;
}
void damon_folio_mkold(struct folio *folio)
{
struct rmap_walk_control rwc = {
.rmap_one = damon_folio_mkold_one,
.anon_lock = folio_lock_anon_vma_read,
};
if (!folio_mapped(folio) || !folio_raw_mapping(folio)) {
folio_set_idle(folio);
return;
}
if (!folio_trylock(folio))
return;
rmap_walk(folio, &rwc);
folio_unlock(folio);
}
static bool damon_folio_young_one(struct folio *folio,
struct vm_area_struct *vma, unsigned long addr, void *arg)
{
bool *accessed = arg;
DEFINE_FOLIO_VMA_WALK(pvmw, folio, vma, addr, 0);
pte_t pte;
*accessed = false;
while (page_vma_mapped_walk(&pvmw)) {
addr = pvmw.address;
if (pvmw.pte) {
pte = ptep_get(pvmw.pte);
/*
* PFN swap PTEs, such as device-exclusive ones, that
* actually map pages are "old" from a CPU perspective.
* The MMU notifier takes care of any device aspects.
*/
*accessed = (pte_present(pte) && pte_young(pte)) ||
!folio_test_idle(folio) ||
mmu_notifier_test_young(vma->vm_mm, addr);
} else {
#ifdef CONFIG_TRANSPARENT_HUGEPAGE
pmd_t pmd = pmdp_get(pvmw.pmd);
*accessed = (pmd_present(pmd) && pmd_young(pmd)) ||
!folio_test_idle(folio) ||
mmu_notifier_test_young(vma->vm_mm, addr);
#else
WARN_ON_ONCE(1);
#endif /* CONFIG_TRANSPARENT_HUGEPAGE */
}
if (*accessed) {
page_vma_mapped_walk_done(&pvmw);
break;
}
}
/* If accessed, stop walking */
return *accessed == false;
}
bool damon_folio_young(struct folio *folio)
{
bool accessed = false;
struct rmap_walk_control rwc = {
.arg = &accessed,
.rmap_one = damon_folio_young_one,
.anon_lock = folio_lock_anon_vma_read,
};
if (!folio_mapped(folio) || !folio_raw_mapping(folio)) {
if (folio_test_idle(folio))
return false;
else
return true;
}
if (!folio_trylock(folio))
return false;
rmap_walk(folio, &rwc);
folio_unlock(folio);
return accessed;
}
bool damos_folio_filter_match(struct damos_filter *filter, struct folio *folio)
{
bool matched = false;
struct mem_cgroup *memcg;
size_t folio_sz;
switch (filter->type) {
case DAMOS_FILTER_TYPE_ANON:
matched = folio_test_anon(folio);
break;
case DAMOS_FILTER_TYPE_ACTIVE:
matched = folio_test_active(folio);
break;
case DAMOS_FILTER_TYPE_MEMCG:
rcu_read_lock();
memcg = folio_memcg_check(folio);
if (!memcg)
matched = false;
else
matched = filter->memcg_id == mem_cgroup_id(memcg);
rcu_read_unlock();
break;
case DAMOS_FILTER_TYPE_YOUNG:
matched = damon_folio_young(folio);
if (matched)
damon_folio_mkold(folio);
break;
case DAMOS_FILTER_TYPE_HUGEPAGE_SIZE:
folio_sz = folio_size(folio);
matched = filter->sz_range.min <= folio_sz &&
folio_sz <= filter->sz_range.max;
break;
case DAMOS_FILTER_TYPE_UNMAPPED:
matched = !folio_mapped(folio) || !folio_raw_mapping(folio);
break;
default:
break;
}
return matched == filter->matching;
}
static unsigned int __damon_migrate_folio_list(
struct list_head *migrate_folios, struct pglist_data *pgdat,
int target_nid)
{
unsigned int nr_succeeded = 0;
struct migration_target_control mtc = {
/*
* Allocate from 'node', or fail quickly and quietly.
* When this happens, 'page' will likely just be discarded
* instead of migrated.
*/
.gfp_mask = (GFP_HIGHUSER_MOVABLE & ~__GFP_RECLAIM) |
__GFP_NOMEMALLOC | GFP_NOWAIT,
.nid = target_nid,
};
if (pgdat->node_id == target_nid || target_nid == NUMA_NO_NODE)
return 0;
if (list_empty(migrate_folios))
return 0;
/* Migration ignores all cpuset and mempolicy settings */
migrate_pages(migrate_folios, alloc_migration_target, NULL,
(unsigned long)&mtc, MIGRATE_ASYNC, MR_DAMON,
&nr_succeeded);
return nr_succeeded;
}
static unsigned int damon_migrate_folio_list(struct list_head *folio_list,
struct pglist_data *pgdat,
int target_nid)
{
unsigned int nr_migrated = 0;
struct folio *folio;
LIST_HEAD(ret_folios);
LIST_HEAD(migrate_folios);
while (!list_empty(folio_list)) {
struct folio *folio;
cond_resched();
folio = lru_to_folio(folio_list);
list_del(&folio->lru);
if (!folio_trylock(folio))
goto keep;
/* Relocate its contents to another node. */
list_add(&folio->lru, &migrate_folios);
folio_unlock(folio);
continue;
keep:
list_add(&folio->lru, &ret_folios);
}
/* 'folio_list' is always empty here */
/* Migrate folios selected for migration */
nr_migrated += __damon_migrate_folio_list(
&migrate_folios, pgdat, target_nid);
/*
* Folios that could not be migrated are still in @migrate_folios. Add
* those back on @folio_list
*/
if (!list_empty(&migrate_folios))
list_splice_init(&migrate_folios, folio_list);
try_to_unmap_flush();
list_splice(&ret_folios, folio_list);
while (!list_empty(folio_list)) {
folio = lru_to_folio(folio_list);
list_del(&folio->lru);
node_stat_sub_folio(folio, NR_ISOLATED_ANON +
folio_is_file_lru(folio));
folio_putback_lru(folio);
}
return nr_migrated;
}
unsigned long damon_migrate_pages(struct list_head *folio_list, int target_nid)
{
int nid;
unsigned long nr_migrated = 0;
LIST_HEAD(node_folio_list);
unsigned int noreclaim_flag;
if (list_empty(folio_list))
return nr_migrated;
if (target_nid < 0 || target_nid >= MAX_NUMNODES ||
!node_state(target_nid, N_MEMORY)) {
while (!list_empty(folio_list)) {
struct folio *folio = lru_to_folio(folio_list);
list_del(&folio->lru);
node_stat_sub_folio(folio, NR_ISOLATED_ANON +
folio_is_file_lru(folio));
folio_putback_lru(folio);
}
return nr_migrated;
}
noreclaim_flag = memalloc_noreclaim_save();
nid = folio_nid(lru_to_folio(folio_list));
do {
struct folio *folio = lru_to_folio(folio_list);
if (nid == folio_nid(folio)) {
list_move(&folio->lru, &node_folio_list);
continue;
}
nr_migrated += damon_migrate_folio_list(&node_folio_list,
NODE_DATA(nid),
target_nid);
nid = folio_nid(lru_to_folio(folio_list));
} while (!list_empty(folio_list));
nr_migrated += damon_migrate_folio_list(&node_folio_list,
NODE_DATA(nid),
target_nid);
memalloc_noreclaim_restore(noreclaim_flag);
return nr_migrated;
}
bool damos_ops_has_filter(struct damos *s)
{
struct damos_filter *f;
damos_for_each_ops_filter(f, s)
return true;
return false;
}
+23
View File
@@ -0,0 +1,23 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Common Code for Data Access Monitoring
*/
#include <linux/damon.h>
struct folio *damon_get_folio(unsigned long pfn);
void damon_ptep_mkold(pte_t *pte, struct vm_area_struct *vma, unsigned long addr);
void damon_pmdp_mkold(pmd_t *pmd, struct vm_area_struct *vma, unsigned long addr);
void damon_folio_mkold(struct folio *folio);
bool damon_folio_young(struct folio *folio);
int damon_cold_score(struct damon_ctx *c, struct damon_region *r,
struct damos *s);
int damon_hot_score(struct damon_ctx *c, struct damon_region *r,
struct damos *s);
bool damos_folio_filter_match(struct damos_filter *filter, struct folio *folio);
unsigned long damon_migrate_pages(struct list_head *folio_list, int target_nid);
bool damos_ops_has_filter(struct damos *s);
+458
View File
@@ -0,0 +1,458 @@
// SPDX-License-Identifier: GPL-2.0
/*
* DAMON Code for The Physical Address Space
*/
#define pr_fmt(fmt) "damon-pa: " fmt
#include <linux/mmu_notifier.h>
#include <linux/page_idle.h>
#include <linux/pagemap.h>
#include <linux/rmap.h>
#include <linux/swap.h>
#include <linux/memory-tiers.h>
#include <linux/mm_inline.h>
#include "../internal.h"
#include "ops-common.h"
static phys_addr_t damon_pa_phys_addr(
unsigned long addr, unsigned long addr_unit)
{
return (phys_addr_t)addr * addr_unit;
}
static unsigned long damon_pa_core_addr(
phys_addr_t pa, unsigned long addr_unit)
{
/*
* Use div_u64() for avoiding linking errors related with __udivdi3,
* __aeabi_uldivmod, or similar problems. This should also improve the
* performance optimization (read div_u64() comment for the detail).
*/
if (sizeof(pa) == 8 && sizeof(addr_unit) == 4)
return div_u64(pa, addr_unit);
return pa / addr_unit;
}
static void damon_pa_mkold(phys_addr_t paddr)
{
struct folio *folio = damon_get_folio(PHYS_PFN(paddr));
if (!folio)
return;
damon_folio_mkold(folio);
folio_put(folio);
}
static void __damon_pa_prepare_access_check(struct damon_region *r,
struct damon_ctx *ctx)
{
r->sampling_addr = damon_rand(ctx, r->ar.start, r->ar.end);
damon_pa_mkold(damon_pa_phys_addr(r->sampling_addr, ctx->addr_unit));
}
static void damon_pa_prepare_access_checks(struct damon_ctx *ctx)
{
struct damon_target *t;
struct damon_region *r;
damon_for_each_target(t, ctx) {
damon_for_each_region(r, t)
__damon_pa_prepare_access_check(r, ctx);
}
}
static bool damon_pa_young(phys_addr_t paddr, unsigned long *folio_sz)
{
struct folio *folio = damon_get_folio(PHYS_PFN(paddr));
bool accessed;
if (!folio)
return false;
accessed = damon_folio_young(folio);
*folio_sz = folio_size(folio);
folio_put(folio);
return accessed;
}
static void __damon_pa_check_access(struct damon_region *r,
struct damon_attrs *attrs, unsigned long addr_unit)
{
static phys_addr_t last_addr;
static unsigned long last_folio_sz = PAGE_SIZE;
static bool last_accessed;
phys_addr_t sampling_addr = damon_pa_phys_addr(
r->sampling_addr, addr_unit);
/* If the region is in the last checked page, reuse the result */
if (ALIGN_DOWN(last_addr, last_folio_sz) ==
ALIGN_DOWN(sampling_addr, last_folio_sz)) {
damon_update_region_access_rate(r, last_accessed, attrs);
return;
}
last_accessed = damon_pa_young(sampling_addr, &last_folio_sz);
damon_update_region_access_rate(r, last_accessed, attrs);
last_addr = sampling_addr;
}
static unsigned int damon_pa_check_accesses(struct damon_ctx *ctx)
{
struct damon_target *t;
struct damon_region *r;
unsigned int max_nr_accesses = 0;
damon_for_each_target(t, ctx) {
damon_for_each_region(r, t) {
__damon_pa_check_access(
r, &ctx->attrs, ctx->addr_unit);
max_nr_accesses = max(r->nr_accesses, max_nr_accesses);
}
}
return max_nr_accesses;
}
static bool damon_pa_filter_match(struct damon_filter *filter,
struct folio *folio)
{
bool matched = false;
struct mem_cgroup *memcg;
switch (filter->type) {
case DAMON_FILTER_TYPE_ANON:
if (!folio) {
matched = false;
break;
}
matched = folio_test_anon(folio);
break;
case DAMON_FILTER_TYPE_MEMCG:
if (!folio) {
matched = false;
break;
}
rcu_read_lock();
memcg = folio_memcg_check(folio);
if (!memcg)
matched = false;
else
matched = filter->memcg_id == mem_cgroup_id(memcg);
rcu_read_unlock();
break;
default:
break;
}
return matched == filter->matching;
}
static bool damon_pa_filter_pass(phys_addr_t pa, struct folio *folio,
struct damon_probe *p)
{
struct damon_filter *f;
bool pass = true;
damon_for_each_filter(f, p) {
if (damon_pa_filter_match(f, folio)) {
pass = f->allow;
break;
}
pass = !f->allow;
}
return pass;
}
static void damon_pa_apply_probes(struct damon_ctx *ctx)
{
struct damon_target *t;
struct damon_region *r;
struct damon_probe *p;
damon_for_each_target(t, ctx) {
damon_for_each_region(r, t) {
int i = 0;
phys_addr_t pa;
struct folio *folio;
pa = damon_pa_phys_addr(r->sampling_addr,
ctx->addr_unit);
folio = damon_get_folio(PHYS_PFN(pa));
damon_for_each_probe(p, ctx) {
if (damon_pa_filter_pass(pa, folio, p))
r->probe_hits[i]++;
i++;
}
if (folio)
folio_put(folio);
}
}
}
/*
* damos_pa_filter_out - Return true if the page should be filtered out.
*/
static bool damos_pa_filter_out(struct damos *scheme, struct folio *folio)
{
struct damos_filter *filter;
if (scheme->core_filters_allowed)
return false;
damos_for_each_ops_filter(filter, scheme) {
if (damos_folio_filter_match(filter, folio))
return !filter->allow;
}
return scheme->ops_filters_default_reject;
}
static bool damon_pa_invalid_damos_folio(struct folio *folio, struct damos *s)
{
if (!folio)
return true;
if (folio == s->last_applied) {
folio_put(folio);
return true;
}
return false;
}
static unsigned long damon_pa_pageout(struct damon_region *r,
unsigned long addr_unit, struct damos *s,
unsigned long *sz_filter_passed)
{
phys_addr_t addr, applied;
LIST_HEAD(folio_list);
bool install_young_filter = true;
struct damos_filter *filter;
struct folio *folio = NULL;
/* check access in page level again by default */
damos_for_each_ops_filter(filter, s) {
if (filter->type == DAMOS_FILTER_TYPE_YOUNG) {
install_young_filter = false;
break;
}
}
if (install_young_filter) {
filter = damos_new_filter(
DAMOS_FILTER_TYPE_YOUNG, true, false);
if (!filter)
return 0;
damos_add_filter(s, filter);
}
addr = damon_pa_phys_addr(r->ar.start, addr_unit);
while (addr < damon_pa_phys_addr(r->ar.end, addr_unit)) {
folio = damon_get_folio(PHYS_PFN(addr));
if (damon_pa_invalid_damos_folio(folio, s)) {
addr += PAGE_SIZE;
continue;
}
if (damos_pa_filter_out(s, folio))
goto put_folio;
else
*sz_filter_passed += folio_size(folio) / addr_unit;
folio_clear_referenced(folio);
folio_test_clear_young(folio);
if (!folio_isolate_lru(folio))
goto put_folio;
if (folio_test_unevictable(folio))
folio_putback_lru(folio);
else
list_add(&folio->lru, &folio_list);
put_folio:
addr += folio_size(folio);
folio_put(folio);
}
if (install_young_filter)
damos_destroy_filter(filter);
applied = reclaim_pages(&folio_list);
cond_resched();
s->last_applied = folio;
return damon_pa_core_addr(applied * PAGE_SIZE, addr_unit);
}
static inline unsigned long damon_pa_de_activate(
struct damon_region *r, unsigned long addr_unit,
struct damos *s, bool activate,
unsigned long *sz_filter_passed)
{
phys_addr_t addr, applied = 0;
struct folio *folio = NULL;
addr = damon_pa_phys_addr(r->ar.start, addr_unit);
while (addr < damon_pa_phys_addr(r->ar.end, addr_unit)) {
folio = damon_get_folio(PHYS_PFN(addr));
if (damon_pa_invalid_damos_folio(folio, s)) {
addr += PAGE_SIZE;
continue;
}
if (damos_pa_filter_out(s, folio))
goto put_folio;
else
*sz_filter_passed += folio_size(folio) / addr_unit;
if (activate)
folio_activate(folio);
else
folio_deactivate(folio);
applied += folio_nr_pages(folio);
put_folio:
addr += folio_size(folio);
folio_put(folio);
}
s->last_applied = folio;
return damon_pa_core_addr(applied * PAGE_SIZE, addr_unit);
}
static unsigned long damon_pa_activate_pages(struct damon_region *r,
unsigned long addr_unit, struct damos *s,
unsigned long *sz_filter_passed)
{
return damon_pa_de_activate(r, addr_unit, s, true, sz_filter_passed);
}
static unsigned long damon_pa_deactivate_pages(struct damon_region *r,
unsigned long addr_unit, struct damos *s,
unsigned long *sz_filter_passed)
{
return damon_pa_de_activate(r, addr_unit, s, false, sz_filter_passed);
}
static unsigned long damon_pa_migrate(struct damon_region *r,
unsigned long addr_unit, struct damos *s,
unsigned long *sz_filter_passed)
{
phys_addr_t addr, applied;
LIST_HEAD(folio_list);
struct folio *folio = NULL;
addr = damon_pa_phys_addr(r->ar.start, addr_unit);
while (addr < damon_pa_phys_addr(r->ar.end, addr_unit)) {
folio = damon_get_folio(PHYS_PFN(addr));
if (damon_pa_invalid_damos_folio(folio, s)) {
addr += PAGE_SIZE;
continue;
}
if (damos_pa_filter_out(s, folio))
goto put_folio;
else
*sz_filter_passed += folio_size(folio) / addr_unit;
if (!folio_isolate_lru(folio))
goto put_folio;
node_stat_add_folio(folio, NR_ISOLATED_ANON +
folio_is_file_lru(folio));
list_add(&folio->lru, &folio_list);
put_folio:
addr += folio_size(folio);
folio_put(folio);
}
applied = damon_migrate_pages(&folio_list, s->target_nid);
cond_resched();
s->last_applied = folio;
return damon_pa_core_addr(applied * PAGE_SIZE, addr_unit);
}
static unsigned long damon_pa_stat(struct damon_region *r,
unsigned long addr_unit, struct damos *s,
unsigned long *sz_filter_passed)
{
phys_addr_t addr;
struct folio *folio = NULL;
if (!damos_ops_has_filter(s))
return 0;
addr = damon_pa_phys_addr(r->ar.start, addr_unit);
while (addr < damon_pa_phys_addr(r->ar.end, addr_unit)) {
folio = damon_get_folio(PHYS_PFN(addr));
if (damon_pa_invalid_damos_folio(folio, s)) {
addr += PAGE_SIZE;
continue;
}
if (!damos_pa_filter_out(s, folio))
*sz_filter_passed += folio_size(folio) / addr_unit;
addr += folio_size(folio);
folio_put(folio);
}
s->last_applied = folio;
return 0;
}
static unsigned long damon_pa_apply_scheme(struct damon_ctx *ctx,
struct damon_target *t, struct damon_region *r,
struct damos *scheme, unsigned long *sz_filter_passed)
{
unsigned long aunit = ctx->addr_unit;
switch (scheme->action) {
case DAMOS_PAGEOUT:
return damon_pa_pageout(r, aunit, scheme, sz_filter_passed);
case DAMOS_LRU_PRIO:
return damon_pa_activate_pages(r, aunit, scheme,
sz_filter_passed);
case DAMOS_LRU_DEPRIO:
return damon_pa_deactivate_pages(r, aunit, scheme,
sz_filter_passed);
case DAMOS_MIGRATE_HOT:
case DAMOS_MIGRATE_COLD:
return damon_pa_migrate(r, aunit, scheme, sz_filter_passed);
case DAMOS_STAT:
return damon_pa_stat(r, aunit, scheme, sz_filter_passed);
default:
/* DAMOS actions that not yet supported by 'paddr'. */
break;
}
return 0;
}
static int damon_pa_scheme_score(struct damon_ctx *context,
struct damon_region *r, struct damos *scheme)
{
switch (scheme->action) {
case DAMOS_PAGEOUT:
return damon_cold_score(context, r, scheme);
case DAMOS_LRU_PRIO:
return damon_hot_score(context, r, scheme);
case DAMOS_LRU_DEPRIO:
return damon_cold_score(context, r, scheme);
case DAMOS_MIGRATE_HOT:
return damon_hot_score(context, r, scheme);
case DAMOS_MIGRATE_COLD:
return damon_cold_score(context, r, scheme);
default:
break;
}
return DAMOS_MAX_SCORE;
}
static int __init damon_pa_initcall(void)
{
struct damon_operations ops = {
.id = DAMON_OPS_PADDR,
.init = NULL,
.update = NULL,
.prepare_access_checks = damon_pa_prepare_access_checks,
.check_accesses = damon_pa_check_accesses,
.apply_probes = damon_pa_apply_probes,
.target_valid = NULL,
.apply_scheme = damon_pa_apply_scheme,
.get_scheme_score = damon_pa_scheme_score,
};
return damon_register_ops(&ops);
};
subsys_initcall(damon_pa_initcall);
+483
View File
@@ -0,0 +1,483 @@
// SPDX-License-Identifier: GPL-2.0
/*
* DAMON-based page reclamation
*/
#define pr_fmt(fmt) "damon-reclaim: " fmt
#include <linux/damon.h>
#include <linux/kstrtox.h>
#include <linux/module.h>
#include "modules-common.h"
#ifdef MODULE_PARAM_PREFIX
#undef MODULE_PARAM_PREFIX
#endif
#define MODULE_PARAM_PREFIX "damon_reclaim."
/*
* Enable or disable DAMON_RECLAIM.
*
* You can enable DAMON_RCLAIM by setting the value of this parameter as ``Y``.
* Setting it as ``N`` disables DAMON_RECLAIM. Note that DAMON_RECLAIM could
* do no real monitoring and reclamation due to the watermarks-based activation
* condition. Refer to below descriptions for the watermarks parameter for
* this.
*/
static bool enabled __read_mostly;
/*
* Make DAMON_RECLAIM reads the input parameters again, except ``enabled``.
*
* Input parameters that updated while DAMON_RECLAIM is running are not applied
* by default. Once this parameter is set as ``Y``, DAMON_RECLAIM reads values
* of parameters except ``enabled`` again. Once the re-reading is done, this
* parameter is set as ``N``. If invalid parameters are found while the
* re-reading, DAMON_RECLAIM will be disabled.
*/
static bool commit_inputs __read_mostly;
/*
* Time threshold for cold memory regions identification in microseconds.
*
* If a memory region is not accessed for this or longer time, DAMON_RECLAIM
* identifies the region as cold, and reclaims. 120 seconds by default.
*/
static unsigned long min_age __read_mostly = 120000000;
module_param(min_age, ulong, 0600);
static struct damos_quota damon_reclaim_quota = {
/* use up to 10 ms time, reclaim up to 128 MiB per 1 sec by default */
.ms = 10,
.sz = 128 * 1024 * 1024,
.reset_interval = 1000,
/* Within the quota, page out older regions first. */
.weight_sz = 0,
.weight_nr_accesses = 0,
.weight_age = 1
};
DEFINE_DAMON_MODULES_DAMOS_QUOTAS(damon_reclaim_quota);
/*
* Desired level of memory pressure-stall time in microseconds.
*
* While keeping the caps that set by other quotas, DAMON_RECLAIM automatically
* increases and decreases the effective level of the quota aiming this level of
* memory pressure is incurred. System-wide ``some`` memory PSI in microseconds
* per quota reset interval (``quota_reset_interval_ms``) is collected and
* compared to this value to see if the aim is satisfied. Value zero means
* disabling this auto-tuning feature.
*
* Disabled by default.
*/
static unsigned long quota_mem_pressure_us __read_mostly;
module_param(quota_mem_pressure_us, ulong, 0600);
/*
* User-specifiable feedback for auto-tuning of the effective quota.
*
* While keeping the caps that set by other quotas, DAMON_RECLAIM automatically
* increases and decreases the effective level of the quota aiming receiving this
* feedback of value ``10,000`` from the user. DAMON_RECLAIM assumes the feedback
* value and the quota are positively proportional. Value zero means disabling
* this auto-tuning feature.
*
* Disabled by default.
*
*/
static unsigned long quota_autotune_feedback __read_mostly;
module_param(quota_autotune_feedback, ulong, 0600);
/*
* Auto-tune monitoring intervals.
*
* If this parameter is set as ``Y``, DAMON_RECLAIM automatically tunes DAMON's
* sampling and aggregation intervals. The auto-tuning aims to capture
* meaningful amount of access events in each DAMON-snapshot, while keeping the
* sampling intervals 5 milliseconds in minimum, and 10 seconds in maximum.
* Setting this as ``N`` disables the auto-tuning.
*
* Disabled by default.
*/
static bool autotune_monitoring_intervals __read_mostly;
module_param(autotune_monitoring_intervals, bool, 0600);
static struct damos_watermarks damon_reclaim_wmarks = {
.metric = DAMOS_WMARK_FREE_MEM_RATE,
.interval = 5000000, /* 5 seconds */
.high = 500, /* 50 percent */
.mid = 400, /* 40 percent */
.low = 200, /* 20 percent */
};
DEFINE_DAMON_MODULES_WMARKS_PARAMS(damon_reclaim_wmarks);
static struct damon_attrs damon_reclaim_mon_attrs = {
.sample_interval = 5000, /* 5 ms */
.aggr_interval = 100000, /* 100 ms */
.ops_update_interval = 0,
.min_nr_regions = 10,
.max_nr_regions = 1000,
};
DEFINE_DAMON_MODULES_MON_ATTRS_PARAMS(damon_reclaim_mon_attrs);
/*
* Start of the target memory region in physical address.
*
* The start physical address of memory region that DAMON_RECLAIM will do work
* against. By default, the system's entire physical memory is used as the
* region.
*/
static unsigned long monitor_region_start __read_mostly;
module_param(monitor_region_start, ulong, 0600);
/*
* End of the target memory region in physical address.
*
* The end physical address of memory region that DAMON_RECLAIM will do work
* against. By default, the system's entire physical memory is used as the
* region.
*/
static unsigned long monitor_region_end __read_mostly;
module_param(monitor_region_end, ulong, 0600);
/*
* Scale factor for DAMON_RECLAIM to ops address conversion.
*
* This parameter must not be set to 0.
*/
static unsigned long addr_unit __read_mostly = 1;
/*
* Skip anonymous pages reclamation.
*
* If this parameter is set as ``Y``, DAMON_RECLAIM does not reclaim anonymous
* pages. By default, ``N``.
*/
static bool skip_anon __read_mostly;
module_param(skip_anon, bool, 0600);
static struct damos_stat damon_reclaim_stat;
DEFINE_DAMON_MODULES_DAMOS_STATS_PARAMS(damon_reclaim_stat,
reclaim_tried_regions, reclaimed_regions, quota_exceeds);
static struct damon_ctx *ctx;
static struct damon_target *target;
static struct damos *damon_reclaim_new_scheme(unsigned long aggr_interval)
{
struct damos_access_pattern pattern = {
/* Find regions having PAGE_SIZE or larger size */
.min_sz_region = PAGE_SIZE,
.max_sz_region = ULONG_MAX,
/* and not accessed at all */
.min_nr_accesses = 0,
.max_nr_accesses = 0,
/* for min_age or more micro-seconds */
.min_age_region = min_age / aggr_interval,
.max_age_region = UINT_MAX,
};
return damon_new_scheme(
&pattern,
/* page out those, as soon as found */
DAMOS_PAGEOUT,
/* for each aggregation interval */
0,
/* under the quota. */
&damon_reclaim_quota,
/* (De)activate this according to the watermarks. */
&damon_reclaim_wmarks,
NUMA_NO_NODE);
}
static int damon_reclaim_apply_parameters(void)
{
struct damon_ctx *param_ctx;
struct damon_target *param_target;
struct damon_attrs attrs;
struct damos *scheme;
struct damos_quota_goal *goal;
struct damos_filter *filter;
int err;
err = damon_modules_new_paddr_ctx_target(&param_ctx, &param_target);
if (err)
return err;
param_ctx->addr_unit = addr_unit;
param_ctx->min_region_sz = max(DAMON_MIN_REGION_SZ / addr_unit, 1);
if (!is_power_of_2(param_ctx->min_region_sz)) {
err = -EINVAL;
goto out;
}
if (!damon_reclaim_mon_attrs.aggr_interval) {
err = -EINVAL;
goto out;
}
attrs = damon_reclaim_mon_attrs;
if (autotune_monitoring_intervals) {
attrs.sample_interval = 5000;
attrs.aggr_interval = 100000;
attrs.intervals_goal.access_bp = 40;
attrs.intervals_goal.aggrs = 3;
attrs.intervals_goal.min_sample_us = 5000;
attrs.intervals_goal.max_sample_us = 10 * 1000 * 1000;
}
err = damon_set_attrs(param_ctx, &attrs);
if (err)
goto out;
err = -ENOMEM;
scheme = damon_reclaim_new_scheme(attrs.aggr_interval);
if (!scheme)
goto out;
damon_set_schemes(param_ctx, &scheme, 1);
if (quota_mem_pressure_us) {
goal = damos_new_quota_goal(DAMOS_QUOTA_SOME_MEM_PSI_US,
quota_mem_pressure_us);
if (!goal)
goto out;
damos_add_quota_goal(&scheme->quota, goal);
}
if (quota_autotune_feedback) {
goal = damos_new_quota_goal(DAMOS_QUOTA_USER_INPUT, 10000);
if (!goal)
goto out;
goal->current_value = quota_autotune_feedback;
damos_add_quota_goal(&scheme->quota, goal);
}
if (skip_anon) {
filter = damos_new_filter(DAMOS_FILTER_TYPE_ANON, true, false);
if (!filter)
goto out;
damos_add_filter(scheme, filter);
}
err = damon_set_region_system_rams_default(param_target,
&monitor_region_start, &monitor_region_end,
param_ctx->addr_unit, param_ctx->min_region_sz);
if (err)
goto out;
err = damon_commit_ctx(ctx, param_ctx);
out:
damon_destroy_ctx(param_ctx);
return err;
}
static int damon_reclaim_commit_inputs_fn(void *arg)
{
return damon_reclaim_apply_parameters();
}
static bool damon_reclaim_damon_has_started;
static int damon_reclaim_commit_inputs_store(const char *val,
const struct kernel_param *kp)
{
bool commit_inputs_request;
int err;
struct damon_call_control control = {
.fn = damon_reclaim_commit_inputs_fn,
};
if (!val) {
commit_inputs_request = true;
} else {
err = kstrtobool(val, &commit_inputs_request);
if (err)
return err;
}
if (!commit_inputs_request)
return 0;
/* Skip damon_call() if ctx has not successfully started. */
if (!damon_reclaim_damon_has_started)
return -EINVAL;
err = damon_call(ctx, &control);
return err ? err : control.return_code;
}
static const struct kernel_param_ops commit_inputs_param_ops = {
.flags = KERNEL_PARAM_OPS_FL_NOARG,
.set = damon_reclaim_commit_inputs_store,
.get = param_get_bool,
};
module_param_cb(commit_inputs, &commit_inputs_param_ops, &commit_inputs, 0600);
static int damon_reclaim_damon_call_fn(void *arg)
{
struct damon_ctx *c = arg;
struct damos *s;
/* update the stats parameter */
damon_for_each_scheme(s, c)
damon_reclaim_stat = s->stat;
return 0;
}
static struct damon_call_control call_control = {
.fn = damon_reclaim_damon_call_fn,
.repeat = true,
};
static int damon_reclaim_turn(bool on)
{
int err;
if (!on)
return damon_stop(&ctx, 1);
err = damon_reclaim_apply_parameters();
if (err)
return err;
err = damon_start(&ctx, 1, true);
if (err)
return err;
if (!damon_reclaim_damon_has_started)
damon_reclaim_damon_has_started = true;
return damon_call(ctx, &call_control);
}
static int damon_reclaim_addr_unit_store(const char *val,
const struct kernel_param *kp)
{
unsigned long input_addr_unit;
int err = kstrtoul(val, 0, &input_addr_unit);
if (err)
return err;
if (!input_addr_unit)
return -EINVAL;
addr_unit = input_addr_unit;
return 0;
}
static const struct kernel_param_ops addr_unit_param_ops = {
.set = damon_reclaim_addr_unit_store,
.get = param_get_ulong,
};
module_param_cb(addr_unit, &addr_unit_param_ops, &addr_unit, 0600);
MODULE_PARM_DESC(addr_unit,
"Scale factor for DAMON_RECLAIM to ops address conversion (default: 1)");
static bool damon_reclaim_enabled(void)
{
if (!ctx)
return false;
return damon_is_running(ctx);
}
static int damon_reclaim_enabled_store(const char *val,
const struct kernel_param *kp)
{
int err;
err = kstrtobool(val, &enabled);
if (err)
return err;
if (damon_reclaim_enabled() == enabled)
return 0;
/* Called before init function. The function will handle this. */
if (!damon_initialized())
return 0;
/* damon_modules_new_paddr_ctx_target() in the init function failed. */
if (!ctx)
return -ENOMEM;
return damon_reclaim_turn(enabled);
}
static int damon_reclaim_enabled_load(char *buffer,
const struct kernel_param *kp)
{
return sprintf(buffer, "%c\n", damon_reclaim_enabled() ? 'Y' : 'N');
}
static const struct kernel_param_ops enabled_param_ops = {
.set = damon_reclaim_enabled_store,
.get = damon_reclaim_enabled_load,
};
module_param_cb(enabled, &enabled_param_ops, &enabled, 0600);
MODULE_PARM_DESC(enabled,
"Enable or disable DAMON_RECLAIM (default: disabled)");
static int damon_reclaim_kdamond_pid_store(const char *val,
const struct kernel_param *kp)
{
/*
* kdamond_pid is read-only, but kernel command line could write it.
* Do nothing here.
*/
return 0;
}
static int damon_reclaim_kdamond_pid_load(char *buffer,
const struct kernel_param *kp)
{
int kdamond_pid = -1;
if (ctx) {
kdamond_pid = damon_kdamond_pid(ctx);
if (kdamond_pid < 0)
kdamond_pid = -1;
}
return sprintf(buffer, "%d\n", kdamond_pid);
}
static const struct kernel_param_ops kdamond_pid_param_ops = {
.set = damon_reclaim_kdamond_pid_store,
.get = damon_reclaim_kdamond_pid_load,
};
/*
* PID of the DAMON thread
*
* If DAMON_RECLAIM is enabled, this becomes the PID of the worker thread.
* Else, -1.
*/
module_param_cb(kdamond_pid, &kdamond_pid_param_ops, NULL, 0400);
static int __init damon_reclaim_init(void)
{
int err;
if (!damon_initialized()) {
err = -ENOMEM;
goto out;
}
err = damon_modules_new_paddr_ctx_target(&ctx, &target);
if (err)
goto out;
call_control.data = ctx;
/* 'enabled' has set before this function, probably via command line */
if (enabled)
err = damon_reclaim_turn(true);
out:
if (err && enabled)
enabled = false;
return err;
}
module_init(damon_reclaim_init);
+327
View File
@@ -0,0 +1,327 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Shows data access monitoring results in simple metrics.
*/
#define pr_fmt(fmt) "damon-stat: " fmt
#include <linux/damon.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/sort.h>
#ifdef MODULE_PARAM_PREFIX
#undef MODULE_PARAM_PREFIX
#endif
#define MODULE_PARAM_PREFIX "damon_stat."
static int damon_stat_enabled_store(
const char *val, const struct kernel_param *kp);
static int damon_stat_enabled_load(char *buffer,
const struct kernel_param *kp);
static const struct kernel_param_ops enabled_param_ops = {
.set = damon_stat_enabled_store,
.get = damon_stat_enabled_load,
};
static bool enabled __read_mostly = IS_ENABLED(
CONFIG_DAMON_STAT_ENABLED_DEFAULT);
module_param_cb(enabled, &enabled_param_ops, NULL, 0600);
MODULE_PARM_DESC(enabled, "Enable of disable DAMON_STAT");
static unsigned long estimated_memory_bandwidth __read_mostly;
module_param(estimated_memory_bandwidth, ulong, 0400);
MODULE_PARM_DESC(estimated_memory_bandwidth,
"Estimated memory bandwidth usage in bytes per second");
static long memory_idle_ms_percentiles[101] = {0,};
module_param_array(memory_idle_ms_percentiles, long, NULL, 0400);
MODULE_PARM_DESC(memory_idle_ms_percentiles,
"Memory idle time percentiles in milliseconds");
static unsigned long aggr_interval_us;
module_param(aggr_interval_us, ulong, 0400);
MODULE_PARM_DESC(aggr_interval_us,
"Current tuned aggregation interval in microseconds");
static struct damon_ctx *damon_stat_context;
static unsigned long damon_stat_last_refresh_jiffies;
static void damon_stat_set_estimated_memory_bandwidth(struct damon_ctx *c)
{
struct damon_target *t;
struct damon_region *r;
unsigned long access_bytes = 0;
damon_for_each_target(t, c) {
damon_for_each_region(r, t)
access_bytes += (r->ar.end - r->ar.start) *
r->nr_accesses;
}
estimated_memory_bandwidth = access_bytes * USEC_PER_MSEC *
MSEC_PER_SEC / c->attrs.aggr_interval;
}
static int damon_stat_idletime(const struct damon_region *r)
{
if (r->nr_accesses)
return -1 * (r->age + 1);
return r->age + 1;
}
static int damon_stat_cmp_regions(const void *a, const void *b)
{
const struct damon_region *ra = *(const struct damon_region **)a;
const struct damon_region *rb = *(const struct damon_region **)b;
return damon_stat_idletime(ra) - damon_stat_idletime(rb);
}
static int damon_stat_sort_regions(struct damon_ctx *c,
struct damon_region ***sorted_ptr, int *nr_regions_ptr,
unsigned long *total_sz_ptr)
{
struct damon_target *t;
struct damon_region *r;
struct damon_region **region_pointers;
unsigned int nr_regions = 0;
unsigned long total_sz = 0;
damon_for_each_target(t, c) {
/* there is only one target */
region_pointers = kmalloc_objs(*region_pointers,
damon_nr_regions(t));
if (!region_pointers)
return -ENOMEM;
damon_for_each_region(r, t) {
region_pointers[nr_regions++] = r;
total_sz += r->ar.end - r->ar.start;
}
}
sort(region_pointers, nr_regions, sizeof(*region_pointers),
damon_stat_cmp_regions, NULL);
*sorted_ptr = region_pointers;
*nr_regions_ptr = nr_regions;
*total_sz_ptr = total_sz;
return 0;
}
static void damon_stat_set_idletime_percentiles(struct damon_ctx *c)
{
struct damon_region **sorted_regions, *region;
int nr_regions;
unsigned long total_sz, accounted_bytes = 0;
int err, i, next_percentile = 0;
err = damon_stat_sort_regions(c, &sorted_regions, &nr_regions,
&total_sz);
if (err)
return;
for (i = 0; i < nr_regions; i++) {
region = sorted_regions[i];
accounted_bytes += region->ar.end - region->ar.start;
while (next_percentile <= accounted_bytes * 100 / total_sz)
memory_idle_ms_percentiles[next_percentile++] =
damon_stat_idletime(region) *
(long)c->attrs.aggr_interval / USEC_PER_MSEC;
}
kfree(sorted_regions);
}
static int damon_stat_damon_call_fn(void *data)
{
struct damon_ctx *c = data;
/* avoid unnecessarily frequent stat update */
if (time_before_eq(jiffies, damon_stat_last_refresh_jiffies +
msecs_to_jiffies(5 * MSEC_PER_SEC)))
return 0;
damon_stat_last_refresh_jiffies = jiffies;
aggr_interval_us = c->attrs.aggr_interval;
damon_stat_set_estimated_memory_bandwidth(c);
damon_stat_set_idletime_percentiles(c);
return 0;
}
static struct damon_ctx *damon_stat_build_ctx(void)
{
struct damon_ctx *ctx;
struct damon_attrs attrs;
struct damon_target *target;
unsigned long start = 0, end = 0;
ctx = damon_new_ctx();
if (!ctx)
return NULL;
attrs = (struct damon_attrs) {
.sample_interval = 5 * USEC_PER_MSEC,
.aggr_interval = 100 * USEC_PER_MSEC,
.ops_update_interval = 60 * USEC_PER_MSEC * MSEC_PER_SEC,
.min_nr_regions = 10,
.max_nr_regions = 1000,
};
/*
* auto-tune sampling and aggregation interval aiming 4% DAMON-observed
* accesses ratio, keeping sampling interval in [5ms, 10s] range.
*/
attrs.intervals_goal = (struct damon_intervals_goal) {
.access_bp = 400, .aggrs = 3,
.min_sample_us = 5000, .max_sample_us = 10000000,
};
if (damon_set_attrs(ctx, &attrs))
goto free_out;
if (damon_select_ops(ctx, DAMON_OPS_PADDR))
goto free_out;
target = damon_new_target();
if (!target)
goto free_out;
damon_add_target(ctx, target);
if (damon_set_region_system_rams_default(target, &start, &end,
ctx->addr_unit, ctx->min_region_sz))
goto free_out;
return ctx;
free_out:
damon_destroy_ctx(ctx);
return NULL;
}
static struct damon_call_control call_control = {
.fn = damon_stat_damon_call_fn,
.repeat = true,
};
static int damon_stat_start(void)
{
int err;
if (damon_stat_context) {
if (damon_is_running(damon_stat_context))
return -EAGAIN;
damon_destroy_ctx(damon_stat_context);
}
damon_stat_context = damon_stat_build_ctx();
if (!damon_stat_context)
return -ENOMEM;
err = damon_start(&damon_stat_context, 1, true);
if (err) {
damon_destroy_ctx(damon_stat_context);
damon_stat_context = NULL;
return err;
}
damon_stat_last_refresh_jiffies = jiffies;
call_control.data = damon_stat_context;
return damon_call(damon_stat_context, &call_control);
}
static void damon_stat_stop(void)
{
damon_stop(&damon_stat_context, 1);
damon_destroy_ctx(damon_stat_context);
damon_stat_context = NULL;
}
static bool damon_stat_enabled(void)
{
if (!damon_stat_context)
return false;
return damon_is_running(damon_stat_context);
}
static int damon_stat_enabled_store(
const char *val, const struct kernel_param *kp)
{
int err;
err = kstrtobool(val, &enabled);
if (err)
return err;
if (damon_stat_enabled() == enabled)
return 0;
if (!damon_initialized())
/*
* probably called from command line parsing (parse_args()).
* Cannot call damon_new_ctx(). Let damon_stat_init() handle.
*/
return 0;
if (enabled)
return damon_stat_start();
damon_stat_stop();
return 0;
}
static int damon_stat_enabled_load(char *buffer, const struct kernel_param *kp)
{
return sprintf(buffer, "%c\n", damon_stat_enabled() ? 'Y' : 'N');
}
static int damon_stat_kdamond_pid_store(
const char *val, const struct kernel_param *kp)
{
/*
* kdamond_pid is read-only, but kernel command line could write it.
* Do nothing here.
*/
return 0;
}
static int damon_stat_kdamond_pid_load(
char *buffer, const struct kernel_param *kp)
{
int pid;
if (!damon_stat_context) {
pid = -1;
} else {
pid = damon_kdamond_pid(damon_stat_context);
if (pid < 1)
pid = -1;
}
return sprintf(buffer, "%d\n", pid);
}
static const struct kernel_param_ops kdamond_pid_param_ops = {
.set = damon_stat_kdamond_pid_store,
.get = damon_stat_kdamond_pid_load,
};
/*
* PID of the DAMON thread
*
* If DAMON_STAT is enabled, this becomes the PID of the worker thread.
* Else, -1.
*/
module_param_cb(kdamond_pid, &kdamond_pid_param_ops, NULL, 0400);
MODULE_PARM_DESC(kdamond_pid, "pid of the kdamond");
static int __init damon_stat_init(void)
{
int err = 0;
if (!damon_initialized()) {
err = -ENOMEM;
goto out;
}
/* probably set via command line */
if (enabled)
err = damon_stat_start();
out:
if (err && enabled)
enabled = false;
return err;
}
module_init(damon_stat_init);
+145
View File
@@ -0,0 +1,145 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Common Code for DAMON Sysfs Interface
*/
#include <linux/slab.h>
#include "sysfs-common.h"
DEFINE_MUTEX(damon_sysfs_lock);
/*
* unsigned long range directory
*/
struct damon_sysfs_ul_range *damon_sysfs_ul_range_alloc(
unsigned long min,
unsigned long max)
{
struct damon_sysfs_ul_range *range = kmalloc_obj(*range);
if (!range)
return NULL;
range->kobj = (struct kobject){};
range->min = min;
range->max = max;
return range;
}
static ssize_t min_show(struct kobject *kobj, struct kobj_attribute *attr,
char *buf)
{
struct damon_sysfs_ul_range *range = container_of(kobj,
struct damon_sysfs_ul_range, kobj);
return sysfs_emit(buf, "%lu\n", range->min);
}
static ssize_t min_store(struct kobject *kobj, struct kobj_attribute *attr,
const char *buf, size_t count)
{
struct damon_sysfs_ul_range *range = container_of(kobj,
struct damon_sysfs_ul_range, kobj);
unsigned long min;
int err;
err = kstrtoul(buf, 0, &min);
if (err)
return err;
range->min = min;
return count;
}
static ssize_t max_show(struct kobject *kobj, struct kobj_attribute *attr,
char *buf)
{
struct damon_sysfs_ul_range *range = container_of(kobj,
struct damon_sysfs_ul_range, kobj);
return sysfs_emit(buf, "%lu\n", range->max);
}
static ssize_t max_store(struct kobject *kobj, struct kobj_attribute *attr,
const char *buf, size_t count)
{
struct damon_sysfs_ul_range *range = container_of(kobj,
struct damon_sysfs_ul_range, kobj);
unsigned long max;
int err;
err = kstrtoul(buf, 0, &max);
if (err)
return err;
range->max = max;
return count;
}
void damon_sysfs_ul_range_release(struct kobject *kobj)
{
kfree(container_of(kobj, struct damon_sysfs_ul_range, kobj));
}
static struct kobj_attribute damon_sysfs_ul_range_min_attr =
__ATTR_RW_MODE(min, 0600);
static struct kobj_attribute damon_sysfs_ul_range_max_attr =
__ATTR_RW_MODE(max, 0600);
static struct attribute *damon_sysfs_ul_range_attrs[] = {
&damon_sysfs_ul_range_min_attr.attr,
&damon_sysfs_ul_range_max_attr.attr,
NULL,
};
ATTRIBUTE_GROUPS(damon_sysfs_ul_range);
const struct kobj_type damon_sysfs_ul_range_ktype = {
.release = damon_sysfs_ul_range_release,
.sysfs_ops = &kobj_sysfs_ops,
.default_groups = damon_sysfs_ul_range_groups,
};
static bool damon_sysfs_memcg_path_eq(struct mem_cgroup *memcg,
char *memcg_path_buf, char *path)
{
#ifdef CONFIG_MEMCG
cgroup_path(memcg->css.cgroup, memcg_path_buf, PATH_MAX);
if (sysfs_streq(memcg_path_buf, path))
return true;
#endif /* CONFIG_MEMCG */
return false;
}
int damon_sysfs_memcg_path_to_id(char *memcg_path, u64 *id)
{
struct mem_cgroup *memcg;
char *path;
bool found = false;
if (!memcg_path)
return -EINVAL;
path = kmalloc_array(PATH_MAX, sizeof(*path), GFP_KERNEL);
if (!path)
return -ENOMEM;
for (memcg = mem_cgroup_iter(NULL, NULL, NULL); memcg;
memcg = mem_cgroup_iter(NULL, memcg, NULL)) {
/* skip offlined memcg */
if (!mem_cgroup_online(memcg))
continue;
if (damon_sysfs_memcg_path_eq(memcg, path, memcg_path)) {
*id = mem_cgroup_id(memcg);
found = true;
mem_cgroup_iter_break(NULL, memcg);
break;
}
}
kfree(path);
return found ? 0 : -EINVAL;
}
+61
View File
@@ -0,0 +1,61 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Common Code for DAMON Sysfs Interface
*/
#include <linux/damon.h>
#include <linux/kobject.h>
extern struct mutex damon_sysfs_lock;
struct damon_sysfs_ul_range {
struct kobject kobj;
unsigned long min;
unsigned long max;
};
struct damon_sysfs_ul_range *damon_sysfs_ul_range_alloc(
unsigned long min,
unsigned long max);
void damon_sysfs_ul_range_release(struct kobject *kobj);
extern const struct kobj_type damon_sysfs_ul_range_ktype;
/*
* schemes directory
*/
struct damon_sysfs_schemes {
struct kobject kobj;
struct damon_sysfs_scheme **schemes_arr;
int nr;
};
struct damon_sysfs_schemes *damon_sysfs_schemes_alloc(void);
void damon_sysfs_schemes_rm_dirs(struct damon_sysfs_schemes *schemes);
extern const struct kobj_type damon_sysfs_schemes_ktype;
int damon_sysfs_add_schemes(struct damon_ctx *ctx,
struct damon_sysfs_schemes *sysfs_schemes);
void damon_sysfs_schemes_update_stats(
struct damon_sysfs_schemes *sysfs_schemes,
struct damon_ctx *ctx);
void damos_sysfs_populate_region_dir(struct damon_sysfs_schemes *sysfs_schemes,
struct damon_ctx *ctx, struct damon_target *t,
struct damon_region *r, struct damos *s,
bool total_bytes_only, unsigned long sz_filter_passed);
int damon_sysfs_schemes_clear_regions(
struct damon_sysfs_schemes *sysfs_schemes);
int damos_sysfs_set_quota_scores(struct damon_sysfs_schemes *sysfs_schemes,
struct damon_ctx *ctx);
void damos_sysfs_update_effective_quotas(
struct damon_sysfs_schemes *sysfs_schemes,
struct damon_ctx *ctx);
int damon_sysfs_memcg_path_to_id(char *memcg_path, u64 *id);
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+18
View File
@@ -0,0 +1,18 @@
# for DAMON core
CONFIG_KUNIT=y
CONFIG_DAMON=y
CONFIG_DAMON_KUNIT_TEST=y
# for DAMON vaddr ops
CONFIG_MMU=y
CONFIG_PAGE_IDLE_FLAG=y
CONFIG_DAMON_VADDR=y
CONFIG_DAMON_VADDR_KUNIT_TEST=y
# for DAMON sysfs interface
CONFIG_SYSFS=y
CONFIG_DAMON_SYSFS=y
CONFIG_DAMON_SYSFS_KUNIT_TEST=y
# enable DAMON_DEBUG_SANITY to catch any bug
CONFIG_DAMON_DEBUG_SANITY=y
File diff suppressed because it is too large Load Diff
+110
View File
@@ -0,0 +1,110 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Data Access Monitor Unit Tests
*/
#ifdef CONFIG_DAMON_SYSFS_KUNIT_TEST
#ifndef _DAMON_SYSFS_TEST_H
#define _DAMON_SYSFS_TEST_H
#include <kunit/test.h>
static unsigned int nr_damon_targets(struct damon_ctx *ctx)
{
struct damon_target *t;
unsigned int nr_targets = 0;
damon_for_each_target(t, ctx)
nr_targets++;
return nr_targets;
}
static int __damon_sysfs_test_get_any_pid(int min, int max)
{
struct pid *pid;
int i;
for (i = min; i <= max; i++) {
pid = find_get_pid(i);
if (pid) {
put_pid(pid);
return i;
}
}
return -1;
}
static void damon_sysfs_test_add_targets(struct kunit *test)
{
struct damon_sysfs_targets *sysfs_targets;
struct damon_sysfs_target *sysfs_target;
struct damon_ctx *ctx;
sysfs_targets = damon_sysfs_targets_alloc();
if (!sysfs_targets)
kunit_skip(test, "sysfs_targets alloc fail");
sysfs_targets->nr = 1;
sysfs_targets->targets_arr = kmalloc_objs(*sysfs_targets->targets_arr,
1);
if (!sysfs_targets->targets_arr) {
kfree(sysfs_targets);
kunit_skip(test, "targets_arr alloc fail");
}
sysfs_target = damon_sysfs_target_alloc();
if (!sysfs_target) {
kfree(sysfs_targets->targets_arr);
kfree(sysfs_targets);
kunit_skip(test, "sysfs_target alloc fail");
}
sysfs_target->pid = __damon_sysfs_test_get_any_pid(12, 100);
sysfs_target->regions = damon_sysfs_regions_alloc();
if (!sysfs_target->regions) {
kfree(sysfs_targets->targets_arr);
kfree(sysfs_targets);
kfree(sysfs_target);
kunit_skip(test, "sysfs_regions alloc fail");
}
sysfs_targets->targets_arr[0] = sysfs_target;
ctx = damon_new_ctx();
if (!ctx) {
kfree(sysfs_targets->targets_arr);
kfree(sysfs_targets);
kfree(sysfs_target->regions);
kfree(sysfs_target);
kunit_skip(test, "ctx alloc fail");
}
damon_sysfs_add_targets(ctx, sysfs_targets);
KUNIT_EXPECT_EQ(test, 1u, nr_damon_targets(ctx));
sysfs_target->pid = __damon_sysfs_test_get_any_pid(
sysfs_target->pid + 1, 200);
damon_sysfs_add_targets(ctx, sysfs_targets);
KUNIT_EXPECT_EQ(test, 2u, nr_damon_targets(ctx));
damon_destroy_ctx(ctx);
kfree(sysfs_targets->targets_arr);
kfree(sysfs_targets);
kfree(sysfs_target->regions);
kfree(sysfs_target);
}
static struct kunit_case damon_sysfs_test_cases[] = {
KUNIT_CASE(damon_sysfs_test_add_targets),
{},
};
static struct kunit_suite damon_sysfs_test_suite = {
.name = "damon-sysfs",
.test_cases = damon_sysfs_test_cases,
};
kunit_test_suite(damon_sysfs_test_suite);
#endif /* _DAMON_SYSFS_TEST_H */
#endif /* CONFIG_DAMON_SYSFS_KUNIT_TEST */
+281
View File
@@ -0,0 +1,281 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Data Access Monitor Unit Tests
*/
#ifdef CONFIG_DAMON_VADDR_KUNIT_TEST
#ifndef _DAMON_VADDR_TEST_H
#define _DAMON_VADDR_TEST_H
#include <kunit/test.h>
static int __link_vmas(struct maple_tree *mt, struct vm_area_struct *vmas,
ssize_t nr_vmas)
{
int i, ret = -ENOMEM;
MA_STATE(mas, mt, 0, 0);
if (!nr_vmas)
return 0;
mas_lock(&mas);
for (i = 0; i < nr_vmas; i++) {
mas_set_range(&mas, vmas[i].vm_start, vmas[i].vm_end - 1);
if (mas_store_gfp(&mas, &vmas[i], GFP_KERNEL))
goto failed;
}
ret = 0;
failed:
mas_unlock(&mas);
return ret;
}
/*
* Test __damon_va_three_regions() function
*
* In case of virtual memory address spaces monitoring, DAMON converts the
* complex and dynamic memory mappings of each target task to three
* discontiguous regions which cover every mapped areas. However, the three
* regions should not include the two biggest unmapped areas in the original
* mapping, because the two biggest areas are normally the areas between 1)
* heap and the mmap()-ed regions, and 2) the mmap()-ed regions and stack.
* Because these two unmapped areas are very huge but obviously never accessed,
* covering the region is just a waste.
*
* '__damon_va_three_regions() receives an address space of a process. It
* first identifies the start of mappings, end of mappings, and the two biggest
* unmapped areas. After that, based on the information, it constructs the
* three regions and returns. For more detail, refer to the comment of
* 'damon_init_regions_of()' function definition in 'mm/damon.c' file.
*
* For example, suppose virtual address ranges of 10-20, 20-25, 200-210,
* 210-220, 300-305, and 307-330 (Other comments represent this mappings in
* more short form: 10-20-25, 200-210-220, 300-305, 307-330) of a process are
* mapped. To cover every mappings, the three regions should start with 10,
* and end with 305. The process also has three unmapped areas, 25-200,
* 220-300, and 305-307. Among those, 25-200 and 220-300 are the biggest two
* unmapped areas, and thus it should be converted to three regions of 10-25,
* 200-220, and 300-330.
*/
static void damon_test_three_regions_in_vmas(struct kunit *test)
{
static struct mm_struct mm;
struct damon_addr_range regions[3] = {0};
/* 10-20-25, 200-210-220, 300-305, 307-330 */
static struct vm_area_struct vmas[] = {
(struct vm_area_struct) {.vm_start = 10, .vm_end = 20},
(struct vm_area_struct) {.vm_start = 20, .vm_end = 25},
(struct vm_area_struct) {.vm_start = 200, .vm_end = 210},
(struct vm_area_struct) {.vm_start = 210, .vm_end = 220},
(struct vm_area_struct) {.vm_start = 300, .vm_end = 305},
(struct vm_area_struct) {.vm_start = 307, .vm_end = 330},
};
mt_init_flags(&mm.mm_mt, MT_FLAGS_ALLOC_RANGE | MT_FLAGS_USE_RCU);
if (__link_vmas(&mm.mm_mt, vmas, ARRAY_SIZE(vmas)))
kunit_skip(test, "Failed to create VMA tree");
__damon_va_three_regions(&mm, regions);
KUNIT_EXPECT_EQ(test, 10ul, regions[0].start);
KUNIT_EXPECT_EQ(test, 25ul, regions[0].end);
KUNIT_EXPECT_EQ(test, 200ul, regions[1].start);
KUNIT_EXPECT_EQ(test, 220ul, regions[1].end);
KUNIT_EXPECT_EQ(test, 300ul, regions[2].start);
KUNIT_EXPECT_EQ(test, 330ul, regions[2].end);
}
static struct damon_region *__nth_region_of(struct damon_target *t, int idx)
{
struct damon_region *r;
unsigned int i = 0;
damon_for_each_region(r, t) {
if (i++ == idx)
return r;
}
return NULL;
}
/*
* Test 'damon_set_regions()'
*
* test kunit object
* regions an array containing start/end addresses of current
* monitoring target regions
* nr_regions the number of the addresses in 'regions'
* three_regions The three regions that need to be applied now
* expected start/end addresses of monitoring target regions that
* 'three_regions' are applied
* nr_expected the number of addresses in 'expected'
*
* The memory mapping of the target processes changes dynamically. To follow
* the change, DAMON periodically reads the mappings, simplifies it to the
* three regions, and updates the monitoring target regions to fit in the three
* regions. The update of current target regions is the role of
* 'damon_set_regions()'.
*
* This test passes the given target regions and the new three regions that
* need to be applied to the function and check whether it updates the regions
* as expected.
*/
static void damon_do_test_apply_three_regions(struct kunit *test,
unsigned long *regions, int nr_regions,
struct damon_addr_range *three_regions,
unsigned long *expected, int nr_expected)
{
struct damon_target *t;
struct damon_addr_range *ranges;
struct damon_region *r;
int i;
t = damon_new_target();
if (!t)
kunit_skip(test, "target alloc fail");
ranges = kmalloc_array(nr_regions / 2, sizeof(*ranges), GFP_KERNEL);
if (!ranges) {
damon_destroy_target(t, NULL);
kunit_skip(test, "ranges alloc fail");
}
for (i = 0; i < nr_regions / 2; i++) {
ranges[i].start = regions[i * 2];
ranges[i].end = regions[i * 2 + 1];
}
if (damon_set_regions(t, ranges, nr_regions / 2,
DAMON_MIN_REGION_SZ)) {
kfree(ranges);
damon_destroy_target(t, NULL);
kunit_skip(test, "damon_set_regions() fail");
}
kfree(ranges);
if (damon_set_regions(t, three_regions, 3, DAMON_MIN_REGION_SZ)) {
damon_destroy_target(t, NULL);
kunit_skip(test, "second damon_set_regions() fail");
}
for (i = 0; i < nr_expected / 2; i++) {
r = __nth_region_of(t, i);
KUNIT_EXPECT_EQ(test, r->ar.start, expected[i * 2]);
KUNIT_EXPECT_EQ(test, r->ar.end, expected[i * 2 + 1]);
}
damon_destroy_target(t, NULL);
}
/*
* This function test most common case where the three big regions are only
* slightly changed. Target regions should adjust their boundary (10-20-30,
* 50-55, 70-80, 90-100) to fit with the new big regions or remove target
* regions (57-79) that now out of the three regions.
*/
static void damon_test_apply_three_regions1(struct kunit *test)
{
/* 10-20-30, 50-55-57-59, 70-80-90-100 */
unsigned long regions[] = {10, 20, 20, 30, 50, 55, 55, 57, 57, 59,
70, 80, 80, 90, 90, 100};
/* 5-27, 45-55, 73-104 */
struct damon_addr_range new_three_regions[3] = {
(struct damon_addr_range){.start = 5, .end = 27},
(struct damon_addr_range){.start = 45, .end = 55},
(struct damon_addr_range){.start = 73, .end = 104} };
/* 5-20-27, 45-55, 73-80-90-104 */
unsigned long expected[] = {5, 20, 20, 27, 45, 55,
73, 80, 80, 90, 90, 104};
damon_do_test_apply_three_regions(test, regions, ARRAY_SIZE(regions),
new_three_regions, expected, ARRAY_SIZE(expected));
}
/*
* Test slightly bigger change. Similar to above, but the second big region
* now require two target regions (50-55, 57-59) to be removed.
*/
static void damon_test_apply_three_regions2(struct kunit *test)
{
/* 10-20-30, 50-55-57-59, 70-80-90-100 */
unsigned long regions[] = {10, 20, 20, 30, 50, 55, 55, 57, 57, 59,
70, 80, 80, 90, 90, 100};
/* 5-27, 56-57, 65-104 */
struct damon_addr_range new_three_regions[3] = {
(struct damon_addr_range){.start = 5, .end = 27},
(struct damon_addr_range){.start = 56, .end = 57},
(struct damon_addr_range){.start = 65, .end = 104} };
/* 5-20-27, 56-57, 65-80-90-104 */
unsigned long expected[] = {5, 20, 20, 27, 56, 57,
65, 80, 80, 90, 90, 104};
damon_do_test_apply_three_regions(test, regions, ARRAY_SIZE(regions),
new_three_regions, expected, ARRAY_SIZE(expected));
}
/*
* Test a big change. The second big region has totally freed and mapped to
* different area (50-59 -> 61-63). The target regions which were in the old
* second big region (50-55-57-59) should be removed and new target region
* covering the second big region (61-63) should be created.
*/
static void damon_test_apply_three_regions3(struct kunit *test)
{
/* 10-20-30, 50-55-57-59, 70-80-90-100 */
unsigned long regions[] = {10, 20, 20, 30, 50, 55, 55, 57, 57, 59,
70, 80, 80, 90, 90, 100};
/* 5-27, 61-63, 65-104 */
struct damon_addr_range new_three_regions[3] = {
(struct damon_addr_range){.start = 5, .end = 27},
(struct damon_addr_range){.start = 61, .end = 63},
(struct damon_addr_range){.start = 65, .end = 104} };
/* 5-20-27, 61-63, 65-80-90-104 */
unsigned long expected[] = {5, 20, 20, 27, 61, 63,
65, 80, 80, 90, 90, 104};
damon_do_test_apply_three_regions(test, regions, ARRAY_SIZE(regions),
new_three_regions, expected, ARRAY_SIZE(expected));
}
/*
* Test another big change. Both of the second and third big regions (50-59
* and 70-100) has totally freed and mapped to different area (30-32 and
* 65-68). The target regions which were in the old second and third big
* regions should now be removed and new target regions covering the new second
* and third big regions should be created.
*/
static void damon_test_apply_three_regions4(struct kunit *test)
{
/* 10-20-30, 50-55-57-59, 70-80-90-100 */
unsigned long regions[] = {10, 20, 20, 30, 50, 55, 55, 57, 57, 59,
70, 80, 80, 90, 90, 100};
/* 5-7, 30-32, 65-68 */
struct damon_addr_range new_three_regions[3] = {
(struct damon_addr_range){.start = 5, .end = 7},
(struct damon_addr_range){.start = 30, .end = 32},
(struct damon_addr_range){.start = 65, .end = 68} };
/* expect 5-7, 30-32, 65-68 */
unsigned long expected[] = {5, 7, 30, 32, 65, 68};
damon_do_test_apply_three_regions(test, regions, ARRAY_SIZE(regions),
new_three_regions, expected, ARRAY_SIZE(expected));
}
static struct kunit_case damon_test_cases[] = {
KUNIT_CASE(damon_test_three_regions_in_vmas),
KUNIT_CASE(damon_test_apply_three_regions1),
KUNIT_CASE(damon_test_apply_three_regions2),
KUNIT_CASE(damon_test_apply_three_regions3),
KUNIT_CASE(damon_test_apply_three_regions4),
{},
};
static struct kunit_suite damon_test_suite = {
.name = "damon-operations",
.test_cases = damon_test_cases,
};
kunit_test_suite(damon_test_suite);
#endif /* _DAMON_VADDR_TEST_H */
#endif /* CONFIG_DAMON_VADDR_KUNIT_TEST */
+989
View File
@@ -0,0 +1,989 @@
// SPDX-License-Identifier: GPL-2.0
/*
* DAMON Code for Virtual Address Spaces
*/
#define pr_fmt(fmt) "damon-va: " fmt
#include <linux/highmem.h>
#include <linux/hugetlb.h>
#include <linux/mman.h>
#include <linux/mmu_notifier.h>
#include <linux/page_idle.h>
#include <linux/pagewalk.h>
#include <linux/sched/mm.h>
#include "../internal.h"
#include "ops-common.h"
#ifdef CONFIG_DAMON_VADDR_KUNIT_TEST
#undef DAMON_MIN_REGION_SZ
#define DAMON_MIN_REGION_SZ 1
#endif
/*
* 't->pid' should be the pointer to the relevant 'struct pid' having reference
* count. Caller must put the returned task, unless it is NULL.
*/
static inline struct task_struct *damon_get_task_struct(struct damon_target *t)
{
return get_pid_task(t->pid, PIDTYPE_PID);
}
/*
* Get the mm_struct of the given target
*
* Caller _must_ put the mm_struct after use, unless it is NULL.
*
* Returns the mm_struct of the target on success, NULL on failure
*/
static struct mm_struct *damon_get_mm(struct damon_target *t)
{
struct task_struct *task;
struct mm_struct *mm;
task = damon_get_task_struct(t);
if (!task)
return NULL;
mm = get_task_mm(task);
put_task_struct(task);
return mm;
}
static unsigned long sz_range(struct damon_addr_range *r)
{
return r->end - r->start;
}
/*
* Find three regions separated by two biggest unmapped regions
*
* vma the head vma of the target address space
* regions an array of three address ranges that results will be saved
*
* This function receives an address space and finds three regions in it which
* separated by the two biggest unmapped regions in the space. Please refer to
* below comments of '__damon_va_init_regions()' function to know why this is
* necessary.
*
* Returns 0 if success, or negative error code otherwise.
*/
static int __damon_va_three_regions(struct mm_struct *mm,
struct damon_addr_range regions[3])
{
struct damon_addr_range first_gap = {0}, second_gap = {0};
VMA_ITERATOR(vmi, mm, 0);
struct vm_area_struct *vma, *prev = NULL;
unsigned long start;
/*
* Find the two biggest gaps so that first_gap > second_gap > others.
* If this is too slow, it can be optimised to examine the maple
* tree gaps.
*/
rcu_read_lock();
for_each_vma(vmi, vma) {
unsigned long gap;
if (!prev) {
start = vma->vm_start;
goto next;
}
gap = vma->vm_start - prev->vm_end;
if (gap > sz_range(&first_gap)) {
second_gap = first_gap;
first_gap.start = prev->vm_end;
first_gap.end = vma->vm_start;
} else if (gap > sz_range(&second_gap)) {
second_gap.start = prev->vm_end;
second_gap.end = vma->vm_start;
}
next:
prev = vma;
}
rcu_read_unlock();
if (!sz_range(&second_gap) || !sz_range(&first_gap))
return -EINVAL;
/* Sort the two biggest gaps by address */
if (first_gap.start > second_gap.start)
swap(first_gap, second_gap);
/* Store the result */
regions[0].start = ALIGN(start, DAMON_MIN_REGION_SZ);
regions[0].end = ALIGN(first_gap.start, DAMON_MIN_REGION_SZ);
regions[1].start = ALIGN(first_gap.end, DAMON_MIN_REGION_SZ);
regions[1].end = ALIGN(second_gap.start, DAMON_MIN_REGION_SZ);
regions[2].start = ALIGN(second_gap.end, DAMON_MIN_REGION_SZ);
regions[2].end = ALIGN(prev->vm_end, DAMON_MIN_REGION_SZ);
return 0;
}
/*
* Get the three regions in the given target (task)
*
* Returns 0 on success, negative error code otherwise.
*/
static int damon_va_three_regions(struct damon_target *t,
struct damon_addr_range regions[3])
{
struct mm_struct *mm;
int rc;
mm = damon_get_mm(t);
if (!mm)
return -EINVAL;
mmap_read_lock(mm);
rc = __damon_va_three_regions(mm, regions);
mmap_read_unlock(mm);
mmput(mm);
return rc;
}
/*
* Initialize the monitoring target regions for the given target (task)
*
* t the given target
*
* Because only a number of small portions of the entire address space
* is actually mapped to the memory and accessed, monitoring the unmapped
* regions is wasteful. That said, because we can deal with small noises,
* tracking every mapping is not strictly required but could even incur a high
* overhead if the mapping frequently changes or the number of mappings is
* high. The adaptive regions adjustment mechanism will further help to deal
* with the noise by simply identifying the unmapped areas as a region that
* has no access. Moreover, applying the real mappings that would have many
* unmapped areas inside will make the adaptive mechanism quite complex. That
* said, too huge unmapped areas inside the monitoring target should be removed
* to not take the time for the adaptive mechanism.
*
* For the reason, we convert the complex mappings to three distinct regions
* that cover every mapped area of the address space. Also the two gaps
* between the three regions are the two biggest unmapped areas in the given
* address space. In detail, this function first identifies the start and the
* end of the mappings and the two biggest unmapped areas of the address space.
* Then, it constructs the three regions as below:
*
* [mappings[0]->start, big_two_unmapped_areas[0]->start)
* [big_two_unmapped_areas[0]->end, big_two_unmapped_areas[1]->start)
* [big_two_unmapped_areas[1]->end, mappings[nr_mappings - 1]->end)
*
* As usual memory map of processes is as below, the gap between the heap and
* the uppermost mmap()-ed region, and the gap between the lowermost mmap()-ed
* region and the stack will be two biggest unmapped regions. Because these
* gaps are exceptionally huge areas in usual address space, excluding these
* two biggest unmapped regions will be sufficient to make a trade-off.
*
* <heap>
* <BIG UNMAPPED REGION 1>
* <uppermost mmap()-ed region>
* (other mmap()-ed regions and small unmapped regions)
* <lowermost mmap()-ed region>
* <BIG UNMAPPED REGION 2>
* <stack>
*/
static void __damon_va_init_regions(struct damon_ctx *ctx,
struct damon_target *t)
{
struct damon_target *ti;
struct damon_addr_range regions[3];
int tidx = 0;
if (damon_va_three_regions(t, regions)) {
damon_for_each_target(ti, ctx) {
if (ti == t)
break;
tidx++;
}
pr_debug("Failed to get three regions of %dth target\n", tidx);
return;
}
damon_set_regions(t, regions, 3, DAMON_MIN_REGION_SZ);
}
/* Initialize '->regions_list' of every target (task) */
static void damon_va_init(struct damon_ctx *ctx)
{
struct damon_target *t;
damon_for_each_target(t, ctx) {
/* the user may set the target regions as they want */
if (!damon_nr_regions(t))
__damon_va_init_regions(ctx, t);
}
}
/*
* Update regions for current memory mappings
*/
static void damon_va_update(struct damon_ctx *ctx)
{
struct damon_addr_range three_regions[3];
struct damon_target *t;
damon_for_each_target(t, ctx) {
if (damon_va_three_regions(t, three_regions))
continue;
damon_set_regions(t, three_regions, 3, DAMON_MIN_REGION_SZ);
}
}
static void damon_va_walk_page_range(struct mm_struct *mm, unsigned long start,
unsigned long end, struct mm_walk_ops *ops, void *private)
{
struct vm_area_struct *vma;
vma = lock_vma_under_rcu(mm, start);
if (!vma)
goto lock_mmap;
if (end > vma->vm_end) {
vma_end_read(vma);
goto lock_mmap;
}
if (!(vma->vm_flags & VM_PFNMAP)) {
ops->walk_lock = PGWALK_VMA_RDLOCK_VERIFY;
walk_page_range_vma(vma, start, end, ops, private);
}
vma_end_read(vma);
return;
lock_mmap:
mmap_read_lock(mm);
ops->walk_lock = PGWALK_RDLOCK;
walk_page_range(mm, start, end, ops, private);
mmap_read_unlock(mm);
}
static int damon_mkold_pmd_entry(pmd_t *pmd, unsigned long addr,
unsigned long next, struct mm_walk *walk)
{
pte_t *pte;
spinlock_t *ptl;
ptl = pmd_trans_huge_lock(pmd, walk->vma);
if (ptl) {
pmd_t pmde = pmdp_get(pmd);
if (pmd_present(pmde))
damon_pmdp_mkold(pmd, walk->vma, addr);
spin_unlock(ptl);
return 0;
}
pte = pte_offset_map_lock(walk->mm, pmd, addr, &ptl);
if (!pte)
return 0;
if (!pte_present(ptep_get(pte)))
goto out;
damon_ptep_mkold(pte, walk->vma, addr);
out:
pte_unmap_unlock(pte, ptl);
return 0;
}
#ifdef CONFIG_HUGETLB_PAGE
static void damon_hugetlb_mkold(pte_t *pte, struct mm_struct *mm,
struct vm_area_struct *vma, unsigned long addr)
{
bool referenced = false;
pte_t entry = huge_ptep_get(mm, addr, pte);
struct folio *folio = pfn_folio(pte_pfn(entry));
unsigned long psize = huge_page_size(hstate_vma(vma));
folio_get(folio);
if (pte_young(entry)) {
referenced = true;
entry = pte_mkold(entry);
set_huge_pte_at(mm, addr, pte, entry, psize);
}
if (mmu_notifier_clear_young(mm, addr,
addr + huge_page_size(hstate_vma(vma))))
referenced = true;
if (referenced)
folio_set_young(folio);
folio_set_idle(folio);
folio_put(folio);
}
static int damon_mkold_hugetlb_entry(pte_t *pte, unsigned long hmask,
unsigned long addr, unsigned long end,
struct mm_walk *walk)
{
struct hstate *h = hstate_vma(walk->vma);
spinlock_t *ptl;
pte_t entry;
ptl = huge_pte_lock(h, walk->mm, pte);
entry = huge_ptep_get(walk->mm, addr, pte);
if (!pte_present(entry))
goto out;
damon_hugetlb_mkold(pte, walk->mm, walk->vma, addr);
out:
spin_unlock(ptl);
return 0;
}
#else
#define damon_mkold_hugetlb_entry NULL
#endif /* CONFIG_HUGETLB_PAGE */
static void damon_va_mkold(struct mm_struct *mm, unsigned long addr)
{
struct mm_walk_ops damon_mkold_ops = {
.pmd_entry = damon_mkold_pmd_entry,
.hugetlb_entry = damon_mkold_hugetlb_entry,
};
damon_va_walk_page_range(mm, addr, addr + 1, &damon_mkold_ops, NULL);
}
/*
* Functions for the access checking of the regions
*/
static void __damon_va_prepare_access_check(struct mm_struct *mm,
struct damon_region *r,
struct damon_ctx *ctx)
{
r->sampling_addr = damon_rand(ctx, r->ar.start, r->ar.end);
damon_va_mkold(mm, r->sampling_addr);
}
static void damon_va_prepare_access_checks(struct damon_ctx *ctx)
{
struct damon_target *t;
struct mm_struct *mm;
struct damon_region *r;
damon_for_each_target(t, ctx) {
mm = damon_get_mm(t);
if (!mm)
continue;
damon_for_each_region(r, t)
__damon_va_prepare_access_check(mm, r, ctx);
mmput(mm);
}
}
struct damon_young_walk_private {
/* size of the folio for the access checked virtual memory address */
unsigned long *folio_sz;
bool young;
};
static int damon_young_pmd_entry(pmd_t *pmd, unsigned long addr,
unsigned long next, struct mm_walk *walk)
{
pte_t *pte;
pte_t ptent;
spinlock_t *ptl;
struct folio *folio;
struct damon_young_walk_private *priv = walk->private;
#ifdef CONFIG_TRANSPARENT_HUGEPAGE
ptl = pmd_trans_huge_lock(pmd, walk->vma);
if (ptl) {
pmd_t pmde = pmdp_get(pmd);
if (!pmd_present(pmde))
goto huge_out;
folio = vm_normal_folio_pmd(walk->vma, addr, pmde);
if (!folio)
goto huge_out;
if (pmd_young(pmde) || !folio_test_idle(folio) ||
mmu_notifier_test_young(walk->mm,
addr))
priv->young = true;
*priv->folio_sz = HPAGE_PMD_SIZE;
huge_out:
spin_unlock(ptl);
return 0;
}
#endif /* CONFIG_TRANSPARENT_HUGEPAGE */
pte = pte_offset_map_lock(walk->mm, pmd, addr, &ptl);
if (!pte)
return 0;
ptent = ptep_get(pte);
if (!pte_present(ptent))
goto out;
folio = vm_normal_folio(walk->vma, addr, ptent);
if (!folio)
goto out;
if (pte_young(ptent) || !folio_test_idle(folio) ||
mmu_notifier_test_young(walk->mm, addr))
priv->young = true;
*priv->folio_sz = folio_size(folio);
out:
pte_unmap_unlock(pte, ptl);
return 0;
}
#ifdef CONFIG_HUGETLB_PAGE
static int damon_young_hugetlb_entry(pte_t *pte, unsigned long hmask,
unsigned long addr, unsigned long end,
struct mm_walk *walk)
{
struct damon_young_walk_private *priv = walk->private;
struct hstate *h = hstate_vma(walk->vma);
struct folio *folio;
spinlock_t *ptl;
pte_t entry;
ptl = huge_pte_lock(h, walk->mm, pte);
entry = huge_ptep_get(walk->mm, addr, pte);
if (!pte_present(entry))
goto out;
folio = pfn_folio(pte_pfn(entry));
folio_get(folio);
if (pte_young(entry) || !folio_test_idle(folio) ||
mmu_notifier_test_young(walk->mm, addr))
priv->young = true;
*priv->folio_sz = huge_page_size(h);
folio_put(folio);
out:
spin_unlock(ptl);
return 0;
}
#else
#define damon_young_hugetlb_entry NULL
#endif /* CONFIG_HUGETLB_PAGE */
static bool damon_va_young(struct mm_struct *mm, unsigned long addr,
unsigned long *folio_sz)
{
struct damon_young_walk_private arg = {
.folio_sz = folio_sz,
.young = false,
};
struct mm_walk_ops damon_young_ops = {
.pmd_entry = damon_young_pmd_entry,
.hugetlb_entry = damon_young_hugetlb_entry,
};
damon_va_walk_page_range(mm, addr, addr + 1, &damon_young_ops, &arg);
return arg.young;
}
/*
* Check whether the region was accessed after the last preparation
*
* mm 'mm_struct' for the given virtual address space
* r the region to be checked
*/
static void __damon_va_check_access(struct mm_struct *mm,
struct damon_region *r, bool same_target,
struct damon_attrs *attrs)
{
static unsigned long last_addr;
static unsigned long last_folio_sz = PAGE_SIZE;
static bool last_accessed;
if (!mm) {
damon_update_region_access_rate(r, false, attrs);
return;
}
/* If the region is in the last checked page, reuse the result */
if (same_target && (ALIGN_DOWN(last_addr, last_folio_sz) ==
ALIGN_DOWN(r->sampling_addr, last_folio_sz))) {
damon_update_region_access_rate(r, last_accessed, attrs);
return;
}
last_accessed = damon_va_young(mm, r->sampling_addr, &last_folio_sz);
damon_update_region_access_rate(r, last_accessed, attrs);
last_addr = r->sampling_addr;
}
static unsigned int damon_va_check_accesses(struct damon_ctx *ctx)
{
struct damon_target *t;
struct mm_struct *mm;
struct damon_region *r;
unsigned int max_nr_accesses = 0;
bool same_target;
damon_for_each_target(t, ctx) {
mm = damon_get_mm(t);
same_target = false;
damon_for_each_region(r, t) {
__damon_va_check_access(mm, r, same_target,
&ctx->attrs);
max_nr_accesses = max(r->nr_accesses, max_nr_accesses);
same_target = true;
}
if (mm)
mmput(mm);
}
return max_nr_accesses;
}
static bool damos_va_filter_young_match(struct damos_filter *filter,
struct folio *folio, struct vm_area_struct *vma,
unsigned long addr, pte_t *ptep, pmd_t *pmdp)
{
bool young = false;
if (ptep)
young = pte_young(ptep_get(ptep));
else if (pmdp)
young = pmd_young(pmdp_get(pmdp));
young = young || !folio_test_idle(folio) ||
mmu_notifier_test_young(vma->vm_mm, addr);
if (young && ptep)
damon_ptep_mkold(ptep, vma, addr);
else if (young && pmdp)
damon_pmdp_mkold(pmdp, vma, addr);
return young == filter->matching;
}
static bool damos_va_filter_out(struct damos *scheme, struct folio *folio,
struct vm_area_struct *vma, unsigned long addr,
pte_t *ptep, pmd_t *pmdp)
{
struct damos_filter *filter;
bool matched;
if (scheme->core_filters_allowed)
return false;
damos_for_each_ops_filter(filter, scheme) {
/*
* damos_folio_filter_match checks the young filter by doing an
* rmap on the folio to find its page table. However, being the
* vaddr scheme, we have direct access to the page tables, so
* use that instead.
*/
if (filter->type == DAMOS_FILTER_TYPE_YOUNG)
matched = damos_va_filter_young_match(filter, folio,
vma, addr, ptep, pmdp);
else
matched = damos_folio_filter_match(filter, folio);
if (matched)
return !filter->allow;
}
return scheme->ops_filters_default_reject;
}
struct damos_va_migrate_private {
struct list_head *migration_lists;
struct damos *scheme;
};
/*
* Place the given folio in the migration_list corresponding to where the folio
* should be migrated.
*
* The algorithm used here is similar to weighted_interleave_nid()
*/
static void damos_va_migrate_dests_add(struct folio *folio,
struct vm_area_struct *vma, unsigned long addr,
struct damos_migrate_dests *dests,
struct list_head *migration_lists)
{
pgoff_t ilx;
int order;
unsigned int target;
unsigned int weight_total = 0;
int i;
/*
* If dests is empty, there is only one migration list corresponding
* to s->target_nid.
*/
if (!dests->nr_dests) {
i = 0;
goto isolate;
}
order = folio_order(folio);
ilx = vma->vm_pgoff >> order;
ilx += (addr - vma->vm_start) >> (PAGE_SHIFT + order);
for (i = 0; i < dests->nr_dests; i++)
weight_total += dests->weight_arr[i];
/* If the total weights are somehow 0, don't migrate at all */
if (!weight_total)
return;
target = ilx % weight_total;
for (i = 0; i < dests->nr_dests; i++) {
if (target < dests->weight_arr[i])
break;
target -= dests->weight_arr[i];
}
/* If the folio is already in the right node, don't do anything */
if (folio_nid(folio) == dests->node_id_arr[i])
return;
isolate:
if (!folio_isolate_lru(folio))
return;
node_stat_add_folio(folio, NR_ISOLATED_ANON +
folio_is_file_lru(folio));
list_add(&folio->lru, &migration_lists[i]);
}
static int damos_va_migrate_pmd_entry(pmd_t *pmd, unsigned long addr,
unsigned long next, struct mm_walk *walk)
{
struct damos_va_migrate_private *priv = walk->private;
struct list_head *migration_lists = priv->migration_lists;
struct damos *s = priv->scheme;
struct damos_migrate_dests *dests = &s->migrate_dests;
struct folio *folio;
spinlock_t *ptl;
pte_t *start_pte, *pte, ptent;
int nr;
#ifdef CONFIG_TRANSPARENT_HUGEPAGE
ptl = pmd_trans_huge_lock(pmd, walk->vma);
if (ptl) {
pmd_t pmde = pmdp_get(pmd);
if (!pmd_present(pmde))
goto huge_out;
folio = vm_normal_folio_pmd(walk->vma, addr, pmde);
if (!folio)
goto huge_out;
if (damos_va_filter_out(s, folio, walk->vma, addr, NULL, pmd))
goto huge_out;
damos_va_migrate_dests_add(folio, walk->vma, addr, dests,
migration_lists);
huge_out:
spin_unlock(ptl);
return 0;
}
#endif /* CONFIG_TRANSPARENT_HUGEPAGE */
start_pte = pte = pte_offset_map_lock(walk->mm, pmd, addr, &ptl);
if (!pte)
return 0;
for (; addr < next; pte += nr, addr += nr * PAGE_SIZE) {
nr = 1;
ptent = ptep_get(pte);
if (pte_none(ptent) || !pte_present(ptent))
continue;
folio = vm_normal_folio(walk->vma, addr, ptent);
if (!folio)
continue;
if (damos_va_filter_out(s, folio, walk->vma, addr, pte, NULL))
continue;
damos_va_migrate_dests_add(folio, walk->vma, addr, dests,
migration_lists);
nr = folio_nr_pages(folio);
}
pte_unmap_unlock(start_pte, ptl);
return 0;
}
/*
* Functions for the target validity check and cleanup
*/
static bool damon_va_target_valid(struct damon_target *t)
{
struct task_struct *task;
task = damon_get_task_struct(t);
if (task) {
put_task_struct(task);
return true;
}
return false;
}
static void damon_va_cleanup_target(struct damon_target *t)
{
put_pid(t->pid);
}
#ifndef CONFIG_ADVISE_SYSCALLS
static unsigned long damos_madvise(struct damon_target *target,
struct damon_region *r, int behavior)
{
return 0;
}
#else
static unsigned long damos_madvise(struct damon_target *target,
struct damon_region *r, int behavior)
{
struct mm_struct *mm;
unsigned long start = PAGE_ALIGN(r->ar.start);
unsigned long len = PAGE_ALIGN(damon_sz_region(r));
unsigned long applied;
mm = damon_get_mm(target);
if (!mm)
return 0;
applied = do_madvise(mm, start, len, behavior) ? 0 : len;
mmput(mm);
return applied;
}
#endif /* CONFIG_ADVISE_SYSCALLS */
static unsigned long damos_va_migrate(struct damon_target *target,
struct damon_region *r, struct damos *s,
unsigned long *sz_filter_passed)
{
LIST_HEAD(folio_list);
struct damos_va_migrate_private priv;
struct mm_struct *mm;
int nr_dests;
int nid;
bool use_target_nid;
unsigned long applied = 0;
struct damos_migrate_dests *dests = &s->migrate_dests;
struct mm_walk_ops walk_ops = {
.pmd_entry = damos_va_migrate_pmd_entry,
.pte_entry = NULL,
};
use_target_nid = dests->nr_dests == 0;
nr_dests = use_target_nid ? 1 : dests->nr_dests;
priv.scheme = s;
priv.migration_lists = kmalloc_objs(*priv.migration_lists, nr_dests);
if (!priv.migration_lists)
return 0;
for (int i = 0; i < nr_dests; i++)
INIT_LIST_HEAD(&priv.migration_lists[i]);
mm = damon_get_mm(target);
if (!mm)
goto free_lists;
damon_va_walk_page_range(mm, r->ar.start, r->ar.end, &walk_ops, &priv);
mmput(mm);
for (int i = 0; i < nr_dests; i++) {
nid = use_target_nid ? s->target_nid : dests->node_id_arr[i];
applied += damon_migrate_pages(&priv.migration_lists[i], nid);
cond_resched();
}
free_lists:
kfree(priv.migration_lists);
return applied * PAGE_SIZE;
}
struct damos_va_stat_private {
struct damos *scheme;
unsigned long *sz_filter_passed;
};
static inline bool damos_va_invalid_folio(struct folio *folio,
struct damos *s)
{
return !folio || folio == s->last_applied;
}
static int damos_va_stat_pmd_entry(pmd_t *pmd, unsigned long addr,
unsigned long next, struct mm_walk *walk)
{
struct damos_va_stat_private *priv = walk->private;
struct damos *s = priv->scheme;
unsigned long *sz_filter_passed = priv->sz_filter_passed;
struct vm_area_struct *vma = walk->vma;
struct folio *folio;
spinlock_t *ptl;
pte_t *start_pte, *pte, ptent;
int nr;
#ifdef CONFIG_TRANSPARENT_HUGEPAGE
ptl = pmd_trans_huge_lock(pmd, vma);
if (ptl) {
pmd_t pmde = pmdp_get(pmd);
if (!pmd_present(pmde))
goto huge_unlock;
folio = vm_normal_folio_pmd(vma, addr, pmde);
if (damos_va_invalid_folio(folio, s))
goto huge_unlock;
if (!damos_va_filter_out(s, folio, vma, addr, NULL, pmd))
*sz_filter_passed += folio_size(folio);
s->last_applied = folio;
huge_unlock:
spin_unlock(ptl);
return 0;
}
#endif
start_pte = pte = pte_offset_map_lock(vma->vm_mm, pmd, addr, &ptl);
if (!start_pte)
return 0;
for (; addr < next; pte += nr, addr += nr * PAGE_SIZE) {
nr = 1;
ptent = ptep_get(pte);
if (pte_none(ptent) || !pte_present(ptent))
continue;
folio = vm_normal_folio(vma, addr, ptent);
if (damos_va_invalid_folio(folio, s))
continue;
if (!damos_va_filter_out(s, folio, vma, addr, pte, NULL))
*sz_filter_passed += folio_size(folio);
nr = folio_nr_pages(folio);
s->last_applied = folio;
}
pte_unmap_unlock(start_pte, ptl);
return 0;
}
static unsigned long damos_va_stat(struct damon_target *target,
struct damon_region *r, struct damos *s,
unsigned long *sz_filter_passed)
{
struct damos_va_stat_private priv;
struct mm_struct *mm;
struct mm_walk_ops walk_ops = {
.pmd_entry = damos_va_stat_pmd_entry,
};
priv.scheme = s;
priv.sz_filter_passed = sz_filter_passed;
if (!damos_ops_has_filter(s))
return 0;
mm = damon_get_mm(target);
if (!mm)
return 0;
damon_va_walk_page_range(mm, r->ar.start, r->ar.end, &walk_ops, &priv);
mmput(mm);
return 0;
}
static unsigned long damon_va_apply_scheme(struct damon_ctx *ctx,
struct damon_target *t, struct damon_region *r,
struct damos *scheme, unsigned long *sz_filter_passed)
{
int madv_action;
switch (scheme->action) {
case DAMOS_WILLNEED:
madv_action = MADV_WILLNEED;
break;
case DAMOS_COLD:
madv_action = MADV_COLD;
break;
case DAMOS_PAGEOUT:
madv_action = MADV_PAGEOUT;
break;
case DAMOS_HUGEPAGE:
madv_action = MADV_HUGEPAGE;
break;
case DAMOS_NOHUGEPAGE:
madv_action = MADV_NOHUGEPAGE;
break;
case DAMOS_COLLAPSE:
madv_action = MADV_COLLAPSE;
break;
case DAMOS_MIGRATE_HOT:
case DAMOS_MIGRATE_COLD:
return damos_va_migrate(t, r, scheme, sz_filter_passed);
case DAMOS_STAT:
return damos_va_stat(t, r, scheme, sz_filter_passed);
default:
/*
* DAMOS actions that are not yet supported by 'vaddr'.
*/
return 0;
}
return damos_madvise(t, r, madv_action);
}
static int damon_va_scheme_score(struct damon_ctx *context,
struct damon_region *r, struct damos *scheme)
{
switch (scheme->action) {
case DAMOS_PAGEOUT:
return damon_cold_score(context, r, scheme);
case DAMOS_MIGRATE_HOT:
return damon_hot_score(context, r, scheme);
case DAMOS_MIGRATE_COLD:
return damon_cold_score(context, r, scheme);
default:
break;
}
return DAMOS_MAX_SCORE;
}
static int __init damon_va_initcall(void)
{
struct damon_operations ops = {
.id = DAMON_OPS_VADDR,
.init = damon_va_init,
.update = damon_va_update,
.prepare_access_checks = damon_va_prepare_access_checks,
.check_accesses = damon_va_check_accesses,
.target_valid = damon_va_target_valid,
.cleanup_target = damon_va_cleanup_target,
.apply_scheme = damon_va_apply_scheme,
.get_scheme_score = damon_va_scheme_score,
};
/* ops for fixed virtual address ranges */
struct damon_operations ops_fvaddr = ops;
int err;
/* Don't set the monitoring target regions for the entire mapping */
ops_fvaddr.id = DAMON_OPS_FVADDR;
ops_fvaddr.init = NULL;
ops_fvaddr.update = NULL;
err = damon_register_ops(&ops);
if (err)
return err;
return damon_register_ops(&ops_fvaddr);
};
subsys_initcall(damon_va_initcall);
#include "tests/vaddr-kunit.h"