kswapd0가 CPU를 사용한다고 해서 지금 swap I/O가 발생하고 있다고 단정할 수는 없습니다. 이 thread는 node의 background reclaim을 담당합니다. clean file cache만 회수할 수도 있고, active anon을 aging하거나 writeback에 막힌 상황을 판단할 수도 있습니다.
kswapd를 이해하는 데에는 세 단계를 나누는 것이 좋습니다. allocation이 요청 정보를 남기는 단계, sleeping thread에 실제 wakeup을 전달하는 단계, 깨어난 thread가 node를 balance하는 단계입니다. 요청 한 번이 reclaim pass 한 번에 대응하지는 않습니다.
Memory가 있는 node마다 thread를 준비합니다
초기화 경로의 kswapd_init()은 swap 관련 초기 설정을 수행하고 memory가 있는 node마다 kswapd_run()을 호출합니다. 생성한 task는 node metadata의 kswapd pointer에 보관됩니다. boot 시 필수 thread 생성에 실패한 경우와 memory hotplug 중의 처리는 context가 다릅니다.
이미 그 node의 pgdat->kswapd가 존재하면 같은 thread를 중복 생성하지 않습니다. 고전 구현은 kthread_run()으로 kswapd%d를 시작하며, v6.18은 node별 생성 상태를 lock으로 보호하고 kthread_create_on_node() 뒤에 깨웁니다. 생성 실패가 SYSTEM_RUNNING 이전이면 치명적인 boot 실패 조건이고, 그 이후의 실패 경로는 오류를 남기고 kswapd pointer를 NULL로 돌립니다. 단순히 실패 pointer를 정상 task처럼 보관하지 않습니다.
thread는 가능한 경우 해당 node의 CPU 범위에 맞춰 실행되고, PF_KSWAPD로 자신을 표시합니다. PF_MEMALLOC도 설정하여 reclaim 중 필요한 allocation이 또 reclaim으로 재귀 진입하지 않도록 합니다. 종료할 때에는 이 task 상태를 정리합니다. node 전체를 관리하는 worker이지, 처음 요청한 process의 전용 reclaim thread는 아닙니다.
다음 두 함수는 v6.12의 전체 정의입니다. 이 tag는 생성 lock과 kthread_run()을 함께 사용합니다. 앞에서 비교한 v6.18의 kthread_create_on_node() 구현으로 바꾸어 읽지는 않습니다. kswapd_init()의 node 순회는 N_MEMORY 기준이며, 실제 thread 생성의 오류 처리와 중복 방지는 kswapd_run()에 있습니다.
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 1991, 1992, 1993, 1994 Linus Torvalds
*
* Swap reorganised 29.12.95, Stephen Tweedie.
* kswapd added: 7.1.96 sct
* Removed kswapd_ctl limits, and swap out as many pages as needed
* to bring the system back to freepages.high: 2.4.97, Rik van Riel.
* Zone aware kswapd started 02/00, Kanoj Sarcar (kanoj@sgi.com).
* Multiqueue VM started 5.8.00, Rik van Riel.
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
/* Extracted unchanged from Linux v6.12, mm/vmscan.c:7373-7381.
* Complete named function; surrounding kernel context is required.
* Source: https://github.com/torvalds/linux/blob/v6.12/mm/vmscan.c#L7373-L7381
* License text: GPL-2.0.txt (in this directory).
*/
static int __init kswapd_init(void)
{
int nid;
swap_setup();
for_each_node_state(nid, N_MEMORY)
kswapd_run(nid);
return 0;
}
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 1991, 1992, 1993, 1994 Linus Torvalds
*
* Swap reorganised 29.12.95, Stephen Tweedie.
* kswapd added: 7.1.96 sct
* Removed kswapd_ctl limits, and swap out as many pages as needed
* to bring the system back to freepages.high: 2.4.97, Rik van Riel.
* Zone aware kswapd started 02/00, Kanoj Sarcar (kanoj@sgi.com).
* Multiqueue VM started 5.8.00, Rik van Riel.
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
/* Extracted unchanged from Linux v6.12, mm/vmscan.c:7337-7353.
* Complete named function; surrounding kernel context is required.
* Source: https://github.com/torvalds/linux/blob/v6.12/mm/vmscan.c#L7337-L7353
* License text: GPL-2.0.txt (in this directory).
*/
void __meminit kswapd_run(int nid)
{
pg_data_t *pgdat = NODE_DATA(nid);
pgdat_kswapd_lock(pgdat);
if (!pgdat->kswapd) {
pgdat->kswapd = kthread_run(kswapd, pgdat, "kswapd%d", nid);
if (IS_ERR(pgdat->kswapd)) {
/* failure at boot is fatal */
pr_err("Failed to start kswapd on node %d,ret=%ld\n",
nid, PTR_ERR(pgdat->kswapd));
BUG_ON(system_state < SYSTEM_RUNNING);
pgdat->kswapd = NULL;
}
}
pgdat_kswapd_unlock(pgdat);
}
Wakeup을 요청해도 바로 깨우지는 않습니다
wake_all_kswapds()는 allocation이 사용할 수 있는 zonelist를 따라 buddy allocator가 관리하는 zone을 찾고 node별로 wakeup을 요청합니다. wakeup_kswapd()는 managed zone과 cpuset 조건을 확인하고 요청 order와 highest zone index를 node 상태에 반영합니다. 여러 요청이 모이면 더 큰 order나 더 넓은 범위를 처리해야 할 수 있습니다.
zonelist 순회는 allocation의 highest zone index와 nodemask를 따르며 managed_zone()이 거짓인 항목은 건너뜁니다. last_pgdat과 같은 node의 zone이 연속해서 나타나면 중복 wakeup 호출을 피합니다. 이 변수는 모든 node를 기록하는 집합이 아니므로 임의의 zonelist ordering에서도 node당 정확히 한 번이라고 일반화하지는 않습니다. 요청 정보는 kswapd_order와 kswapd_highest_zoneidx에 기록되며, sleeping waiter가 있는지 확인하는 것보다 먼저 갱신됩니다.
하지만 기록과 wakeup은 다릅니다. wait queue에 실제 sleeping waiter가 없다면 깨울 대상이 없습니다. node의 kswapd_failures가 한계에 도달했거나, 이미 balance된 데다 watermark boost도 없다면 불필요한 wakeup을 억제합니다. direct reclaim이 허용되지 않는 요청에서는 compaction worker를 깨우는 별도 경로도 고려합니다.
이 때문에 wakeup counter만으로 allocation 실패 수나 reclaim 횟수를 환산할 수 없습니다. thread가 이미 실행 중일 때 새 요청이 도착할 수도 있고, 깨어나기 전에 다른 CPU가 충분한 page를 회수할 수도 있습니다.
아래 v6.12의 전체 wake_all_kswapds()에서 last_pgdat이 갱신되는 위치와 managed-zone 검사를 직접 확인할 수 있습니다. 다음에 나오는 wakeup_kswapd()의 sleeping·balance 판정과는 별개 단계입니다.
v6.12 zonelist wakeup · 전체 code · GPL-2.0
// SPDX-License-Identifier: GPL-2.0-only
/*
* linux/mm/page_alloc.c
*
* Manages the free list, the system allocates free pages here.
* Note that kmalloc() lives in slab.c
*
* Copyright (C) 1991, 1992, 1993, 1994 Linus Torvalds
* Swap reorganised 29.12.95, Stephen Tweedie
* Support of BIGMEM added by Gerhard Wichert, Siemens AG, July 1999
* Reshaped it to be a zoned allocator, Ingo Molnar, Red Hat, 1999
* Discontiguous memory support, Kanoj Sarcar, SGI, Nov 1999
* Zone balancing, Kanoj Sarcar, SGI, Jan 2000
* Per cpu hot/cold page lists, bulk allocation, Martin J. Bligh, Sept 2002
* (lots of bits borrowed from Ingo Molnar & Andrew Morton)
*/
/* Extracted unchanged from Linux v6.12, mm/page_alloc.c:3973-3990.
* Complete named function; surrounding kernel context is required.
* Source: https://github.com/torvalds/linux/blob/v6.12/mm/page_alloc.c#L3973-L3990
* License text: GPL-2.0.txt (in this directory).
*/
static void wake_all_kswapds(unsigned int order, gfp_t gfp_mask,
const struct alloc_context *ac)
{
struct zoneref *z;
struct zone *zone;
pg_data_t *last_pgdat = NULL;
enum zone_type highest_zoneidx = ac->highest_zoneidx;
for_each_zone_zonelist_nodemask(zone, z, ac->zonelist, highest_zoneidx,
ac->nodemask) {
if (!managed_zone(zone))
continue;
if (last_pgdat != zone->zone_pgdat) {
wakeup_kswapd(zone, gfp_mask, order, highest_zoneidx);
last_pgdat = zone->zone_pgdat;
}
}
}
아래는 v6.12의 wakeup 함수 전체입니다. kcompactd를 대신 깨우는 branch는 모든 non-direct-reclaim 요청의 공통 후처리가 아니라, kswapd 실패 한도 또는 balanced-without-boost 때문에 background reclaim을 진행하지 않는 branch 안에 있습니다.
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 1991, 1992, 1993, 1994 Linus Torvalds
*
* Swap reorganised 29.12.95, Stephen Tweedie.
* kswapd added: 7.1.96 sct
* Removed kswapd_ctl limits, and swap out as many pages as needed
* to bring the system back to freepages.high: 2.4.97, Rik van Riel.
* Zone aware kswapd started 02/00, Kanoj Sarcar (kanoj@sgi.com).
* Multiqueue VM started 5.8.00, Rik van Riel.
*/
/* Extracted unchanged from Linux v6.12, mm/vmscan.c:7250-7293.
* Full function definition; surrounding kernel context is required.
* Source: https://github.com/torvalds/linux/blob/v6.12/mm/vmscan.c#L7250
* License text: GPL-2.0.txt (in this directory).
*/
void wakeup_kswapd(struct zone *zone, gfp_t gfp_flags, int order,
enum zone_type highest_zoneidx)
{
pg_data_t *pgdat;
enum zone_type curr_idx;
if (!managed_zone(zone))
return;
if (!cpuset_zone_allowed(zone, gfp_flags))
return;
pgdat = zone->zone_pgdat;
curr_idx = READ_ONCE(pgdat->kswapd_highest_zoneidx);
if (curr_idx == MAX_NR_ZONES || curr_idx < highest_zoneidx)
WRITE_ONCE(pgdat->kswapd_highest_zoneidx, highest_zoneidx);
if (READ_ONCE(pgdat->kswapd_order) < order)
WRITE_ONCE(pgdat->kswapd_order, order);
if (!waitqueue_active(&pgdat->kswapd_wait))
return;
/* Hopeless node, leave it to direct reclaim if possible */
if (pgdat->kswapd_failures >= MAX_RECLAIM_RETRIES ||
(pgdat_balanced(pgdat, order, highest_zoneidx) &&
!pgdat_watermark_boosted(pgdat, highest_zoneidx))) {
/*
* There may be plenty of free memory available, but it's too
* fragmented for high-order allocations. Wake up kcompactd
* and rely on compaction_suitable() to determine if it's
* needed. If it fails, it will defer subsequent attempts to
* ratelimit its work.
*/
if (!(gfp_flags & __GFP_DIRECT_RECLAIM))
wakeup_kcompactd(pgdat, order, highest_zoneidx);
return;
}
trace_mm_vmscan_wakeup_kswapd(pgdat->node_id, highest_zoneidx, order,
gfp_flags);
wake_up_interruptible(&pgdat->kswapd_wait);
}
Main loop는 요청과 실제 reclaim order를 구분합니다
kswapd()는 node에 쌓인 요청 order와 highest zone index를 읽고 그 field를 다음 요청을 위해 정리합니다. highest zone 값이 아직 설정되지 않은 상태라면 이전에 사용하던 적절한 범위를 이어받습니다. 처음에는 유효한 최대 zone 범위에서 시작할 수 있습니다.
balance_pgdat()가 반환하는 값은 회수를 마친 order입니다. 처음 요청한 higher order와 다를 수 있습니다. 충분한 base page를 회수했다면 이후 연속성 문제는 compaction에 넘기고 reclaim은 order-0으로 낮출 수 있기 때문입니다. 이전 request와 마친 order를 버리고 매번 새 요청만 읽으면 조기 wakeup과 higher-order 실패 상황을 잘못 처리할 수 있습니다.
시작할 때 kswapd_order는 0, kswapd_highest_zoneidx는 유효 index가 아닌 MAX_NR_ZONES, writeback-throttle 관련 counter는 0으로 준비합니다. local highest zone은 MAX_NR_ZONES - 1이므로 첫 요청이 없는 경우에도 이전 유효 범위를 사용할 수 있습니다. kswapd_highest_zoneidx()는 sentinel이면 이전 index를 반환할 뿐, 그 helper 안에서 node field를 덮어쓰지는 않습니다. 공유 요청 field를 읽고 지우는 동작에는 READ_ONCE()·WRITE_ONCE()를 사용합니다. 이것을 요청 전체를 담는 단일 lock-protected transaction과 같다고 보아서는 안 됩니다.
v6.12 highest-zone helper · 전체 code
v6_12_kswapd_highest_zoneidx.c
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 1991, 1992, 1993, 1994 Linus Torvalds
*
* Swap reorganised 29.12.95, Stephen Tweedie.
* kswapd added: 7.1.96 sct
* Removed kswapd_ctl limits, and swap out as many pages as needed
* to bring the system back to freepages.high: 2.4.97, Rik van Riel.
* Zone aware kswapd started 02/00, Kanoj Sarcar (kanoj@sgi.com).
* Multiqueue VM started 5.8.00, Rik van Riel.
*/
/* Extracted unchanged from Linux v6.12, mm/vmscan.c:7060-7066.
* Full function definition; surrounding kernel context is required.
* Source: https://github.com/torvalds/linux/blob/v6.12/mm/vmscan.c#L7060
* License text: GPL-2.0.txt (in this directory).
*/
static enum zone_type kswapd_highest_zoneidx(pg_data_t *pgdat,
enum zone_type prev_highest_zoneidx)
{
enum zone_type curr_idx = READ_ONCE(pgdat->kswapd_highest_zoneidx);
return curr_idx == MAX_NR_ZONES ? prev_highest_zoneidx : curr_idx;
}
다음은 kswapd() 전체 loop입니다. set_freezable()과 kthread_freezable_should_stop()도 남겨 두었습니다. stop 요청이면 종료하지만 freeze에서 돌아온 경우에는 즉시 balance_pgdat()를 호출하지 않고 loop를 다시 돕니다. reclaim_order < alloc_order일 때는 loop 맨 위가 아닌 kswapd_try_sleep로 가므로, compaction에 전달할 원래 order와 sleep 판단용 order를 잃지 않습니다.
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 1991, 1992, 1993, 1994 Linus Torvalds
*
* Swap reorganised 29.12.95, Stephen Tweedie.
* kswapd added: 7.1.96 sct
* Removed kswapd_ctl limits, and swap out as many pages as needed
* to bring the system back to freepages.high: 2.4.97, Rik van Riel.
* Zone aware kswapd started 02/00, Kanoj Sarcar (kanoj@sgi.com).
* Multiqueue VM started 5.8.00, Rik van Riel.
*/
/* Extracted unchanged from Linux v6.12, mm/vmscan.c:7165-7241.
* Full function definition; surrounding kernel context is required.
* Source: https://github.com/torvalds/linux/blob/v6.12/mm/vmscan.c#L7165
* License text: GPL-2.0.txt (in this directory).
*/
static int kswapd(void *p)
{
unsigned int alloc_order, reclaim_order;
unsigned int highest_zoneidx = MAX_NR_ZONES - 1;
pg_data_t *pgdat = (pg_data_t *)p;
struct task_struct *tsk = current;
const struct cpumask *cpumask = cpumask_of_node(pgdat->node_id);
if (!cpumask_empty(cpumask))
set_cpus_allowed_ptr(tsk, cpumask);
/*
* Tell the memory management that we're a "memory allocator",
* and that if we need more memory we should get access to it
* regardless (see "__alloc_pages()"). "kswapd" should
* never get caught in the normal page freeing logic.
*
* (Kswapd normally doesn't need memory anyway, but sometimes
* you need a small amount of memory in order to be able to
* page out something else, and this flag essentially protects
* us from recursively trying to free more memory as we're
* trying to free the first piece of memory in the first place).
*/
tsk->flags |= PF_MEMALLOC | PF_KSWAPD;
set_freezable();
WRITE_ONCE(pgdat->kswapd_order, 0);
WRITE_ONCE(pgdat->kswapd_highest_zoneidx, MAX_NR_ZONES);
atomic_set(&pgdat->nr_writeback_throttled, 0);
for ( ; ; ) {
bool was_frozen;
alloc_order = reclaim_order = READ_ONCE(pgdat->kswapd_order);
highest_zoneidx = kswapd_highest_zoneidx(pgdat,
highest_zoneidx);
kswapd_try_sleep:
kswapd_try_to_sleep(pgdat, alloc_order, reclaim_order,
highest_zoneidx);
/* Read the new order and highest_zoneidx */
alloc_order = READ_ONCE(pgdat->kswapd_order);
highest_zoneidx = kswapd_highest_zoneidx(pgdat,
highest_zoneidx);
WRITE_ONCE(pgdat->kswapd_order, 0);
WRITE_ONCE(pgdat->kswapd_highest_zoneidx, MAX_NR_ZONES);
if (kthread_freezable_should_stop(&was_frozen))
break;
/*
* We can speed up thawing tasks if we don't call balance_pgdat
* after returning from the refrigerator
*/
if (was_frozen)
continue;
/*
* Reclaim begins at the requested order but if a high-order
* reclaim fails then kswapd falls back to reclaiming for
* order-0. If that happens, kswapd will consider sleeping
* for the order it finished reclaiming at (reclaim_order)
* but kcompactd is woken to compact for the original
* request (alloc_order).
*/
trace_mm_vmscan_kswapd_wake(pgdat->node_id, highest_zoneidx,
alloc_order);
reclaim_order = balance_pgdat(pgdat, alloc_order,
highest_zoneidx);
if (reclaim_order < alloc_order)
goto kswapd_try_sleep;
}
tsk->flags &= ~(PF_MEMALLOC | PF_KSWAPD);
return 0;
}
Sleep 직전에는 race를 다시 확인합니다
kswapd_try_to_sleep()은 wait queue에 자신을 등록하고 task의 sleep 상태를 설정한 뒤 조건을 검사합니다. 할 일이 없다고 판단한 직후 새 요청이 들어오는 race를 막으려면 이 등록·상태 변경·재검사 순서가 중요합니다. 단순한 if (balanced) schedule()로 바꿀 수 없습니다.
먼저 약 HZ/10의 짧은 timeout sleep을 시도합니다. 이 전에 isolation/compaction 관련 상태를 정리하고 kcompactd를 깨울 수도 있습니다. 조기에 깨었다면 반환된 remaining time을 통해 이를 구분하며, 원래 order와 새로 들어온 order·zone 정보를 보존하여 너무 일찍 낮은 order로 정착하지 않도록 합니다.
짧은 sleep이 timeout으로 끝났더라도 곧바로 장기 sleep에 들어가지는 않습니다. wait 상태를 다시 준비하고 prepare_kswapd_sleep()를 재검사합니다. vmstat 통계가 per-CPU로 batching되므로 pressure 때에는 더 작은 threshold로 오차를 줄입니다. v6.12 code는 장기 sleep 직전에 calculate_normal_threshold를 적용하고, 깨어난 뒤 calculate_pressure_threshold로 바꿉니다. sleep 직전에 더 정밀한 pressure threshold를 켜는 순서가 아닙니다. 마지막에는 finish_wait()로 wait 상태를 정리합니다.
reset_isolation_suitable()도 직접 free page를 만드는 함수가 아닙니다. 앞서 compaction이 isolation에 실패해 건너뛰도록 표시한 pageblock의 skip 상태를 초기화하여 다시 검사할 기회를 줍니다. compaction 자체 역시 사용 중인 movable page를 옮겨 연속성을 개선하는 과정이지, 내용의 backing을 마련해 reclaim하는 과정과 같지 않습니다.
short sleep의 schedule_timeout(HZ/10)이 양수를 반환하면 timeout 전에 깨어난 것입니다. 이때 highest-zone sentinel을 이전 유효 범위로 보완하고 요청 order를 최소한 직전 reclaim_order만큼 유지합니다. remaining이 0이어도 sleep 조건이 다시 실패할 수 있습니다. 이 경우와 조기 wakeup의 경우에는 각각 KSWAPD_HIGH_WMARK_HIT_QUICKLY와 KSWAPD_LOW_WMARK_HIT_QUICKLY event를 집계합니다. event 이름만 보고 특정 allocation 한 건의 성공·실패를 확정하지 않습니다.
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 1991, 1992, 1993, 1994 Linus Torvalds
*
* Swap reorganised 29.12.95, Stephen Tweedie.
* kswapd added: 7.1.96 sct
* Removed kswapd_ctl limits, and swap out as many pages as needed
* to bring the system back to freepages.high: 2.4.97, Rik van Riel.
* Zone aware kswapd started 02/00, Kanoj Sarcar (kanoj@sgi.com).
* Multiqueue VM started 5.8.00, Rik van Riel.
*/
/* Extracted unchanged from Linux v6.12, mm/vmscan.c:7068-7150.
* Full function definition; surrounding kernel context is required.
* Source: https://github.com/torvalds/linux/blob/v6.12/mm/vmscan.c#L7068
* License text: GPL-2.0.txt (in this directory).
*/
static void kswapd_try_to_sleep(pg_data_t *pgdat, int alloc_order, int reclaim_order,
unsigned int highest_zoneidx)
{
long remaining = 0;
DEFINE_WAIT(wait);
if (freezing(current) || kthread_should_stop())
return;
prepare_to_wait(&pgdat->kswapd_wait, &wait, TASK_INTERRUPTIBLE);
/*
* Try to sleep for a short interval. Note that kcompactd will only be
* woken if it is possible to sleep for a short interval. This is
* deliberate on the assumption that if reclaim cannot keep an
* eligible zone balanced that it's also unlikely that compaction will
* succeed.
*/
if (prepare_kswapd_sleep(pgdat, reclaim_order, highest_zoneidx)) {
/*
* Compaction records what page blocks it recently failed to
* isolate pages from and skips them in the future scanning.
* When kswapd is going to sleep, it is reasonable to assume
* that pages and compaction may succeed so reset the cache.
*/
reset_isolation_suitable(pgdat);
/*
* We have freed the memory, now we should compact it to make
* allocation of the requested order possible.
*/
wakeup_kcompactd(pgdat, alloc_order, highest_zoneidx);
remaining = schedule_timeout(HZ/10);
/*
* If woken prematurely then reset kswapd_highest_zoneidx and
* order. The values will either be from a wakeup request or
* the previous request that slept prematurely.
*/
if (remaining) {
WRITE_ONCE(pgdat->kswapd_highest_zoneidx,
kswapd_highest_zoneidx(pgdat,
highest_zoneidx));
if (READ_ONCE(pgdat->kswapd_order) < reclaim_order)
WRITE_ONCE(pgdat->kswapd_order, reclaim_order);
}
finish_wait(&pgdat->kswapd_wait, &wait);
prepare_to_wait(&pgdat->kswapd_wait, &wait, TASK_INTERRUPTIBLE);
}
/*
* After a short sleep, check if it was a premature sleep. If not, then
* go fully to sleep until explicitly woken up.
*/
if (!remaining &&
prepare_kswapd_sleep(pgdat, reclaim_order, highest_zoneidx)) {
trace_mm_vmscan_kswapd_sleep(pgdat->node_id);
/*
* vmstat counters are not perfectly accurate and the estimated
* value for counters such as NR_FREE_PAGES can deviate from the
* true value by nr_online_cpus * threshold. To avoid the zone
* watermarks being breached while under pressure, we reduce the
* per-cpu vmstat threshold while kswapd is awake and restore
* them before going back to sleep.
*/
set_pgdat_percpu_threshold(pgdat, calculate_normal_threshold);
if (!kthread_should_stop())
schedule();
set_pgdat_percpu_threshold(pgdat, calculate_pressure_threshold);
} else {
if (remaining)
count_vm_event(KSWAPD_LOW_WMARK_HIT_QUICKLY);
else
count_vm_event(KSWAPD_HIGH_WMARK_HIT_QUICKLY);
}
finish_wait(&pgdat->kswapd_wait, &wait);
}

Balanced는 모든 zone이 high 이상이라는 뜻이 아닙니다
prepare_kswapd_sleep()은 먼저 background reclaim을 기다리는 direct reclaimer를 깨웁니다. 자신이 잠들면서 다른 task가 자신만 기다리게 두면 안 되기 때문입니다. 실패 횟수가 한도에 도달한 경우에도 더 도움을 줄 수 없으므로 sleep을 허용합니다.
그 밖에는 pgdat_balanced()로 requested order와 highest zone 범위에서 allocation에 쓸 수 있는 zone의 여유를 봅니다. 중요한 것은 모든 zone의 high watermark를 동시에 채우는 조건이 아니라는 점입니다. 적어도 하나의 적격 zone이 해당 order·reserve 조건을 만족하면 balance된 것으로 판단할 수 있습니다. managed zone이 없는 특수 경우도 일반적인 부족 상황과 구분합니다.
따라서 kswapd가 sleeping이라는 관찰에는 여러 원인이 있습니다. 목표를 달성했을 수도 있고, 실패 한도에 걸렸을 수도 있으며, 새 요청이 아직 전달되지 않았을 수도 있습니다. node 전체 free 양이나 application의 다음 allocation 성공을 보증하지 않습니다.
v6.12의 sleep 준비에서는 balance된 경우 congestion 상태도 정리합니다. 실패 한도에 도달해 잠드는 branch는 같은 의미의 balance 성공이 아닙니다.
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 1991, 1992, 1993, 1994 Linus Torvalds
*
* Swap reorganised 29.12.95, Stephen Tweedie.
* kswapd added: 7.1.96 sct
* Removed kswapd_ctl limits, and swap out as many pages as needed
* to bring the system back to freepages.high: 2.4.97, Rik van Riel.
* Zone aware kswapd started 02/00, Kanoj Sarcar (kanoj@sgi.com).
* Multiqueue VM started 5.8.00, Rik van Riel.
*/
/* Extracted unchanged from Linux v6.12, mm/vmscan.c:6711-6740.
* Full function definition; surrounding kernel context is required.
* Source: https://github.com/torvalds/linux/blob/v6.12/mm/vmscan.c#L6711
* License text: GPL-2.0.txt (in this directory).
*/
static bool prepare_kswapd_sleep(pg_data_t *pgdat, int order,
int highest_zoneidx)
{
/*
* The throttled processes are normally woken up in balance_pgdat() as
* soon as allow_direct_reclaim() is true. But there is a potential
* race between when kswapd checks the watermarks and a process gets
* throttled. There is also a potential race if processes get
* throttled, kswapd wakes, a large process exits thereby balancing the
* zones, which causes kswapd to exit balance_pgdat() before reaching
* the wake up checks. If kswapd is going to sleep, no process should
* be sleeping on pfmemalloc_wait, so wake them now if necessary. If
* the wake up is premature, processes will wake kswapd and get
* throttled again. The difference from wake ups in balance_pgdat() is
* that here we are under prepare_to_wait().
*/
if (waitqueue_active(&pgdat->pfmemalloc_wait))
wake_up_all(&pgdat->pfmemalloc_wait);
/* Hopeless node, leave it to direct reclaim */
if (pgdat->kswapd_failures >= MAX_RECLAIM_RETRIES)
return true;
if (pgdat_balanced(pgdat, order, highest_zoneidx)) {
clear_pgdat_congested(pgdat);
return true;
}
return false;
}
pgdat_balanced()는 낮은 zone부터 검사하고, 조건을 만족하는 적격 zone 하나를 만나면 참을 반환합니다. 아래 전체 함수에서는 memory-tiering mode의 promo_wmark_pages() 예외도 보실 수 있습니다. 일반적인 high watermark 설명을 이 예외까지 없애는 규칙으로 일반화하지 마십시오.
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 1991, 1992, 1993, 1994 Linus Torvalds
*
* Swap reorganised 29.12.95, Stephen Tweedie.
* kswapd added: 7.1.96 sct
* Removed kswapd_ctl limits, and swap out as many pages as needed
* to bring the system back to freepages.high: 2.4.97, Rik van Riel.
* Zone aware kswapd started 02/00, Kanoj Sarcar (kanoj@sgi.com).
* Multiqueue VM started 5.8.00, Rik van Riel.
*/
/* Extracted unchanged from Linux v6.12, mm/vmscan.c:6659-6692.
* Full function definition; surrounding kernel context is required.
* Source: https://github.com/torvalds/linux/blob/v6.12/mm/vmscan.c#L6659
* License text: GPL-2.0.txt (in this directory).
*/
static bool pgdat_balanced(pg_data_t *pgdat, int order, int highest_zoneidx)
{
int i;
unsigned long mark = -1;
struct zone *zone;
/*
* Check watermarks bottom-up as lower zones are more likely to
* meet watermarks.
*/
for (i = 0; i <= highest_zoneidx; i++) {
zone = pgdat->node_zones + i;
if (!managed_zone(zone))
continue;
if (sysctl_numa_balancing_mode & NUMA_BALANCING_MEMORY_TIERING)
mark = promo_wmark_pages(zone);
else
mark = high_wmark_pages(zone);
if (zone_watermark_ok_safe(zone, order, mark, highest_zoneidx))
return true;
}
/*
* If a node has no managed zone within highest_zoneidx, it does not
* need balancing by definition. This can happen if a zone-restricted
* allocation tries to wake a remote kswapd.
*/
if (mark == -1)
return true;
return false;
}
v6.18의 전체 비교 code에는 defrag_mode && order일 때 NR_FREE_PAGES_BLOCKS를 사용하는 branch가 더 있습니다. 이때는 pageblock 단위 여유도 중요합니다. order-0으로 낮춘 뒤에는 compaction을 위한 base page 여유를 확인합니다. 따라서 v6.12의 zone_watermark_ok_safe() 호출만을 모든 version의 최종 balance 구현으로 설명하면 안 됩니다.
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 1991, 1992, 1993, 1994 Linus Torvalds
*
* Swap reorganised 29.12.95, Stephen Tweedie.
* kswapd added: 7.1.96 sct
* Removed kswapd_ctl limits, and swap out as many pages as needed
* to bring the system back to freepages.high: 2.4.97, Rik van Riel.
* Zone aware kswapd started 02/00, Kanoj Sarcar (kanoj@sgi.com).
* Multiqueue VM started 5.8.00, Rik van Riel.
*/
/* Extracted unchanged from Linux v6.18, mm/vmscan.c:6802-6866.
* Full function definition; surrounding kernel context is required.
* Source: https://github.com/torvalds/linux/blob/v6.18/mm/vmscan.c#L6802
* License text: GPL-2.0.txt (in this directory).
*/
static bool pgdat_balanced(pg_data_t *pgdat, int order, int highest_zoneidx)
{
int i;
unsigned long mark = -1;
struct zone *zone;
/*
* Check watermarks bottom-up as lower zones are more likely to
* meet watermarks.
*/
for_each_managed_zone_pgdat(zone, pgdat, i, highest_zoneidx) {
enum zone_stat_item item;
unsigned long free_pages;
if (sysctl_numa_balancing_mode & NUMA_BALANCING_MEMORY_TIERING)
mark = promo_wmark_pages(zone);
else
mark = high_wmark_pages(zone);
/*
* In defrag_mode, watermarks must be met in whole
* blocks to avoid polluting allocator fallbacks.
*
* However, kswapd usually cannot accomplish this on
* its own and needs kcompactd support. Once it's
* reclaimed a compaction gap, and kswapd_shrink_node
* has dropped order, simply ensure there are enough
* base pages for compaction, wake kcompactd & sleep.
*/
if (defrag_mode && order)
item = NR_FREE_PAGES_BLOCKS;
else
item = NR_FREE_PAGES;
/*
* When there is a high number of CPUs in the system,
* the cumulative error from the vmstat per-cpu cache
* can blur the line between the watermarks. In that
* case, be safe and get an accurate snapshot.
*
* TODO: NR_FREE_PAGES_BLOCKS moves in steps of
* pageblock_nr_pages, while the vmstat pcp threshold
* is limited to 125. On many configurations that
* counter won't actually be per-cpu cached. But keep
* things simple for now; revisit when somebody cares.
*/
free_pages = zone_page_state(zone, item);
if (zone->percpu_drift_mark && free_pages < zone->percpu_drift_mark)
free_pages = zone_page_state_snapshot(zone, item);
if (__zone_watermark_ok(zone, order, mark, highest_zoneidx,
0, free_pages))
return true;
}
/*
* If a node has no managed zone within highest_zoneidx, it does not
* need balancing by definition. This can happen if a zone-restricted
* allocation tries to wake a remote kswapd.
*/
if (mark == -1)
return true;
return false;
}
balance_pgdat은 일반 pressure와 watermark boost를 나눕니다
balance_pgdat()는 scan_control과 slab reclaim accounting을 준비하고, 각 zone의 watermark boost를 수집합니다. 다른 migratetype의 pageblock을 빌려 쓰면 movable·unmovable page가 섞여 fragmentation이 커질 수 있습니다. boost는 더 많은 free 공간을 마련해 이런 fallback이 반복되지 않도록 유도하는 정책입니다.
pageblock은 migratetype을 관리하는 단위이며 크기는 pageblock_order에 따릅니다. 특정 x86-64 구성에서 order-9인 사례를 모든 architecture의 상수로 고정해서는 안 됩니다. v6.12의 boost_watermark()는 factor가 0이거나 managed zone이 네 pageblock보다 작으면 boost하지 않습니다. high watermark와 factor/10000에서 얻은 상한이 0인 boot 초기 상황도 제외합니다. 그 밖에는 pageblock 단위로 boost를 더하되 계산한 최대값에 제한합니다. 이 상한은 최소 한 pageblock 크기로 보정합니다.
steal_suitable_fallback()의 해당 fallback branch는 boost가 가능하고 ALLOC_KSWAPD가 있을 때 ZONE_BOOSTED_WATERMARK를 남깁니다. rmqueue()는 이 bit를 확인·해제하고 kswapd를 깨웁니다. node가 평소 watermark에서는 이미 balanced라서 자연스럽게 깨어나지 않는 경우에도 fragmentation 완화 작업을 요청하기 위한 연결입니다. 모든 fallback이 동일하게 boost하는 것은 아닙니다.
node 자체가 부족하면 일반 reclaim을 우선하며, 이미 balance되었지만 boost가 남아 있으면 fragmentation 완화용 reclaim을 수행할 수 있습니다. boost pass에서는 비싼 swap·writeback을 유발하여 오래 붙잡히지 않도록 허용 작업과 priority 상승을 제한합니다. 일반 pressure에서 laptop mode를 완화하는 상황과 구분해야 합니다.
각 pass는 대상 zone에 reclaim-active 상태를 표시하고 DEF_PRIORITY에서 scan을 시작합니다. inactive anon이 적으면 active anon을 aging해 다음 회수 후보를 마련합니다. 고전 설명에 나오는 age_active_anon()과 아래 v6.12의 kswapd_age_node()는 version을 구분해서 읽어야 합니다. file을 먼저 회수하기 쉬운 정책만 믿고 anon의 aging을 방치하면 나중에 사용할 후보가 부족해질 수 있기 때문입니다.
실제 회수는 kswapd_shrink_node()를 거쳐 shrink_node()로 갑니다. v6.12 helper의 boolean은 scan량과 이번 호출에서 증가한 reclaim accounting 중 큰 값이 목표에 도달했는지를 나타냅니다. scan을 충분히 했다는 것과 그만큼 physical page를 free했다는 것은 여전히 다른 판단입니다. 목표를 충족하면 같은 priority로 다시 시도할 수 있고, 진전이 없으면 더 적극적으로 list를 검사합니다. boost mode에서 I/O 없이는 한 장도 회수할 수 없다면 무의미한 반복을 중단합니다.
v6.12의 background aging 진입점은 kswapd_age_node()입니다. MGLRU가 켜졌다면 generation aging 경로로 먼저 갈라집니다. classic 경로는 anon aging이 가능하고 node의 inactive anon이 부족한지 확인한 다음 memcg별 active anon에 SWAP_CLUSTER_MAX의 aging batch를 보냅니다. 이 32는 base page budget이며 실제로 32 folio를 반드시 이동한다는 뜻은 아닙니다. zone 제한에 따른 즉시 free를 목표로 하는 단계와 달리, 재접근 기회를 유지하기 위한 aging입니다.
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 1991, 1992, 1993, 1994 Linus Torvalds
*
* Swap reorganised 29.12.95, Stephen Tweedie.
* kswapd added: 7.1.96 sct
* Removed kswapd_ctl limits, and swap out as many pages as needed
* to bring the system back to freepages.high: 2.4.97, Rik van Riel.
* Zone aware kswapd started 02/00, Kanoj Sarcar (kanoj@sgi.com).
* Multiqueue VM started 5.8.00, Rik van Riel.
*/
/* Extracted unchanged from Linux v6.12, mm/vmscan.c:6605-6629.
* Full function definition; surrounding kernel context is required.
* Source: https://github.com/torvalds/linux/blob/v6.12/mm/vmscan.c#L6605
* License text: GPL-2.0.txt (in this directory).
*/
static void kswapd_age_node(struct pglist_data *pgdat, struct scan_control *sc)
{
struct mem_cgroup *memcg;
struct lruvec *lruvec;
if (lru_gen_enabled()) {
lru_gen_age_node(pgdat, sc);
return;
}
if (!can_age_anon_pages(pgdat, sc))
return;
lruvec = mem_cgroup_lruvec(NULL, pgdat);
if (!inactive_is_low(lruvec, LRU_INACTIVE_ANON))
return;
memcg = mem_cgroup_iter(NULL, NULL, NULL);
do {
lruvec = mem_cgroup_lruvec(memcg, pgdat);
shrink_active_list(SWAP_CLUSTER_MAX, lruvec,
sc, LRU_ACTIVE_ANON);
memcg = mem_cgroup_iter(NULL, memcg, NULL);
} while (memcg);
}
boost가 남은 상태에서 node가 unbalanced라면 boost budget을 0으로 만들고 restart하여 일반 pressure를 먼저 처리합니다. zone의 boost field 자체는 이 순간 지우지 않습니다. balanced이고 budget도 없다면 out으로 갑니다. boost 중 priority 10에 도달하면 raise_priority를 끄고, 회수 진전도 없으면 반복 자체를 중단합니다. 일반 pressure에서는 priority < DEF_PRIORITY - 2, 즉 기본값 기준 9 이하에서 laptop-mode writeback 제한을 풀 수 있습니다. priority를 내리는 실제 식은 raise_priority || !nr_reclaimed이며, flag가 꺼졌기 때문에 더 적극적인 scan으로 간다는 뜻은 아닙니다.
Pass를 마칠 때도 상태를 돌려놓습니다
중간에는 reserve가 회복되었는지 확인해 direct-reclaim wait queue의 task를 깨웁니다. 마지막에는 reclaim-active 상태를 지우고, 해당 pass에서 다룬 watermark boost를 정리하며, compaction worker에게 기회를 줍니다. 새 boost가 동시에 추가될 수 있으므로 시작 시점에 기록한 양과 현재 값을 구분해야 합니다.
회수한 page가 전혀 없었다면 failure 이력을 누적합니다. 반대로 진전이 있으면 이를 반영하여 무조건 실패 한도에 도달하지 않게 합니다. refault snapshot과 reclaim state도 정리한 뒤 마친 order를 main loop에 돌려줍니다.
kswapd_shrink_node()의 목표는 관련 zone의 high watermark를 바탕으로 계산하며 각 zone에 최소 scan granularity를 둡니다. 회수량이 compact_gap(order) 수준에 도달하면 높은 order의 연속성을 reclaim만으로 해결하려 하지 않고 order-0으로 낮출 수 있습니다. kswapd의 성과를 판단할 때 scanned·reclaimed·allocation order·compaction을 따로 보는 이유입니다.
scan 목표의 식은 각 managed zone에 대한 max(high_wmark_pages(zone), SWAP_CLUSTER_MAX)의 합입니다. v6.12의 kswapd_shrink_node()는 shrink_node() 뒤 cumulative reclaimed가 compact_gap(order) 이상이면 sc->order를 0으로 낮춥니다. 이 기준은 2 << order base pages이며 compaction의 실제 free량을 예언하는 값이 아닙니다. 마지막 반환식은 max(sc->nr_scanned, sc->nr_reclaimed - nr_reclaimed) >= sc->nr_to_reclaim입니다. 여기서 local nr_reclaimed는 호출 전 snapshot이므로 두 번째 값은 이번 호출의 증가량입니다. mm_account_reclaimed_pages()로 반영된 회수도 평가하려는 식이며, scan량만 비교하는 고전 반환식과 같지 않습니다. 그렇다고 writeback 완료 자체를 성공으로 반환하는 별도 branch가 있는 것도 아닙니다.
v6.12 node shrinking helper · 전체 code
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 1991, 1992, 1993, 1994 Linus Torvalds
*
* Swap reorganised 29.12.95, Stephen Tweedie.
* kswapd added: 7.1.96 sct
* Removed kswapd_ctl limits, and swap out as many pages as needed
* to bring the system back to freepages.high: 2.4.97, Rik van Riel.
* Zone aware kswapd started 02/00, Kanoj Sarcar (kanoj@sgi.com).
* Multiqueue VM started 5.8.00, Rik van Riel.
*/
/* Extracted unchanged from Linux v6.12, mm/vmscan.c:6750-6785.
* Full function definition; surrounding kernel context is required.
* Source: https://github.com/torvalds/linux/blob/v6.12/mm/vmscan.c#L6750
* License text: GPL-2.0.txt (in this directory).
*/
static bool kswapd_shrink_node(pg_data_t *pgdat,
struct scan_control *sc)
{
struct zone *zone;
int z;
unsigned long nr_reclaimed = sc->nr_reclaimed;
/* Reclaim a number of pages proportional to the number of zones */
sc->nr_to_reclaim = 0;
for (z = 0; z <= sc->reclaim_idx; z++) {
zone = pgdat->node_zones + z;
if (!managed_zone(zone))
continue;
sc->nr_to_reclaim += max(high_wmark_pages(zone), SWAP_CLUSTER_MAX);
}
/*
* Historically care was taken to put equal pressure on all zones but
* now pressure is applied based on node LRU order.
*/
shrink_node(pgdat, sc);
/*
* Fragmentation may mean that the system cannot be rebalanced for
* high-order allocations. If twice the allocation size has been
* reclaimed then recheck watermarks only at order-0 to prevent
* excessive reclaim. Assume that a process requested a high-order
* can direct reclaim/compact.
*/
if (sc->order && sc->nr_reclaimed >= compact_gap(sc->order))
sc->order = 0;
/* account for progress from mm_account_reclaimed_pages() */
return max(sc->nr_scanned, sc->nr_reclaimed - nr_reclaimed) >= sc->nr_to_reclaim;
}
아래 balance_pgdat()는 전체 함수입니다. 각 pass의 scan counter 초기화, cgroup v1 soft-limit reclaim, PSI·lockdep·freezing 처리도 포함합니다. priority loop를 끝까지 내려도 cache trim이 한 장도 회수하지 못한 v6.12의 경우에는 no_cache_trim_mode를 켜 한 번 더 시작하는 예외가 있습니다. buffer head가 과다하면 scan zone 범위를 넓힐 수 있지만 원래 allocation 범위의 balance 판정과 혼동하지 않습니다.
마지막 zone boost 차감은 zone lock 안에서 min(현재 boost, 시작 시 기록한 boost)만큼 수행합니다. 동시 allocation이 추가한 boost를 무조건 0으로 덮어쓰는 code가 아닙니다. PAGEOUTRUN은 pass 진입 event이며, kswapd_failures는 회수량이 없을 때 증가합니다. 이미 balanced여서 out으로 빠지는 경로까지 무조건 실패 한 번으로 세지는 않습니다. 이 함수의 반환값은 page 수가 아닌 최종 sc.order입니다.
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 1991, 1992, 1993, 1994 Linus Torvalds
*
* Swap reorganised 29.12.95, Stephen Tweedie.
* kswapd added: 7.1.96 sct
* Removed kswapd_ctl limits, and swap out as many pages as needed
* to bring the system back to freepages.high: 2.4.97, Rik van Riel.
* Zone aware kswapd started 02/00, Kanoj Sarcar (kanoj@sgi.com).
* Multiqueue VM started 5.8.00, Rik van Riel.
*/
/* Extracted unchanged from Linux v6.12, mm/vmscan.c:6832-7051.
* Full function definition; surrounding kernel context is required.
* Source: https://github.com/torvalds/linux/blob/v6.12/mm/vmscan.c#L6832
* License text: GPL-2.0.txt (in this directory).
*/
static int balance_pgdat(pg_data_t *pgdat, int order, int highest_zoneidx)
{
int i;
unsigned long nr_soft_reclaimed;
unsigned long nr_soft_scanned;
unsigned long pflags;
unsigned long nr_boost_reclaim;
unsigned long zone_boosts[MAX_NR_ZONES] = { 0, };
bool boosted;
struct zone *zone;
struct scan_control sc = {
.gfp_mask = GFP_KERNEL,
.order = order,
.may_unmap = 1,
};
set_task_reclaim_state(current, &sc.reclaim_state);
psi_memstall_enter(&pflags);
__fs_reclaim_acquire(_THIS_IP_);
count_vm_event(PAGEOUTRUN);
/*
* Account for the reclaim boost. Note that the zone boost is left in
* place so that parallel allocations that are near the watermark will
* stall or direct reclaim until kswapd is finished.
*/
nr_boost_reclaim = 0;
for (i = 0; i <= highest_zoneidx; i++) {
zone = pgdat->node_zones + i;
if (!managed_zone(zone))
continue;
nr_boost_reclaim += zone->watermark_boost;
zone_boosts[i] = zone->watermark_boost;
}
boosted = nr_boost_reclaim;
restart:
set_reclaim_active(pgdat, highest_zoneidx);
sc.priority = DEF_PRIORITY;
do {
unsigned long nr_reclaimed = sc.nr_reclaimed;
bool raise_priority = true;
bool balanced;
bool ret;
bool was_frozen;
sc.reclaim_idx = highest_zoneidx;
/*
* If the number of buffer_heads exceeds the maximum allowed
* then consider reclaiming from all zones. This has a dual
* purpose -- on 64-bit systems it is expected that
* buffer_heads are stripped during active rotation. On 32-bit
* systems, highmem pages can pin lowmem memory and shrinking
* buffers can relieve lowmem pressure. Reclaim may still not
* go ahead if all eligible zones for the original allocation
* request are balanced to avoid excessive reclaim from kswapd.
*/
if (buffer_heads_over_limit) {
for (i = MAX_NR_ZONES - 1; i >= 0; i--) {
zone = pgdat->node_zones + i;
if (!managed_zone(zone))
continue;
sc.reclaim_idx = i;
break;
}
}
/*
* If the pgdat is imbalanced then ignore boosting and preserve
* the watermarks for a later time and restart. Note that the
* zone watermarks will be still reset at the end of balancing
* on the grounds that the normal reclaim should be enough to
* re-evaluate if boosting is required when kswapd next wakes.
*/
balanced = pgdat_balanced(pgdat, sc.order, highest_zoneidx);
if (!balanced && nr_boost_reclaim) {
nr_boost_reclaim = 0;
goto restart;
}
/*
* If boosting is not active then only reclaim if there are no
* eligible zones. Note that sc.reclaim_idx is not used as
* buffer_heads_over_limit may have adjusted it.
*/
if (!nr_boost_reclaim && balanced)
goto out;
/* Limit the priority of boosting to avoid reclaim writeback */
if (nr_boost_reclaim && sc.priority == DEF_PRIORITY - 2)
raise_priority = false;
/*
* Do not writeback or swap pages for boosted reclaim. The
* intent is to relieve pressure not issue sub-optimal IO
* from reclaim context. If no pages are reclaimed, the
* reclaim will be aborted.
*/
sc.may_writepage = !laptop_mode && !nr_boost_reclaim;
sc.may_swap = !nr_boost_reclaim;
/*
* Do some background aging, to give pages a chance to be
* referenced before reclaiming. All pages are rotated
* regardless of classzone as this is about consistent aging.
*/
kswapd_age_node(pgdat, &sc);
/*
* If we're getting trouble reclaiming, start doing writepage
* even in laptop mode.
*/
if (sc.priority < DEF_PRIORITY - 2)
sc.may_writepage = 1;
/* Call soft limit reclaim before calling shrink_node. */
sc.nr_scanned = 0;
nr_soft_scanned = 0;
nr_soft_reclaimed = memcg1_soft_limit_reclaim(pgdat, sc.order,
sc.gfp_mask, &nr_soft_scanned);
sc.nr_reclaimed += nr_soft_reclaimed;
/*
* There should be no need to raise the scanning priority if
* enough pages are already being scanned that that high
* watermark would be met at 100% efficiency.
*/
if (kswapd_shrink_node(pgdat, &sc))
raise_priority = false;
/*
* If the low watermark is met there is no need for processes
* to be throttled on pfmemalloc_wait as they should not be
* able to safely make forward progress. Wake them
*/
if (waitqueue_active(&pgdat->pfmemalloc_wait) &&
allow_direct_reclaim(pgdat))
wake_up_all(&pgdat->pfmemalloc_wait);
/* Check if kswapd should be suspending */
__fs_reclaim_release(_THIS_IP_);
ret = kthread_freezable_should_stop(&was_frozen);
__fs_reclaim_acquire(_THIS_IP_);
if (was_frozen || ret)
break;
/*
* Raise priority if scanning rate is too low or there was no
* progress in reclaiming pages
*/
nr_reclaimed = sc.nr_reclaimed - nr_reclaimed;
nr_boost_reclaim -= min(nr_boost_reclaim, nr_reclaimed);
/*
* If reclaim made no progress for a boost, stop reclaim as
* IO cannot be queued and it could be an infinite loop in
* extreme circumstances.
*/
if (nr_boost_reclaim && !nr_reclaimed)
break;
if (raise_priority || !nr_reclaimed)
sc.priority--;
} while (sc.priority >= 1);
/*
* Restart only if it went through the priority loop all the way,
* but cache_trim_mode didn't work.
*/
if (!sc.nr_reclaimed && sc.priority < 1 &&
!sc.no_cache_trim_mode && sc.cache_trim_mode_failed) {
sc.no_cache_trim_mode = 1;
goto restart;
}
if (!sc.nr_reclaimed)
pgdat->kswapd_failures++;
out:
clear_reclaim_active(pgdat, highest_zoneidx);
/* If reclaim was boosted, account for the reclaim done in this pass */
if (boosted) {
unsigned long flags;
for (i = 0; i <= highest_zoneidx; i++) {
if (!zone_boosts[i])
continue;
/* Increments are under the zone lock */
zone = pgdat->node_zones + i;
spin_lock_irqsave(&zone->lock, flags);
zone->watermark_boost -= min(zone->watermark_boost, zone_boosts[i]);
spin_unlock_irqrestore(&zone->lock, flags);
}
/*
* As there is now likely space, wakeup kcompact to defragment
* pageblocks.
*/
wakeup_kcompactd(pgdat, pageblock_order, highest_zoneidx);
}
snapshot_refaults(NULL, pgdat);
__fs_reclaim_release(_THIS_IP_);
psi_memstall_leave(&pflags);
set_task_reclaim_state(current, NULL);
/*
* Return the order kswapd stopped reclaiming at as
* prepare_kswapd_sleep() takes it into account. If another caller
* entered the allocator slow path while kswapd was awake, order will
* remain at the higher level.
*/
return sc.order;
}
참고 자료
- Lorenzo Stoakes, The Linux Memory Manager, Early Access 2025, Chapter 11, Indirect Reclaim.
- Linux v6.18 mm/vmscan.c: kswapd 초기화·wakeup·sleep·balance 경로.
- Linux v6.12 mm/page_alloc.c:
wake_all_kswapds()와 allocation 측 요청.