From: "Vlastimil Babka (SUSE)" <vbabka@kernel.org>
To: Thadeu Lima de Souza Cascardo <cascardo@igalia.com>
Cc: Johannes Weiner <hannes@cmpxchg.org>,
Suren Baghdasaryan <surenb@google.com>,
Michal Hocko <mhocko@suse.com>,
Brendan Jackman <brendan.jackman@linux.dev>,
Zi Yan <ziy@nvidia.com>, David Hildenbrand <david@kernel.org>,
Qi Zheng <qi.zheng@linux.dev>,
Shakeel Butt <shakeel.butt@linux.dev>,
Lorenzo Stoakes <ljs@kernel.org>,
Kairui Song <kasong@tencent.com>, Barry Song <baohua@kernel.org>,
Axel Rasmussen <axelrasmussen@google.com>,
Yuanchu Xie <yuanchu@google.com>, Wei Xu <weixugc@google.com>,
Baoquan He <baoquan.he@linux.dev>,
Baolin Wang <baolin.wang@linux.alibaba.com>,
JP Kobryn <jp.kobryn@linux.dev>,
Frank van der Linden <fvdl@google.com>,
Andrew Morton <akpm@linux-foundation.org>,
linux-mm@kvack.org, linux-kernel@vger.kernel.org
Subject: Re: [PATCH] mm, vmscan: make kswapd unreserve highatomic pageblocks
Date: Wed, 4 Nov 2026 22:17:00 +0100 [thread overview]
Message-ID: <963b9722-ca48-49f9-ac04-857d3d1526a2@kernel.org> (raw)
In-Reply-To: <auuPhqCcgZBt_lUR@quatroqueijos.cascardo.eti.br>
On 11/4/26 21:03, Thadeu Lima de Souza Cascardo wrote:
> On Wed, Nov 04, 2026 at 07:38:44PM +0100, Vlastimil Babka (SUSE) wrote:
>> A customer has reported excessive reclaim activity despite memory above
>> high watermarks. Analyzing support data (including sar log) revealed the
>> following scenario:
>>
>> - the system has almost all memory reserved as 1G hugepages for kvm
>> guests, and operates normally
>> - due to a spike of memory pressure from userspace, highatomic
>> reservations occur (probably due to e.g. concurrent network receive
>> activity)
>
> I think you might mean 6a204d4b14c9 ("mm/page_alloc: only set
> ALLOC_HIGHATOMIC for __GPF_HIGH allocations") as one of the fixes you
> mention below.
Indeed.
> In that case, GFP_NOWAIT (which is typical for network activity, AFAIR)
> would be one candidate that would produce such reservations. But I agree
> that if any other real highatomic allocation burst would happen, it might
> produce such reservations.
Ack.
>> - on one of the 8 NUMA nodes, this creates a situation where free pages
>> are above high watermark, but when __zone_watermark_unusable_free()
>> considers highatomic pageblocks, the result appears to be below the
>> low watermark
>> - pgdat_balanced() is false, kswapd hopelessly keeps trying to balance,
>> spending cpu time and disrupting the system. It has nothing to reclaim
>> because the node is filled with hugetlbfs hugepages.
>> - since the overal memory pressure is under control again and there are
>> no allocations restricted to the affected node, they are satisfied
>> from other nodes without going through direct reclaim
>> - since direct reclaim is the only code that calls
>> unreserve_highatomic_pageblock(), this never happens and nothing fixes
>> the situation
>>
>
> I am trying to understand what is going on here: so allocations on that
> node are only !__GFP_DIRECT_RECLAIM? That would explain the highatomic
> reservations in the presence of the bug just mentioned.
They are probably __GFP_DIRECT_RECLAIM, but if they only origin on a cpu
local to the affected node but can fallback to any other (which is the usual
case) and zone_reclaim_mode is disabled (by default it is), they will just
succeed on another node and not attempt the direct reclaim.
>> Note this was observed on a 6.4-based kernel and there have been recent
>> fixes upstream limiting the highatomic reservation side, but I don't
>> think they can prevent the same happening in mainline, only perhaps
>> lower the chances. Nothing seems to prevent the issue completely.
>>
>> For example, while we limit the amount of highatomic pageblocks to 1% of
>> the zone, this calculation does not subtract the amount of hugepages or
>> otherwise permanent allocations, so the highatomic pageblocks can end up
>> taking much higher share of actually reclaimable memory.
>>
>> The most targeted fix thus appears to be to allow kswapd perform the
>> highatomic pageblock unreserve to match direct reclaim. It's also
>> somewhat similar in principle to how kswapd reduces watermark boosting.
>>
>> Hook this highatomic unreserve to the moment we are increasing kswapd
>> reclaim priority, as that is when kswapd is struggling to make progress
>> and it should roughly match the case of direct reclaim failure.
>>
>
> I tried considering a different approach, making the allocation do the
> unreserve in the absence of __GFP_DIRECT_RECLAIM, but I don't see why not
> let kswapd do it.
>
> Regards.
> Cascardo.
>
>> The only caveat is that direct reclaim knows which migratetype it wants
>> to allocate and thus changes the highatomic pageblock to. Kswapd doesn't
>> track that, so use MIGRATE_UNMOVABLE. Highatomic allocations usually are
>> not movable, and this will avoid creating nominally movable pageblocks
>> polluted with unmovable pages, so it should be the least problematic
>> default.
>>
>> Signed-off-by: Vlastimil Babka (SUSE) <vbabka@kernel.org>
>> ---
>> We interrupt the regular stream of AI fixes by something a bit
>> different. RFC to get agreement on the approach. Haven't tried
>> reproducing the issue in mainline, but it seems quite clear to me from
>> the data I've seen.
>> ---
>> mm/page_alloc.c | 126 +++++++++++++++++++++++++++++---------------------------
>> mm/page_alloc.h | 3 ++
>> mm/vmscan.c | 16 ++++++-
>> 3 files changed, 83 insertions(+), 62 deletions(-)
>>
>> diff --git a/mm/page_alloc.c b/mm/page_alloc.c
>> index a8cac5e01066..785a13570788 100644
>> --- a/mm/page_alloc.c
>> +++ b/mm/page_alloc.c
>> @@ -3476,6 +3476,68 @@ static void reserve_highatomic_pageblock(struct page *page, int order,
>> }
>> }
>>
>> +bool unreserve_highatomic_pageblock_zone(struct zone *zone, int migratetype,
>> + bool force)
>> +{
>> + /*
>> + * Preserve at least one pageblock unless memory pressure
>> + * is really high.
>> + */
>> + if (!force && zone->nr_reserved_highatomic <= pageblock_nr_pages)
>> + return false;
>> +
>> + guard(spinlock_irqsave)(&zone->lock);
>> + for (int order = 0; order < NR_PAGE_ORDERS; order++) {
>> + struct free_area *area = &(zone->free_area[order]);
>> + unsigned long size;
>> + struct page *page;
>> + int ret;
>> +
>> + page = get_page_from_free_area(area, MIGRATE_HIGHATOMIC);
>> + if (!page)
>> + continue;
>> +
>> + size = max(pageblock_nr_pages, 1UL << order);
>> + /*
>> + * It should never happen but changes to locking could
>> + * inadvertently allow a per-cpu drain to add pages to
>> + * MIGRATE_HIGHATOMIC while unreserving so be safe and watch for
>> + * underflows.
>> + */
>> + if (WARN_ON_ONCE(size > zone->nr_reserved_highatomic))
>> + size = zone->nr_reserved_highatomic;
>> + zone->nr_reserved_highatomic -= size;
>> +
>> + /*
>> + * Convert to migratetype and avoid the normal pageblock
>> + * stealing heuristics. Minimally, the caller is doing the work
>> + * and needs the pages. More importantly, if the block was
>> + * always converted to MIGRATE_UNMOVABLE or another type then
>> + * the number of pageblocks that cannot be completely freed may
>> + * increase.
>> + */
>> + if (order < pageblock_order)
>> + ret = move_freepages_block(zone, page,
>> + MIGRATE_HIGHATOMIC, migratetype);
>> + else {
>> + move_to_free_list(page, zone, order,
>> + MIGRATE_HIGHATOMIC,
>> + migratetype);
>> + change_pageblock_range(page, order, migratetype);
>> + ret = 1;
>> + }
>> + /*
>> + * Reserving the block(s) already succeeded,
>> + * so this should not fail on zone boundaries.
>> + */
>> + WARN_ON_ONCE(ret == -1);
>> + if (ret > 0)
>> + return true;
>> + }
>> +
>> + return false;
>> +}
>> +
>> /*
>> * Used when an allocation is about to fail under memory pressure. This
>> * potentially hurts the reliability of high-order allocations when under
>> @@ -3491,70 +3553,12 @@ static bool unreserve_highatomic_pageblock(const struct alloc_context *ac,
>> struct zonelist *zonelist = ac->zonelist;
>> struct zoneref *z;
>> struct zone *zone;
>> - struct page *page;
>> - int order;
>> - int ret;
>>
>> for_each_zone_zonelist_nodemask(zone, z, zonelist, ac->highest_zoneidx,
>> ac->nodemask) {
>> - /*
>> - * Preserve at least one pageblock unless memory pressure
>> - * is really high.
>> - */
>> - if (!force && zone->nr_reserved_highatomic <=
>> - pageblock_nr_pages)
>> - continue;
>> -
>> - guard(spinlock_irqsave)(&zone->lock);
>> - for (order = 0; order < NR_PAGE_ORDERS; order++) {
>> - struct free_area *area = &(zone->free_area[order]);
>> - unsigned long size;
>> -
>> - page = get_page_from_free_area(area, MIGRATE_HIGHATOMIC);
>> - if (!page)
>> - continue;
>> -
>> - size = max(pageblock_nr_pages, 1UL << order);
>> - /*
>> - * It should never happen but changes to
>> - * locking could inadvertently allow a per-cpu
>> - * drain to add pages to MIGRATE_HIGHATOMIC
>> - * while unreserving so be safe and watch for
>> - * underflows.
>> - */
>> - if (WARN_ON_ONCE(size > zone->nr_reserved_highatomic))
>> - size = zone->nr_reserved_highatomic;
>> - zone->nr_reserved_highatomic -= size;
>> -
>> - /*
>> - * Convert to ac->migratetype and avoid the normal
>> - * pageblock stealing heuristics. Minimally, the caller
>> - * is doing the work and needs the pages. More
>> - * importantly, if the block was always converted to
>> - * MIGRATE_UNMOVABLE or another type then the number
>> - * of pageblocks that cannot be completely freed
>> - * may increase.
>> - */
>> - if (order < pageblock_order)
>> - ret = move_freepages_block(zone, page,
>> - MIGRATE_HIGHATOMIC,
>> - ac->migratetype);
>> - else {
>> - move_to_free_list(page, zone, order,
>> - MIGRATE_HIGHATOMIC,
>> - ac->migratetype);
>> - change_pageblock_range(page, order,
>> - ac->migratetype);
>> - ret = 1;
>> - }
>> - /*
>> - * Reserving the block(s) already succeeded,
>> - * so this should not fail on zone boundaries.
>> - */
>> - WARN_ON_ONCE(ret == -1);
>> - if (ret > 0)
>> - return ret;
>> - }
>> + if (unreserve_highatomic_pageblock_zone(zone,
>> + ac->migratetype, force))
>> + return true;
>> }
>>
>> return false;
>> diff --git a/mm/page_alloc.h b/mm/page_alloc.h
>> index e238ee91ed24..9fba6480b18d 100644
>> --- a/mm/page_alloc.h
>> +++ b/mm/page_alloc.h
>> @@ -282,6 +282,9 @@ static inline bool free_area_empty(struct free_area *area, int migratetype)
>> return list_empty(&area->free_list[migratetype]);
>> }
>>
>> +bool unreserve_highatomic_pageblock_zone(struct zone *zone, int migratetype,
>> + bool force);
>> +
>> /* Convert GFP flags to their corresponding migrate type */
>> #define GFP_MOVABLE_MASK (__GFP_RECLAIMABLE|__GFP_MOVABLE)
>> #define GFP_MOVABLE_SHIFT 3
>> diff --git a/mm/vmscan.c b/mm/vmscan.c
>> index 9959ca158ce8..91c622068d99 100644
>> --- a/mm/vmscan.c
>> +++ b/mm/vmscan.c
>> @@ -7460,8 +7460,22 @@ static int balance_pgdat(pg_data_t *pgdat, int order, int highest_zoneidx)
>> if (nr_boost_reclaim && !nr_reclaimed)
>> break;
>>
>> - if (raise_priority || !nr_reclaimed)
>> + if (raise_priority || !nr_reclaimed) {
>> + /*
>> + * Similarly to when direct reclaim is retrying, try to
>> + * unreserve highatomic pageblocks on the node. Convert
>> + * them to movable ones as kswapd doesn't have a target
>> + * migratetype, and passing unmovable should lead to
>> + * least permanent fragmentation.
>> + */
>> + for (int i = 0; i <= highest_zoneidx; i++) {
>> + struct zone *zone = pgdat->node_zones + i;
>> +
>> + unreserve_highatomic_pageblock_zone(zone,
>> + MIGRATE_UNMOVABLE, false);
>> + }
>> sc.priority--;
>> + }
>> } while (sc.priority >= 1);
>>
>> /*
>>
>> ---
>> base-commit: 6628e7d4c6b25c8d18f1345e3a20d21829aae7e4
>> change-id: 20261030-kswapd-highatomic-unreserve-7e6629ec65d0
>>
next prev parent reply other threads:[~2026-11-04 21:17 UTC|newest]
Thread overview: 7+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-04 18:38 Vlastimil Babka (SUSE)
2026-11-04 20:03 ` Thadeu Lima de Souza Cascardo
2026-11-04 21:17 ` Vlastimil Babka (SUSE) [this message]
2026-11-04 21:56 ` Jose A. Perez de Azpillaga
2026-11-05 8:02 ` Vlastimil Babka (SUSE)
2026-11-05 11:43 ` Jose A. Perez de Azpillaga
2026-11-05 11:52 ` Vlastimil Babka (SUSE)
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=963b9722-ca48-49f9-ac04-857d3d1526a2@kernel.org \
--to=vbabka@kernel.org \
--cc=akpm@linux-foundation.org \
--cc=axelrasmussen@google.com \
--cc=baohua@kernel.org \
--cc=baolin.wang@linux.alibaba.com \
--cc=baoquan.he@linux.dev \
--cc=brendan.jackman@linux.dev \
--cc=cascardo@igalia.com \
--cc=david@kernel.org \
--cc=fvdl@google.com \
--cc=hannes@cmpxchg.org \
--cc=jp.kobryn@linux.dev \
--cc=kasong@tencent.com \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-mm@kvack.org \
--cc=ljs@kernel.org \
--cc=mhocko@suse.com \
--cc=qi.zheng@linux.dev \
--cc=shakeel.butt@linux.dev \
--cc=surenb@google.com \
--cc=weixugc@google.com \
--cc=yuanchu@google.com \
--cc=ziy@nvidia.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
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®