Merge branch 'akpm' (patches from Andrew)
Merge more updates from Andrew Morton:
"147 patches, based on 7d2a07b769.
Subsystems affected by this patch series: mm (memory-hotplug, rmap,
ioremap, highmem, cleanups, secretmem, kfence, damon, and vmscan),
alpha, percpu, procfs, misc, core-kernel, MAINTAINERS, lib,
checkpatch, epoll, init, nilfs2, coredump, fork, pids, criu, kconfig,
selftests, ipc, and scripts"
* emailed patches from Andrew Morton <akpm@linux-foundation.org>: (94 commits)
scripts: check_extable: fix typo in user error message
mm/workingset: correct kernel-doc notations
ipc: replace costly bailout check in sysvipc_find_ipc()
selftests/memfd: remove unused variable
Kconfig.debug: drop selecting non-existing HARDLOCKUP_DETECTOR_ARCH
configs: remove the obsolete CONFIG_INPUT_POLLDEV
prctl: allow to setup brk for et_dyn executables
pid: cleanup the stale comment mentioning pidmap_init().
kernel/fork.c: unexport get_{mm,task}_exe_file
coredump: fix memleak in dump_vma_snapshot()
fs/coredump.c: log if a core dump is aborted due to changed file permissions
nilfs2: use refcount_dec_and_lock() to fix potential UAF
nilfs2: fix memory leak in nilfs_sysfs_delete_snapshot_group
nilfs2: fix memory leak in nilfs_sysfs_create_snapshot_group
nilfs2: fix memory leak in nilfs_sysfs_delete_##name##_group
nilfs2: fix memory leak in nilfs_sysfs_create_##name##_group
nilfs2: fix NULL pointer in nilfs_##name##_attr_release
nilfs2: fix memory leak in nilfs_sysfs_create_device_group
trap: cleanup trap_init()
init: move usermodehelper_enable() to populate_rootfs()
...
This commit is contained in:
+11
-4
@@ -96,9 +96,6 @@ config HAVE_FAST_GUP
|
||||
depends on MMU
|
||||
bool
|
||||
|
||||
config HOLES_IN_ZONE
|
||||
bool
|
||||
|
||||
# Don't discard allocated memory used to track "memory" and "reserved" memblocks
|
||||
# after early boot, so it can still be used to test for validity of memory.
|
||||
# Also, memblocks are updated with memory hot(un)plug.
|
||||
@@ -742,10 +739,18 @@ config DEFERRED_STRUCT_PAGE_INIT
|
||||
lifetime of the system until these kthreads finish the
|
||||
initialisation.
|
||||
|
||||
config PAGE_IDLE_FLAG
|
||||
bool
|
||||
select PAGE_EXTENSION if !64BIT
|
||||
help
|
||||
This adds PG_idle and PG_young flags to 'struct page'. PTE Accessed
|
||||
bit writers can set the state of the bit in the flags so that PTE
|
||||
Accessed bit readers may avoid disturbance.
|
||||
|
||||
config IDLE_PAGE_TRACKING
|
||||
bool "Enable idle page tracking"
|
||||
depends on SYSFS && MMU
|
||||
select PAGE_EXTENSION if !64BIT
|
||||
select PAGE_IDLE_FLAG
|
||||
help
|
||||
This feature allows to estimate the amount of user pages that have
|
||||
not been touched during a given period of time. This information can
|
||||
@@ -889,4 +894,6 @@ config IO_MAPPING
|
||||
config SECRETMEM
|
||||
def_bool ARCH_HAS_SET_DIRECT_MAP && !EMBEDDED
|
||||
|
||||
source "mm/damon/Kconfig"
|
||||
|
||||
endmenu
|
||||
|
||||
+3
-1
@@ -38,7 +38,7 @@ mmu-y := nommu.o
|
||||
mmu-$(CONFIG_MMU) := highmem.o memory.o mincore.o \
|
||||
mlock.o mmap.o mmu_gather.o mprotect.o mremap.o \
|
||||
msync.o page_vma_mapped.o pagewalk.o \
|
||||
pgtable-generic.o rmap.o vmalloc.o ioremap.o
|
||||
pgtable-generic.o rmap.o vmalloc.o
|
||||
|
||||
|
||||
ifdef CONFIG_CROSS_MEMORY_ATTACH
|
||||
@@ -118,6 +118,7 @@ obj-$(CONFIG_CMA_SYSFS) += cma_sysfs.o
|
||||
obj-$(CONFIG_USERFAULTFD) += userfaultfd.o
|
||||
obj-$(CONFIG_IDLE_PAGE_TRACKING) += page_idle.o
|
||||
obj-$(CONFIG_DEBUG_PAGE_REF) += debug_page_ref.o
|
||||
obj-$(CONFIG_DAMON) += damon/
|
||||
obj-$(CONFIG_HARDENED_USERCOPY) += usercopy.o
|
||||
obj-$(CONFIG_PERCPU_STATS) += percpu-stats.o
|
||||
obj-$(CONFIG_ZONE_DEVICE) += memremap.o
|
||||
@@ -128,3 +129,4 @@ obj-$(CONFIG_PTDUMP_CORE) += ptdump.o
|
||||
obj-$(CONFIG_PAGE_REPORTING) += page_reporting.o
|
||||
obj-$(CONFIG_IO_MAPPING) += io-mapping.o
|
||||
obj-$(CONFIG_HAVE_BOOTMEM_INFO_NODE) += bootmem_info.o
|
||||
obj-$(CONFIG_GENERIC_IOREMAP) += ioremap.o
|
||||
|
||||
+7
-13
@@ -306,16 +306,14 @@ __reset_isolation_pfn(struct zone *zone, unsigned long pfn, bool check_source,
|
||||
* is necessary for the block to be a migration source/target.
|
||||
*/
|
||||
do {
|
||||
if (pfn_valid_within(pfn)) {
|
||||
if (check_source && PageLRU(page)) {
|
||||
clear_pageblock_skip(page);
|
||||
return true;
|
||||
}
|
||||
if (check_source && PageLRU(page)) {
|
||||
clear_pageblock_skip(page);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (check_target && PageBuddy(page)) {
|
||||
clear_pageblock_skip(page);
|
||||
return true;
|
||||
}
|
||||
if (check_target && PageBuddy(page)) {
|
||||
clear_pageblock_skip(page);
|
||||
return true;
|
||||
}
|
||||
|
||||
page += (1 << PAGE_ALLOC_COSTLY_ORDER);
|
||||
@@ -585,8 +583,6 @@ static unsigned long isolate_freepages_block(struct compact_control *cc,
|
||||
break;
|
||||
|
||||
nr_scanned++;
|
||||
if (!pfn_valid_within(blockpfn))
|
||||
goto isolate_fail;
|
||||
|
||||
/*
|
||||
* For compound pages such as THP and hugetlbfs, we can save
|
||||
@@ -885,8 +881,6 @@ isolate_migratepages_block(struct compact_control *cc, unsigned long low_pfn,
|
||||
cond_resched();
|
||||
}
|
||||
|
||||
if (!pfn_valid_within(low_pfn))
|
||||
goto isolate_fail;
|
||||
nr_scanned++;
|
||||
|
||||
page = pfn_to_page(low_pfn);
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
# 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://damonitor.github.io/doc/html/latest-damon/index.html for
|
||||
more information.
|
||||
|
||||
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 primitives for virtual address spaces"
|
||||
depends on DAMON && MMU
|
||||
select PAGE_IDLE_FLAG
|
||||
help
|
||||
This builds the default data access monitoring primitives for DAMON
|
||||
that works for virtual address spaces.
|
||||
|
||||
config DAMON_VADDR_KUNIT_TEST
|
||||
bool "Test for DAMON primitives" if !KUNIT_ALL_TESTS
|
||||
depends on DAMON_VADDR && KUNIT=y
|
||||
default KUNIT_ALL_TESTS
|
||||
help
|
||||
This builds the DAMON virtual addresses primitives 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_DBGFS
|
||||
bool "DAMON debugfs interface"
|
||||
depends on DAMON_VADDR && DEBUG_FS
|
||||
help
|
||||
This builds the debugfs interface for DAMON. The user space admins
|
||||
can use the interface for arbitrary data access monitoring.
|
||||
|
||||
If unsure, say N.
|
||||
|
||||
config DAMON_DBGFS_KUNIT_TEST
|
||||
bool "Test for damon debugfs interface" if !KUNIT_ALL_TESTS
|
||||
depends on DAMON_DBGFS && KUNIT=y
|
||||
default KUNIT_ALL_TESTS
|
||||
help
|
||||
This builds the DAMON debugfs interface Kunit test suite.
|
||||
|
||||
For more information on KUnit and unit tests in general, please refer
|
||||
to the KUnit documentation.
|
||||
|
||||
If unsure, say N.
|
||||
|
||||
endmenu
|
||||
@@ -0,0 +1,5 @@
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
obj-$(CONFIG_DAMON) := core.o
|
||||
obj-$(CONFIG_DAMON_VADDR) += vaddr.o
|
||||
obj-$(CONFIG_DAMON_DBGFS) += dbgfs.o
|
||||
@@ -0,0 +1,253 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
/*
|
||||
* Data Access Monitor Unit Tests
|
||||
*
|
||||
* Copyright 2019 Amazon.com, Inc. or its affiliates. All rights reserved.
|
||||
*
|
||||
* Author: SeongJae Park <sjpark@amazon.de>
|
||||
*/
|
||||
|
||||
#ifdef CONFIG_DAMON_KUNIT_TEST
|
||||
|
||||
#ifndef _DAMON_CORE_TEST_H
|
||||
#define _DAMON_CORE_TEST_H
|
||||
|
||||
#include <kunit/test.h>
|
||||
|
||||
static void damon_test_regions(struct kunit *test)
|
||||
{
|
||||
struct damon_region *r;
|
||||
struct damon_target *t;
|
||||
|
||||
r = damon_new_region(1, 2);
|
||||
KUNIT_EXPECT_EQ(test, 1ul, r->ar.start);
|
||||
KUNIT_EXPECT_EQ(test, 2ul, r->ar.end);
|
||||
KUNIT_EXPECT_EQ(test, 0u, r->nr_accesses);
|
||||
|
||||
t = damon_new_target(42);
|
||||
KUNIT_EXPECT_EQ(test, 0u, damon_nr_regions(t));
|
||||
|
||||
damon_add_region(r, t);
|
||||
KUNIT_EXPECT_EQ(test, 1u, damon_nr_regions(t));
|
||||
|
||||
damon_del_region(r, t);
|
||||
KUNIT_EXPECT_EQ(test, 0u, damon_nr_regions(t));
|
||||
|
||||
damon_free_target(t);
|
||||
}
|
||||
|
||||
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 void damon_test_target(struct kunit *test)
|
||||
{
|
||||
struct damon_ctx *c = damon_new_ctx();
|
||||
struct damon_target *t;
|
||||
|
||||
t = damon_new_target(42);
|
||||
KUNIT_EXPECT_EQ(test, 42ul, t->id);
|
||||
KUNIT_EXPECT_EQ(test, 0u, nr_damon_targets(c));
|
||||
|
||||
damon_add_target(c, t);
|
||||
KUNIT_EXPECT_EQ(test, 1u, nr_damon_targets(c));
|
||||
|
||||
damon_destroy_target(t);
|
||||
KUNIT_EXPECT_EQ(test, 0u, nr_damon_targets(c));
|
||||
|
||||
damon_destroy_ctx(c);
|
||||
}
|
||||
|
||||
/*
|
||||
* Test kdamond_reset_aggregated()
|
||||
*
|
||||
* DAMON checks access to each region and aggregates this information as the
|
||||
* access frequency of each region. In detail, it increases '->nr_accesses' of
|
||||
* regions that an access has confirmed. 'kdamond_reset_aggregated()' flushes
|
||||
* the aggregated information ('->nr_accesses' of each regions) to the result
|
||||
* buffer. As a result of the flushing, the '->nr_accesses' of regions are
|
||||
* initialized to zero.
|
||||
*/
|
||||
static void damon_test_aggregate(struct kunit *test)
|
||||
{
|
||||
struct damon_ctx *ctx = damon_new_ctx();
|
||||
unsigned long target_ids[] = {1, 2, 3};
|
||||
unsigned long saddr[][3] = {{10, 20, 30}, {5, 42, 49}, {13, 33, 55} };
|
||||
unsigned long eaddr[][3] = {{15, 27, 40}, {31, 45, 55}, {23, 44, 66} };
|
||||
unsigned long accesses[][3] = {{42, 95, 84}, {10, 20, 30}, {0, 1, 2} };
|
||||
struct damon_target *t;
|
||||
struct damon_region *r;
|
||||
int it, ir;
|
||||
|
||||
damon_set_targets(ctx, target_ids, 3);
|
||||
|
||||
it = 0;
|
||||
damon_for_each_target(t, ctx) {
|
||||
for (ir = 0; ir < 3; ir++) {
|
||||
r = damon_new_region(saddr[it][ir], eaddr[it][ir]);
|
||||
r->nr_accesses = accesses[it][ir];
|
||||
damon_add_region(r, t);
|
||||
}
|
||||
it++;
|
||||
}
|
||||
kdamond_reset_aggregated(ctx);
|
||||
it = 0;
|
||||
damon_for_each_target(t, ctx) {
|
||||
ir = 0;
|
||||
/* '->nr_accesses' should be zeroed */
|
||||
damon_for_each_region(r, t) {
|
||||
KUNIT_EXPECT_EQ(test, 0u, r->nr_accesses);
|
||||
ir++;
|
||||
}
|
||||
/* regions should be preserved */
|
||||
KUNIT_EXPECT_EQ(test, 3, ir);
|
||||
it++;
|
||||
}
|
||||
/* targets also should be preserved */
|
||||
KUNIT_EXPECT_EQ(test, 3, it);
|
||||
|
||||
damon_destroy_ctx(ctx);
|
||||
}
|
||||
|
||||
static void damon_test_split_at(struct kunit *test)
|
||||
{
|
||||
struct damon_ctx *c = damon_new_ctx();
|
||||
struct damon_target *t;
|
||||
struct damon_region *r;
|
||||
|
||||
t = damon_new_target(42);
|
||||
r = damon_new_region(0, 100);
|
||||
damon_add_region(r, t);
|
||||
damon_split_region_at(c, t, r, 25);
|
||||
KUNIT_EXPECT_EQ(test, r->ar.start, 0ul);
|
||||
KUNIT_EXPECT_EQ(test, r->ar.end, 25ul);
|
||||
|
||||
r = damon_next_region(r);
|
||||
KUNIT_EXPECT_EQ(test, r->ar.start, 25ul);
|
||||
KUNIT_EXPECT_EQ(test, r->ar.end, 100ul);
|
||||
|
||||
damon_free_target(t);
|
||||
damon_destroy_ctx(c);
|
||||
}
|
||||
|
||||
static void damon_test_merge_two(struct kunit *test)
|
||||
{
|
||||
struct damon_target *t;
|
||||
struct damon_region *r, *r2, *r3;
|
||||
int i;
|
||||
|
||||
t = damon_new_target(42);
|
||||
r = damon_new_region(0, 100);
|
||||
r->nr_accesses = 10;
|
||||
damon_add_region(r, t);
|
||||
r2 = damon_new_region(100, 300);
|
||||
r2->nr_accesses = 20;
|
||||
damon_add_region(r2, t);
|
||||
|
||||
damon_merge_two_regions(t, r, r2);
|
||||
KUNIT_EXPECT_EQ(test, r->ar.start, 0ul);
|
||||
KUNIT_EXPECT_EQ(test, r->ar.end, 300ul);
|
||||
KUNIT_EXPECT_EQ(test, r->nr_accesses, 16u);
|
||||
|
||||
i = 0;
|
||||
damon_for_each_region(r3, t) {
|
||||
KUNIT_EXPECT_PTR_EQ(test, r, r3);
|
||||
i++;
|
||||
}
|
||||
KUNIT_EXPECT_EQ(test, i, 1);
|
||||
|
||||
damon_free_target(t);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
static void damon_test_merge_regions_of(struct kunit *test)
|
||||
{
|
||||
struct damon_target *t;
|
||||
struct damon_region *r;
|
||||
unsigned long sa[] = {0, 100, 114, 122, 130, 156, 170, 184};
|
||||
unsigned long ea[] = {100, 112, 122, 130, 156, 170, 184, 230};
|
||||
unsigned int nrs[] = {0, 0, 10, 10, 20, 30, 1, 2};
|
||||
|
||||
unsigned long saddrs[] = {0, 114, 130, 156, 170};
|
||||
unsigned long eaddrs[] = {112, 130, 156, 170, 230};
|
||||
int i;
|
||||
|
||||
t = damon_new_target(42);
|
||||
for (i = 0; i < ARRAY_SIZE(sa); i++) {
|
||||
r = damon_new_region(sa[i], ea[i]);
|
||||
r->nr_accesses = nrs[i];
|
||||
damon_add_region(r, t);
|
||||
}
|
||||
|
||||
damon_merge_regions_of(t, 9, 9999);
|
||||
/* 0-112, 114-130, 130-156, 156-170 */
|
||||
KUNIT_EXPECT_EQ(test, damon_nr_regions(t), 5u);
|
||||
for (i = 0; i < 5; i++) {
|
||||
r = __nth_region_of(t, i);
|
||||
KUNIT_EXPECT_EQ(test, r->ar.start, saddrs[i]);
|
||||
KUNIT_EXPECT_EQ(test, r->ar.end, eaddrs[i]);
|
||||
}
|
||||
damon_free_target(t);
|
||||
}
|
||||
|
||||
static void damon_test_split_regions_of(struct kunit *test)
|
||||
{
|
||||
struct damon_ctx *c = damon_new_ctx();
|
||||
struct damon_target *t;
|
||||
struct damon_region *r;
|
||||
|
||||
t = damon_new_target(42);
|
||||
r = damon_new_region(0, 22);
|
||||
damon_add_region(r, t);
|
||||
damon_split_regions_of(c, t, 2);
|
||||
KUNIT_EXPECT_EQ(test, damon_nr_regions(t), 2u);
|
||||
damon_free_target(t);
|
||||
|
||||
t = damon_new_target(42);
|
||||
r = damon_new_region(0, 220);
|
||||
damon_add_region(r, t);
|
||||
damon_split_regions_of(c, t, 4);
|
||||
KUNIT_EXPECT_EQ(test, damon_nr_regions(t), 4u);
|
||||
damon_free_target(t);
|
||||
damon_destroy_ctx(c);
|
||||
}
|
||||
|
||||
static struct kunit_case damon_test_cases[] = {
|
||||
KUNIT_CASE(damon_test_target),
|
||||
KUNIT_CASE(damon_test_regions),
|
||||
KUNIT_CASE(damon_test_aggregate),
|
||||
KUNIT_CASE(damon_test_split_at),
|
||||
KUNIT_CASE(damon_test_merge_two),
|
||||
KUNIT_CASE(damon_test_merge_regions_of),
|
||||
KUNIT_CASE(damon_test_split_regions_of),
|
||||
{},
|
||||
};
|
||||
|
||||
static struct kunit_suite damon_test_suite = {
|
||||
.name = "damon",
|
||||
.test_cases = damon_test_cases,
|
||||
};
|
||||
kunit_test_suite(damon_test_suite);
|
||||
|
||||
#endif /* _DAMON_CORE_TEST_H */
|
||||
|
||||
#endif /* CONFIG_DAMON_KUNIT_TEST */
|
||||
+720
@@ -0,0 +1,720 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Data Access Monitor
|
||||
*
|
||||
* Author: SeongJae Park <sjpark@amazon.de>
|
||||
*/
|
||||
|
||||
#define pr_fmt(fmt) "damon: " fmt
|
||||
|
||||
#include <linux/damon.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/kthread.h>
|
||||
#include <linux/random.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
#define CREATE_TRACE_POINTS
|
||||
#include <trace/events/damon.h>
|
||||
|
||||
#ifdef CONFIG_DAMON_KUNIT_TEST
|
||||
#undef DAMON_MIN_REGION
|
||||
#define DAMON_MIN_REGION 1
|
||||
#endif
|
||||
|
||||
/* Get a random number in [l, r) */
|
||||
#define damon_rand(l, r) (l + prandom_u32_max(r - l))
|
||||
|
||||
static DEFINE_MUTEX(damon_lock);
|
||||
static int nr_running_ctxs;
|
||||
|
||||
/*
|
||||
* Construct a damon_region struct
|
||||
*
|
||||
* Returns the pointer to the new struct if success, or NULL otherwise
|
||||
*/
|
||||
struct damon_region *damon_new_region(unsigned long start, unsigned long end)
|
||||
{
|
||||
struct damon_region *region;
|
||||
|
||||
region = kmalloc(sizeof(*region), GFP_KERNEL);
|
||||
if (!region)
|
||||
return NULL;
|
||||
|
||||
region->ar.start = start;
|
||||
region->ar.end = end;
|
||||
region->nr_accesses = 0;
|
||||
INIT_LIST_HEAD(®ion->list);
|
||||
|
||||
return region;
|
||||
}
|
||||
|
||||
/*
|
||||
* Add a region between two other regions
|
||||
*/
|
||||
inline void damon_insert_region(struct damon_region *r,
|
||||
struct damon_region *prev, struct damon_region *next,
|
||||
struct damon_target *t)
|
||||
{
|
||||
__list_add(&r->list, &prev->list, &next->list);
|
||||
t->nr_regions++;
|
||||
}
|
||||
|
||||
void damon_add_region(struct damon_region *r, struct damon_target *t)
|
||||
{
|
||||
list_add_tail(&r->list, &t->regions_list);
|
||||
t->nr_regions++;
|
||||
}
|
||||
|
||||
static void damon_del_region(struct damon_region *r, struct damon_target *t)
|
||||
{
|
||||
list_del(&r->list);
|
||||
t->nr_regions--;
|
||||
}
|
||||
|
||||
static void damon_free_region(struct damon_region *r)
|
||||
{
|
||||
kfree(r);
|
||||
}
|
||||
|
||||
void damon_destroy_region(struct damon_region *r, struct damon_target *t)
|
||||
{
|
||||
damon_del_region(r, t);
|
||||
damon_free_region(r);
|
||||
}
|
||||
|
||||
/*
|
||||
* Construct a damon_target struct
|
||||
*
|
||||
* Returns the pointer to the new struct if success, or NULL otherwise
|
||||
*/
|
||||
struct damon_target *damon_new_target(unsigned long id)
|
||||
{
|
||||
struct damon_target *t;
|
||||
|
||||
t = kmalloc(sizeof(*t), GFP_KERNEL);
|
||||
if (!t)
|
||||
return NULL;
|
||||
|
||||
t->id = id;
|
||||
t->nr_regions = 0;
|
||||
INIT_LIST_HEAD(&t->regions_list);
|
||||
|
||||
return t;
|
||||
}
|
||||
|
||||
void damon_add_target(struct damon_ctx *ctx, struct damon_target *t)
|
||||
{
|
||||
list_add_tail(&t->list, &ctx->adaptive_targets);
|
||||
}
|
||||
|
||||
static void damon_del_target(struct damon_target *t)
|
||||
{
|
||||
list_del(&t->list);
|
||||
}
|
||||
|
||||
void damon_free_target(struct damon_target *t)
|
||||
{
|
||||
struct damon_region *r, *next;
|
||||
|
||||
damon_for_each_region_safe(r, next, t)
|
||||
damon_free_region(r);
|
||||
kfree(t);
|
||||
}
|
||||
|
||||
void damon_destroy_target(struct damon_target *t)
|
||||
{
|
||||
damon_del_target(t);
|
||||
damon_free_target(t);
|
||||
}
|
||||
|
||||
unsigned int damon_nr_regions(struct damon_target *t)
|
||||
{
|
||||
return t->nr_regions;
|
||||
}
|
||||
|
||||
struct damon_ctx *damon_new_ctx(void)
|
||||
{
|
||||
struct damon_ctx *ctx;
|
||||
|
||||
ctx = kzalloc(sizeof(*ctx), GFP_KERNEL);
|
||||
if (!ctx)
|
||||
return NULL;
|
||||
|
||||
ctx->sample_interval = 5 * 1000;
|
||||
ctx->aggr_interval = 100 * 1000;
|
||||
ctx->primitive_update_interval = 60 * 1000 * 1000;
|
||||
|
||||
ktime_get_coarse_ts64(&ctx->last_aggregation);
|
||||
ctx->last_primitive_update = ctx->last_aggregation;
|
||||
|
||||
mutex_init(&ctx->kdamond_lock);
|
||||
|
||||
ctx->min_nr_regions = 10;
|
||||
ctx->max_nr_regions = 1000;
|
||||
|
||||
INIT_LIST_HEAD(&ctx->adaptive_targets);
|
||||
|
||||
return ctx;
|
||||
}
|
||||
|
||||
static void damon_destroy_targets(struct damon_ctx *ctx)
|
||||
{
|
||||
struct damon_target *t, *next_t;
|
||||
|
||||
if (ctx->primitive.cleanup) {
|
||||
ctx->primitive.cleanup(ctx);
|
||||
return;
|
||||
}
|
||||
|
||||
damon_for_each_target_safe(t, next_t, ctx)
|
||||
damon_destroy_target(t);
|
||||
}
|
||||
|
||||
void damon_destroy_ctx(struct damon_ctx *ctx)
|
||||
{
|
||||
damon_destroy_targets(ctx);
|
||||
kfree(ctx);
|
||||
}
|
||||
|
||||
/**
|
||||
* damon_set_targets() - Set monitoring targets.
|
||||
* @ctx: monitoring context
|
||||
* @ids: array of target ids
|
||||
* @nr_ids: number of entries in @ids
|
||||
*
|
||||
* This function should not be called while the kdamond is running.
|
||||
*
|
||||
* Return: 0 on success, negative error code otherwise.
|
||||
*/
|
||||
int damon_set_targets(struct damon_ctx *ctx,
|
||||
unsigned long *ids, ssize_t nr_ids)
|
||||
{
|
||||
ssize_t i;
|
||||
struct damon_target *t, *next;
|
||||
|
||||
damon_destroy_targets(ctx);
|
||||
|
||||
for (i = 0; i < nr_ids; i++) {
|
||||
t = damon_new_target(ids[i]);
|
||||
if (!t) {
|
||||
pr_err("Failed to alloc damon_target\n");
|
||||
/* The caller should do cleanup of the ids itself */
|
||||
damon_for_each_target_safe(t, next, ctx)
|
||||
damon_destroy_target(t);
|
||||
return -ENOMEM;
|
||||
}
|
||||
damon_add_target(ctx, t);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* damon_set_attrs() - Set attributes for the monitoring.
|
||||
* @ctx: monitoring context
|
||||
* @sample_int: time interval between samplings
|
||||
* @aggr_int: time interval between aggregations
|
||||
* @primitive_upd_int: time interval between monitoring primitive updates
|
||||
* @min_nr_reg: minimal number of regions
|
||||
* @max_nr_reg: maximum number of regions
|
||||
*
|
||||
* This function should not be called while the kdamond is running.
|
||||
* Every time interval is in micro-seconds.
|
||||
*
|
||||
* Return: 0 on success, negative error code otherwise.
|
||||
*/
|
||||
int damon_set_attrs(struct damon_ctx *ctx, unsigned long sample_int,
|
||||
unsigned long aggr_int, unsigned long primitive_upd_int,
|
||||
unsigned long min_nr_reg, unsigned long max_nr_reg)
|
||||
{
|
||||
if (min_nr_reg < 3) {
|
||||
pr_err("min_nr_regions (%lu) must be at least 3\n",
|
||||
min_nr_reg);
|
||||
return -EINVAL;
|
||||
}
|
||||
if (min_nr_reg > max_nr_reg) {
|
||||
pr_err("invalid nr_regions. min (%lu) > max (%lu)\n",
|
||||
min_nr_reg, max_nr_reg);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ctx->sample_interval = sample_int;
|
||||
ctx->aggr_interval = aggr_int;
|
||||
ctx->primitive_update_interval = primitive_upd_int;
|
||||
ctx->min_nr_regions = min_nr_reg;
|
||||
ctx->max_nr_regions = max_nr_reg;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* damon_nr_running_ctxs() - Return number of currently running contexts.
|
||||
*/
|
||||
int damon_nr_running_ctxs(void)
|
||||
{
|
||||
int nr_ctxs;
|
||||
|
||||
mutex_lock(&damon_lock);
|
||||
nr_ctxs = nr_running_ctxs;
|
||||
mutex_unlock(&damon_lock);
|
||||
|
||||
return nr_ctxs;
|
||||
}
|
||||
|
||||
/* Returns the size upper limit for each monitoring region */
|
||||
static unsigned long damon_region_sz_limit(struct damon_ctx *ctx)
|
||||
{
|
||||
struct damon_target *t;
|
||||
struct damon_region *r;
|
||||
unsigned long sz = 0;
|
||||
|
||||
damon_for_each_target(t, ctx) {
|
||||
damon_for_each_region(r, t)
|
||||
sz += r->ar.end - r->ar.start;
|
||||
}
|
||||
|
||||
if (ctx->min_nr_regions)
|
||||
sz /= ctx->min_nr_regions;
|
||||
if (sz < DAMON_MIN_REGION)
|
||||
sz = DAMON_MIN_REGION;
|
||||
|
||||
return sz;
|
||||
}
|
||||
|
||||
static bool damon_kdamond_running(struct damon_ctx *ctx)
|
||||
{
|
||||
bool running;
|
||||
|
||||
mutex_lock(&ctx->kdamond_lock);
|
||||
running = ctx->kdamond != NULL;
|
||||
mutex_unlock(&ctx->kdamond_lock);
|
||||
|
||||
return running;
|
||||
}
|
||||
|
||||
static int kdamond_fn(void *data);
|
||||
|
||||
/*
|
||||
* __damon_start() - Starts monitoring with given context.
|
||||
* @ctx: monitoring context
|
||||
*
|
||||
* This function should be called while damon_lock is hold.
|
||||
*
|
||||
* Return: 0 on success, negative error code otherwise.
|
||||
*/
|
||||
static int __damon_start(struct damon_ctx *ctx)
|
||||
{
|
||||
int err = -EBUSY;
|
||||
|
||||
mutex_lock(&ctx->kdamond_lock);
|
||||
if (!ctx->kdamond) {
|
||||
err = 0;
|
||||
ctx->kdamond_stop = false;
|
||||
ctx->kdamond = kthread_run(kdamond_fn, ctx, "kdamond.%d",
|
||||
nr_running_ctxs);
|
||||
if (IS_ERR(ctx->kdamond)) {
|
||||
err = PTR_ERR(ctx->kdamond);
|
||||
ctx->kdamond = 0;
|
||||
}
|
||||
}
|
||||
mutex_unlock(&ctx->kdamond_lock);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
/**
|
||||
* damon_start() - Starts the monitorings for a given group of contexts.
|
||||
* @ctxs: an array of the pointers for contexts to start monitoring
|
||||
* @nr_ctxs: size of @ctxs
|
||||
*
|
||||
* This function starts a group of monitoring threads for a group of monitoring
|
||||
* contexts. One thread per each context is created and run in parallel. The
|
||||
* caller should handle synchronization between the threads by itself. If a
|
||||
* group of threads that created by other 'damon_start()' call is currently
|
||||
* running, this function does nothing but returns -EBUSY.
|
||||
*
|
||||
* Return: 0 on success, negative error code otherwise.
|
||||
*/
|
||||
int damon_start(struct damon_ctx **ctxs, int nr_ctxs)
|
||||
{
|
||||
int i;
|
||||
int err = 0;
|
||||
|
||||
mutex_lock(&damon_lock);
|
||||
if (nr_running_ctxs) {
|
||||
mutex_unlock(&damon_lock);
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
for (i = 0; i < nr_ctxs; i++) {
|
||||
err = __damon_start(ctxs[i]);
|
||||
if (err)
|
||||
break;
|
||||
nr_running_ctxs++;
|
||||
}
|
||||
mutex_unlock(&damon_lock);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
/*
|
||||
* __damon_stop() - Stops monitoring of given context.
|
||||
* @ctx: monitoring context
|
||||
*
|
||||
* Return: 0 on success, negative error code otherwise.
|
||||
*/
|
||||
static int __damon_stop(struct damon_ctx *ctx)
|
||||
{
|
||||
mutex_lock(&ctx->kdamond_lock);
|
||||
if (ctx->kdamond) {
|
||||
ctx->kdamond_stop = true;
|
||||
mutex_unlock(&ctx->kdamond_lock);
|
||||
while (damon_kdamond_running(ctx))
|
||||
usleep_range(ctx->sample_interval,
|
||||
ctx->sample_interval * 2);
|
||||
return 0;
|
||||
}
|
||||
mutex_unlock(&ctx->kdamond_lock);
|
||||
|
||||
return -EPERM;
|
||||
}
|
||||
|
||||
/**
|
||||
* damon_stop() - Stops the monitorings for a given group of contexts.
|
||||
* @ctxs: an array of the pointers for contexts to stop monitoring
|
||||
* @nr_ctxs: size of @ctxs
|
||||
*
|
||||
* Return: 0 on success, negative error code otherwise.
|
||||
*/
|
||||
int damon_stop(struct damon_ctx **ctxs, int nr_ctxs)
|
||||
{
|
||||
int i, err = 0;
|
||||
|
||||
for (i = 0; i < nr_ctxs; i++) {
|
||||
/* nr_running_ctxs is decremented in kdamond_fn */
|
||||
err = __damon_stop(ctxs[i]);
|
||||
if (err)
|
||||
return err;
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
/*
|
||||
* damon_check_reset_time_interval() - Check if a time interval is elapsed.
|
||||
* @baseline: the time to check whether the interval has elapsed since
|
||||
* @interval: the time interval (microseconds)
|
||||
*
|
||||
* See whether the given time interval has passed since the given baseline
|
||||
* time. If so, it also updates the baseline to current time for next check.
|
||||
*
|
||||
* Return: true if the time interval has passed, or false otherwise.
|
||||
*/
|
||||
static bool damon_check_reset_time_interval(struct timespec64 *baseline,
|
||||
unsigned long interval)
|
||||
{
|
||||
struct timespec64 now;
|
||||
|
||||
ktime_get_coarse_ts64(&now);
|
||||
if ((timespec64_to_ns(&now) - timespec64_to_ns(baseline)) <
|
||||
interval * 1000)
|
||||
return false;
|
||||
*baseline = now;
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* Check whether it is time to flush the aggregated information
|
||||
*/
|
||||
static bool kdamond_aggregate_interval_passed(struct damon_ctx *ctx)
|
||||
{
|
||||
return damon_check_reset_time_interval(&ctx->last_aggregation,
|
||||
ctx->aggr_interval);
|
||||
}
|
||||
|
||||
/*
|
||||
* Reset the aggregated monitoring results ('nr_accesses' of each region).
|
||||
*/
|
||||
static void kdamond_reset_aggregated(struct damon_ctx *c)
|
||||
{
|
||||
struct damon_target *t;
|
||||
|
||||
damon_for_each_target(t, c) {
|
||||
struct damon_region *r;
|
||||
|
||||
damon_for_each_region(r, t) {
|
||||
trace_damon_aggregated(t, r, damon_nr_regions(t));
|
||||
r->nr_accesses = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#define sz_damon_region(r) (r->ar.end - r->ar.start)
|
||||
|
||||
/*
|
||||
* Merge two adjacent regions into one region
|
||||
*/
|
||||
static void damon_merge_two_regions(struct damon_target *t,
|
||||
struct damon_region *l, struct damon_region *r)
|
||||
{
|
||||
unsigned long sz_l = sz_damon_region(l), sz_r = sz_damon_region(r);
|
||||
|
||||
l->nr_accesses = (l->nr_accesses * sz_l + r->nr_accesses * sz_r) /
|
||||
(sz_l + sz_r);
|
||||
l->ar.end = r->ar.end;
|
||||
damon_destroy_region(r, t);
|
||||
}
|
||||
|
||||
#define diff_of(a, b) (a > b ? a - b : b - a)
|
||||
|
||||
/*
|
||||
* Merge adjacent regions having similar access frequencies
|
||||
*
|
||||
* t target affected by this merge operation
|
||||
* thres '->nr_accesses' diff threshold for the merge
|
||||
* sz_limit size upper limit of each region
|
||||
*/
|
||||
static void damon_merge_regions_of(struct damon_target *t, unsigned int thres,
|
||||
unsigned long sz_limit)
|
||||
{
|
||||
struct damon_region *r, *prev = NULL, *next;
|
||||
|
||||
damon_for_each_region_safe(r, next, t) {
|
||||
if (prev && prev->ar.end == r->ar.start &&
|
||||
diff_of(prev->nr_accesses, r->nr_accesses) <= thres &&
|
||||
sz_damon_region(prev) + sz_damon_region(r) <= sz_limit)
|
||||
damon_merge_two_regions(t, prev, r);
|
||||
else
|
||||
prev = r;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Merge adjacent regions having similar access frequencies
|
||||
*
|
||||
* threshold '->nr_accesses' diff threshold for the merge
|
||||
* sz_limit size upper limit of each region
|
||||
*
|
||||
* This function merges monitoring target regions which are adjacent and their
|
||||
* access frequencies are similar. This is for minimizing the monitoring
|
||||
* overhead under the dynamically changeable access pattern. If a merge was
|
||||
* unnecessarily made, later 'kdamond_split_regions()' will revert it.
|
||||
*/
|
||||
static void kdamond_merge_regions(struct damon_ctx *c, unsigned int threshold,
|
||||
unsigned long sz_limit)
|
||||
{
|
||||
struct damon_target *t;
|
||||
|
||||
damon_for_each_target(t, c)
|
||||
damon_merge_regions_of(t, threshold, sz_limit);
|
||||
}
|
||||
|
||||
/*
|
||||
* Split a region in two
|
||||
*
|
||||
* r the region to be split
|
||||
* sz_r size of the first sub-region that will be made
|
||||
*/
|
||||
static void damon_split_region_at(struct damon_ctx *ctx,
|
||||
struct damon_target *t, struct damon_region *r,
|
||||
unsigned long sz_r)
|
||||
{
|
||||
struct damon_region *new;
|
||||
|
||||
new = damon_new_region(r->ar.start + sz_r, r->ar.end);
|
||||
if (!new)
|
||||
return;
|
||||
|
||||
r->ar.end = new->ar.start;
|
||||
|
||||
damon_insert_region(new, r, damon_next_region(r), t);
|
||||
}
|
||||
|
||||
/* Split every region in the given target into 'nr_subs' regions */
|
||||
static void damon_split_regions_of(struct damon_ctx *ctx,
|
||||
struct damon_target *t, int nr_subs)
|
||||
{
|
||||
struct damon_region *r, *next;
|
||||
unsigned long sz_region, sz_sub = 0;
|
||||
int i;
|
||||
|
||||
damon_for_each_region_safe(r, next, t) {
|
||||
sz_region = r->ar.end - r->ar.start;
|
||||
|
||||
for (i = 0; i < nr_subs - 1 &&
|
||||
sz_region > 2 * DAMON_MIN_REGION; i++) {
|
||||
/*
|
||||
* Randomly select size of left sub-region to be at
|
||||
* least 10 percent and at most 90% of original region
|
||||
*/
|
||||
sz_sub = ALIGN_DOWN(damon_rand(1, 10) *
|
||||
sz_region / 10, DAMON_MIN_REGION);
|
||||
/* Do not allow blank region */
|
||||
if (sz_sub == 0 || sz_sub >= sz_region)
|
||||
continue;
|
||||
|
||||
damon_split_region_at(ctx, t, r, sz_sub);
|
||||
sz_region = sz_sub;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Split every target region into randomly-sized small regions
|
||||
*
|
||||
* This function splits every target region into random-sized small regions if
|
||||
* current total number of the regions is equal or smaller than half of the
|
||||
* user-specified maximum number of regions. This is for maximizing the
|
||||
* monitoring accuracy under the dynamically changeable access patterns. If a
|
||||
* split was unnecessarily made, later 'kdamond_merge_regions()' will revert
|
||||
* it.
|
||||
*/
|
||||
static void kdamond_split_regions(struct damon_ctx *ctx)
|
||||
{
|
||||
struct damon_target *t;
|
||||
unsigned int nr_regions = 0;
|
||||
static unsigned int last_nr_regions;
|
||||
int nr_subregions = 2;
|
||||
|
||||
damon_for_each_target(t, ctx)
|
||||
nr_regions += damon_nr_regions(t);
|
||||
|
||||
if (nr_regions > ctx->max_nr_regions / 2)
|
||||
return;
|
||||
|
||||
/* Maybe the middle of the region has different access frequency */
|
||||
if (last_nr_regions == nr_regions &&
|
||||
nr_regions < ctx->max_nr_regions / 3)
|
||||
nr_subregions = 3;
|
||||
|
||||
damon_for_each_target(t, ctx)
|
||||
damon_split_regions_of(ctx, t, nr_subregions);
|
||||
|
||||
last_nr_regions = nr_regions;
|
||||
}
|
||||
|
||||
/*
|
||||
* Check whether it is time to check and apply the target monitoring regions
|
||||
*
|
||||
* Returns true if it is.
|
||||
*/
|
||||
static bool kdamond_need_update_primitive(struct damon_ctx *ctx)
|
||||
{
|
||||
return damon_check_reset_time_interval(&ctx->last_primitive_update,
|
||||
ctx->primitive_update_interval);
|
||||
}
|
||||
|
||||
/*
|
||||
* Check whether current monitoring should be stopped
|
||||
*
|
||||
* The monitoring is stopped when either the user requested to stop, or all
|
||||
* monitoring targets are invalid.
|
||||
*
|
||||
* Returns true if need to stop current monitoring.
|
||||
*/
|
||||
static bool kdamond_need_stop(struct damon_ctx *ctx)
|
||||
{
|
||||
struct damon_target *t;
|
||||
bool stop;
|
||||
|
||||
mutex_lock(&ctx->kdamond_lock);
|
||||
stop = ctx->kdamond_stop;
|
||||
mutex_unlock(&ctx->kdamond_lock);
|
||||
if (stop)
|
||||
return true;
|
||||
|
||||
if (!ctx->primitive.target_valid)
|
||||
return false;
|
||||
|
||||
damon_for_each_target(t, ctx) {
|
||||
if (ctx->primitive.target_valid(t))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void set_kdamond_stop(struct damon_ctx *ctx)
|
||||
{
|
||||
mutex_lock(&ctx->kdamond_lock);
|
||||
ctx->kdamond_stop = true;
|
||||
mutex_unlock(&ctx->kdamond_lock);
|
||||
}
|
||||
|
||||
/*
|
||||
* The monitoring daemon that runs as a kernel thread
|
||||
*/
|
||||
static int kdamond_fn(void *data)
|
||||
{
|
||||
struct damon_ctx *ctx = (struct damon_ctx *)data;
|
||||
struct damon_target *t;
|
||||
struct damon_region *r, *next;
|
||||
unsigned int max_nr_accesses = 0;
|
||||
unsigned long sz_limit = 0;
|
||||
|
||||
mutex_lock(&ctx->kdamond_lock);
|
||||
pr_info("kdamond (%d) starts\n", ctx->kdamond->pid);
|
||||
mutex_unlock(&ctx->kdamond_lock);
|
||||
|
||||
if (ctx->primitive.init)
|
||||
ctx->primitive.init(ctx);
|
||||
if (ctx->callback.before_start && ctx->callback.before_start(ctx))
|
||||
set_kdamond_stop(ctx);
|
||||
|
||||
sz_limit = damon_region_sz_limit(ctx);
|
||||
|
||||
while (!kdamond_need_stop(ctx)) {
|
||||
if (ctx->primitive.prepare_access_checks)
|
||||
ctx->primitive.prepare_access_checks(ctx);
|
||||
if (ctx->callback.after_sampling &&
|
||||
ctx->callback.after_sampling(ctx))
|
||||
set_kdamond_stop(ctx);
|
||||
|
||||
usleep_range(ctx->sample_interval, ctx->sample_interval + 1);
|
||||
|
||||
if (ctx->primitive.check_accesses)
|
||||
max_nr_accesses = ctx->primitive.check_accesses(ctx);
|
||||
|
||||
if (kdamond_aggregate_interval_passed(ctx)) {
|
||||
kdamond_merge_regions(ctx,
|
||||
max_nr_accesses / 10,
|
||||
sz_limit);
|
||||
if (ctx->callback.after_aggregation &&
|
||||
ctx->callback.after_aggregation(ctx))
|
||||
set_kdamond_stop(ctx);
|
||||
kdamond_reset_aggregated(ctx);
|
||||
kdamond_split_regions(ctx);
|
||||
if (ctx->primitive.reset_aggregated)
|
||||
ctx->primitive.reset_aggregated(ctx);
|
||||
}
|
||||
|
||||
if (kdamond_need_update_primitive(ctx)) {
|
||||
if (ctx->primitive.update)
|
||||
ctx->primitive.update(ctx);
|
||||
sz_limit = damon_region_sz_limit(ctx);
|
||||
}
|
||||
}
|
||||
damon_for_each_target(t, ctx) {
|
||||
damon_for_each_region_safe(r, next, t)
|
||||
damon_destroy_region(r, t);
|
||||
}
|
||||
|
||||
if (ctx->callback.before_terminate &&
|
||||
ctx->callback.before_terminate(ctx))
|
||||
set_kdamond_stop(ctx);
|
||||
if (ctx->primitive.cleanup)
|
||||
ctx->primitive.cleanup(ctx);
|
||||
|
||||
pr_debug("kdamond (%d) finishes\n", ctx->kdamond->pid);
|
||||
mutex_lock(&ctx->kdamond_lock);
|
||||
ctx->kdamond = NULL;
|
||||
mutex_unlock(&ctx->kdamond_lock);
|
||||
|
||||
mutex_lock(&damon_lock);
|
||||
nr_running_ctxs--;
|
||||
mutex_unlock(&damon_lock);
|
||||
|
||||
do_exit(0);
|
||||
}
|
||||
|
||||
#include "core-test.h"
|
||||
@@ -0,0 +1,126 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
/*
|
||||
* DAMON Debugfs Interface Unit Tests
|
||||
*
|
||||
* Author: SeongJae Park <sjpark@amazon.de>
|
||||
*/
|
||||
|
||||
#ifdef CONFIG_DAMON_DBGFS_KUNIT_TEST
|
||||
|
||||
#ifndef _DAMON_DBGFS_TEST_H
|
||||
#define _DAMON_DBGFS_TEST_H
|
||||
|
||||
#include <kunit/test.h>
|
||||
|
||||
static void damon_dbgfs_test_str_to_target_ids(struct kunit *test)
|
||||
{
|
||||
char *question;
|
||||
unsigned long *answers;
|
||||
unsigned long expected[] = {12, 35, 46};
|
||||
ssize_t nr_integers = 0, i;
|
||||
|
||||
question = "123";
|
||||
answers = str_to_target_ids(question, strnlen(question, 128),
|
||||
&nr_integers);
|
||||
KUNIT_EXPECT_EQ(test, (ssize_t)1, nr_integers);
|
||||
KUNIT_EXPECT_EQ(test, 123ul, answers[0]);
|
||||
kfree(answers);
|
||||
|
||||
question = "123abc";
|
||||
answers = str_to_target_ids(question, strnlen(question, 128),
|
||||
&nr_integers);
|
||||
KUNIT_EXPECT_EQ(test, (ssize_t)1, nr_integers);
|
||||
KUNIT_EXPECT_EQ(test, 123ul, answers[0]);
|
||||
kfree(answers);
|
||||
|
||||
question = "a123";
|
||||
answers = str_to_target_ids(question, strnlen(question, 128),
|
||||
&nr_integers);
|
||||
KUNIT_EXPECT_EQ(test, (ssize_t)0, nr_integers);
|
||||
kfree(answers);
|
||||
|
||||
question = "12 35";
|
||||
answers = str_to_target_ids(question, strnlen(question, 128),
|
||||
&nr_integers);
|
||||
KUNIT_EXPECT_EQ(test, (ssize_t)2, nr_integers);
|
||||
for (i = 0; i < nr_integers; i++)
|
||||
KUNIT_EXPECT_EQ(test, expected[i], answers[i]);
|
||||
kfree(answers);
|
||||
|
||||
question = "12 35 46";
|
||||
answers = str_to_target_ids(question, strnlen(question, 128),
|
||||
&nr_integers);
|
||||
KUNIT_EXPECT_EQ(test, (ssize_t)3, nr_integers);
|
||||
for (i = 0; i < nr_integers; i++)
|
||||
KUNIT_EXPECT_EQ(test, expected[i], answers[i]);
|
||||
kfree(answers);
|
||||
|
||||
question = "12 35 abc 46";
|
||||
answers = str_to_target_ids(question, strnlen(question, 128),
|
||||
&nr_integers);
|
||||
KUNIT_EXPECT_EQ(test, (ssize_t)2, nr_integers);
|
||||
for (i = 0; i < 2; i++)
|
||||
KUNIT_EXPECT_EQ(test, expected[i], answers[i]);
|
||||
kfree(answers);
|
||||
|
||||
question = "";
|
||||
answers = str_to_target_ids(question, strnlen(question, 128),
|
||||
&nr_integers);
|
||||
KUNIT_EXPECT_EQ(test, (ssize_t)0, nr_integers);
|
||||
kfree(answers);
|
||||
|
||||
question = "\n";
|
||||
answers = str_to_target_ids(question, strnlen(question, 128),
|
||||
&nr_integers);
|
||||
KUNIT_EXPECT_EQ(test, (ssize_t)0, nr_integers);
|
||||
kfree(answers);
|
||||
}
|
||||
|
||||
static void damon_dbgfs_test_set_targets(struct kunit *test)
|
||||
{
|
||||
struct damon_ctx *ctx = dbgfs_new_ctx();
|
||||
unsigned long ids[] = {1, 2, 3};
|
||||
char buf[64];
|
||||
|
||||
/* Make DAMON consider target id as plain number */
|
||||
ctx->primitive.target_valid = NULL;
|
||||
ctx->primitive.cleanup = NULL;
|
||||
|
||||
damon_set_targets(ctx, ids, 3);
|
||||
sprint_target_ids(ctx, buf, 64);
|
||||
KUNIT_EXPECT_STREQ(test, (char *)buf, "1 2 3\n");
|
||||
|
||||
damon_set_targets(ctx, NULL, 0);
|
||||
sprint_target_ids(ctx, buf, 64);
|
||||
KUNIT_EXPECT_STREQ(test, (char *)buf, "\n");
|
||||
|
||||
damon_set_targets(ctx, (unsigned long []){1, 2}, 2);
|
||||
sprint_target_ids(ctx, buf, 64);
|
||||
KUNIT_EXPECT_STREQ(test, (char *)buf, "1 2\n");
|
||||
|
||||
damon_set_targets(ctx, (unsigned long []){2}, 1);
|
||||
sprint_target_ids(ctx, buf, 64);
|
||||
KUNIT_EXPECT_STREQ(test, (char *)buf, "2\n");
|
||||
|
||||
damon_set_targets(ctx, NULL, 0);
|
||||
sprint_target_ids(ctx, buf, 64);
|
||||
KUNIT_EXPECT_STREQ(test, (char *)buf, "\n");
|
||||
|
||||
dbgfs_destroy_ctx(ctx);
|
||||
}
|
||||
|
||||
static struct kunit_case damon_test_cases[] = {
|
||||
KUNIT_CASE(damon_dbgfs_test_str_to_target_ids),
|
||||
KUNIT_CASE(damon_dbgfs_test_set_targets),
|
||||
{},
|
||||
};
|
||||
|
||||
static struct kunit_suite damon_test_suite = {
|
||||
.name = "damon-dbgfs",
|
||||
.test_cases = damon_test_cases,
|
||||
};
|
||||
kunit_test_suite(damon_test_suite);
|
||||
|
||||
#endif /* _DAMON_TEST_H */
|
||||
|
||||
#endif /* CONFIG_DAMON_KUNIT_TEST */
|
||||
@@ -0,0 +1,623 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* DAMON Debugfs Interface
|
||||
*
|
||||
* Author: SeongJae Park <sjpark@amazon.de>
|
||||
*/
|
||||
|
||||
#define pr_fmt(fmt) "damon-dbgfs: " fmt
|
||||
|
||||
#include <linux/damon.h>
|
||||
#include <linux/debugfs.h>
|
||||
#include <linux/file.h>
|
||||
#include <linux/mm.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/page_idle.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
static struct damon_ctx **dbgfs_ctxs;
|
||||
static int dbgfs_nr_ctxs;
|
||||
static struct dentry **dbgfs_dirs;
|
||||
static DEFINE_MUTEX(damon_dbgfs_lock);
|
||||
|
||||
/*
|
||||
* Returns non-empty string on success, negative error code otherwise.
|
||||
*/
|
||||
static char *user_input_str(const char __user *buf, size_t count, loff_t *ppos)
|
||||
{
|
||||
char *kbuf;
|
||||
ssize_t ret;
|
||||
|
||||
/* We do not accept continuous write */
|
||||
if (*ppos)
|
||||
return ERR_PTR(-EINVAL);
|
||||
|
||||
kbuf = kmalloc(count + 1, GFP_KERNEL);
|
||||
if (!kbuf)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
ret = simple_write_to_buffer(kbuf, count + 1, ppos, buf, count);
|
||||
if (ret != count) {
|
||||
kfree(kbuf);
|
||||
return ERR_PTR(-EIO);
|
||||
}
|
||||
kbuf[ret] = '\0';
|
||||
|
||||
return kbuf;
|
||||
}
|
||||
|
||||
static ssize_t dbgfs_attrs_read(struct file *file,
|
||||
char __user *buf, size_t count, loff_t *ppos)
|
||||
{
|
||||
struct damon_ctx *ctx = file->private_data;
|
||||
char kbuf[128];
|
||||
int ret;
|
||||
|
||||
mutex_lock(&ctx->kdamond_lock);
|
||||
ret = scnprintf(kbuf, ARRAY_SIZE(kbuf), "%lu %lu %lu %lu %lu\n",
|
||||
ctx->sample_interval, ctx->aggr_interval,
|
||||
ctx->primitive_update_interval, ctx->min_nr_regions,
|
||||
ctx->max_nr_regions);
|
||||
mutex_unlock(&ctx->kdamond_lock);
|
||||
|
||||
return simple_read_from_buffer(buf, count, ppos, kbuf, ret);
|
||||
}
|
||||
|
||||
static ssize_t dbgfs_attrs_write(struct file *file,
|
||||
const char __user *buf, size_t count, loff_t *ppos)
|
||||
{
|
||||
struct damon_ctx *ctx = file->private_data;
|
||||
unsigned long s, a, r, minr, maxr;
|
||||
char *kbuf;
|
||||
ssize_t ret = count;
|
||||
int err;
|
||||
|
||||
kbuf = user_input_str(buf, count, ppos);
|
||||
if (IS_ERR(kbuf))
|
||||
return PTR_ERR(kbuf);
|
||||
|
||||
if (sscanf(kbuf, "%lu %lu %lu %lu %lu",
|
||||
&s, &a, &r, &minr, &maxr) != 5) {
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
mutex_lock(&ctx->kdamond_lock);
|
||||
if (ctx->kdamond) {
|
||||
ret = -EBUSY;
|
||||
goto unlock_out;
|
||||
}
|
||||
|
||||
err = damon_set_attrs(ctx, s, a, r, minr, maxr);
|
||||
if (err)
|
||||
ret = err;
|
||||
unlock_out:
|
||||
mutex_unlock(&ctx->kdamond_lock);
|
||||
out:
|
||||
kfree(kbuf);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static inline bool targetid_is_pid(const struct damon_ctx *ctx)
|
||||
{
|
||||
return ctx->primitive.target_valid == damon_va_target_valid;
|
||||
}
|
||||
|
||||
static ssize_t sprint_target_ids(struct damon_ctx *ctx, char *buf, ssize_t len)
|
||||
{
|
||||
struct damon_target *t;
|
||||
unsigned long id;
|
||||
int written = 0;
|
||||
int rc;
|
||||
|
||||
damon_for_each_target(t, ctx) {
|
||||
id = t->id;
|
||||
if (targetid_is_pid(ctx))
|
||||
/* Show pid numbers to debugfs users */
|
||||
id = (unsigned long)pid_vnr((struct pid *)id);
|
||||
|
||||
rc = scnprintf(&buf[written], len - written, "%lu ", id);
|
||||
if (!rc)
|
||||
return -ENOMEM;
|
||||
written += rc;
|
||||
}
|
||||
if (written)
|
||||
written -= 1;
|
||||
written += scnprintf(&buf[written], len - written, "\n");
|
||||
return written;
|
||||
}
|
||||
|
||||
static ssize_t dbgfs_target_ids_read(struct file *file,
|
||||
char __user *buf, size_t count, loff_t *ppos)
|
||||
{
|
||||
struct damon_ctx *ctx = file->private_data;
|
||||
ssize_t len;
|
||||
char ids_buf[320];
|
||||
|
||||
mutex_lock(&ctx->kdamond_lock);
|
||||
len = sprint_target_ids(ctx, ids_buf, 320);
|
||||
mutex_unlock(&ctx->kdamond_lock);
|
||||
if (len < 0)
|
||||
return len;
|
||||
|
||||
return simple_read_from_buffer(buf, count, ppos, ids_buf, len);
|
||||
}
|
||||
|
||||
/*
|
||||
* Converts a string into an array of unsigned long integers
|
||||
*
|
||||
* Returns an array of unsigned long integers if the conversion success, or
|
||||
* NULL otherwise.
|
||||
*/
|
||||
static unsigned long *str_to_target_ids(const char *str, ssize_t len,
|
||||
ssize_t *nr_ids)
|
||||
{
|
||||
unsigned long *ids;
|
||||
const int max_nr_ids = 32;
|
||||
unsigned long id;
|
||||
int pos = 0, parsed, ret;
|
||||
|
||||
*nr_ids = 0;
|
||||
ids = kmalloc_array(max_nr_ids, sizeof(id), GFP_KERNEL);
|
||||
if (!ids)
|
||||
return NULL;
|
||||
while (*nr_ids < max_nr_ids && pos < len) {
|
||||
ret = sscanf(&str[pos], "%lu%n", &id, &parsed);
|
||||
pos += parsed;
|
||||
if (ret != 1)
|
||||
break;
|
||||
ids[*nr_ids] = id;
|
||||
*nr_ids += 1;
|
||||
}
|
||||
|
||||
return ids;
|
||||
}
|
||||
|
||||
static void dbgfs_put_pids(unsigned long *ids, int nr_ids)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < nr_ids; i++)
|
||||
put_pid((struct pid *)ids[i]);
|
||||
}
|
||||
|
||||
static ssize_t dbgfs_target_ids_write(struct file *file,
|
||||
const char __user *buf, size_t count, loff_t *ppos)
|
||||
{
|
||||
struct damon_ctx *ctx = file->private_data;
|
||||
char *kbuf, *nrs;
|
||||
unsigned long *targets;
|
||||
ssize_t nr_targets;
|
||||
ssize_t ret = count;
|
||||
int i;
|
||||
int err;
|
||||
|
||||
kbuf = user_input_str(buf, count, ppos);
|
||||
if (IS_ERR(kbuf))
|
||||
return PTR_ERR(kbuf);
|
||||
|
||||
nrs = kbuf;
|
||||
|
||||
targets = str_to_target_ids(nrs, ret, &nr_targets);
|
||||
if (!targets) {
|
||||
ret = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (targetid_is_pid(ctx)) {
|
||||
for (i = 0; i < nr_targets; i++) {
|
||||
targets[i] = (unsigned long)find_get_pid(
|
||||
(int)targets[i]);
|
||||
if (!targets[i]) {
|
||||
dbgfs_put_pids(targets, i);
|
||||
ret = -EINVAL;
|
||||
goto free_targets_out;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mutex_lock(&ctx->kdamond_lock);
|
||||
if (ctx->kdamond) {
|
||||
if (targetid_is_pid(ctx))
|
||||
dbgfs_put_pids(targets, nr_targets);
|
||||
ret = -EBUSY;
|
||||
goto unlock_out;
|
||||
}
|
||||
|
||||
err = damon_set_targets(ctx, targets, nr_targets);
|
||||
if (err) {
|
||||
if (targetid_is_pid(ctx))
|
||||
dbgfs_put_pids(targets, nr_targets);
|
||||
ret = err;
|
||||
}
|
||||
|
||||
unlock_out:
|
||||
mutex_unlock(&ctx->kdamond_lock);
|
||||
free_targets_out:
|
||||
kfree(targets);
|
||||
out:
|
||||
kfree(kbuf);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static ssize_t dbgfs_kdamond_pid_read(struct file *file,
|
||||
char __user *buf, size_t count, loff_t *ppos)
|
||||
{
|
||||
struct damon_ctx *ctx = file->private_data;
|
||||
char *kbuf;
|
||||
ssize_t len;
|
||||
|
||||
kbuf = kmalloc(count, GFP_KERNEL);
|
||||
if (!kbuf)
|
||||
return -ENOMEM;
|
||||
|
||||
mutex_lock(&ctx->kdamond_lock);
|
||||
if (ctx->kdamond)
|
||||
len = scnprintf(kbuf, count, "%d\n", ctx->kdamond->pid);
|
||||
else
|
||||
len = scnprintf(kbuf, count, "none\n");
|
||||
mutex_unlock(&ctx->kdamond_lock);
|
||||
if (!len)
|
||||
goto out;
|
||||
len = simple_read_from_buffer(buf, count, ppos, kbuf, len);
|
||||
|
||||
out:
|
||||
kfree(kbuf);
|
||||
return len;
|
||||
}
|
||||
|
||||
static int damon_dbgfs_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
file->private_data = inode->i_private;
|
||||
|
||||
return nonseekable_open(inode, file);
|
||||
}
|
||||
|
||||
static const struct file_operations attrs_fops = {
|
||||
.open = damon_dbgfs_open,
|
||||
.read = dbgfs_attrs_read,
|
||||
.write = dbgfs_attrs_write,
|
||||
};
|
||||
|
||||
static const struct file_operations target_ids_fops = {
|
||||
.open = damon_dbgfs_open,
|
||||
.read = dbgfs_target_ids_read,
|
||||
.write = dbgfs_target_ids_write,
|
||||
};
|
||||
|
||||
static const struct file_operations kdamond_pid_fops = {
|
||||
.open = damon_dbgfs_open,
|
||||
.read = dbgfs_kdamond_pid_read,
|
||||
};
|
||||
|
||||
static void dbgfs_fill_ctx_dir(struct dentry *dir, struct damon_ctx *ctx)
|
||||
{
|
||||
const char * const file_names[] = {"attrs", "target_ids",
|
||||
"kdamond_pid"};
|
||||
const struct file_operations *fops[] = {&attrs_fops, &target_ids_fops,
|
||||
&kdamond_pid_fops};
|
||||
int i;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(file_names); i++)
|
||||
debugfs_create_file(file_names[i], 0600, dir, ctx, fops[i]);
|
||||
}
|
||||
|
||||
static int dbgfs_before_terminate(struct damon_ctx *ctx)
|
||||
{
|
||||
struct damon_target *t, *next;
|
||||
|
||||
if (!targetid_is_pid(ctx))
|
||||
return 0;
|
||||
|
||||
damon_for_each_target_safe(t, next, ctx) {
|
||||
put_pid((struct pid *)t->id);
|
||||
damon_destroy_target(t);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct damon_ctx *dbgfs_new_ctx(void)
|
||||
{
|
||||
struct damon_ctx *ctx;
|
||||
|
||||
ctx = damon_new_ctx();
|
||||
if (!ctx)
|
||||
return NULL;
|
||||
|
||||
damon_va_set_primitives(ctx);
|
||||
ctx->callback.before_terminate = dbgfs_before_terminate;
|
||||
return ctx;
|
||||
}
|
||||
|
||||
static void dbgfs_destroy_ctx(struct damon_ctx *ctx)
|
||||
{
|
||||
damon_destroy_ctx(ctx);
|
||||
}
|
||||
|
||||
/*
|
||||
* Make a context of @name and create a debugfs directory for it.
|
||||
*
|
||||
* This function should be called while holding damon_dbgfs_lock.
|
||||
*
|
||||
* Returns 0 on success, negative error code otherwise.
|
||||
*/
|
||||
static int dbgfs_mk_context(char *name)
|
||||
{
|
||||
struct dentry *root, **new_dirs, *new_dir;
|
||||
struct damon_ctx **new_ctxs, *new_ctx;
|
||||
|
||||
if (damon_nr_running_ctxs())
|
||||
return -EBUSY;
|
||||
|
||||
new_ctxs = krealloc(dbgfs_ctxs, sizeof(*dbgfs_ctxs) *
|
||||
(dbgfs_nr_ctxs + 1), GFP_KERNEL);
|
||||
if (!new_ctxs)
|
||||
return -ENOMEM;
|
||||
dbgfs_ctxs = new_ctxs;
|
||||
|
||||
new_dirs = krealloc(dbgfs_dirs, sizeof(*dbgfs_dirs) *
|
||||
(dbgfs_nr_ctxs + 1), GFP_KERNEL);
|
||||
if (!new_dirs)
|
||||
return -ENOMEM;
|
||||
dbgfs_dirs = new_dirs;
|
||||
|
||||
root = dbgfs_dirs[0];
|
||||
if (!root)
|
||||
return -ENOENT;
|
||||
|
||||
new_dir = debugfs_create_dir(name, root);
|
||||
dbgfs_dirs[dbgfs_nr_ctxs] = new_dir;
|
||||
|
||||
new_ctx = dbgfs_new_ctx();
|
||||
if (!new_ctx) {
|
||||
debugfs_remove(new_dir);
|
||||
dbgfs_dirs[dbgfs_nr_ctxs] = NULL;
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
dbgfs_ctxs[dbgfs_nr_ctxs] = new_ctx;
|
||||
dbgfs_fill_ctx_dir(dbgfs_dirs[dbgfs_nr_ctxs],
|
||||
dbgfs_ctxs[dbgfs_nr_ctxs]);
|
||||
dbgfs_nr_ctxs++;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static ssize_t dbgfs_mk_context_write(struct file *file,
|
||||
const char __user *buf, size_t count, loff_t *ppos)
|
||||
{
|
||||
char *kbuf;
|
||||
char *ctx_name;
|
||||
ssize_t ret = count;
|
||||
int err;
|
||||
|
||||
kbuf = user_input_str(buf, count, ppos);
|
||||
if (IS_ERR(kbuf))
|
||||
return PTR_ERR(kbuf);
|
||||
ctx_name = kmalloc(count + 1, GFP_KERNEL);
|
||||
if (!ctx_name) {
|
||||
kfree(kbuf);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
/* Trim white space */
|
||||
if (sscanf(kbuf, "%s", ctx_name) != 1) {
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
mutex_lock(&damon_dbgfs_lock);
|
||||
err = dbgfs_mk_context(ctx_name);
|
||||
if (err)
|
||||
ret = err;
|
||||
mutex_unlock(&damon_dbgfs_lock);
|
||||
|
||||
out:
|
||||
kfree(kbuf);
|
||||
kfree(ctx_name);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Remove a context of @name and its debugfs directory.
|
||||
*
|
||||
* This function should be called while holding damon_dbgfs_lock.
|
||||
*
|
||||
* Return 0 on success, negative error code otherwise.
|
||||
*/
|
||||
static int dbgfs_rm_context(char *name)
|
||||
{
|
||||
struct dentry *root, *dir, **new_dirs;
|
||||
struct damon_ctx **new_ctxs;
|
||||
int i, j;
|
||||
|
||||
if (damon_nr_running_ctxs())
|
||||
return -EBUSY;
|
||||
|
||||
root = dbgfs_dirs[0];
|
||||
if (!root)
|
||||
return -ENOENT;
|
||||
|
||||
dir = debugfs_lookup(name, root);
|
||||
if (!dir)
|
||||
return -ENOENT;
|
||||
|
||||
new_dirs = kmalloc_array(dbgfs_nr_ctxs - 1, sizeof(*dbgfs_dirs),
|
||||
GFP_KERNEL);
|
||||
if (!new_dirs)
|
||||
return -ENOMEM;
|
||||
|
||||
new_ctxs = kmalloc_array(dbgfs_nr_ctxs - 1, sizeof(*dbgfs_ctxs),
|
||||
GFP_KERNEL);
|
||||
if (!new_ctxs) {
|
||||
kfree(new_dirs);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
for (i = 0, j = 0; i < dbgfs_nr_ctxs; i++) {
|
||||
if (dbgfs_dirs[i] == dir) {
|
||||
debugfs_remove(dbgfs_dirs[i]);
|
||||
dbgfs_destroy_ctx(dbgfs_ctxs[i]);
|
||||
continue;
|
||||
}
|
||||
new_dirs[j] = dbgfs_dirs[i];
|
||||
new_ctxs[j++] = dbgfs_ctxs[i];
|
||||
}
|
||||
|
||||
kfree(dbgfs_dirs);
|
||||
kfree(dbgfs_ctxs);
|
||||
|
||||
dbgfs_dirs = new_dirs;
|
||||
dbgfs_ctxs = new_ctxs;
|
||||
dbgfs_nr_ctxs--;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static ssize_t dbgfs_rm_context_write(struct file *file,
|
||||
const char __user *buf, size_t count, loff_t *ppos)
|
||||
{
|
||||
char *kbuf;
|
||||
ssize_t ret = count;
|
||||
int err;
|
||||
char *ctx_name;
|
||||
|
||||
kbuf = user_input_str(buf, count, ppos);
|
||||
if (IS_ERR(kbuf))
|
||||
return PTR_ERR(kbuf);
|
||||
ctx_name = kmalloc(count + 1, GFP_KERNEL);
|
||||
if (!ctx_name) {
|
||||
kfree(kbuf);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
/* Trim white space */
|
||||
if (sscanf(kbuf, "%s", ctx_name) != 1) {
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
mutex_lock(&damon_dbgfs_lock);
|
||||
err = dbgfs_rm_context(ctx_name);
|
||||
if (err)
|
||||
ret = err;
|
||||
mutex_unlock(&damon_dbgfs_lock);
|
||||
|
||||
out:
|
||||
kfree(kbuf);
|
||||
kfree(ctx_name);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static ssize_t dbgfs_monitor_on_read(struct file *file,
|
||||
char __user *buf, size_t count, loff_t *ppos)
|
||||
{
|
||||
char monitor_on_buf[5];
|
||||
bool monitor_on = damon_nr_running_ctxs() != 0;
|
||||
int len;
|
||||
|
||||
len = scnprintf(monitor_on_buf, 5, monitor_on ? "on\n" : "off\n");
|
||||
|
||||
return simple_read_from_buffer(buf, count, ppos, monitor_on_buf, len);
|
||||
}
|
||||
|
||||
static ssize_t dbgfs_monitor_on_write(struct file *file,
|
||||
const char __user *buf, size_t count, loff_t *ppos)
|
||||
{
|
||||
ssize_t ret = count;
|
||||
char *kbuf;
|
||||
int err;
|
||||
|
||||
kbuf = user_input_str(buf, count, ppos);
|
||||
if (IS_ERR(kbuf))
|
||||
return PTR_ERR(kbuf);
|
||||
|
||||
/* Remove white space */
|
||||
if (sscanf(kbuf, "%s", kbuf) != 1) {
|
||||
kfree(kbuf);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (!strncmp(kbuf, "on", count))
|
||||
err = damon_start(dbgfs_ctxs, dbgfs_nr_ctxs);
|
||||
else if (!strncmp(kbuf, "off", count))
|
||||
err = damon_stop(dbgfs_ctxs, dbgfs_nr_ctxs);
|
||||
else
|
||||
err = -EINVAL;
|
||||
|
||||
if (err)
|
||||
ret = err;
|
||||
kfree(kbuf);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct file_operations mk_contexts_fops = {
|
||||
.write = dbgfs_mk_context_write,
|
||||
};
|
||||
|
||||
static const struct file_operations rm_contexts_fops = {
|
||||
.write = dbgfs_rm_context_write,
|
||||
};
|
||||
|
||||
static const struct file_operations monitor_on_fops = {
|
||||
.read = dbgfs_monitor_on_read,
|
||||
.write = dbgfs_monitor_on_write,
|
||||
};
|
||||
|
||||
static int __init __damon_dbgfs_init(void)
|
||||
{
|
||||
struct dentry *dbgfs_root;
|
||||
const char * const file_names[] = {"mk_contexts", "rm_contexts",
|
||||
"monitor_on"};
|
||||
const struct file_operations *fops[] = {&mk_contexts_fops,
|
||||
&rm_contexts_fops, &monitor_on_fops};
|
||||
int i;
|
||||
|
||||
dbgfs_root = debugfs_create_dir("damon", NULL);
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(file_names); i++)
|
||||
debugfs_create_file(file_names[i], 0600, dbgfs_root, NULL,
|
||||
fops[i]);
|
||||
dbgfs_fill_ctx_dir(dbgfs_root, dbgfs_ctxs[0]);
|
||||
|
||||
dbgfs_dirs = kmalloc_array(1, sizeof(dbgfs_root), GFP_KERNEL);
|
||||
if (!dbgfs_dirs) {
|
||||
debugfs_remove(dbgfs_root);
|
||||
return -ENOMEM;
|
||||
}
|
||||
dbgfs_dirs[0] = dbgfs_root;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Functions for the initialization
|
||||
*/
|
||||
|
||||
static int __init damon_dbgfs_init(void)
|
||||
{
|
||||
int rc;
|
||||
|
||||
dbgfs_ctxs = kmalloc(sizeof(*dbgfs_ctxs), GFP_KERNEL);
|
||||
if (!dbgfs_ctxs)
|
||||
return -ENOMEM;
|
||||
dbgfs_ctxs[0] = dbgfs_new_ctx();
|
||||
if (!dbgfs_ctxs[0]) {
|
||||
kfree(dbgfs_ctxs);
|
||||
return -ENOMEM;
|
||||
}
|
||||
dbgfs_nr_ctxs = 1;
|
||||
|
||||
rc = __damon_dbgfs_init();
|
||||
if (rc) {
|
||||
kfree(dbgfs_ctxs[0]);
|
||||
kfree(dbgfs_ctxs);
|
||||
pr_err("%s: dbgfs init failed\n", __func__);
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
module_init(damon_dbgfs_init);
|
||||
|
||||
#include "dbgfs-test.h"
|
||||
@@ -0,0 +1,329 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
/*
|
||||
* Data Access Monitor Unit Tests
|
||||
*
|
||||
* Copyright 2019 Amazon.com, Inc. or its affiliates. All rights reserved.
|
||||
*
|
||||
* Author: SeongJae Park <sjpark@amazon.de>
|
||||
*/
|
||||
|
||||
#ifdef CONFIG_DAMON_VADDR_KUNIT_TEST
|
||||
|
||||
#ifndef _DAMON_VADDR_TEST_H
|
||||
#define _DAMON_VADDR_TEST_H
|
||||
|
||||
#include <kunit/test.h>
|
||||
|
||||
static void __link_vmas(struct vm_area_struct *vmas, ssize_t nr_vmas)
|
||||
{
|
||||
int i, j;
|
||||
unsigned long largest_gap, gap;
|
||||
|
||||
if (!nr_vmas)
|
||||
return;
|
||||
|
||||
for (i = 0; i < nr_vmas - 1; i++) {
|
||||
vmas[i].vm_next = &vmas[i + 1];
|
||||
|
||||
vmas[i].vm_rb.rb_left = NULL;
|
||||
vmas[i].vm_rb.rb_right = &vmas[i + 1].vm_rb;
|
||||
|
||||
largest_gap = 0;
|
||||
for (j = i; j < nr_vmas; j++) {
|
||||
if (j == 0)
|
||||
continue;
|
||||
gap = vmas[j].vm_start - vmas[j - 1].vm_end;
|
||||
if (gap > largest_gap)
|
||||
largest_gap = gap;
|
||||
}
|
||||
vmas[i].rb_subtree_gap = largest_gap;
|
||||
}
|
||||
vmas[i].vm_next = NULL;
|
||||
vmas[i].vm_rb.rb_right = NULL;
|
||||
vmas[i].rb_subtree_gap = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* 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)
|
||||
{
|
||||
struct damon_addr_range regions[3] = {0,};
|
||||
/* 10-20-25, 200-210-220, 300-305, 307-330 */
|
||||
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},
|
||||
};
|
||||
|
||||
__link_vmas(vmas, 6);
|
||||
|
||||
__damon_va_three_regions(&vmas[0], 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_va_apply_three_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_va_apply_three_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_ctx *ctx = damon_new_ctx();
|
||||
struct damon_target *t;
|
||||
struct damon_region *r;
|
||||
int i;
|
||||
|
||||
t = damon_new_target(42);
|
||||
for (i = 0; i < nr_regions / 2; i++) {
|
||||
r = damon_new_region(regions[i * 2], regions[i * 2 + 1]);
|
||||
damon_add_region(r, t);
|
||||
}
|
||||
damon_add_target(ctx, t);
|
||||
|
||||
damon_va_apply_three_regions(t, three_regions);
|
||||
|
||||
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_ctx(ctx);
|
||||
}
|
||||
|
||||
/*
|
||||
* 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 crated.
|
||||
*/
|
||||
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 void damon_test_split_evenly(struct kunit *test)
|
||||
{
|
||||
struct damon_ctx *c = damon_new_ctx();
|
||||
struct damon_target *t;
|
||||
struct damon_region *r;
|
||||
unsigned long i;
|
||||
|
||||
KUNIT_EXPECT_EQ(test, damon_va_evenly_split_region(NULL, NULL, 5),
|
||||
-EINVAL);
|
||||
|
||||
t = damon_new_target(42);
|
||||
r = damon_new_region(0, 100);
|
||||
KUNIT_EXPECT_EQ(test, damon_va_evenly_split_region(t, r, 0), -EINVAL);
|
||||
|
||||
damon_add_region(r, t);
|
||||
KUNIT_EXPECT_EQ(test, damon_va_evenly_split_region(t, r, 10), 0);
|
||||
KUNIT_EXPECT_EQ(test, damon_nr_regions(t), 10u);
|
||||
|
||||
i = 0;
|
||||
damon_for_each_region(r, t) {
|
||||
KUNIT_EXPECT_EQ(test, r->ar.start, i++ * 10);
|
||||
KUNIT_EXPECT_EQ(test, r->ar.end, i * 10);
|
||||
}
|
||||
damon_free_target(t);
|
||||
|
||||
t = damon_new_target(42);
|
||||
r = damon_new_region(5, 59);
|
||||
damon_add_region(r, t);
|
||||
KUNIT_EXPECT_EQ(test, damon_va_evenly_split_region(t, r, 5), 0);
|
||||
KUNIT_EXPECT_EQ(test, damon_nr_regions(t), 5u);
|
||||
|
||||
i = 0;
|
||||
damon_for_each_region(r, t) {
|
||||
if (i == 4)
|
||||
break;
|
||||
KUNIT_EXPECT_EQ(test, r->ar.start, 5 + 10 * i++);
|
||||
KUNIT_EXPECT_EQ(test, r->ar.end, 5 + 10 * i);
|
||||
}
|
||||
KUNIT_EXPECT_EQ(test, r->ar.start, 5 + 10 * i);
|
||||
KUNIT_EXPECT_EQ(test, r->ar.end, 59ul);
|
||||
damon_free_target(t);
|
||||
|
||||
t = damon_new_target(42);
|
||||
r = damon_new_region(5, 6);
|
||||
damon_add_region(r, t);
|
||||
KUNIT_EXPECT_EQ(test, damon_va_evenly_split_region(t, r, 2), -EINVAL);
|
||||
KUNIT_EXPECT_EQ(test, damon_nr_regions(t), 1u);
|
||||
|
||||
damon_for_each_region(r, t) {
|
||||
KUNIT_EXPECT_EQ(test, r->ar.start, 5ul);
|
||||
KUNIT_EXPECT_EQ(test, r->ar.end, 6ul);
|
||||
}
|
||||
damon_free_target(t);
|
||||
damon_destroy_ctx(c);
|
||||
}
|
||||
|
||||
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),
|
||||
KUNIT_CASE(damon_test_split_evenly),
|
||||
{},
|
||||
};
|
||||
|
||||
static struct kunit_suite damon_test_suite = {
|
||||
.name = "damon-primitives",
|
||||
.test_cases = damon_test_cases,
|
||||
};
|
||||
kunit_test_suite(damon_test_suite);
|
||||
|
||||
#endif /* _DAMON_VADDR_TEST_H */
|
||||
|
||||
#endif /* CONFIG_DAMON_VADDR_KUNIT_TEST */
|
||||
@@ -0,0 +1,672 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* DAMON Primitives for Virtual Address Spaces
|
||||
*
|
||||
* Author: SeongJae Park <sjpark@amazon.de>
|
||||
*/
|
||||
|
||||
#define pr_fmt(fmt) "damon-va: " fmt
|
||||
|
||||
#include <linux/damon.h>
|
||||
#include <linux/hugetlb.h>
|
||||
#include <linux/mm.h>
|
||||
#include <linux/mmu_notifier.h>
|
||||
#include <linux/highmem.h>
|
||||
#include <linux/page_idle.h>
|
||||
#include <linux/pagewalk.h>
|
||||
#include <linux/random.h>
|
||||
#include <linux/sched/mm.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
#ifdef CONFIG_DAMON_VADDR_KUNIT_TEST
|
||||
#undef DAMON_MIN_REGION
|
||||
#define DAMON_MIN_REGION 1
|
||||
#endif
|
||||
|
||||
/* Get a random number in [l, r) */
|
||||
#define damon_rand(l, r) (l + prandom_u32_max(r - l))
|
||||
|
||||
/*
|
||||
* 't->id' should be the pointer to the relevant 'struct pid' having reference
|
||||
* count. Caller must put the returned task, unless it is NULL.
|
||||
*/
|
||||
#define damon_get_task_struct(t) \
|
||||
(get_pid_task((struct pid *)t->id, 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;
|
||||
}
|
||||
|
||||
/*
|
||||
* Functions for the initial monitoring target regions construction
|
||||
*/
|
||||
|
||||
/*
|
||||
* Size-evenly split a region into 'nr_pieces' small regions
|
||||
*
|
||||
* Returns 0 on success, or negative error code otherwise.
|
||||
*/
|
||||
static int damon_va_evenly_split_region(struct damon_target *t,
|
||||
struct damon_region *r, unsigned int nr_pieces)
|
||||
{
|
||||
unsigned long sz_orig, sz_piece, orig_end;
|
||||
struct damon_region *n = NULL, *next;
|
||||
unsigned long start;
|
||||
|
||||
if (!r || !nr_pieces)
|
||||
return -EINVAL;
|
||||
|
||||
orig_end = r->ar.end;
|
||||
sz_orig = r->ar.end - r->ar.start;
|
||||
sz_piece = ALIGN_DOWN(sz_orig / nr_pieces, DAMON_MIN_REGION);
|
||||
|
||||
if (!sz_piece)
|
||||
return -EINVAL;
|
||||
|
||||
r->ar.end = r->ar.start + sz_piece;
|
||||
next = damon_next_region(r);
|
||||
for (start = r->ar.end; start + sz_piece <= orig_end;
|
||||
start += sz_piece) {
|
||||
n = damon_new_region(start, start + sz_piece);
|
||||
if (!n)
|
||||
return -ENOMEM;
|
||||
damon_insert_region(n, r, next, t);
|
||||
r = n;
|
||||
}
|
||||
/* complement last region for possible rounding error */
|
||||
if (n)
|
||||
n->ar.end = orig_end;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static unsigned long sz_range(struct damon_addr_range *r)
|
||||
{
|
||||
return r->end - r->start;
|
||||
}
|
||||
|
||||
static void swap_ranges(struct damon_addr_range *r1,
|
||||
struct damon_addr_range *r2)
|
||||
{
|
||||
struct damon_addr_range tmp;
|
||||
|
||||
tmp = *r1;
|
||||
*r1 = *r2;
|
||||
*r2 = tmp;
|
||||
}
|
||||
|
||||
/*
|
||||
* 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 vm_area_struct *vma,
|
||||
struct damon_addr_range regions[3])
|
||||
{
|
||||
struct damon_addr_range gap = {0}, first_gap = {0}, second_gap = {0};
|
||||
struct vm_area_struct *last_vma = NULL;
|
||||
unsigned long start = 0;
|
||||
struct rb_root rbroot;
|
||||
|
||||
/* Find two biggest gaps so that first_gap > second_gap > others */
|
||||
for (; vma; vma = vma->vm_next) {
|
||||
if (!last_vma) {
|
||||
start = vma->vm_start;
|
||||
goto next;
|
||||
}
|
||||
|
||||
if (vma->rb_subtree_gap <= sz_range(&second_gap)) {
|
||||
rbroot.rb_node = &vma->vm_rb;
|
||||
vma = rb_entry(rb_last(&rbroot),
|
||||
struct vm_area_struct, vm_rb);
|
||||
goto next;
|
||||
}
|
||||
|
||||
gap.start = last_vma->vm_end;
|
||||
gap.end = vma->vm_start;
|
||||
if (sz_range(&gap) > sz_range(&second_gap)) {
|
||||
swap_ranges(&gap, &second_gap);
|
||||
if (sz_range(&second_gap) > sz_range(&first_gap))
|
||||
swap_ranges(&second_gap, &first_gap);
|
||||
}
|
||||
next:
|
||||
last_vma = vma;
|
||||
}
|
||||
|
||||
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_ranges(&first_gap, &second_gap);
|
||||
|
||||
/* Store the result */
|
||||
regions[0].start = ALIGN(start, DAMON_MIN_REGION);
|
||||
regions[0].end = ALIGN(first_gap.start, DAMON_MIN_REGION);
|
||||
regions[1].start = ALIGN(first_gap.end, DAMON_MIN_REGION);
|
||||
regions[1].end = ALIGN(second_gap.start, DAMON_MIN_REGION);
|
||||
regions[2].start = ALIGN(second_gap.end, DAMON_MIN_REGION);
|
||||
regions[2].end = ALIGN(last_vma->vm_end, DAMON_MIN_REGION);
|
||||
|
||||
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->mmap, 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_region *r;
|
||||
struct damon_addr_range regions[3];
|
||||
unsigned long sz = 0, nr_pieces;
|
||||
int i;
|
||||
|
||||
if (damon_va_three_regions(t, regions)) {
|
||||
pr_err("Failed to get three regions of target %lu\n", t->id);
|
||||
return;
|
||||
}
|
||||
|
||||
for (i = 0; i < 3; i++)
|
||||
sz += regions[i].end - regions[i].start;
|
||||
if (ctx->min_nr_regions)
|
||||
sz /= ctx->min_nr_regions;
|
||||
if (sz < DAMON_MIN_REGION)
|
||||
sz = DAMON_MIN_REGION;
|
||||
|
||||
/* Set the initial three regions of the target */
|
||||
for (i = 0; i < 3; i++) {
|
||||
r = damon_new_region(regions[i].start, regions[i].end);
|
||||
if (!r) {
|
||||
pr_err("%d'th init region creation failed\n", i);
|
||||
return;
|
||||
}
|
||||
damon_add_region(r, t);
|
||||
|
||||
nr_pieces = (regions[i].end - regions[i].start) / sz;
|
||||
damon_va_evenly_split_region(t, r, nr_pieces);
|
||||
}
|
||||
}
|
||||
|
||||
/* Initialize '->regions_list' of every target (task) */
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Functions for the dynamic monitoring target regions update
|
||||
*/
|
||||
|
||||
/*
|
||||
* Check whether a region is intersecting an address range
|
||||
*
|
||||
* Returns true if it is.
|
||||
*/
|
||||
static bool damon_intersect(struct damon_region *r, struct damon_addr_range *re)
|
||||
{
|
||||
return !(r->ar.end <= re->start || re->end <= r->ar.start);
|
||||
}
|
||||
|
||||
/*
|
||||
* Update damon regions for the three big regions of the given target
|
||||
*
|
||||
* t the given target
|
||||
* bregions the three big regions of the target
|
||||
*/
|
||||
static void damon_va_apply_three_regions(struct damon_target *t,
|
||||
struct damon_addr_range bregions[3])
|
||||
{
|
||||
struct damon_region *r, *next;
|
||||
unsigned int i = 0;
|
||||
|
||||
/* Remove regions which are not in the three big regions now */
|
||||
damon_for_each_region_safe(r, next, t) {
|
||||
for (i = 0; i < 3; i++) {
|
||||
if (damon_intersect(r, &bregions[i]))
|
||||
break;
|
||||
}
|
||||
if (i == 3)
|
||||
damon_destroy_region(r, t);
|
||||
}
|
||||
|
||||
/* Adjust intersecting regions to fit with the three big regions */
|
||||
for (i = 0; i < 3; i++) {
|
||||
struct damon_region *first = NULL, *last;
|
||||
struct damon_region *newr;
|
||||
struct damon_addr_range *br;
|
||||
|
||||
br = &bregions[i];
|
||||
/* Get the first and last regions which intersects with br */
|
||||
damon_for_each_region(r, t) {
|
||||
if (damon_intersect(r, br)) {
|
||||
if (!first)
|
||||
first = r;
|
||||
last = r;
|
||||
}
|
||||
if (r->ar.start >= br->end)
|
||||
break;
|
||||
}
|
||||
if (!first) {
|
||||
/* no damon_region intersects with this big region */
|
||||
newr = damon_new_region(
|
||||
ALIGN_DOWN(br->start,
|
||||
DAMON_MIN_REGION),
|
||||
ALIGN(br->end, DAMON_MIN_REGION));
|
||||
if (!newr)
|
||||
continue;
|
||||
damon_insert_region(newr, damon_prev_region(r), r, t);
|
||||
} else {
|
||||
first->ar.start = ALIGN_DOWN(br->start,
|
||||
DAMON_MIN_REGION);
|
||||
last->ar.end = ALIGN(br->end, DAMON_MIN_REGION);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Update regions for current memory mappings
|
||||
*/
|
||||
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_va_apply_three_regions(t, three_regions);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* 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_page()'. We
|
||||
* steal rather than reuse it because the code is quite simple.
|
||||
*/
|
||||
static struct page *damon_get_page(unsigned long pfn)
|
||||
{
|
||||
struct page *page = pfn_to_online_page(pfn);
|
||||
|
||||
if (!page || !PageLRU(page) || !get_page_unless_zero(page))
|
||||
return NULL;
|
||||
|
||||
if (unlikely(!PageLRU(page))) {
|
||||
put_page(page);
|
||||
page = NULL;
|
||||
}
|
||||
return page;
|
||||
}
|
||||
|
||||
static void damon_ptep_mkold(pte_t *pte, struct mm_struct *mm,
|
||||
unsigned long addr)
|
||||
{
|
||||
bool referenced = false;
|
||||
struct page *page = damon_get_page(pte_pfn(*pte));
|
||||
|
||||
if (!page)
|
||||
return;
|
||||
|
||||
if (pte_young(*pte)) {
|
||||
referenced = true;
|
||||
*pte = pte_mkold(*pte);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_MMU_NOTIFIER
|
||||
if (mmu_notifier_clear_young(mm, addr, addr + PAGE_SIZE))
|
||||
referenced = true;
|
||||
#endif /* CONFIG_MMU_NOTIFIER */
|
||||
|
||||
if (referenced)
|
||||
set_page_young(page);
|
||||
|
||||
set_page_idle(page);
|
||||
put_page(page);
|
||||
}
|
||||
|
||||
static void damon_pmdp_mkold(pmd_t *pmd, struct mm_struct *mm,
|
||||
unsigned long addr)
|
||||
{
|
||||
#ifdef CONFIG_TRANSPARENT_HUGEPAGE
|
||||
bool referenced = false;
|
||||
struct page *page = damon_get_page(pmd_pfn(*pmd));
|
||||
|
||||
if (!page)
|
||||
return;
|
||||
|
||||
if (pmd_young(*pmd)) {
|
||||
referenced = true;
|
||||
*pmd = pmd_mkold(*pmd);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_MMU_NOTIFIER
|
||||
if (mmu_notifier_clear_young(mm, addr,
|
||||
addr + ((1UL) << HPAGE_PMD_SHIFT)))
|
||||
referenced = true;
|
||||
#endif /* CONFIG_MMU_NOTIFIER */
|
||||
|
||||
if (referenced)
|
||||
set_page_young(page);
|
||||
|
||||
set_page_idle(page);
|
||||
put_page(page);
|
||||
#endif /* CONFIG_TRANSPARENT_HUGEPAGE */
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
if (pmd_huge(*pmd)) {
|
||||
ptl = pmd_lock(walk->mm, pmd);
|
||||
if (pmd_huge(*pmd)) {
|
||||
damon_pmdp_mkold(pmd, walk->mm, addr);
|
||||
spin_unlock(ptl);
|
||||
return 0;
|
||||
}
|
||||
spin_unlock(ptl);
|
||||
}
|
||||
|
||||
if (pmd_none(*pmd) || unlikely(pmd_bad(*pmd)))
|
||||
return 0;
|
||||
pte = pte_offset_map_lock(walk->mm, pmd, addr, &ptl);
|
||||
if (!pte_present(*pte))
|
||||
goto out;
|
||||
damon_ptep_mkold(pte, walk->mm, addr);
|
||||
out:
|
||||
pte_unmap_unlock(pte, ptl);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct mm_walk_ops damon_mkold_ops = {
|
||||
.pmd_entry = damon_mkold_pmd_entry,
|
||||
};
|
||||
|
||||
static void damon_va_mkold(struct mm_struct *mm, unsigned long addr)
|
||||
{
|
||||
mmap_read_lock(mm);
|
||||
walk_page_range(mm, addr, addr + 1, &damon_mkold_ops, NULL);
|
||||
mmap_read_unlock(mm);
|
||||
}
|
||||
|
||||
/*
|
||||
* Functions for the access checking of the regions
|
||||
*/
|
||||
|
||||
static void damon_va_prepare_access_check(struct damon_ctx *ctx,
|
||||
struct mm_struct *mm, struct damon_region *r)
|
||||
{
|
||||
r->sampling_addr = damon_rand(r->ar.start, r->ar.end);
|
||||
|
||||
damon_va_mkold(mm, r->sampling_addr);
|
||||
}
|
||||
|
||||
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(ctx, mm, r);
|
||||
mmput(mm);
|
||||
}
|
||||
}
|
||||
|
||||
struct damon_young_walk_private {
|
||||
unsigned long *page_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;
|
||||
spinlock_t *ptl;
|
||||
struct page *page;
|
||||
struct damon_young_walk_private *priv = walk->private;
|
||||
|
||||
#ifdef CONFIG_TRANSPARENT_HUGEPAGE
|
||||
if (pmd_huge(*pmd)) {
|
||||
ptl = pmd_lock(walk->mm, pmd);
|
||||
if (!pmd_huge(*pmd)) {
|
||||
spin_unlock(ptl);
|
||||
goto regular_page;
|
||||
}
|
||||
page = damon_get_page(pmd_pfn(*pmd));
|
||||
if (!page)
|
||||
goto huge_out;
|
||||
if (pmd_young(*pmd) || !page_is_idle(page) ||
|
||||
mmu_notifier_test_young(walk->mm,
|
||||
addr)) {
|
||||
*priv->page_sz = ((1UL) << HPAGE_PMD_SHIFT);
|
||||
priv->young = true;
|
||||
}
|
||||
put_page(page);
|
||||
huge_out:
|
||||
spin_unlock(ptl);
|
||||
return 0;
|
||||
}
|
||||
|
||||
regular_page:
|
||||
#endif /* CONFIG_TRANSPARENT_HUGEPAGE */
|
||||
|
||||
if (pmd_none(*pmd) || unlikely(pmd_bad(*pmd)))
|
||||
return -EINVAL;
|
||||
pte = pte_offset_map_lock(walk->mm, pmd, addr, &ptl);
|
||||
if (!pte_present(*pte))
|
||||
goto out;
|
||||
page = damon_get_page(pte_pfn(*pte));
|
||||
if (!page)
|
||||
goto out;
|
||||
if (pte_young(*pte) || !page_is_idle(page) ||
|
||||
mmu_notifier_test_young(walk->mm, addr)) {
|
||||
*priv->page_sz = PAGE_SIZE;
|
||||
priv->young = true;
|
||||
}
|
||||
put_page(page);
|
||||
out:
|
||||
pte_unmap_unlock(pte, ptl);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct mm_walk_ops damon_young_ops = {
|
||||
.pmd_entry = damon_young_pmd_entry,
|
||||
};
|
||||
|
||||
static bool damon_va_young(struct mm_struct *mm, unsigned long addr,
|
||||
unsigned long *page_sz)
|
||||
{
|
||||
struct damon_young_walk_private arg = {
|
||||
.page_sz = page_sz,
|
||||
.young = false,
|
||||
};
|
||||
|
||||
mmap_read_lock(mm);
|
||||
walk_page_range(mm, addr, addr + 1, &damon_young_ops, &arg);
|
||||
mmap_read_unlock(mm);
|
||||
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 damon_ctx *ctx,
|
||||
struct mm_struct *mm, struct damon_region *r)
|
||||
{
|
||||
static struct mm_struct *last_mm;
|
||||
static unsigned long last_addr;
|
||||
static unsigned long last_page_sz = PAGE_SIZE;
|
||||
static bool last_accessed;
|
||||
|
||||
/* If the region is in the last checked page, reuse the result */
|
||||
if (mm == last_mm && (ALIGN_DOWN(last_addr, last_page_sz) ==
|
||||
ALIGN_DOWN(r->sampling_addr, last_page_sz))) {
|
||||
if (last_accessed)
|
||||
r->nr_accesses++;
|
||||
return;
|
||||
}
|
||||
|
||||
last_accessed = damon_va_young(mm, r->sampling_addr, &last_page_sz);
|
||||
if (last_accessed)
|
||||
r->nr_accesses++;
|
||||
|
||||
last_mm = mm;
|
||||
last_addr = r->sampling_addr;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
damon_for_each_target(t, ctx) {
|
||||
mm = damon_get_mm(t);
|
||||
if (!mm)
|
||||
continue;
|
||||
damon_for_each_region(r, t) {
|
||||
damon_va_check_access(ctx, mm, r);
|
||||
max_nr_accesses = max(r->nr_accesses, max_nr_accesses);
|
||||
}
|
||||
mmput(mm);
|
||||
}
|
||||
|
||||
return max_nr_accesses;
|
||||
}
|
||||
|
||||
/*
|
||||
* Functions for the target validity check and cleanup
|
||||
*/
|
||||
|
||||
bool damon_va_target_valid(void *target)
|
||||
{
|
||||
struct damon_target *t = target;
|
||||
struct task_struct *task;
|
||||
|
||||
task = damon_get_task_struct(t);
|
||||
if (task) {
|
||||
put_task_struct(task);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void damon_va_set_primitives(struct damon_ctx *ctx)
|
||||
{
|
||||
ctx->primitive.init = damon_va_init;
|
||||
ctx->primitive.update = damon_va_update;
|
||||
ctx->primitive.prepare_access_checks = damon_va_prepare_access_checks;
|
||||
ctx->primitive.check_accesses = damon_va_check_accesses;
|
||||
ctx->primitive.reset_aggregated = NULL;
|
||||
ctx->primitive.target_valid = damon_va_target_valid;
|
||||
ctx->primitive.cleanup = NULL;
|
||||
}
|
||||
|
||||
#include "vaddr-test.h"
|
||||
@@ -38,13 +38,8 @@ pgprot_t __init __weak early_memremap_pgprot_adjust(resource_size_t phys_addr,
|
||||
return prot;
|
||||
}
|
||||
|
||||
void __init __weak early_ioremap_shutdown(void)
|
||||
{
|
||||
}
|
||||
|
||||
void __init early_ioremap_reset(void)
|
||||
{
|
||||
early_ioremap_shutdown();
|
||||
after_paging_init = 1;
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -436,7 +436,7 @@ EXPORT_SYMBOL(zero_user_segments);
|
||||
|
||||
static inline int kmap_local_idx_push(void)
|
||||
{
|
||||
WARN_ON_ONCE(in_irq() && !irqs_disabled());
|
||||
WARN_ON_ONCE(in_hardirq() && !irqs_disabled());
|
||||
current->kmap_ctrl.idx += KM_INCR;
|
||||
BUG_ON(current->kmap_ctrl.idx >= KM_MAX_IDX);
|
||||
return current->kmap_ctrl.idx - 1;
|
||||
|
||||
@@ -8,33 +8,9 @@
|
||||
*/
|
||||
#include <linux/vmalloc.h>
|
||||
#include <linux/mm.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/export.h>
|
||||
#include <asm/cacheflush.h>
|
||||
|
||||
#include "pgalloc-track.h"
|
||||
|
||||
#ifdef CONFIG_HAVE_ARCH_HUGE_VMAP
|
||||
static unsigned int __ro_after_init iomap_max_page_shift = BITS_PER_LONG - 1;
|
||||
|
||||
static int __init set_nohugeiomap(char *str)
|
||||
{
|
||||
iomap_max_page_shift = PAGE_SHIFT;
|
||||
return 0;
|
||||
}
|
||||
early_param("nohugeiomap", set_nohugeiomap);
|
||||
#else /* CONFIG_HAVE_ARCH_HUGE_VMAP */
|
||||
static const unsigned int iomap_max_page_shift = PAGE_SHIFT;
|
||||
#endif /* CONFIG_HAVE_ARCH_HUGE_VMAP */
|
||||
|
||||
int ioremap_page_range(unsigned long addr,
|
||||
unsigned long end, phys_addr_t phys_addr, pgprot_t prot)
|
||||
{
|
||||
return vmap_range(addr, end, phys_addr, prot, iomap_max_page_shift);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_GENERIC_IOREMAP
|
||||
void __iomem *ioremap_prot(phys_addr_t addr, size_t size, unsigned long prot)
|
||||
{
|
||||
unsigned long offset, vaddr;
|
||||
@@ -71,4 +47,3 @@ void iounmap(volatile void __iomem *addr)
|
||||
vunmap((void *)((unsigned long)addr & PAGE_MASK));
|
||||
}
|
||||
EXPORT_SYMBOL(iounmap);
|
||||
#endif /* CONFIG_GENERIC_IOREMAP */
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#include <linux/moduleparam.h>
|
||||
#include <linux/random.h>
|
||||
#include <linux/rcupdate.h>
|
||||
#include <linux/sched/clock.h>
|
||||
#include <linux/sched/sysctl.h>
|
||||
#include <linux/seq_file.h>
|
||||
#include <linux/slab.h>
|
||||
@@ -196,6 +197,8 @@ static noinline void metadata_update_state(struct kfence_metadata *meta,
|
||||
*/
|
||||
track->num_stack_entries = stack_trace_save(track->stack_entries, KFENCE_STACK_DEPTH, 1);
|
||||
track->pid = task_pid_nr(current);
|
||||
track->cpu = raw_smp_processor_id();
|
||||
track->ts_nsec = local_clock(); /* Same source as printk timestamps. */
|
||||
|
||||
/*
|
||||
* Pairs with READ_ONCE() in
|
||||
|
||||
@@ -36,6 +36,8 @@ enum kfence_object_state {
|
||||
/* Alloc/free tracking information. */
|
||||
struct kfence_track {
|
||||
pid_t pid;
|
||||
int cpu;
|
||||
u64 ts_nsec;
|
||||
int num_stack_entries;
|
||||
unsigned long stack_entries[KFENCE_STACK_DEPTH];
|
||||
};
|
||||
|
||||
@@ -800,6 +800,9 @@ static int test_init(struct kunit *test)
|
||||
unsigned long flags;
|
||||
int i;
|
||||
|
||||
if (!__kfence_pool)
|
||||
return -EINVAL;
|
||||
|
||||
spin_lock_irqsave(&observed.lock, flags);
|
||||
for (i = 0; i < ARRAY_SIZE(observed.lines); i++)
|
||||
observed.lines[i][0] = '\0';
|
||||
|
||||
+13
-6
@@ -9,6 +9,7 @@
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/lockdep.h>
|
||||
#include <linux/math.h>
|
||||
#include <linux/printk.h>
|
||||
#include <linux/sched/debug.h>
|
||||
#include <linux/seq_file.h>
|
||||
@@ -100,6 +101,13 @@ static void kfence_print_stack(struct seq_file *seq, const struct kfence_metadat
|
||||
bool show_alloc)
|
||||
{
|
||||
const struct kfence_track *track = show_alloc ? &meta->alloc_track : &meta->free_track;
|
||||
u64 ts_sec = track->ts_nsec;
|
||||
unsigned long rem_nsec = do_div(ts_sec, NSEC_PER_SEC);
|
||||
|
||||
/* Timestamp matches printk timestamp format. */
|
||||
seq_con_printf(seq, "%s by task %d on cpu %d at %lu.%06lus:\n",
|
||||
show_alloc ? "allocated" : "freed", track->pid,
|
||||
track->cpu, (unsigned long)ts_sec, rem_nsec / 1000);
|
||||
|
||||
if (track->num_stack_entries) {
|
||||
/* Skip allocation/free internals stack. */
|
||||
@@ -126,15 +134,14 @@ void kfence_print_object(struct seq_file *seq, const struct kfence_metadata *met
|
||||
return;
|
||||
}
|
||||
|
||||
seq_con_printf(seq,
|
||||
"kfence-#%td [0x%p-0x%p"
|
||||
", size=%d, cache=%s] allocated by task %d:\n",
|
||||
meta - kfence_metadata, (void *)start, (void *)(start + size - 1), size,
|
||||
(cache && cache->name) ? cache->name : "<destroyed>", meta->alloc_track.pid);
|
||||
seq_con_printf(seq, "kfence-#%td: 0x%p-0x%p, size=%d, cache=%s\n\n",
|
||||
meta - kfence_metadata, (void *)start, (void *)(start + size - 1),
|
||||
size, (cache && cache->name) ? cache->name : "<destroyed>");
|
||||
|
||||
kfence_print_stack(seq, meta, true);
|
||||
|
||||
if (meta->state == KFENCE_OBJECT_FREED) {
|
||||
seq_con_printf(seq, "\nfreed by task %d:\n", meta->free_track.pid);
|
||||
seq_con_printf(seq, "\n");
|
||||
kfence_print_stack(seq, meta, false);
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -598,7 +598,7 @@ static struct kmemleak_object *create_object(unsigned long ptr, size_t size,
|
||||
object->checksum = 0;
|
||||
|
||||
/* task information */
|
||||
if (in_irq()) {
|
||||
if (in_hardirq()) {
|
||||
object->pid = 0;
|
||||
strncpy(object->comm, "hardirq", sizeof(object->comm));
|
||||
} else if (in_serving_softirq()) {
|
||||
|
||||
+341
-33
@@ -52,6 +52,73 @@ module_param(memmap_on_memory, bool, 0444);
|
||||
MODULE_PARM_DESC(memmap_on_memory, "Enable memmap on memory for memory hotplug");
|
||||
#endif
|
||||
|
||||
enum {
|
||||
ONLINE_POLICY_CONTIG_ZONES = 0,
|
||||
ONLINE_POLICY_AUTO_MOVABLE,
|
||||
};
|
||||
|
||||
const char *online_policy_to_str[] = {
|
||||
[ONLINE_POLICY_CONTIG_ZONES] = "contig-zones",
|
||||
[ONLINE_POLICY_AUTO_MOVABLE] = "auto-movable",
|
||||
};
|
||||
|
||||
static int set_online_policy(const char *val, const struct kernel_param *kp)
|
||||
{
|
||||
int ret = sysfs_match_string(online_policy_to_str, val);
|
||||
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
*((int *)kp->arg) = ret;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int get_online_policy(char *buffer, const struct kernel_param *kp)
|
||||
{
|
||||
return sprintf(buffer, "%s\n", online_policy_to_str[*((int *)kp->arg)]);
|
||||
}
|
||||
|
||||
/*
|
||||
* memory_hotplug.online_policy: configure online behavior when onlining without
|
||||
* specifying a zone (MMOP_ONLINE)
|
||||
*
|
||||
* "contig-zones": keep zone contiguous
|
||||
* "auto-movable": online memory to ZONE_MOVABLE if the configuration
|
||||
* (auto_movable_ratio, auto_movable_numa_aware) allows for it
|
||||
*/
|
||||
static int online_policy __read_mostly = ONLINE_POLICY_CONTIG_ZONES;
|
||||
static const struct kernel_param_ops online_policy_ops = {
|
||||
.set = set_online_policy,
|
||||
.get = get_online_policy,
|
||||
};
|
||||
module_param_cb(online_policy, &online_policy_ops, &online_policy, 0644);
|
||||
MODULE_PARM_DESC(online_policy,
|
||||
"Set the online policy (\"contig-zones\", \"auto-movable\") "
|
||||
"Default: \"contig-zones\"");
|
||||
|
||||
/*
|
||||
* memory_hotplug.auto_movable_ratio: specify maximum MOVABLE:KERNEL ratio
|
||||
*
|
||||
* The ratio represent an upper limit and the kernel might decide to not
|
||||
* online some memory to ZONE_MOVABLE -- e.g., because hotplugged KERNEL memory
|
||||
* doesn't allow for more MOVABLE memory.
|
||||
*/
|
||||
static unsigned int auto_movable_ratio __read_mostly = 301;
|
||||
module_param(auto_movable_ratio, uint, 0644);
|
||||
MODULE_PARM_DESC(auto_movable_ratio,
|
||||
"Set the maximum ratio of MOVABLE:KERNEL memory in the system "
|
||||
"in percent for \"auto-movable\" online policy. Default: 301");
|
||||
|
||||
/*
|
||||
* memory_hotplug.auto_movable_numa_aware: consider numa node stats
|
||||
*/
|
||||
#ifdef CONFIG_NUMA
|
||||
static bool auto_movable_numa_aware __read_mostly = true;
|
||||
module_param(auto_movable_numa_aware, bool, 0644);
|
||||
MODULE_PARM_DESC(auto_movable_numa_aware,
|
||||
"Consider numa node stats in addition to global stats in "
|
||||
"\"auto-movable\" online policy. Default: true");
|
||||
#endif /* CONFIG_NUMA */
|
||||
|
||||
/*
|
||||
* online_page_callback contains pointer to current page onlining function.
|
||||
* Initially it is generic_online_page(). If it is required it could be
|
||||
@@ -410,15 +477,13 @@ void __ref remove_pfn_range_from_zone(struct zone *zone,
|
||||
sizeof(struct page) * cur_nr_pages);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_ZONE_DEVICE
|
||||
/*
|
||||
* Zone shrinking code cannot properly deal with ZONE_DEVICE. So
|
||||
* we will not try to shrink the zones - which is okay as
|
||||
* set_zone_contiguous() cannot deal with ZONE_DEVICE either way.
|
||||
*/
|
||||
if (zone_idx(zone) == ZONE_DEVICE)
|
||||
if (zone_is_zone_device(zone))
|
||||
return;
|
||||
#endif
|
||||
|
||||
clear_zone_contiguous(zone);
|
||||
|
||||
@@ -663,6 +728,109 @@ void __ref move_pfn_range_to_zone(struct zone *zone, unsigned long start_pfn,
|
||||
set_zone_contiguous(zone);
|
||||
}
|
||||
|
||||
struct auto_movable_stats {
|
||||
unsigned long kernel_early_pages;
|
||||
unsigned long movable_pages;
|
||||
};
|
||||
|
||||
static void auto_movable_stats_account_zone(struct auto_movable_stats *stats,
|
||||
struct zone *zone)
|
||||
{
|
||||
if (zone_idx(zone) == ZONE_MOVABLE) {
|
||||
stats->movable_pages += zone->present_pages;
|
||||
} else {
|
||||
stats->kernel_early_pages += zone->present_early_pages;
|
||||
#ifdef CONFIG_CMA
|
||||
/*
|
||||
* CMA pages (never on hotplugged memory) behave like
|
||||
* ZONE_MOVABLE.
|
||||
*/
|
||||
stats->movable_pages += zone->cma_pages;
|
||||
stats->kernel_early_pages -= zone->cma_pages;
|
||||
#endif /* CONFIG_CMA */
|
||||
}
|
||||
}
|
||||
struct auto_movable_group_stats {
|
||||
unsigned long movable_pages;
|
||||
unsigned long req_kernel_early_pages;
|
||||
};
|
||||
|
||||
static int auto_movable_stats_account_group(struct memory_group *group,
|
||||
void *arg)
|
||||
{
|
||||
const int ratio = READ_ONCE(auto_movable_ratio);
|
||||
struct auto_movable_group_stats *stats = arg;
|
||||
long pages;
|
||||
|
||||
/*
|
||||
* We don't support modifying the config while the auto-movable online
|
||||
* policy is already enabled. Just avoid the division by zero below.
|
||||
*/
|
||||
if (!ratio)
|
||||
return 0;
|
||||
|
||||
/*
|
||||
* Calculate how many early kernel pages this group requires to
|
||||
* satisfy the configured zone ratio.
|
||||
*/
|
||||
pages = group->present_movable_pages * 100 / ratio;
|
||||
pages -= group->present_kernel_pages;
|
||||
|
||||
if (pages > 0)
|
||||
stats->req_kernel_early_pages += pages;
|
||||
stats->movable_pages += group->present_movable_pages;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool auto_movable_can_online_movable(int nid, struct memory_group *group,
|
||||
unsigned long nr_pages)
|
||||
{
|
||||
unsigned long kernel_early_pages, movable_pages;
|
||||
struct auto_movable_group_stats group_stats = {};
|
||||
struct auto_movable_stats stats = {};
|
||||
pg_data_t *pgdat = NODE_DATA(nid);
|
||||
struct zone *zone;
|
||||
int i;
|
||||
|
||||
/* Walk all relevant zones and collect MOVABLE vs. KERNEL stats. */
|
||||
if (nid == NUMA_NO_NODE) {
|
||||
/* TODO: cache values */
|
||||
for_each_populated_zone(zone)
|
||||
auto_movable_stats_account_zone(&stats, zone);
|
||||
} else {
|
||||
for (i = 0; i < MAX_NR_ZONES; i++) {
|
||||
zone = pgdat->node_zones + i;
|
||||
if (populated_zone(zone))
|
||||
auto_movable_stats_account_zone(&stats, zone);
|
||||
}
|
||||
}
|
||||
|
||||
kernel_early_pages = stats.kernel_early_pages;
|
||||
movable_pages = stats.movable_pages;
|
||||
|
||||
/*
|
||||
* Kernel memory inside dynamic memory group allows for more MOVABLE
|
||||
* memory within the same group. Remove the effect of all but the
|
||||
* current group from the stats.
|
||||
*/
|
||||
walk_dynamic_memory_groups(nid, auto_movable_stats_account_group,
|
||||
group, &group_stats);
|
||||
if (kernel_early_pages <= group_stats.req_kernel_early_pages)
|
||||
return false;
|
||||
kernel_early_pages -= group_stats.req_kernel_early_pages;
|
||||
movable_pages -= group_stats.movable_pages;
|
||||
|
||||
if (group && group->is_dynamic)
|
||||
kernel_early_pages += group->present_kernel_pages;
|
||||
|
||||
/*
|
||||
* Test if we could online the given number of pages to ZONE_MOVABLE
|
||||
* and still stay in the configured ratio.
|
||||
*/
|
||||
movable_pages += nr_pages;
|
||||
return movable_pages <= (auto_movable_ratio * kernel_early_pages) / 100;
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns a default kernel memory zone for the given pfn range.
|
||||
* If no kernel zone covers this pfn range it will automatically go
|
||||
@@ -684,6 +852,117 @@ static struct zone *default_kernel_zone_for_pfn(int nid, unsigned long start_pfn
|
||||
return &pgdat->node_zones[ZONE_NORMAL];
|
||||
}
|
||||
|
||||
/*
|
||||
* Determine to which zone to online memory dynamically based on user
|
||||
* configuration and system stats. We care about the following ratio:
|
||||
*
|
||||
* MOVABLE : KERNEL
|
||||
*
|
||||
* Whereby MOVABLE is memory in ZONE_MOVABLE and KERNEL is memory in
|
||||
* one of the kernel zones. CMA pages inside one of the kernel zones really
|
||||
* behaves like ZONE_MOVABLE, so we treat them accordingly.
|
||||
*
|
||||
* We don't allow for hotplugged memory in a KERNEL zone to increase the
|
||||
* amount of MOVABLE memory we can have, so we end up with:
|
||||
*
|
||||
* MOVABLE : KERNEL_EARLY
|
||||
*
|
||||
* Whereby KERNEL_EARLY is memory in one of the kernel zones, available sinze
|
||||
* boot. We base our calculation on KERNEL_EARLY internally, because:
|
||||
*
|
||||
* a) Hotplugged memory in one of the kernel zones can sometimes still get
|
||||
* hotunplugged, especially when hot(un)plugging individual memory blocks.
|
||||
* There is no coordination across memory devices, therefore "automatic"
|
||||
* hotunplugging, as implemented in hypervisors, could result in zone
|
||||
* imbalances.
|
||||
* b) Early/boot memory in one of the kernel zones can usually not get
|
||||
* hotunplugged again (e.g., no firmware interface to unplug, fragmented
|
||||
* with unmovable allocations). While there are corner cases where it might
|
||||
* still work, it is barely relevant in practice.
|
||||
*
|
||||
* Exceptions are dynamic memory groups, which allow for more MOVABLE
|
||||
* memory within the same memory group -- because in that case, there is
|
||||
* coordination within the single memory device managed by a single driver.
|
||||
*
|
||||
* We rely on "present pages" instead of "managed pages", as the latter is
|
||||
* highly unreliable and dynamic in virtualized environments, and does not
|
||||
* consider boot time allocations. For example, memory ballooning adjusts the
|
||||
* managed pages when inflating/deflating the balloon, and balloon compaction
|
||||
* can even migrate inflated pages between zones.
|
||||
*
|
||||
* Using "present pages" is better but some things to keep in mind are:
|
||||
*
|
||||
* a) Some memblock allocations, such as for the crashkernel area, are
|
||||
* effectively unused by the kernel, yet they account to "present pages".
|
||||
* Fortunately, these allocations are comparatively small in relevant setups
|
||||
* (e.g., fraction of system memory).
|
||||
* b) Some hotplugged memory blocks in virtualized environments, esecially
|
||||
* hotplugged by virtio-mem, look like they are completely present, however,
|
||||
* only parts of the memory block are actually currently usable.
|
||||
* "present pages" is an upper limit that can get reached at runtime. As
|
||||
* we base our calculations on KERNEL_EARLY, this is not an issue.
|
||||
*/
|
||||
static struct zone *auto_movable_zone_for_pfn(int nid,
|
||||
struct memory_group *group,
|
||||
unsigned long pfn,
|
||||
unsigned long nr_pages)
|
||||
{
|
||||
unsigned long online_pages = 0, max_pages, end_pfn;
|
||||
struct page *page;
|
||||
|
||||
if (!auto_movable_ratio)
|
||||
goto kernel_zone;
|
||||
|
||||
if (group && !group->is_dynamic) {
|
||||
max_pages = group->s.max_pages;
|
||||
online_pages = group->present_movable_pages;
|
||||
|
||||
/* If anything is !MOVABLE online the rest !MOVABLE. */
|
||||
if (group->present_kernel_pages)
|
||||
goto kernel_zone;
|
||||
} else if (!group || group->d.unit_pages == nr_pages) {
|
||||
max_pages = nr_pages;
|
||||
} else {
|
||||
max_pages = group->d.unit_pages;
|
||||
/*
|
||||
* Take a look at all online sections in the current unit.
|
||||
* We can safely assume that all pages within a section belong
|
||||
* to the same zone, because dynamic memory groups only deal
|
||||
* with hotplugged memory.
|
||||
*/
|
||||
pfn = ALIGN_DOWN(pfn, group->d.unit_pages);
|
||||
end_pfn = pfn + group->d.unit_pages;
|
||||
for (; pfn < end_pfn; pfn += PAGES_PER_SECTION) {
|
||||
page = pfn_to_online_page(pfn);
|
||||
if (!page)
|
||||
continue;
|
||||
/* If anything is !MOVABLE online the rest !MOVABLE. */
|
||||
if (page_zonenum(page) != ZONE_MOVABLE)
|
||||
goto kernel_zone;
|
||||
online_pages += PAGES_PER_SECTION;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Online MOVABLE if we could *currently* online all remaining parts
|
||||
* MOVABLE. We expect to (add+) online them immediately next, so if
|
||||
* nobody interferes, all will be MOVABLE if possible.
|
||||
*/
|
||||
nr_pages = max_pages - online_pages;
|
||||
if (!auto_movable_can_online_movable(NUMA_NO_NODE, group, nr_pages))
|
||||
goto kernel_zone;
|
||||
|
||||
#ifdef CONFIG_NUMA
|
||||
if (auto_movable_numa_aware &&
|
||||
!auto_movable_can_online_movable(nid, group, nr_pages))
|
||||
goto kernel_zone;
|
||||
#endif /* CONFIG_NUMA */
|
||||
|
||||
return &NODE_DATA(nid)->node_zones[ZONE_MOVABLE];
|
||||
kernel_zone:
|
||||
return default_kernel_zone_for_pfn(nid, pfn, nr_pages);
|
||||
}
|
||||
|
||||
static inline struct zone *default_zone_for_pfn(int nid, unsigned long start_pfn,
|
||||
unsigned long nr_pages)
|
||||
{
|
||||
@@ -708,7 +987,8 @@ static inline struct zone *default_zone_for_pfn(int nid, unsigned long start_pfn
|
||||
return movable_node_enabled ? movable_zone : kernel_zone;
|
||||
}
|
||||
|
||||
struct zone *zone_for_pfn_range(int online_type, int nid, unsigned start_pfn,
|
||||
struct zone *zone_for_pfn_range(int online_type, int nid,
|
||||
struct memory_group *group, unsigned long start_pfn,
|
||||
unsigned long nr_pages)
|
||||
{
|
||||
if (online_type == MMOP_ONLINE_KERNEL)
|
||||
@@ -717,6 +997,9 @@ struct zone *zone_for_pfn_range(int online_type, int nid, unsigned start_pfn,
|
||||
if (online_type == MMOP_ONLINE_MOVABLE)
|
||||
return &NODE_DATA(nid)->node_zones[ZONE_MOVABLE];
|
||||
|
||||
if (online_policy == ONLINE_POLICY_AUTO_MOVABLE)
|
||||
return auto_movable_zone_for_pfn(nid, group, start_pfn, nr_pages);
|
||||
|
||||
return default_zone_for_pfn(nid, start_pfn, nr_pages);
|
||||
}
|
||||
|
||||
@@ -724,10 +1007,25 @@ struct zone *zone_for_pfn_range(int online_type, int nid, unsigned start_pfn,
|
||||
* This function should only be called by memory_block_{online,offline},
|
||||
* and {online,offline}_pages.
|
||||
*/
|
||||
void adjust_present_page_count(struct zone *zone, long nr_pages)
|
||||
void adjust_present_page_count(struct page *page, struct memory_group *group,
|
||||
long nr_pages)
|
||||
{
|
||||
struct zone *zone = page_zone(page);
|
||||
const bool movable = zone_idx(zone) == ZONE_MOVABLE;
|
||||
|
||||
/*
|
||||
* We only support onlining/offlining/adding/removing of complete
|
||||
* memory blocks; therefore, either all is either early or hotplugged.
|
||||
*/
|
||||
if (early_section(__pfn_to_section(page_to_pfn(page))))
|
||||
zone->present_early_pages += nr_pages;
|
||||
zone->present_pages += nr_pages;
|
||||
zone->zone_pgdat->node_present_pages += nr_pages;
|
||||
|
||||
if (group && movable)
|
||||
group->present_movable_pages += nr_pages;
|
||||
else if (group && !movable)
|
||||
group->present_kernel_pages += nr_pages;
|
||||
}
|
||||
|
||||
int mhp_init_memmap_on_memory(unsigned long pfn, unsigned long nr_pages,
|
||||
@@ -773,7 +1071,8 @@ void mhp_deinit_memmap_on_memory(unsigned long pfn, unsigned long nr_pages)
|
||||
kasan_remove_zero_shadow(__va(PFN_PHYS(pfn)), PFN_PHYS(nr_pages));
|
||||
}
|
||||
|
||||
int __ref online_pages(unsigned long pfn, unsigned long nr_pages, struct zone *zone)
|
||||
int __ref online_pages(unsigned long pfn, unsigned long nr_pages,
|
||||
struct zone *zone, struct memory_group *group)
|
||||
{
|
||||
unsigned long flags;
|
||||
int need_zonelists_rebuild = 0;
|
||||
@@ -826,7 +1125,7 @@ int __ref online_pages(unsigned long pfn, unsigned long nr_pages, struct zone *z
|
||||
}
|
||||
|
||||
online_pages_range(pfn, nr_pages);
|
||||
adjust_present_page_count(zone, nr_pages);
|
||||
adjust_present_page_count(pfn_to_page(pfn), group, nr_pages);
|
||||
|
||||
node_states_set_node(nid, &arg);
|
||||
if (need_zonelists_rebuild)
|
||||
@@ -1059,6 +1358,7 @@ int __ref add_memory_resource(int nid, struct resource *res, mhp_t mhp_flags)
|
||||
{
|
||||
struct mhp_params params = { .pgprot = pgprot_mhp(PAGE_KERNEL) };
|
||||
struct vmem_altmap mhp_altmap = {};
|
||||
struct memory_group *group = NULL;
|
||||
u64 start, size;
|
||||
bool new_node = false;
|
||||
int ret;
|
||||
@@ -1070,6 +1370,13 @@ int __ref add_memory_resource(int nid, struct resource *res, mhp_t mhp_flags)
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (mhp_flags & MHP_NID_IS_MGID) {
|
||||
group = memory_group_find_by_id(nid);
|
||||
if (!group)
|
||||
return -EINVAL;
|
||||
nid = group->nid;
|
||||
}
|
||||
|
||||
if (!node_possible(nid)) {
|
||||
WARN(1, "node %d was absent from the node_possible_map\n", nid);
|
||||
return -EINVAL;
|
||||
@@ -1104,9 +1411,10 @@ int __ref add_memory_resource(int nid, struct resource *res, mhp_t mhp_flags)
|
||||
goto error;
|
||||
|
||||
/* create memory block devices after memory was added */
|
||||
ret = create_memory_block_devices(start, size, mhp_altmap.alloc);
|
||||
ret = create_memory_block_devices(start, size, mhp_altmap.alloc,
|
||||
group);
|
||||
if (ret) {
|
||||
arch_remove_memory(nid, start, size, NULL);
|
||||
arch_remove_memory(start, size, NULL);
|
||||
goto error;
|
||||
}
|
||||
|
||||
@@ -1298,7 +1606,7 @@ struct zone *test_pages_in_a_zone(unsigned long start_pfn,
|
||||
unsigned long pfn, sec_end_pfn;
|
||||
struct zone *zone = NULL;
|
||||
struct page *page;
|
||||
int i;
|
||||
|
||||
for (pfn = start_pfn, sec_end_pfn = SECTION_ALIGN_UP(start_pfn + 1);
|
||||
pfn < end_pfn;
|
||||
pfn = sec_end_pfn, sec_end_pfn += PAGES_PER_SECTION) {
|
||||
@@ -1307,17 +1615,10 @@ struct zone *test_pages_in_a_zone(unsigned long start_pfn,
|
||||
continue;
|
||||
for (; pfn < sec_end_pfn && pfn < end_pfn;
|
||||
pfn += MAX_ORDER_NR_PAGES) {
|
||||
i = 0;
|
||||
/* This is just a CONFIG_HOLES_IN_ZONE check.*/
|
||||
while ((i < MAX_ORDER_NR_PAGES) &&
|
||||
!pfn_valid_within(pfn + i))
|
||||
i++;
|
||||
if (i == MAX_ORDER_NR_PAGES || pfn + i >= end_pfn)
|
||||
continue;
|
||||
/* Check if we got outside of the zone */
|
||||
if (zone && !zone_spans_pfn(zone, pfn + i))
|
||||
if (zone && !zone_spans_pfn(zone, pfn))
|
||||
return NULL;
|
||||
page = pfn_to_page(pfn + i);
|
||||
page = pfn_to_page(pfn);
|
||||
if (zone && page_zone(page) != zone)
|
||||
return NULL;
|
||||
zone = page_zone(page);
|
||||
@@ -1568,7 +1869,8 @@ static int count_system_ram_pages_cb(unsigned long start_pfn,
|
||||
return 0;
|
||||
}
|
||||
|
||||
int __ref offline_pages(unsigned long start_pfn, unsigned long nr_pages)
|
||||
int __ref offline_pages(unsigned long start_pfn, unsigned long nr_pages,
|
||||
struct memory_group *group)
|
||||
{
|
||||
const unsigned long end_pfn = start_pfn + nr_pages;
|
||||
unsigned long pfn, system_ram_pages = 0;
|
||||
@@ -1704,7 +2006,7 @@ int __ref offline_pages(unsigned long start_pfn, unsigned long nr_pages)
|
||||
|
||||
/* removal success */
|
||||
adjust_managed_page_count(pfn_to_page(start_pfn), -nr_pages);
|
||||
adjust_present_page_count(zone, -nr_pages);
|
||||
adjust_present_page_count(pfn_to_page(start_pfn), group, -nr_pages);
|
||||
|
||||
/* reinitialise watermarks and update pcp limits */
|
||||
init_per_zone_wmark_min();
|
||||
@@ -1746,7 +2048,9 @@ failed_removal:
|
||||
static int check_memblock_offlined_cb(struct memory_block *mem, void *arg)
|
||||
{
|
||||
int ret = !is_memblock_offlined(mem);
|
||||
int *nid = arg;
|
||||
|
||||
*nid = mem->nid;
|
||||
if (unlikely(ret)) {
|
||||
phys_addr_t beginpa, endpa;
|
||||
|
||||
@@ -1839,12 +2143,12 @@ void try_offline_node(int nid)
|
||||
}
|
||||
EXPORT_SYMBOL(try_offline_node);
|
||||
|
||||
static int __ref try_remove_memory(int nid, u64 start, u64 size)
|
||||
static int __ref try_remove_memory(u64 start, u64 size)
|
||||
{
|
||||
int rc = 0;
|
||||
struct vmem_altmap mhp_altmap = {};
|
||||
struct vmem_altmap *altmap = NULL;
|
||||
unsigned long nr_vmemmap_pages;
|
||||
int rc = 0, nid = NUMA_NO_NODE;
|
||||
|
||||
BUG_ON(check_hotplug_memory_range(start, size));
|
||||
|
||||
@@ -1852,8 +2156,12 @@ static int __ref try_remove_memory(int nid, u64 start, u64 size)
|
||||
* All memory blocks must be offlined before removing memory. Check
|
||||
* whether all memory blocks in question are offline and return error
|
||||
* if this is not the case.
|
||||
*
|
||||
* While at it, determine the nid. Note that if we'd have mixed nodes,
|
||||
* we'd only try to offline the last determined one -- which is good
|
||||
* enough for the cases we care about.
|
||||
*/
|
||||
rc = walk_memory_blocks(start, size, NULL, check_memblock_offlined_cb);
|
||||
rc = walk_memory_blocks(start, size, &nid, check_memblock_offlined_cb);
|
||||
if (rc)
|
||||
return rc;
|
||||
|
||||
@@ -1893,7 +2201,7 @@ static int __ref try_remove_memory(int nid, u64 start, u64 size)
|
||||
|
||||
mem_hotplug_begin();
|
||||
|
||||
arch_remove_memory(nid, start, size, altmap);
|
||||
arch_remove_memory(start, size, altmap);
|
||||
|
||||
if (IS_ENABLED(CONFIG_ARCH_KEEP_MEMBLOCK)) {
|
||||
memblock_free(start, size);
|
||||
@@ -1902,7 +2210,8 @@ static int __ref try_remove_memory(int nid, u64 start, u64 size)
|
||||
|
||||
release_mem_region_adjustable(start, size);
|
||||
|
||||
try_offline_node(nid);
|
||||
if (nid != NUMA_NO_NODE)
|
||||
try_offline_node(nid);
|
||||
|
||||
mem_hotplug_done();
|
||||
return 0;
|
||||
@@ -1910,7 +2219,6 @@ static int __ref try_remove_memory(int nid, u64 start, u64 size)
|
||||
|
||||
/**
|
||||
* __remove_memory - Remove memory if every memory block is offline
|
||||
* @nid: the node ID
|
||||
* @start: physical address of the region to remove
|
||||
* @size: size of the region to remove
|
||||
*
|
||||
@@ -1918,14 +2226,14 @@ static int __ref try_remove_memory(int nid, u64 start, u64 size)
|
||||
* and online/offline operations before this call, as required by
|
||||
* try_offline_node().
|
||||
*/
|
||||
void __remove_memory(int nid, u64 start, u64 size)
|
||||
void __remove_memory(u64 start, u64 size)
|
||||
{
|
||||
|
||||
/*
|
||||
* trigger BUG() if some memory is not offlined prior to calling this
|
||||
* function
|
||||
*/
|
||||
if (try_remove_memory(nid, start, size))
|
||||
if (try_remove_memory(start, size))
|
||||
BUG();
|
||||
}
|
||||
|
||||
@@ -1933,12 +2241,12 @@ void __remove_memory(int nid, u64 start, u64 size)
|
||||
* Remove memory if every memory block is offline, otherwise return -EBUSY is
|
||||
* some memory is not offline
|
||||
*/
|
||||
int remove_memory(int nid, u64 start, u64 size)
|
||||
int remove_memory(u64 start, u64 size)
|
||||
{
|
||||
int rc;
|
||||
|
||||
lock_device_hotplug();
|
||||
rc = try_remove_memory(nid, start, size);
|
||||
rc = try_remove_memory(start, size);
|
||||
unlock_device_hotplug();
|
||||
|
||||
return rc;
|
||||
@@ -1998,7 +2306,7 @@ static int try_reonline_memory_block(struct memory_block *mem, void *arg)
|
||||
* unplugged all memory (so it's no longer in use) and want to offline + remove
|
||||
* that memory.
|
||||
*/
|
||||
int offline_and_remove_memory(int nid, u64 start, u64 size)
|
||||
int offline_and_remove_memory(u64 start, u64 size)
|
||||
{
|
||||
const unsigned long mb_count = size / memory_block_size_bytes();
|
||||
uint8_t *online_types, *tmp;
|
||||
@@ -2034,7 +2342,7 @@ int offline_and_remove_memory(int nid, u64 start, u64 size)
|
||||
* This cannot fail as it cannot get onlined in the meantime.
|
||||
*/
|
||||
if (!rc) {
|
||||
rc = try_remove_memory(nid, start, size);
|
||||
rc = try_remove_memory(start, size);
|
||||
if (rc)
|
||||
pr_err("%s: Failed to remove memory: %d", __func__, rc);
|
||||
}
|
||||
|
||||
+1
-4
@@ -140,14 +140,11 @@ static void pageunmap_range(struct dev_pagemap *pgmap, int range_id)
|
||||
{
|
||||
struct range *range = &pgmap->ranges[range_id];
|
||||
struct page *first_page;
|
||||
int nid;
|
||||
|
||||
/* make sure to access a memmap that was actually initialized */
|
||||
first_page = pfn_to_page(pfn_first(pgmap, range_id));
|
||||
|
||||
/* pages are dead and unused, undo the arch mapping */
|
||||
nid = page_to_nid(first_page);
|
||||
|
||||
mem_hotplug_begin();
|
||||
remove_pfn_range_from_zone(page_zone(first_page), PHYS_PFN(range->start),
|
||||
PHYS_PFN(range_len(range)));
|
||||
@@ -155,7 +152,7 @@ static void pageunmap_range(struct dev_pagemap *pgmap, int range_id)
|
||||
__remove_pages(PHYS_PFN(range->start),
|
||||
PHYS_PFN(range_len(range)), NULL);
|
||||
} else {
|
||||
arch_remove_memory(nid, range->start, range_len(range),
|
||||
arch_remove_memory(range->start, range_len(range),
|
||||
pgmap_altmap(pgmap));
|
||||
kasan_remove_zero_shadow(__va(range->start), range_len(range));
|
||||
}
|
||||
|
||||
+5
-22
@@ -594,8 +594,6 @@ static int page_outside_zone_boundaries(struct zone *zone, struct page *page)
|
||||
|
||||
static int page_is_consistent(struct zone *zone, struct page *page)
|
||||
{
|
||||
if (!pfn_valid_within(page_to_pfn(page)))
|
||||
return 0;
|
||||
if (zone != page_zone(page))
|
||||
return 0;
|
||||
|
||||
@@ -1025,16 +1023,12 @@ buddy_merge_likely(unsigned long pfn, unsigned long buddy_pfn,
|
||||
if (order >= MAX_ORDER - 2)
|
||||
return false;
|
||||
|
||||
if (!pfn_valid_within(buddy_pfn))
|
||||
return false;
|
||||
|
||||
combined_pfn = buddy_pfn & pfn;
|
||||
higher_page = page + (combined_pfn - pfn);
|
||||
buddy_pfn = __find_buddy_pfn(combined_pfn, order + 1);
|
||||
higher_buddy = higher_page + (buddy_pfn - combined_pfn);
|
||||
|
||||
return pfn_valid_within(buddy_pfn) &&
|
||||
page_is_buddy(higher_page, higher_buddy, order + 1);
|
||||
return page_is_buddy(higher_page, higher_buddy, order + 1);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -1095,8 +1089,6 @@ continue_merging:
|
||||
buddy_pfn = __find_buddy_pfn(pfn, order);
|
||||
buddy = page + (buddy_pfn - pfn);
|
||||
|
||||
if (!pfn_valid_within(buddy_pfn))
|
||||
goto done_merging;
|
||||
if (!page_is_buddy(page, buddy, order))
|
||||
goto done_merging;
|
||||
/*
|
||||
@@ -1754,9 +1746,7 @@ void __init memblock_free_pages(struct page *page, unsigned long pfn,
|
||||
/*
|
||||
* Check that the whole (or subset of) a pageblock given by the interval of
|
||||
* [start_pfn, end_pfn) is valid and within the same zone, before scanning it
|
||||
* with the migration of free compaction scanner. The scanners then need to
|
||||
* use only pfn_valid_within() check for arches that allow holes within
|
||||
* pageblocks.
|
||||
* with the migration of free compaction scanner.
|
||||
*
|
||||
* Return struct page pointer of start_pfn, or NULL if checks were not passed.
|
||||
*
|
||||
@@ -1872,8 +1862,6 @@ static inline void __init pgdat_init_report_one_done(void)
|
||||
*/
|
||||
static inline bool __init deferred_pfn_valid(unsigned long pfn)
|
||||
{
|
||||
if (!pfn_valid_within(pfn))
|
||||
return false;
|
||||
if (!(pfn & (pageblock_nr_pages - 1)) && !pfn_valid(pfn))
|
||||
return false;
|
||||
return true;
|
||||
@@ -2520,11 +2508,6 @@ static int move_freepages(struct zone *zone,
|
||||
int pages_moved = 0;
|
||||
|
||||
for (pfn = start_pfn; pfn <= end_pfn;) {
|
||||
if (!pfn_valid_within(pfn)) {
|
||||
pfn++;
|
||||
continue;
|
||||
}
|
||||
|
||||
page = pfn_to_page(pfn);
|
||||
if (!PageBuddy(page)) {
|
||||
/*
|
||||
@@ -7271,6 +7254,9 @@ static void __init calculate_node_totalpages(struct pglist_data *pgdat,
|
||||
zone->zone_start_pfn = 0;
|
||||
zone->spanned_pages = size;
|
||||
zone->present_pages = real_size;
|
||||
#if defined(CONFIG_MEMORY_HOTPLUG)
|
||||
zone->present_early_pages = real_size;
|
||||
#endif
|
||||
|
||||
totalpages += size;
|
||||
realtotalpages += real_size;
|
||||
@@ -8828,9 +8814,6 @@ struct page *has_unmovable_pages(struct zone *zone, struct page *page,
|
||||
}
|
||||
|
||||
for (; iter < pageblock_nr_pages - offset; iter++) {
|
||||
if (!pfn_valid_within(pfn + iter))
|
||||
continue;
|
||||
|
||||
page = pfn_to_page(pfn + iter);
|
||||
|
||||
/*
|
||||
|
||||
+11
-1
@@ -58,11 +58,21 @@
|
||||
* can utilize this callback to initialize the state of it correctly.
|
||||
*/
|
||||
|
||||
#if defined(CONFIG_PAGE_IDLE_FLAG) && !defined(CONFIG_64BIT)
|
||||
static bool need_page_idle(void)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
struct page_ext_operations page_idle_ops = {
|
||||
.need = need_page_idle,
|
||||
};
|
||||
#endif
|
||||
|
||||
static struct page_ext_operations *page_ext_ops[] = {
|
||||
#ifdef CONFIG_PAGE_OWNER
|
||||
&page_owner_ops,
|
||||
#endif
|
||||
#if defined(CONFIG_IDLE_PAGE_TRACKING) && !defined(CONFIG_64BIT)
|
||||
#if defined(CONFIG_PAGE_IDLE_FLAG) && !defined(CONFIG_64BIT)
|
||||
&page_idle_ops,
|
||||
#endif
|
||||
};
|
||||
|
||||
@@ -207,16 +207,6 @@ static const struct attribute_group page_idle_attr_group = {
|
||||
.name = "page_idle",
|
||||
};
|
||||
|
||||
#ifndef CONFIG_64BIT
|
||||
static bool need_page_idle(void)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
struct page_ext_operations page_idle_ops = {
|
||||
.need = need_page_idle,
|
||||
};
|
||||
#endif
|
||||
|
||||
static int __init page_idle_init(void)
|
||||
{
|
||||
int err;
|
||||
|
||||
+1
-6
@@ -93,8 +93,7 @@ static void unset_migratetype_isolate(struct page *page, unsigned migratetype)
|
||||
buddy_pfn = __find_buddy_pfn(pfn, order);
|
||||
buddy = page + (buddy_pfn - pfn);
|
||||
|
||||
if (pfn_valid_within(buddy_pfn) &&
|
||||
!is_migrate_isolate_page(buddy)) {
|
||||
if (!is_migrate_isolate_page(buddy)) {
|
||||
__isolate_free_page(page, order);
|
||||
isolated_page = true;
|
||||
}
|
||||
@@ -250,10 +249,6 @@ __test_page_isolated_in_pageblock(unsigned long pfn, unsigned long end_pfn,
|
||||
struct page *page;
|
||||
|
||||
while (pfn < end_pfn) {
|
||||
if (!pfn_valid_within(pfn)) {
|
||||
pfn++;
|
||||
continue;
|
||||
}
|
||||
page = pfn_to_page(pfn);
|
||||
if (PageBuddy(page))
|
||||
/*
|
||||
|
||||
+1
-13
@@ -276,9 +276,6 @@ void pagetypeinfo_showmixedcount_print(struct seq_file *m,
|
||||
pageblock_mt = get_pageblock_migratetype(page);
|
||||
|
||||
for (; pfn < block_end_pfn; pfn++) {
|
||||
if (!pfn_valid_within(pfn))
|
||||
continue;
|
||||
|
||||
/* The pageblock is online, no need to recheck. */
|
||||
page = pfn_to_page(pfn);
|
||||
|
||||
@@ -479,10 +476,6 @@ read_page_owner(struct file *file, char __user *buf, size_t count, loff_t *ppos)
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Check for holes within a MAX_ORDER area */
|
||||
if (!pfn_valid_within(pfn))
|
||||
continue;
|
||||
|
||||
page = pfn_to_page(pfn);
|
||||
if (PageBuddy(page)) {
|
||||
unsigned long freepage_order = buddy_order_unsafe(page);
|
||||
@@ -560,14 +553,9 @@ static void init_pages_in_zone(pg_data_t *pgdat, struct zone *zone)
|
||||
block_end_pfn = min(block_end_pfn, end_pfn);
|
||||
|
||||
for (; pfn < block_end_pfn; pfn++) {
|
||||
struct page *page;
|
||||
struct page *page = pfn_to_page(pfn);
|
||||
struct page_ext *page_ext;
|
||||
|
||||
if (!pfn_valid_within(pfn))
|
||||
continue;
|
||||
|
||||
page = pfn_to_page(pfn);
|
||||
|
||||
if (page_zone(page) != zone)
|
||||
continue;
|
||||
|
||||
|
||||
@@ -146,7 +146,6 @@ static unsigned int pcpu_high_unit_cpu __ro_after_init;
|
||||
|
||||
/* the address of the first chunk which starts with the kernel static area */
|
||||
void *pcpu_base_addr __ro_after_init;
|
||||
EXPORT_SYMBOL_GPL(pcpu_base_addr);
|
||||
|
||||
static const int *pcpu_unit_map __ro_after_init; /* cpu -> unit */
|
||||
const unsigned long *pcpu_unit_offsets __ro_after_init; /* cpu -> unit offset */
|
||||
|
||||
@@ -1231,11 +1231,13 @@ void page_add_file_rmap(struct page *page, bool compound)
|
||||
nr_pages);
|
||||
} else {
|
||||
if (PageTransCompound(page) && page_mapping(page)) {
|
||||
struct page *head = compound_head(page);
|
||||
|
||||
VM_WARN_ON_ONCE(!PageLocked(page));
|
||||
|
||||
SetPageDoubleMap(compound_head(page));
|
||||
SetPageDoubleMap(head);
|
||||
if (PageMlocked(page))
|
||||
clear_page_mlock(compound_head(page));
|
||||
clear_page_mlock(head);
|
||||
}
|
||||
if (!atomic_inc_and_test(&page->_mapcount))
|
||||
goto out;
|
||||
|
||||
+5
-4
@@ -18,6 +18,7 @@
|
||||
#include <linux/secretmem.h>
|
||||
#include <linux/set_memory.h>
|
||||
#include <linux/sched/signal.h>
|
||||
#include <linux/refcount.h>
|
||||
|
||||
#include <uapi/linux/magic.h>
|
||||
|
||||
@@ -40,11 +41,11 @@ module_param_named(enable, secretmem_enable, bool, 0400);
|
||||
MODULE_PARM_DESC(secretmem_enable,
|
||||
"Enable secretmem and memfd_secret(2) system call");
|
||||
|
||||
static atomic_t secretmem_users;
|
||||
static refcount_t secretmem_users;
|
||||
|
||||
bool secretmem_active(void)
|
||||
{
|
||||
return !!atomic_read(&secretmem_users);
|
||||
return !!refcount_read(&secretmem_users);
|
||||
}
|
||||
|
||||
static vm_fault_t secretmem_fault(struct vm_fault *vmf)
|
||||
@@ -103,7 +104,7 @@ static const struct vm_operations_struct secretmem_vm_ops = {
|
||||
|
||||
static int secretmem_release(struct inode *inode, struct file *file)
|
||||
{
|
||||
atomic_dec(&secretmem_users);
|
||||
refcount_dec(&secretmem_users);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -217,7 +218,7 @@ SYSCALL_DEFINE1(memfd_secret, unsigned int, flags)
|
||||
file->f_flags |= O_LARGEFILE;
|
||||
|
||||
fd_install(fd, file);
|
||||
atomic_inc(&secretmem_users);
|
||||
refcount_inc(&secretmem_users);
|
||||
return fd;
|
||||
|
||||
err_put_fd:
|
||||
|
||||
+17
-5
@@ -44,6 +44,19 @@
|
||||
#include "internal.h"
|
||||
#include "pgalloc-track.h"
|
||||
|
||||
#ifdef CONFIG_HAVE_ARCH_HUGE_VMAP
|
||||
static unsigned int __ro_after_init ioremap_max_page_shift = BITS_PER_LONG - 1;
|
||||
|
||||
static int __init set_nohugeiomap(char *str)
|
||||
{
|
||||
ioremap_max_page_shift = PAGE_SHIFT;
|
||||
return 0;
|
||||
}
|
||||
early_param("nohugeiomap", set_nohugeiomap);
|
||||
#else /* CONFIG_HAVE_ARCH_HUGE_VMAP */
|
||||
static const unsigned int ioremap_max_page_shift = PAGE_SHIFT;
|
||||
#endif /* CONFIG_HAVE_ARCH_HUGE_VMAP */
|
||||
|
||||
#ifdef CONFIG_HAVE_ARCH_HUGE_VMALLOC
|
||||
static bool __ro_after_init vmap_allow_huge = true;
|
||||
|
||||
@@ -298,15 +311,14 @@ static int vmap_range_noflush(unsigned long addr, unsigned long end,
|
||||
return err;
|
||||
}
|
||||
|
||||
int vmap_range(unsigned long addr, unsigned long end,
|
||||
phys_addr_t phys_addr, pgprot_t prot,
|
||||
unsigned int max_page_shift)
|
||||
int ioremap_page_range(unsigned long addr, unsigned long end,
|
||||
phys_addr_t phys_addr, pgprot_t prot)
|
||||
{
|
||||
int err;
|
||||
|
||||
err = vmap_range_noflush(addr, end, phys_addr, prot, max_page_shift);
|
||||
err = vmap_range_noflush(addr, end, phys_addr, pgprot_nx(prot),
|
||||
ioremap_max_page_shift);
|
||||
flush_cache_vmap(addr, end);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -249,7 +249,7 @@ void workingset_age_nonresident(struct lruvec *lruvec, unsigned long nr_pages)
|
||||
* @target_memcg: the cgroup that is causing the reclaim
|
||||
* @page: the page being evicted
|
||||
*
|
||||
* Returns a shadow entry to be stored in @page->mapping->i_pages in place
|
||||
* Return: a shadow entry to be stored in @page->mapping->i_pages in place
|
||||
* of the evicted @page so that a later refault can be detected.
|
||||
*/
|
||||
void *workingset_eviction(struct page *page, struct mem_cgroup *target_memcg)
|
||||
|
||||
Reference in New Issue
Block a user