LRU에 있는 folio를 움직이는 API들은 이름이 비슷하지만 목적이 다릅니다. activation은 보호를 강화하고, rotation은 다음 reclaim이 볼 위치를 바꾸며, lazy-free는 내용 보존의 계약을 바꿉니다. 실제 list 수정이 batch drain에서 일어난다는 점까지 함께 살펴보겠습니다.
Activation은 evictable inactive folio를 보호합니다
folio_activate()는 아직 active가 아니고 unevictable도 아닌 LRU folio를 대상으로 합니다. enqueue 전에 reference를 얻고 local lock 아래 activation batch에 넣습니다. callback은 shared lruvec lock 아래 상태를 다시 검사하고 기존 list에서 제거한 뒤 active 상태로 재삽입합니다.
local lock이 있다는 사실만으로 다른 CPU가 folio 상태를 바꾸지 못하는 것은 아닙니다. local lock은 producer의 CPU-local batch를 보호하고, shared LRU 이동은 lruvec lock과 state-bit protocol이 보호합니다. callback이 조건을 다시 검사하는 이유입니다. folio_mark_accessed()가 사용하는 software activation과 reclaim의 reference 판정도 서로 다른 caller입니다.
Rotation은 writeback 뒤 재검사 기회를 앞당깁니다
folio_end_writeback()는 reclaim 표시가 있는 folio에 대해 folio_rotate_reclaimable()을 고려합니다. 그러나 모든 writeback folio를 무조건 옮기지 않습니다. v6.18에서는 unlocked·clean·evictable·LRU 상태를 확인하고 IRQ-protected rotation batch를 사용합니다. active 자체는 진입 금지 조건이 아니며 뒤의 callback이 active를 clear할 수 있습니다.
callback도 상태를 다시 확인하여 가능한 항목을 inactive tail로 이동합니다. tail은 reclaim이 먼저 볼 위치입니다. 여기서 '빨리 회수한다'는 것은 검사 순서를 앞당긴다는 뜻이지 callback이 physical page를 free한다는 뜻이 아닙니다. 다시 dirty해지거나 pin이 남으면 이후 회수는 실패할 수 있습니다.
일반 deactivation과 file deactivation은 목적이 다릅니다
deactivate_page() 계열의 일반 deactivation은 active 보호를 줄이는 동작입니다. active·evictable 상태를 확인하고 batch를 거쳐 active와 referenced 상태를 정리한 뒤 inactive로 옮깁니다. DAMON이나 MADV_COLD·MADV_PAGEOUT 관련 경로처럼 사용자가 덜 중요한 내용을 알려 주는 기능과 연결될 수 있습니다.
file deactivation은 invalidation과 writeback 상태를 더 세밀하게 다룹니다. deactivate_file_folio()의 callback은 unevictable이거나 user mapping이 있는 항목을 그대로 처리하지 않습니다. unmapped file folio의 active·referenced를 정리하되 dirty·writeback 중이면 reclaim 표시를 남겨 완료 후 재검사하도록 하고, 이미 clean하면 inactive tail로 보낼 수 있습니다.
dirty folio를 clean folio처럼 단순히 tail에 넣으면 I/O가 끝나기 전에 반복 scan할 수 있습니다. 반대로 dirty라는 이유로 영구 보호할 필요도 없습니다. 두 상황을 나누는 것이 file-specific callback의 역할입니다.
고전 lru_deactivate_file_fn()에서 dirty·writeback 후보는 inactive head에 다시 넣고 PG_reclaim을 설정합니다. flusher가 작업할 시간을 주기 위해서입니다. batch에 머무는 사이 writeback이 끝나 clean이 되었다면 inactive tail로 보내고 PGROTATED를 셉니다. 원래 active였을 때만 PGDEACTIVATE를 증가시키므로 원래 inactive였던 후보의 재배치를 activation 반대 사건으로 중복 계산하지 않습니다. 이 callback은 shared LRU에서 folio를 제거하는 것이지 CPU-local batch 배열에서 제거하는 함수가 아닙니다.
Lazy-free는 swap-out 예약과 반대 방향입니다
MADV_FREE는 해당 anonymous 내용을 pressure 시 보존하지 않고 버려도 된다는 힌트입니다. mark_page_lazyfree()는 LRU·anon·swap-backed·non-swapcache·evictable 조건을 확인해 lazy-free 작업을 enqueue합니다. 이미 swap cache에 연결된 folio를 같은 방식으로 바꾸면 backing과 accounting이 어긋날 수 있습니다.
callback은 lruvec lock 아래 조건을 재확인하고 active·referenced와 swap-backed 성격을 정리합니다. 그 결과 LRU family 판정상 inactive file 쪽으로 분류될 수 있지만, file data로 바뀐 것은 아닙니다. folio_test_anon()인 lazy-free folio라는 성격은 reclaim에서 별도로 확인해야 합니다.
사용자가 다시 write한 내용과 racing할 수 있으므로 실제 폐기는 뒤의 reclaim에서 dirty·reference 조건을 확인합니다. MADV_FREE 호출이 그 순간 RSS를 반드시 줄인다는 뜻도 아니고, swap slot을 새로 예약하라는 뜻도 아닙니다.
여기서 PG_referenced clear는 refcount 감소와 다릅니다. lazy-free callback은 최근 사용의 software flag와 swap-backed 분류를 바꾸고, pending reference의 반환은 batch drain이 끝난 뒤 공통 경로가 맡습니다. 이 흐름은 MADV_FREE의 계약이며 MADV_DONTNEED의 즉시 내용 포기 경로와 같은 동작으로 설명하면 안 됩니다.
mlock은 별도 batch protocol을 사용합니다
mlock된 folio는 ordinary reclaim 후보에서 제외해야 합니다. mlock batch는 작업 종류를 pointer의 정렬상 비어 있는 하위 bit에 tag로 담는 구현을 사용합니다. LRU에 있던 folio를 lock할지, 새 folio를 처리할지, unlock할지 구분한 뒤 drain에서 tag를 해석합니다. 이런 tagged pointer는 실제 folio address로 사용하기 전에 tag를 분리해야 합니다.
lock 작업은 PG_lru ownership을 확보하고 올바른 lruvec lock 아래 현재 evictability를 확인합니다. 그 사이 다른 CPU가 mlock을 해제했다면 unevictable에 고정하지 않고 구조를 복구해야 합니다. 이미 mlocked·unevictable이면 중복 mapping에 대한 mlock_count 등 accounting이 중요합니다. 일반 경로에서는 기존 evictable list에서 빼고 active를 정리하여 unevictable로 분류합니다.
munlock은 무조건 inactive list에 넣는 단순 역연산이 아닙니다. 다른 locked mapping이나 mapping 자체의 unevictable 성격이 남았는지 확인해야 합니다. 상태가 달라진 folio를 rescue하여 evictable list로 되돌리는 예외도 필요합니다.
mlock producer는 local batch lock 아래 flag·통계를 갱신하고 reference를 얻어 enqueue합니다. full batch, large folio, LRU cache disabled 상태에서는 drain하며, drain은 작업 후 pending reference와 batch 상태를 정리합니다. PG_mlocked는 folio data lock을 잡았다는 뜻이 아니므로 두 종류의 'lock'을 혼동하지 않아야 합니다.
mlock의 세 producer는 flag를 바꾸는 시점도 다릅니다
고전 mlock_folio()는 이미 LRU에 있거나 일시적으로 isolate된 folio를 대상으로 합니다. test_set_mlocked가 처음 성공한 경우에만 NR_MLOCK과 PGMLOCKED를 더하고, reference를 얻어 LRU_PAGE tag가 붙은 pointer를 enqueue합니다. mlock_new_page()는 아직 LRU에 없는 새 head page라는 전제로 mlocked와 통계를 설정하고 NEW_PAGE tag를 사용합니다.
반대로 munlock_page()는 enqueue 시점에 PG_mlocked를 무조건 clear하지 않습니다. 여러 locked mapping이 같은 folio를 가리킬 수 있으므로 count 검사와 clear는 __munlock_page()에 남깁니다. untagged pointer가 unlock 작업을 나타내며, 모든 producer가 bit를 즉시 set 또는 clear한다는 공통 설명은 맞지 않습니다.
drain의 mlock_pagevec()는 두 하위 tag bit를 분리하고 배열 항목도 원래 page pointer로 되돌린 뒤 세 helper 중 하나를 호출합니다. 마지막에는 held lruvec lock을 풀고 release_pages()로 pending reference를 반환한 후 batch를 비웁니다. tag를 제거하지 않은 pointer를 release하는 경로가 아닙니다.
기존 page의 __mlock_page()는 LRU bit의 test-and-clear에 실패하면 더 할 수 있는 일이 없어 현재 lruvec을 반환합니다. 성공했는데 이미 evictable로 바뀌었다면 unevictable 표시를 rescue할 수 있습니다. 이미 unevictable·mlocked인 경우에는 count를 늘리고, 일반 경우에는 active를 지우고 mlock_count를 현재 mlocked 여부로 초기화합니다. 새 page용 helper는 아직 LRU가 아니라는 assertion 아래 이 복잡한 분기를 줄인 형태입니다.
__munlock_page()는 LRU ownership을 확보했는지와 unevictable count가 남았는지에 따라 처리가 다릅니다. count가 남으면 mlocked를 유지합니다. 마지막으로 판단되면 mlocked를 clear하고 통계를 줄이며, 그 뒤에야 page_evictable()을 확인합니다. mapping 자체의 unevictable 성격이 남으면 rescue하지 않습니다. count가 보수적으로 작을 수 있어 뒤의 reclaim이 보정한다는 구현의 전제도 있어, 이를 user mapping 수와 항상 정확히 일치하는 일반 refcount로 사용할 수는 없습니다.
전체 code로 ownership과 종료 경로를 확인합니다
v6.0 전체 정의에서 producer가 pending reference를 얻는 지점과 callback이 shared LRU를 바꾸는 지점을 나누어 보세요. SMP·UP activation은 서로 다른 정의이며 mlock은 별도 tagged-pointer protocol입니다. 최신 helper의 이름과 구조가 그대로 같다는 의미는 아닙니다.
v6.0 folio_activate (SMP)
// SPDX-License-Identifier: GPL-2.0-only
/*
* linux/mm/swap.c
*
* Copyright (C) 1991, 1992, 1993, 1994 Linus Torvalds
*/
/*
* This file contains the default values for the operation of the
* Linux VM subsystem. Fine-tuning documentation can be found in
* Documentation/admin-guide/sysctl/vm.rst.
* Started 18.12.91
* Swap aging added 23.2.95, Stephen Tweedie.
* Buffermem limits added 12.3.98, Rik van Riel.
*/
/* Extracted unchanged from Linux v6.0, mm/swap.c:369-381.
* Complete function definition; surrounding kernel context is required.
* Source: https://github.com/torvalds/linux/blob/v6.0/mm/swap.c#L369
* License text: GPL-2.0.txt (in this directory).
*/
static void folio_activate(struct folio *folio)
{
if (folio_test_lru(folio) && !folio_test_active(folio) &&
!folio_test_unevictable(folio)) {
struct folio_batch *fbatch;
folio_get(folio);
local_lock(&cpu_fbatches.lock);
fbatch = this_cpu_ptr(&cpu_fbatches.activate);
folio_batch_add_and_move(fbatch, folio, folio_activate_fn);
local_unlock(&cpu_fbatches.lock);
}
}
v6.0 folio_activate (UP)
// SPDX-License-Identifier: GPL-2.0-only
/*
* linux/mm/swap.c
*
* Copyright (C) 1991, 1992, 1993, 1994 Linus Torvalds
*/
/*
* This file contains the default values for the operation of the
* Linux VM subsystem. Fine-tuning documentation can be found in
* Documentation/admin-guide/sysctl/vm.rst.
* Started 18.12.91
* Swap aging added 23.2.95, Stephen Tweedie.
* Buffermem limits added 12.3.98, Rik van Riel.
*/
/* Extracted unchanged from Linux v6.0, mm/swap.c:388-398.
* Complete function definition; surrounding kernel context is required.
* Source: https://github.com/torvalds/linux/blob/v6.0/mm/swap.c#L388
* License text: GPL-2.0.txt (in this directory).
*/
static void folio_activate(struct folio *folio)
{
struct lruvec *lruvec;
if (folio_test_clear_lru(folio)) {
lruvec = folio_lruvec_lock_irq(folio);
folio_activate_fn(lruvec, folio);
unlock_page_lruvec_irq(lruvec);
folio_set_lru(folio);
}
}
v6.0 folio_activate_fn
// SPDX-License-Identifier: GPL-2.0-only
/*
* linux/mm/swap.c
*
* Copyright (C) 1991, 1992, 1993, 1994 Linus Torvalds
*/
/*
* This file contains the default values for the operation of the
* Linux VM subsystem. Fine-tuning documentation can be found in
* Documentation/admin-guide/sysctl/vm.rst.
* Started 18.12.91
* Swap aging added 23.2.95, Stephen Tweedie.
* Buffermem limits added 12.3.98, Rik van Riel.
*/
/* Extracted unchanged from Linux v6.0, mm/swap.c:344-358.
* Complete function definition; surrounding kernel context is required.
* Source: https://github.com/torvalds/linux/blob/v6.0/mm/swap.c#L344
* License text: GPL-2.0.txt (in this directory).
*/
static void folio_activate_fn(struct lruvec *lruvec, struct folio *folio)
{
if (!folio_test_active(folio) && !folio_test_unevictable(folio)) {
long nr_pages = folio_nr_pages(folio);
lruvec_del_folio(lruvec, folio);
folio_set_active(folio);
lruvec_add_folio(lruvec, folio);
trace_mm_lru_activate(folio);
__count_vm_events(PGACTIVATE, nr_pages);
__count_memcg_events(lruvec_memcg(lruvec), PGACTIVATE,
nr_pages);
}
}
v6.0 folio_rotate_reclaimable
v6_0_folio_rotate_reclaimable.c
// SPDX-License-Identifier: GPL-2.0-only
/*
* linux/mm/swap.c
*
* Copyright (C) 1991, 1992, 1993, 1994 Linus Torvalds
*/
/*
* This file contains the default values for the operation of the
* Linux VM subsystem. Fine-tuning documentation can be found in
* Documentation/admin-guide/sysctl/vm.rst.
* Started 18.12.91
* Swap aging added 23.2.95, Stephen Tweedie.
* Buffermem limits added 12.3.98, Rik van Riel.
*/
/* Extracted unchanged from Linux v6.0, mm/swap.c:283-296.
* Complete function definition; surrounding kernel context is required.
* Source: https://github.com/torvalds/linux/blob/v6.0/mm/swap.c#L283
* License text: GPL-2.0.txt (in this directory).
*/
void folio_rotate_reclaimable(struct folio *folio)
{
if (!folio_test_locked(folio) && !folio_test_dirty(folio) &&
!folio_test_unevictable(folio) && folio_test_lru(folio)) {
struct folio_batch *fbatch;
unsigned long flags;
folio_get(folio);
local_lock_irqsave(&lru_rotate.lock, flags);
fbatch = this_cpu_ptr(&lru_rotate.fbatch);
folio_batch_add_and_move(fbatch, folio, lru_move_tail_fn);
local_unlock_irqrestore(&lru_rotate.lock, flags);
}
}
v6.0 lru_move_tail_fn
// SPDX-License-Identifier: GPL-2.0-only
/*
* linux/mm/swap.c
*
* Copyright (C) 1991, 1992, 1993, 1994 Linus Torvalds
*/
/*
* This file contains the default values for the operation of the
* Linux VM subsystem. Fine-tuning documentation can be found in
* Documentation/admin-guide/sysctl/vm.rst.
* Started 18.12.91
* Swap aging added 23.2.95, Stephen Tweedie.
* Buffermem limits added 12.3.98, Rik van Riel.
*/
/* Extracted unchanged from Linux v6.0, mm/swap.c:266-274.
* Complete function definition; surrounding kernel context is required.
* Source: https://github.com/torvalds/linux/blob/v6.0/mm/swap.c#L266
* License text: GPL-2.0.txt (in this directory).
*/
static void lru_move_tail_fn(struct lruvec *lruvec, struct folio *folio)
{
if (!folio_test_unevictable(folio)) {
lruvec_del_folio(lruvec, folio);
folio_clear_active(folio);
lruvec_add_folio_tail(lruvec, folio);
__count_vm_events(PGROTATED, folio_nr_pages(folio));
}
}
v6.0 deactivate_page
// SPDX-License-Identifier: GPL-2.0-only
/*
* linux/mm/swap.c
*
* Copyright (C) 1991, 1992, 1993, 1994 Linus Torvalds
*/
/*
* This file contains the default values for the operation of the
* Linux VM subsystem. Fine-tuning documentation can be found in
* Documentation/admin-guide/sysctl/vm.rst.
* Started 18.12.91
* Swap aging added 23.2.95, Stephen Tweedie.
* Buffermem limits added 12.3.98, Rik van Riel.
*/
/* Extracted unchanged from Linux v6.0, mm/swap.c:687-701.
* Complete function definition; surrounding kernel context is required.
* Source: https://github.com/torvalds/linux/blob/v6.0/mm/swap.c#L687
* License text: GPL-2.0.txt (in this directory).
*/
void deactivate_page(struct page *page)
{
struct folio *folio = page_folio(page);
if (folio_test_lru(folio) && folio_test_active(folio) &&
!folio_test_unevictable(folio)) {
struct folio_batch *fbatch;
folio_get(folio);
local_lock(&cpu_fbatches.lock);
fbatch = this_cpu_ptr(&cpu_fbatches.lru_deactivate);
folio_batch_add_and_move(fbatch, folio, lru_deactivate_fn);
local_unlock(&cpu_fbatches.lock);
}
}
v6.0 lru_deactivate_fn
// SPDX-License-Identifier: GPL-2.0-only
/*
* linux/mm/swap.c
*
* Copyright (C) 1991, 1992, 1993, 1994 Linus Torvalds
*/
/*
* This file contains the default values for the operation of the
* Linux VM subsystem. Fine-tuning documentation can be found in
* Documentation/admin-guide/sysctl/vm.rst.
* Started 18.12.91
* Swap aging added 23.2.95, Stephen Tweedie.
* Buffermem limits added 12.3.98, Rik van Riel.
*/
/* Extracted unchanged from Linux v6.0, mm/swap.c:576-590.
* Complete function definition; surrounding kernel context is required.
* Source: https://github.com/torvalds/linux/blob/v6.0/mm/swap.c#L576
* License text: GPL-2.0.txt (in this directory).
*/
static void lru_deactivate_fn(struct lruvec *lruvec, struct folio *folio)
{
if (folio_test_active(folio) && !folio_test_unevictable(folio)) {
long nr_pages = folio_nr_pages(folio);
lruvec_del_folio(lruvec, folio);
folio_clear_active(folio);
folio_clear_referenced(folio);
lruvec_add_folio(lruvec, folio);
__count_vm_events(PGDEACTIVATE, nr_pages);
__count_memcg_events(lruvec_memcg(lruvec), PGDEACTIVATE,
nr_pages);
}
}
v6.0 deactivate_file_folio
// SPDX-License-Identifier: GPL-2.0-only
/*
* linux/mm/swap.c
*
* Copyright (C) 1991, 1992, 1993, 1994 Linus Torvalds
*/
/*
* This file contains the default values for the operation of the
* Linux VM subsystem. Fine-tuning documentation can be found in
* Documentation/admin-guide/sysctl/vm.rst.
* Started 18.12.91
* Swap aging added 23.2.95, Stephen Tweedie.
* Buffermem limits added 12.3.98, Rik van Riel.
*/
/* Extracted unchanged from Linux v6.0, mm/swap.c:664-677.
* Complete function definition; surrounding kernel context is required.
* Source: https://github.com/torvalds/linux/blob/v6.0/mm/swap.c#L664
* License text: GPL-2.0.txt (in this directory).
*/
void deactivate_file_folio(struct folio *folio)
{
struct folio_batch *fbatch;
/* Deactivating an unevictable folio will not accelerate reclaim */
if (folio_test_unevictable(folio))
return;
folio_get(folio);
local_lock(&cpu_fbatches.lock);
fbatch = this_cpu_ptr(&cpu_fbatches.lru_deactivate_file);
folio_batch_add_and_move(fbatch, folio, lru_deactivate_file_fn);
local_unlock(&cpu_fbatches.lock);
}
v6.0 lru_deactivate_file_fn
// SPDX-License-Identifier: GPL-2.0-only
/*
* linux/mm/swap.c
*
* Copyright (C) 1991, 1992, 1993, 1994 Linus Torvalds
*/
/*
* This file contains the default values for the operation of the
* Linux VM subsystem. Fine-tuning documentation can be found in
* Documentation/admin-guide/sysctl/vm.rst.
* Started 18.12.91
* Swap aging added 23.2.95, Stephen Tweedie.
* Buffermem limits added 12.3.98, Rik van Riel.
*/
/* Extracted unchanged from Linux v6.0, mm/swap.c:535-574.
* Complete function definition; surrounding kernel context is required.
* Source: https://github.com/torvalds/linux/blob/v6.0/mm/swap.c#L535
* License text: GPL-2.0.txt (in this directory).
*/
static void lru_deactivate_file_fn(struct lruvec *lruvec, struct folio *folio)
{
bool active = folio_test_active(folio);
long nr_pages = folio_nr_pages(folio);
if (folio_test_unevictable(folio))
return;
/* Some processes are using the folio */
if (folio_mapped(folio))
return;
lruvec_del_folio(lruvec, folio);
folio_clear_active(folio);
folio_clear_referenced(folio);
if (folio_test_writeback(folio) || folio_test_dirty(folio)) {
/*
* Setting the reclaim flag could race with
* folio_end_writeback() and confuse readahead. But the
* race window is _really_ small and it's not a critical
* problem.
*/
lruvec_add_folio(lruvec, folio);
folio_set_reclaim(folio);
} else {
/*
* The folio's writeback ended while it was in the batch.
* We move that folio to the tail of the inactive list.
*/
lruvec_add_folio_tail(lruvec, folio);
__count_vm_events(PGROTATED, nr_pages);
}
if (active) {
__count_vm_events(PGDEACTIVATE, nr_pages);
__count_memcg_events(lruvec_memcg(lruvec), PGDEACTIVATE,
nr_pages);
}
}
v6.0 mark_page_lazyfree
// SPDX-License-Identifier: GPL-2.0-only
/*
* linux/mm/swap.c
*
* Copyright (C) 1991, 1992, 1993, 1994 Linus Torvalds
*/
/*
* This file contains the default values for the operation of the
* Linux VM subsystem. Fine-tuning documentation can be found in
* Documentation/admin-guide/sysctl/vm.rst.
* Started 18.12.91
* Swap aging added 23.2.95, Stephen Tweedie.
* Buffermem limits added 12.3.98, Rik van Riel.
*/
/* Extracted unchanged from Linux v6.0, mm/swap.c:710-725.
* Complete function definition; surrounding kernel context is required.
* Source: https://github.com/torvalds/linux/blob/v6.0/mm/swap.c#L710
* License text: GPL-2.0.txt (in this directory).
*/
void mark_page_lazyfree(struct page *page)
{
struct folio *folio = page_folio(page);
if (folio_test_lru(folio) && folio_test_anon(folio) &&
folio_test_swapbacked(folio) && !folio_test_swapcache(folio) &&
!folio_test_unevictable(folio)) {
struct folio_batch *fbatch;
folio_get(folio);
local_lock(&cpu_fbatches.lock);
fbatch = this_cpu_ptr(&cpu_fbatches.lru_lazyfree);
folio_batch_add_and_move(fbatch, folio, lru_lazyfree_fn);
local_unlock(&cpu_fbatches.lock);
}
}
v6.0 lru_lazyfree_fn
// SPDX-License-Identifier: GPL-2.0-only
/*
* linux/mm/swap.c
*
* Copyright (C) 1991, 1992, 1993, 1994 Linus Torvalds
*/
/*
* This file contains the default values for the operation of the
* Linux VM subsystem. Fine-tuning documentation can be found in
* Documentation/admin-guide/sysctl/vm.rst.
* Started 18.12.91
* Swap aging added 23.2.95, Stephen Tweedie.
* Buffermem limits added 12.3.98, Rik van Riel.
*/
/* Extracted unchanged from Linux v6.0, mm/swap.c:592-613.
* Complete function definition; surrounding kernel context is required.
* Source: https://github.com/torvalds/linux/blob/v6.0/mm/swap.c#L592
* License text: GPL-2.0.txt (in this directory).
*/
static void lru_lazyfree_fn(struct lruvec *lruvec, struct folio *folio)
{
if (folio_test_anon(folio) && folio_test_swapbacked(folio) &&
!folio_test_swapcache(folio) && !folio_test_unevictable(folio)) {
long nr_pages = folio_nr_pages(folio);
lruvec_del_folio(lruvec, folio);
folio_clear_active(folio);
folio_clear_referenced(folio);
/*
* Lazyfree folios are clean anonymous folios. They have
* the swapbacked flag cleared, to distinguish them from normal
* anonymous folios
*/
folio_clear_swapbacked(folio);
lruvec_add_folio(lruvec, folio);
__count_vm_events(PGLAZYFREE, nr_pages);
__count_memcg_events(lruvec_memcg(lruvec), PGLAZYFREE,
nr_pages);
}
}
v6.0 __mlock_page
// SPDX-License-Identifier: GPL-2.0
/*
* linux/mm/mlock.c
*
* (C) Copyright 1995 Linus Torvalds
* (C) Copyright 2002 Christoph Hellwig
*/
/* Extracted unchanged from Linux v6.0, mm/mlock.c:61-101.
* Complete function definition; surrounding kernel context is required.
* Source: https://github.com/torvalds/linux/blob/v6.0/mm/mlock.c#L61
* License text: GPL-2.0.txt (in this directory).
*/
static struct lruvec *__mlock_page(struct page *page, struct lruvec *lruvec)
{
/* There is nothing more we can do while it's off LRU */
if (!TestClearPageLRU(page))
return lruvec;
lruvec = folio_lruvec_relock_irq(page_folio(page), lruvec);
if (unlikely(page_evictable(page))) {
/*
* This is a little surprising, but quite possible:
* PageMlocked must have got cleared already by another CPU.
* Could this page be on the Unevictable LRU? I'm not sure,
* but move it now if so.
*/
if (PageUnevictable(page)) {
del_page_from_lru_list(page, lruvec);
ClearPageUnevictable(page);
add_page_to_lru_list(page, lruvec);
__count_vm_events(UNEVICTABLE_PGRESCUED,
thp_nr_pages(page));
}
goto out;
}
if (PageUnevictable(page)) {
if (PageMlocked(page))
page->mlock_count++;
goto out;
}
del_page_from_lru_list(page, lruvec);
ClearPageActive(page);
SetPageUnevictable(page);
page->mlock_count = !!PageMlocked(page);
add_page_to_lru_list(page, lruvec);
__count_vm_events(UNEVICTABLE_PGCULLED, thp_nr_pages(page));
out:
SetPageLRU(page);
return lruvec;
}
v6.0 __mlock_new_page
// SPDX-License-Identifier: GPL-2.0
/*
* linux/mm/mlock.c
*
* (C) Copyright 1995 Linus Torvalds
* (C) Copyright 2002 Christoph Hellwig
*/
/* Extracted unchanged from Linux v6.0, mm/mlock.c:103-120.
* Complete function definition; surrounding kernel context is required.
* Source: https://github.com/torvalds/linux/blob/v6.0/mm/mlock.c#L103
* License text: GPL-2.0.txt (in this directory).
*/
static struct lruvec *__mlock_new_page(struct page *page, struct lruvec *lruvec)
{
VM_BUG_ON_PAGE(PageLRU(page), page);
lruvec = folio_lruvec_relock_irq(page_folio(page), lruvec);
/* As above, this is a little surprising, but possible */
if (unlikely(page_evictable(page)))
goto out;
SetPageUnevictable(page);
page->mlock_count = !!PageMlocked(page);
__count_vm_events(UNEVICTABLE_PGCULLED, thp_nr_pages(page));
out:
add_page_to_lru_list(page, lruvec);
SetPageLRU(page);
return lruvec;
}
v6.0 __munlock_page
// SPDX-License-Identifier: GPL-2.0
/*
* linux/mm/mlock.c
*
* (C) Copyright 1995 Linus Torvalds
* (C) Copyright 2002 Christoph Hellwig
*/
/* Extracted unchanged from Linux v6.0, mm/mlock.c:122-162.
* Complete function definition; surrounding kernel context is required.
* Source: https://github.com/torvalds/linux/blob/v6.0/mm/mlock.c#L122
* License text: GPL-2.0.txt (in this directory).
*/
static struct lruvec *__munlock_page(struct page *page, struct lruvec *lruvec)
{
int nr_pages = thp_nr_pages(page);
bool isolated = false;
if (!TestClearPageLRU(page))
goto munlock;
isolated = true;
lruvec = folio_lruvec_relock_irq(page_folio(page), lruvec);
if (PageUnevictable(page)) {
/* Then mlock_count is maintained, but might undercount */
if (page->mlock_count)
page->mlock_count--;
if (page->mlock_count)
goto out;
}
/* else assume that was the last mlock: reclaim will fix it if not */
munlock:
if (TestClearPageMlocked(page)) {
__mod_zone_page_state(page_zone(page), NR_MLOCK, -nr_pages);
if (isolated || !PageUnevictable(page))
__count_vm_events(UNEVICTABLE_PGMUNLOCKED, nr_pages);
else
__count_vm_events(UNEVICTABLE_PGSTRANDED, nr_pages);
}
/* page_evictable() has to be checked *after* clearing Mlocked */
if (isolated && PageUnevictable(page) && page_evictable(page)) {
del_page_from_lru_list(page, lruvec);
ClearPageUnevictable(page);
add_page_to_lru_list(page, lruvec);
__count_vm_events(UNEVICTABLE_PGRESCUED, nr_pages);
}
out:
if (isolated)
SetPageLRU(page);
return lruvec;
}
v6.0 mlock_pagevec
// SPDX-License-Identifier: GPL-2.0
/*
* linux/mm/mlock.c
*
* (C) Copyright 1995 Linus Torvalds
* (C) Copyright 2002 Christoph Hellwig
*/
/* Extracted unchanged from Linux v6.0, mm/mlock.c:186-211.
* Complete function definition; surrounding kernel context is required.
* Source: https://github.com/torvalds/linux/blob/v6.0/mm/mlock.c#L186
* License text: GPL-2.0.txt (in this directory).
*/
static void mlock_pagevec(struct pagevec *pvec)
{
struct lruvec *lruvec = NULL;
unsigned long mlock;
struct page *page;
int i;
for (i = 0; i < pagevec_count(pvec); i++) {
page = pvec->pages[i];
mlock = (unsigned long)page & (LRU_PAGE | NEW_PAGE);
page = (struct page *)((unsigned long)page - mlock);
pvec->pages[i] = page;
if (mlock & LRU_PAGE)
lruvec = __mlock_page(page, lruvec);
else if (mlock & NEW_PAGE)
lruvec = __mlock_new_page(page, lruvec);
else
lruvec = __munlock_page(page, lruvec);
}
if (lruvec)
unlock_page_lruvec_irq(lruvec);
release_pages(pvec->pages, pvec->nr);
pagevec_reinit(pvec);
}
v6.0 mlock_folio
// SPDX-License-Identifier: GPL-2.0
/*
* linux/mm/mlock.c
*
* (C) Copyright 1995 Linus Torvalds
* (C) Copyright 2002 Christoph Hellwig
*/
/* Extracted unchanged from Linux v6.0, mm/mlock.c:243-262.
* Complete function definition; surrounding kernel context is required.
* Source: https://github.com/torvalds/linux/blob/v6.0/mm/mlock.c#L243
* License text: GPL-2.0.txt (in this directory).
*/
void mlock_folio(struct folio *folio)
{
struct pagevec *pvec;
local_lock(&mlock_pvec.lock);
pvec = this_cpu_ptr(&mlock_pvec.vec);
if (!folio_test_set_mlocked(folio)) {
int nr_pages = folio_nr_pages(folio);
zone_stat_mod_folio(folio, NR_MLOCK, nr_pages);
__count_vm_events(UNEVICTABLE_PGMLOCKED, nr_pages);
}
folio_get(folio);
if (!pagevec_add(pvec, mlock_lru(&folio->page)) ||
folio_test_large(folio) || lru_cache_disabled())
mlock_pagevec(pvec);
local_unlock(&mlock_pvec.lock);
}
v6.0 mlock_new_page
// SPDX-License-Identifier: GPL-2.0
/*
* linux/mm/mlock.c
*
* (C) Copyright 1995 Linus Torvalds
* (C) Copyright 2002 Christoph Hellwig
*/
/* Extracted unchanged from Linux v6.0, mm/mlock.c:268-284.
* Complete function definition; surrounding kernel context is required.
* Source: https://github.com/torvalds/linux/blob/v6.0/mm/mlock.c#L268
* License text: GPL-2.0.txt (in this directory).
*/
void mlock_new_page(struct page *page)
{
struct pagevec *pvec;
int nr_pages = thp_nr_pages(page);
local_lock(&mlock_pvec.lock);
pvec = this_cpu_ptr(&mlock_pvec.vec);
SetPageMlocked(page);
mod_zone_page_state(page_zone(page), NR_MLOCK, nr_pages);
__count_vm_events(UNEVICTABLE_PGMLOCKED, nr_pages);
get_page(page);
if (!pagevec_add(pvec, mlock_new(page)) ||
PageHead(page) || lru_cache_disabled())
mlock_pagevec(pvec);
local_unlock(&mlock_pvec.lock);
}
v6.0 munlock_page
// SPDX-License-Identifier: GPL-2.0
/*
* linux/mm/mlock.c
*
* (C) Copyright 1995 Linus Torvalds
* (C) Copyright 2002 Christoph Hellwig
*/
/* Extracted unchanged from Linux v6.0, mm/mlock.c:290-306.
* Complete function definition; surrounding kernel context is required.
* Source: https://github.com/torvalds/linux/blob/v6.0/mm/mlock.c#L290
* License text: GPL-2.0.txt (in this directory).
*/
void munlock_page(struct page *page)
{
struct pagevec *pvec;
local_lock(&mlock_pvec.lock);
pvec = this_cpu_ptr(&mlock_pvec.vec);
/*
* TestClearPageMlocked(page) must be left to __munlock_page(),
* which will check whether the page is multiply mlocked.
*/
get_page(page);
if (!pagevec_add(pvec, page) ||
PageHead(page) || lru_cache_disabled())
mlock_pagevec(pvec);
local_unlock(&mlock_pvec.lock);
}
v6.0 folio_end_writeback
// SPDX-License-Identifier: GPL-2.0-only
/*
* linux/mm/filemap.c
*
* Copyright (C) 1994-1999 Linus Torvalds
*/
/*
* This file handles the generic file mmap semantics used by
* most "normal" filesystems (but you don't /have/ to use this:
* the NFS filesystem used to do this differently, for example)
*/
/* Extracted unchanged from Linux v6.0, mm/filemap.c:1599-1627.
* Complete function definition; surrounding kernel context is required.
* Source: https://github.com/torvalds/linux/blob/v6.0/mm/filemap.c#L1599
* License text: GPL-2.0.txt (in this directory).
*/
void folio_end_writeback(struct folio *folio)
{
/*
* folio_test_clear_reclaim() could be used here but it is an
* atomic operation and overkill in this particular case. Failing
* to shuffle a folio marked for immediate reclaim is too mild
* a gain to justify taking an atomic operation penalty at the
* end of every folio writeback.
*/
if (folio_test_reclaim(folio)) {
folio_clear_reclaim(folio);
folio_rotate_reclaimable(folio);
}
/*
* Writeback does not hold a folio reference of its own, relying
* on truncation to wait for the clearing of PG_writeback.
* But here we must make sure that the folio is not freed and
* reused before the folio_wake().
*/
folio_get(folio);
if (!__folio_end_writeback(folio))
BUG();
smp_mb__after_atomic();
folio_wake(folio, PG_writeback);
acct_reclaim_writeback(folio);
folio_put(folio);
}
v6.0 mlock_batch_declaration
v6_0_mlock_batch_declaration.c
// SPDX-License-Identifier: GPL-2.0
/*
* linux/mm/mlock.c
*
* (C) Copyright 1995 Linus Torvalds
* (C) Copyright 2002 Christoph Hellwig
*/
/* Exact v6.0 mm/mlock.c:31-38 declaration/macro scope.
* Source: https://github.com/torvalds/linux/blob/v6.0/mm/mlock.c#L31
* License text: GPL-2.0.txt. Kernel context is required.
*/
struct mlock_pvec {
local_lock_t lock;
struct pagevec vec;
};
static DEFINE_PER_CPU(struct mlock_pvec, mlock_pvec) = {
.lock = INIT_LOCAL_LOCK(lock),
};
참고 자료
- Lorenzo Stoakes, The Linux Memory Manager, Early Access 2025, Chapter 11, Folio Activation through mlock Folio Batch.
- Linux v6.18 mm/swap.c: activation·rotation·deactivation·lazy-free.
- Linux v6.18 mm/mlock.c: mlock batch와 unevictable 상태.
- madvise(2), mlock(2).