* [PATCH v2 0/2] mm/ksm: keep smart scan from losing duplicate pages
@ 2026-11-05 9:36 Qiliang Yuan
2026-11-05 9:36 ` [PATCH v2 1/2] mm/ksm: align smart scan skips with the scan number Qiliang Yuan
2026-11-05 9:36 ` [PATCH v2 2/2] selftests/mm: cover merging KSM pages aged by smart scan Qiliang Yuan
0 siblings, 2 replies; 5+ messages in thread
From: Qiliang Yuan @ 2026-11-05 9:36 UTC (permalink / raw)
To: Andrew Morton, David Hildenbrand, Xu Xin, Chengming Zhou,
Stefan Roesch, Lorenzo Stoakes, Liam R. Howlett, Vlastimil Babka,
Mike Rapoport, Suren Baghdasaryan, Michal Hocko, Shuah Khan
Cc: linux-mm, linux-kernel, linux-kselftest, Qiliang Yuan
With smart scan, which is on by default, two identical pages that become
mergeable some scans apart can stay unmerged for as long as they live:
each page times its skipped scans on its own, and once both are old they
may never be looked at in the same scan, which merging through the
unstable tree requires. This was first noticed as intermittent failures
of ksm_tests "KSM NUMA merging" in run_vmtests.sh.
Patch 1 picks the skipped scans by the global scan number, so all old
pages are looked at together every eighth scan. Over 420 pairs made
mergeable 0 to 20 scans apart, 329 and 317 merged before on two boots
and all of them after, at the cost of 10-12% more ksmd time on memory
that never merges.
Patch 2 adds a ksm_functional_tests case that fails 20/20 without patch
1 and 0/20 with it, on both boots.
Signed-off-by: Qiliang Yuan <odys.yuan@gmail.com>
---
V1 -> V2:
- Rebase onto v7.4-rc1-132-g05441d697f7f and retest there
- Drop the ksm_tests -N -m 1 numbers, as it no longer fails on this base
without the fix
- Update the ksmd cost to 10-12%, measured on this base
v1: https://lore.kernel.org/r/20261016-bug-mm-ksm-smart-scan-align-skips-v1-0-2f91d76416be@gmail.com
---
Qiliang Yuan (2):
mm/ksm: align smart scan skips with the scan number
selftests/mm: cover merging KSM pages aged by smart scan
mm/ksm.c | 24 ++---
tools/testing/selftests/mm/ksm_functional_tests.c | 108 +++++++++++++++++++++-
2 files changed, 117 insertions(+), 15 deletions(-)
---
base-commit: 05441d697f7f9a927264f4058cbbc6a03dfcac9d
change-id: 20261016-bug-mm-ksm-smart-scan-align-skips-cd01e5a9889a
Best regards,
--
Qiliang Yuan <odys.yuan@gmail.com>
^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH v2 1/2] mm/ksm: align smart scan skips with the scan number
2026-11-05 9:36 [PATCH v2 0/2] mm/ksm: keep smart scan from losing duplicate pages Qiliang Yuan
@ 2026-11-05 9:36 ` Qiliang Yuan
2026-11-05 10:03 ` David Hildenbrand (Arm)
2026-11-05 9:36 ` [PATCH v2 2/2] selftests/mm: cover merging KSM pages aged by smart scan Qiliang Yuan
1 sibling, 1 reply; 5+ messages in thread
From: Qiliang Yuan @ 2026-11-05 9:36 UTC (permalink / raw)
To: Andrew Morton, David Hildenbrand, Xu Xin, Chengming Zhou,
Stefan Roesch, Lorenzo Stoakes, Liam R. Howlett, Vlastimil Babka,
Mike Rapoport, Suren Baghdasaryan, Michal Hocko, Shuah Khan
Cc: linux-mm, linux-kernel, linux-kselftest, Qiliang Yuan
Smart scan skips pages that KSM has failed to de-duplicate for a while.
Once a page is three scans old, its rmap_item counts down its own number
of scans to skip, 1, 2, 4 and then 8 as it ages, so an old page is looked
at every ninth scan.
The unstable tree is rebuilt on every scan, so two identical pages that
are not in the stable tree yet can only be merged in a scan that looks
at both. Each countdown runs from when its own page became mergeable, so
two pages that became mergeable at different times end up on different
phases and, once both are old, may never be looked at in the same scan
again. They then stay unmerged for as long as they live. Making a page
mergeable, waiting for 0 to 20 full scans before doing the same with an
identical page, 20 times for each wait, and checking the pair after
another 64 full scans shows this, on two boots per kernel:
before after
pairs merged (of 420) 329 / 317 420 / 420
64M of unique pages skipped 89.0% / 89.1% 87.5% / 87.5%
ksmd CPU time per scan 6.25 / 6.25 ms 6.88 / 7.03 ms
Pick the skipped scans by the global scan number instead, with periods
of 2, 4 and 8 scans. The periods being powers of two, every old page is
looked at on each scan that is a multiple of 8, whatever its age, so any
two pages meet at least every eighth scan. The longest period goes from
nine scans to eight, which costs 10-12% more ksmd time on memory that
never merges.
Fixes: 5e924ff54d08 ("mm/ksm: add "smart" page scanning mode")
Signed-off-by: Qiliang Yuan <odys.yuan@gmail.com>
---
mm/ksm.c | 24 ++++++++++--------------
1 file changed, 10 insertions(+), 14 deletions(-)
diff --git a/mm/ksm.c b/mm/ksm.c
index dbfb8f98b1892..a97d7563c2124 100644
--- a/mm/ksm.c
+++ b/mm/ksm.c
@@ -196,7 +196,6 @@ struct ksm_stable_node {
* @head: pointer to stable_node heading this list in the stable tree
* @hlist: link into hlist of rmap_items hanging off that stable_node
* @age: number of scan iterations since creation (unstable node)
- * @remaining_skips: how many scans to skip (unstable node)
* @linear_page_index: the original page's index before merged by KSM (stable node)
*/
struct ksm_rmap_item {
@@ -213,7 +212,6 @@ struct ksm_rmap_item {
struct {
unsigned int oldchecksum;
rmap_age_t age;
- rmap_age_t remaining_skips;
}; /* when unstable */
unsigned long linear_page_index; /* for reverse mapping, when stable */
};
@@ -2446,16 +2444,14 @@ static struct ksm_rmap_item *get_next_rmap_item(struct ksm_mm_slot *mm_slot,
}
/*
- * Calculate skip age for the ksm page age. The age determines how often
- * de-duplicating has already been tried unsuccessfully. If the age is
- * smaller, the scanning of this page is skipped for less scans.
+ * Calculate how often a page of the given ksm page age is scanned. The age
+ * determines how often de-duplicating has already been tried
+ * unsuccessfully. The older the page, the fewer scans look at it.
*
* @age: rmap_item age of page
*/
-static unsigned int skip_age(rmap_age_t age)
+static unsigned int scan_period(rmap_age_t age)
{
- if (age <= 3)
- return 1;
if (age <= 5)
return 2;
if (age <= 8)
@@ -2505,17 +2501,17 @@ static bool should_skip_rmap_item(struct folio *folio,
return false;
/*
- * Are we still allowed to skip? If not, then don't skip it
- * and determine how much more often we are allowed to skip next.
+ * The unstable tree is rebuilt on every scan, so two identical pages
+ * can only be merged if both are looked at in the same scan. Pick the
+ * scans by the global scan number rather than per page: the periods
+ * are powers of two, so pages of any age meet on every scan that is a
+ * multiple of the longest one.
*/
- if (!rmap_item->remaining_skips) {
- rmap_item->remaining_skips = skip_age(age);
+ if (!(ksm_scan.seqnr & (scan_period(age) - 1)))
return false;
- }
/* Skip this page */
ksm_pages_skipped++;
- rmap_item->remaining_skips--;
remove_rmap_item_from_tree(rmap_item);
return true;
}
--
2.43.0
^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH v2 2/2] selftests/mm: cover merging KSM pages aged by smart scan
2026-11-05 9:36 [PATCH v2 0/2] mm/ksm: keep smart scan from losing duplicate pages Qiliang Yuan
2026-11-05 9:36 ` [PATCH v2 1/2] mm/ksm: align smart scan skips with the scan number Qiliang Yuan
@ 2026-11-05 9:36 ` Qiliang Yuan
2026-11-05 10:03 ` David Hildenbrand (Arm)
1 sibling, 1 reply; 5+ messages in thread
From: Qiliang Yuan @ 2026-11-05 9:36 UTC (permalink / raw)
To: Andrew Morton, David Hildenbrand, Xu Xin, Chengming Zhou,
Stefan Roesch, Lorenzo Stoakes, Liam R. Howlett, Vlastimil Babka,
Mike Rapoport, Suren Baghdasaryan, Michal Hocko, Shuah Khan
Cc: linux-mm, linux-kernel, linux-kselftest, Qiliang Yuan
ksm_functional_tests checks merging and unmerging with ksmd's default
settings, but nothing there exercises smart scan, which skips pages KSM
has failed to merge for a while.
Smart scan used to time those skips per page, so two identical pages
that became mergeable some scans apart could end up never being looked
at in the same scan, and were never merged. "mm/ksm: align smart scan
skips with the scan number" fixed that.
Add a case that lets sixteen unique pages age for a dozen scans, then
makes a duplicate of each mergeable one scan apart, and expects every
pair to be merged 32 scans later:
kernel failures, two boots
before the fix 20/20, 20/20
with the fix 0/20, 0/20
Signed-off-by: Qiliang Yuan <odys.yuan@gmail.com>
---
tools/testing/selftests/mm/ksm_functional_tests.c | 108 +++++++++++++++++++++-
1 file changed, 107 insertions(+), 1 deletion(-)
diff --git a/tools/testing/selftests/mm/ksm_functional_tests.c b/tools/testing/selftests/mm/ksm_functional_tests.c
index 31c06c72203fd..71d9b3cbdb8c1 100644
--- a/tools/testing/selftests/mm/ksm_functional_tests.c
+++ b/tools/testing/selftests/mm/ksm_functional_tests.c
@@ -695,6 +695,111 @@ static void test_fork_ksm_merging_page_count(void)
munmap(map, size);
}
+static int ksm_wait_full_scans(long nr)
+{
+ long start, cur;
+
+ start = ksm_get_full_scans();
+ if (start < 0)
+ return start;
+ do {
+ cur = ksm_get_full_scans();
+ if (cur < 0)
+ return cur;
+ } while (cur < start + nr);
+
+ return 0;
+}
+
+static void test_merge_aged_pairs(void)
+{
+ const unsigned int pairs = 16;
+ const unsigned long size = 2 * pairs * pagesize;
+ char *map, old_smart_scan = '1';
+ int smart_scan_fd;
+ unsigned int i;
+
+ ksft_print_msg("[RUN] %s\n", __func__);
+
+ smart_scan_fd = open("/sys/kernel/mm/ksm/smart_scan", O_RDWR);
+ if (smart_scan_fd < 0) {
+ ksft_test_result_skip("smart_scan not supported\n");
+ return;
+ }
+ if (pread(smart_scan_fd, &old_smart_scan, 1, 0) != 1 ||
+ pwrite(smart_scan_fd, "1", 1, 0) != 1) {
+ ksft_test_result_fail("Enabling smart_scan failed\n");
+ goto close_fd;
+ }
+
+ if (ksm_stop() < 0) {
+ ksft_test_result_fail("Disabling (unmerging) KSM failed\n");
+ goto restore;
+ }
+
+ map = mmap(NULL, size, PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANON, -1, 0);
+ if (map == MAP_FAILED) {
+ ksft_test_result_fail("mmap() failed\n");
+ goto restore;
+ }
+ if (madvise(map, size, MADV_NOHUGEPAGE) && errno != EINVAL) {
+ ksft_test_result_fail("MADV_NOHUGEPAGE failed\n");
+ goto unmap;
+ }
+
+ /*
+ * Page i of the first half and page i of the second half form a
+ * pair with content no other page shares.
+ */
+ for (i = 0; i < pairs; i++) {
+ memset(map + i * pagesize, i + 1, pagesize);
+ memset(map + (pairs + i) * pagesize, i + 1, pagesize);
+ }
+
+ /* Let the first page of each pair age without a duplicate. */
+ if (madvise(map, size / 2, MADV_MERGEABLE)) {
+ ksft_test_result_fail("MADV_MERGEABLE failed\n");
+ goto unmap;
+ }
+ if (ksm_start() < 0 || ksm_wait_full_scans(12) < 0) {
+ ksft_test_result_fail("Running KSM failed\n");
+ goto unmap;
+ }
+
+ /*
+ * Make the duplicates mergeable one scan apart so that each pair
+ * starts out with a different distance in scans.
+ */
+ for (i = 0; i < pairs; i++) {
+ if (madvise(map + (pairs + i) * pagesize, pagesize,
+ MADV_MERGEABLE)) {
+ ksft_test_result_fail("MADV_MERGEABLE failed\n");
+ goto unmap;
+ }
+ if (ksm_wait_full_scans(1) < 0) {
+ ksft_test_result_fail("Running KSM failed\n");
+ goto unmap;
+ }
+ }
+
+ if (ksm_wait_full_scans(32) < 0) {
+ ksft_test_result_fail("Running KSM failed\n");
+ goto unmap;
+ }
+
+ ksft_test_result(ksm_get_self_merging_pages() == 2 * pairs,
+ "Pages aged by smart scan got merged\n");
+unmap:
+ ksm_stop();
+ munmap(map, size);
+restore:
+ if (pwrite(smart_scan_fd, &old_smart_scan, 1, 0) != 1)
+ ksft_print_msg("Restoring smart_scan failed\n");
+close_fd:
+ close(smart_scan_fd);
+}
+
static void init_global_file_handles(void)
{
mem_fd = open("/proc/self/mem", O_RDWR);
@@ -720,7 +825,7 @@ static void init_global_file_handles(void)
int main(int argc, char **argv)
{
- unsigned int tests = 9;
+ unsigned int tests = 10;
int err;
if (argc > 1 && !strcmp(argv[1], FORK_EXEC_CHILD_PRG_NAME)) {
@@ -753,6 +858,7 @@ int main(int argc, char **argv)
test_prctl_fork_exec();
test_prctl_unmerge();
test_fork_ksm_merging_page_count();
+ test_merge_aged_pairs();
err = ksft_get_fail_cnt();
if (err)
--
2.43.0
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH v2 2/2] selftests/mm: cover merging KSM pages aged by smart scan
2026-11-05 9:36 ` [PATCH v2 2/2] selftests/mm: cover merging KSM pages aged by smart scan Qiliang Yuan
@ 2026-11-05 10:03 ` David Hildenbrand (Arm)
0 siblings, 0 replies; 5+ messages in thread
From: David Hildenbrand (Arm) @ 2026-11-05 10:03 UTC (permalink / raw)
To: Qiliang Yuan, Andrew Morton, Xu Xin, Chengming Zhou,
Stefan Roesch, Lorenzo Stoakes, Liam R. Howlett, Vlastimil Babka,
Mike Rapoport, Suren Baghdasaryan, Michal Hocko, Shuah Khan
Cc: linux-mm, linux-kernel, linux-kselftest
On 11/5/26 10:36, Qiliang Yuan wrote:
> ksm_functional_tests checks merging and unmerging with ksmd's default
> settings, but nothing there exercises smart scan, which skips pages KSM
> has failed to merge for a while.
>
> Smart scan used to time those skips per page, so two identical pages
> that became mergeable some scans apart could end up never being looked
> at in the same scan, and were never merged. "mm/ksm: align smart scan
> skips with the scan number" fixed that.
>
> Add a case that lets sixteen unique pages age for a dozen scans, then
> makes a duplicate of each mergeable one scan apart, and expects every
> pair to be merged 32 scans later:
>
> kernel failures, two boots
> before the fix 20/20, 20/20
> with the fix 0/20, 0/20
>
> Signed-off-by: Qiliang Yuan <odys.yuan@gmail.com>
> ---
> tools/testing/selftests/mm/ksm_functional_tests.c | 108 +++++++++++++++++++++-
> 1 file changed, 107 insertions(+), 1 deletion(-)
>
> diff --git a/tools/testing/selftests/mm/ksm_functional_tests.c b/tools/testing/selftests/mm/ksm_functional_tests.c
> index 31c06c72203fd..71d9b3cbdb8c1 100644
> --- a/tools/testing/selftests/mm/ksm_functional_tests.c
> +++ b/tools/testing/selftests/mm/ksm_functional_tests.c
> @@ -695,6 +695,111 @@ static void test_fork_ksm_merging_page_count(void)
> munmap(map, size);
> }
>
> +static int ksm_wait_full_scans(long nr)
> +{
> + long start, cur;
> +
> + start = ksm_get_full_scans();
> + if (start < 0)
> + return start;
> + do {
> + cur = ksm_get_full_scans();
> + if (cur < 0)
> + return cur;
> + } while (cur < start + nr);
> +
> + return 0;
> +}
> +
This looks a lot like the logic we have in ksm_start().
Likely that logic there should be factored out into a common helper.
> +static void test_merge_aged_pairs(void)
> +{
> + const unsigned int pairs = 16;
> + const unsigned long size = 2 * pairs * pagesize;
> + char *map, old_smart_scan = '1';
> + int smart_scan_fd;
> + unsigned int i;
> +
> + ksft_print_msg("[RUN] %s\n", __func__);
> +
> + smart_scan_fd = open("/sys/kernel/mm/ksm/smart_scan", O_RDWR);
> + if (smart_scan_fd < 0) {
> + ksft_test_result_skip("smart_scan not supported\n");
> + return;
> + }
> + if (pread(smart_scan_fd, &old_smart_scan, 1, 0) != 1 ||
> + pwrite(smart_scan_fd, "1", 1, 0) != 1) {
> + ksft_test_result_fail("Enabling smart_scan failed\n");
> + goto close_fd;
> + }
Can we have a helper to enable/disable smart scan? Likely belongs into utils as
well.
--
Cheers,
David
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH v2 1/2] mm/ksm: align smart scan skips with the scan number
2026-11-05 9:36 ` [PATCH v2 1/2] mm/ksm: align smart scan skips with the scan number Qiliang Yuan
@ 2026-11-05 10:03 ` David Hildenbrand (Arm)
0 siblings, 0 replies; 5+ messages in thread
From: David Hildenbrand (Arm) @ 2026-11-05 10:03 UTC (permalink / raw)
To: Qiliang Yuan, Andrew Morton, Xu Xin, Chengming Zhou,
Stefan Roesch, Lorenzo Stoakes, Liam R. Howlett, Vlastimil Babka,
Mike Rapoport, Suren Baghdasaryan, Michal Hocko, Shuah Khan
Cc: linux-mm, linux-kernel, linux-kselftest
On 11/5/26 10:36, Qiliang Yuan wrote:
> Smart scan skips pages that KSM has failed to de-duplicate for a while.
> Once a page is three scans old, its rmap_item counts down its own number
> of scans to skip, 1, 2, 4 and then 8 as it ages, so an old page is looked
> at every ninth scan.
>
> The unstable tree is rebuilt on every scan, so two identical pages that
> are not in the stable tree yet can only be merged in a scan that looks
> at both. Each countdown runs from when its own page became mergeable, so
> two pages that became mergeable at different times end up on different
> phases and, once both are old, may never be looked at in the same scan
> again. They then stay unmerged for as long as they live. Making a page
> mergeable, waiting for 0 to 20 full scans before doing the same with an
> identical page, 20 times for each wait, and checking the pair after
> another 64 full scans shows this, on two boots per kernel:
>
> before after
> pairs merged (of 420) 329 / 317 420 / 420
> 64M of unique pages skipped 89.0% / 89.1% 87.5% / 87.5%
> ksmd CPU time per scan 6.25 / 6.25 ms 6.88 / 7.03 ms
>
> Pick the skipped scans by the global scan number instead, with periods
> of 2, 4 and 8 scans. The periods being powers of two, every old page is
> looked at on each scan that is a multiple of 8, whatever its age, so any
> two pages meet at least every eighth scan. The longest period goes from
> nine scans to eight, which costs 10-12% more ksmd time on memory that
> never merges.
>
> Fixes: 5e924ff54d08 ("mm/ksm: add "smart" page scanning mode")
> Signed-off-by: Qiliang Yuan <odys.yuan@gmail.com>
> ---
Makes sense to me, without looking to closely into the details.
Maybe Stefan can give some input?
--
Cheers,
David
^ permalink raw reply [flat|nested] 5+ messages in thread
end of thread, other threads:[~2026-11-05 10:04 UTC | newest]
Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-11-05 9:36 [PATCH v2 0/2] mm/ksm: keep smart scan from losing duplicate pages Qiliang Yuan
2026-11-05 9:36 ` [PATCH v2 1/2] mm/ksm: align smart scan skips with the scan number Qiliang Yuan
2026-11-05 10:03 ` David Hildenbrand (Arm)
2026-11-05 9:36 ` [PATCH v2 2/2] selftests/mm: cover merging KSM pages aged by smart scan Qiliang Yuan
2026-11-05 10:03 ` David Hildenbrand (Arm)
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®