anon을 조금, file을 많이 scan하기로 결정했어도 각 list에서 같은 batch를 한 번씩 처리하고 멈추면 결과는 1:1에 가까워질 수 있습니다. shrink_lruvec()은 목표 scan 비율과 실제 batch 처리를 연결하는 층입니다. 한 번 목표량을 계산하는 것만으로 끝나지 않는 이유를 살펴보겠습니다.
Node에서 list로 내려가는 중간 단계입니다
node reclaim은 memcg별 lruvec을 순회하고 shrink_lruvec()를 호출합니다. cgroup이 없더라도 node에 대응하는 lruvec은 필요합니다. 같은 경로에서 slab cache도 shrink할 수 있지만, slab object의 free와 LRU folio의 free는 서로 다른 subsystem의 결과입니다.
shrink_lruvec()은 get_scan_count()의 네 목표를 nr[]에 받고 원래 값은 targets[]에 남겨 둡니다. nr[]는 앞으로 scan할 양이므로 batch를 처리하면서 줄어듭니다. 원래 목표를 따로 보존해야 나중에 얼마만큼 비율을 따라갔는지 계산할 수 있습니다.
v6.12의 shrink_node_memcgs()는 direct reclaimer의 partial walk와 kswapd의 full walk도 구분합니다. kswapd 또는 memcg_full_walk이면 partial cookie를 사용하지 않습니다. memory.min 아래는 건너뛰고, memory.low 아래는 soft protection을 넘겨 재시도하는 경우가 아니면 건너뛰면서 기록을 남깁니다. eligible group에서 lruvec과 slab을 처리한 뒤 partial walk라면 회수 목표를 채웠을 때 iterator reference를 정리하고 종료할 수 있습니다.
v6.12 memcg 순회 · 전체 code · GPL-2.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.
*/
/* Extracted unchanged from Linux v6.12, mm/vmscan.c:5857-5934.
* Full function definition; surrounding kernel context is required.
* Source: https://github.com/torvalds/linux/blob/v6.12/mm/vmscan.c#L5857
* License text: GPL-2.0.txt (in this directory).
*/
static void shrink_node_memcgs(pg_data_t *pgdat, struct scan_control *sc)
{
struct mem_cgroup *target_memcg = sc->target_mem_cgroup;
struct mem_cgroup_reclaim_cookie reclaim = {
.pgdat = pgdat,
};
struct mem_cgroup_reclaim_cookie *partial = &reclaim;
struct mem_cgroup *memcg;
/*
* In most cases, direct reclaimers can do partial walks
* through the cgroup tree, using an iterator state that
* persists across invocations. This strikes a balance between
* fairness and allocation latency.
*
* For kswapd, reliable forward progress is more important
* than a quick return to idle. Always do full walks.
*/
if (current_is_kswapd() || sc->memcg_full_walk)
partial = NULL;
memcg = mem_cgroup_iter(target_memcg, NULL, partial);
do {
struct lruvec *lruvec = mem_cgroup_lruvec(memcg, pgdat);
unsigned long reclaimed;
unsigned long scanned;
/*
* This loop can become CPU-bound when target memcgs
* aren't eligible for reclaim - either because they
* don't have any reclaimable pages, or because their
* memory is explicitly protected. Avoid soft lockups.
*/
cond_resched();
mem_cgroup_calculate_protection(target_memcg, memcg);
if (mem_cgroup_below_min(target_memcg, memcg)) {
/*
* Hard protection.
* If there is no reclaimable memory, OOM.
*/
continue;
} else if (mem_cgroup_below_low(target_memcg, memcg)) {
/*
* Soft protection.
* Respect the protection only as long as
* there is an unprotected supply
* of reclaimable memory from other cgroups.
*/
if (!sc->memcg_low_reclaim) {
sc->memcg_low_skipped = 1;
continue;
}
memcg_memory_event(memcg, MEMCG_LOW);
}
reclaimed = sc->nr_reclaimed;
scanned = sc->nr_scanned;
shrink_lruvec(lruvec, sc);
shrink_slab(sc->gfp_mask, pgdat->node_id, memcg,
sc->priority);
/* Record the group's reclaim efficiency */
if (!sc->proactive)
vmpressure(sc->gfp_mask, memcg, false,
sc->nr_scanned - scanned,
sc->nr_reclaimed - reclaimed);
/* If partial walks are allowed, bail once goal is reached */
if (partial && sc->nr_reclaimed >= sc->nr_to_reclaim) {
mem_cgroup_iter_break(target_memcg, memcg);
break;
}
} while ((memcg = mem_cgroup_iter(target_memcg, memcg, partial)));
}
Batch를 보냈다는 것은 free했다는 뜻이 아닙니다
주 loop는 각 list를 최대 SWAP_CLUSTER_MAX 단위로 shrink_list()에 넘깁니다. 보낸 scan budget은 곧바로 nr[]에서 빼지만, 실제 회수량은 반환값을 별도로 더합니다. dirty·writeback·reference 때문에 한 장도 회수하지 못할 수 있습니다. active list를 처리하는 경우에는 deactivation이 주목적이므로 더욱 그렇습니다.
active anon만 남은 상황을 주 loop의 다른 세 list와 똑같이 취급하지 않는 점도 중요합니다. anon aging은 끝부분에서 inactive 부족 여부와 backing 조건을 보고 추가로 수행할 수 있습니다. cache trim 때문에 이번 scan에서 anon 목표가 작더라도 다음 reclaim을 위한 후보 공급은 유지해야 합니다.
global direct reclaim이 기본 priority에서 시작한 경우에는 작은 scan 목표를 끝까지 수행하는 특별 처리가 있습니다. 정확한 식은 !cgroup_reclaim(sc) && !current_is_kswapd() && priority == DEF_PRIORITY입니다. memcg direct reclaim까지 모두 포함하지 않습니다. 초기의 매우 작은 scan만으로는 allocation 압력을 따라가지 못해 direct reclaim으로 들어왔을 수 있으므로, 회수 목표를 채웠다고 너무 일찍 끝내지 않는 것입니다. 그러나 기본 priority에 있다는 관찰만으로 실제 system pressure가 반드시 낮다고 확정할 수는 없습니다.
같은 장수와 같은 진행률은 다릅니다
독립적인 숫자 예시를 들어 보겠습니다. anon inactive/active의 목표가 각각 64, file inactive/active의 목표가 각각 256이라면 family 목표는 anon 128, file 512로 20:80입니다. 각 list에서 32씩 scan하면 실제 검사량은 anon 64, file 64입니다. anon은 자기 목표의 50%를 끝냈지만 file은 12.5%밖에 끝내지 못했습니다.
회수 목표를 이미 달성했더라도 여기서 항상 멈추면 anon을 지나치게 자주 검사하는 결과가 됩니다. 반대로 더 많이 진행한 anon을 계속 scan하면 상대 비율을 회복하기 어렵습니다. 이미 scan한 page를 'unscan'할 수는 없으므로 덜 진행한 family에 추가 budget을 주는 방식으로 보정합니다.
비율만 연속적으로 계산하면 file도 목표의 50%까지 진행시킬 때 각 file list의 최종 scan은 128입니다. 이미 32씩 검사했으므로 96씩 추가하면 최종 anon 64, file 256으로 20:80이 됩니다. 이것은 목표 640장을 모두 검사한 것이 아니라 양쪽 목표를 같은 비율로 진행한 모델입니다. 실제 C code의 정수 나눗셈과 종료 조건을 반영한 결과와는 구분해야 합니다.
또한 첫 순회에서 네 list에 각각 32를 보냈다고 128 page를 회수한 것은 아닙니다. active 두 list의 shrink_list() 반환값은 0입니다. 두 inactive list의 후보가 모두 회수된다는 조건에서도 이때의 실제 회수량은 64입니다. 회수 목표가 128이라면 첫 순회 직후 보정 branch에 들어간다는 전제부터 맞지 않습니다.
보정은 한 번만 적용합니다
고전 v5.18 구현은 실제 회수량이 목표 이상이고 아직 scan_adjusted가 아니어야 보정에 들어갑니다. family별 active+inactive 잔여 budget을 비교하여 작은 쪽의 두 목표를 0으로 만듭니다. 그 family의 남은 비율은 remaining * 100 / (original_family_target + 1)로 구합니다. 다른 family의 각 list에는 target * (100-percentage)/100을 적용한 뒤 이미 소비한 budget을 빼며, min()으로 unsigned underflow를 막습니다. 한 family의 잔여량이 이미 0이면 더 큰 쪽을 무리하게 공격하지 않고 종료합니다.
보정의 의도는 비율을 회복하는 것이지만, 실제 family 선택식은 정규화된 진행률 둘의 비교가 아니라 nr_file > nr_anon이라는 잔여 절대량 비교입니다. 이 식을 임의의 초기 목표·진행 이력에서도 정확히 덜 scan된 비율을 찾아내는 수학적 정렬로 설명해서는 안 됩니다. v5.18은 보정 뒤 scan_adjusted=true로 바꾸고 남은 budget을 처리합니다.
scan 비율을 회복했다고 physical free 비율까지 같아지지는 않습니다. 한쪽의 dirty·writeback·pin·reference 조건이 다르면 reclaim 성공률은 달라집니다. 보정의 판단값인 반환 회수량과 보정에 사용하는 잔여 scan budget은 끝까지 따로 읽어야 합니다.
정수 계산을 포함하면 결과도 달라집니다
앞의 목표 [64, 64, 256, 256]에서 이번에는 회수 목표를 64로 두고, global direct-reclaim 기본-priority 예외는 아니라고 가정하겠습니다. 각 list에 32 budget을 보낸 첫 순회에서 두 inactive list가 각각 32를 실제 회수하면 회수 목표를 채웁니다. active 처리의 반환값은 여전히 0입니다. 이 조건은 보정에 실제로 진입할 수 있는 독립 예시입니다.
| 단계 | Inactive anon | Active anon | Inactive file | Active file |
|---|---|---|---|---|
| 최초 scan 목표 | 64 | 64 | 256 | 256 |
| 첫 순회 소비 budget | 32 | 32 | 32 | 32 |
| 첫 순회 뒤 잔여 budget | 32 | 32 | 224 | 224 |
| 보정 직후 잔여 budget | 0 | 0 | 98 | 98 |
| v5.18 loop의 최종 소비 budget | 32 | 32 | 130 | 130 |
anon family의 남은 양은 64, 원래 목표+1은 129이므로 percentage=6400/129=49입니다. file list마다 새 목표는 256*51/100=130, 이미 처리한 32를 빼면 98입니다. v5.18에서는 그 budget까지 소비하므로 총 scan budget은 324, 비율은 약 19.75:80.25가 됩니다. 이상적인 연속 모델의 320과 정확히 같지 않습니다. 이 조건에서 inactive가 모두 회수되어도 active 처리까지 모두 free로 더하지 않으며, suffix의 별도 anon 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 v5.18, mm/vmscan.c:2868-2981.
* Full function definition; surrounding kernel context is required.
* Source: https://github.com/torvalds/linux/blob/v5.18/mm/vmscan.c#L2868
* License text: GPL-2.0.txt (in this directory).
*/
static void shrink_lruvec(struct lruvec *lruvec, struct scan_control *sc)
{
unsigned long nr[NR_LRU_LISTS];
unsigned long targets[NR_LRU_LISTS];
unsigned long nr_to_scan;
enum lru_list lru;
unsigned long nr_reclaimed = 0;
unsigned long nr_to_reclaim = sc->nr_to_reclaim;
struct blk_plug plug;
bool scan_adjusted;
get_scan_count(lruvec, sc, nr);
/* Record the original scan target for proportional adjustments later */
memcpy(targets, nr, sizeof(nr));
/*
* Global reclaiming within direct reclaim at DEF_PRIORITY is a normal
* event that can occur when there is little memory pressure e.g.
* multiple streaming readers/writers. Hence, we do not abort scanning
* when the requested number of pages are reclaimed when scanning at
* DEF_PRIORITY on the assumption that the fact we are direct
* reclaiming implies that kswapd is not keeping up and it is best to
* do a batch of work at once. For memcg reclaim one check is made to
* abort proportional reclaim if either the file or anon lru has already
* dropped to zero at the first pass.
*/
scan_adjusted = (!cgroup_reclaim(sc) && !current_is_kswapd() &&
sc->priority == DEF_PRIORITY);
blk_start_plug(&plug);
while (nr[LRU_INACTIVE_ANON] || nr[LRU_ACTIVE_FILE] ||
nr[LRU_INACTIVE_FILE]) {
unsigned long nr_anon, nr_file, percentage;
unsigned long nr_scanned;
for_each_evictable_lru(lru) {
if (nr[lru]) {
nr_to_scan = min(nr[lru], SWAP_CLUSTER_MAX);
nr[lru] -= nr_to_scan;
nr_reclaimed += shrink_list(lru, nr_to_scan,
lruvec, sc);
}
}
cond_resched();
if (nr_reclaimed < nr_to_reclaim || scan_adjusted)
continue;
/*
* For kswapd and memcg, reclaim at least the number of pages
* requested. Ensure that the anon and file LRUs are scanned
* proportionally what was requested by get_scan_count(). We
* stop reclaiming one LRU and reduce the amount scanning
* proportional to the original scan target.
*/
nr_file = nr[LRU_INACTIVE_FILE] + nr[LRU_ACTIVE_FILE];
nr_anon = nr[LRU_INACTIVE_ANON] + nr[LRU_ACTIVE_ANON];
/*
* It's just vindictive to attack the larger once the smaller
* has gone to zero. And given the way we stop scanning the
* smaller below, this makes sure that we only make one nudge
* towards proportionality once we've got nr_to_reclaim.
*/
if (!nr_file || !nr_anon)
break;
if (nr_file > nr_anon) {
unsigned long scan_target = targets[LRU_INACTIVE_ANON] +
targets[LRU_ACTIVE_ANON] + 1;
lru = LRU_BASE;
percentage = nr_anon * 100 / scan_target;
} else {
unsigned long scan_target = targets[LRU_INACTIVE_FILE] +
targets[LRU_ACTIVE_FILE] + 1;
lru = LRU_FILE;
percentage = nr_file * 100 / scan_target;
}
/* Stop scanning the smaller of the LRU */
nr[lru] = 0;
nr[lru + LRU_ACTIVE] = 0;
/*
* Recalculate the other LRU scan count based on its original
* scan target and the percentage scanning already complete
*/
lru = (lru == LRU_FILE) ? LRU_BASE : LRU_FILE;
nr_scanned = targets[lru] - nr[lru];
nr[lru] = targets[lru] * (100 - percentage) / 100;
nr[lru] -= min(nr[lru], nr_scanned);
lru += LRU_ACTIVE;
nr_scanned = targets[lru] - nr[lru];
nr[lru] = targets[lru] * (100 - percentage) / 100;
nr[lru] -= min(nr[lru], nr_scanned);
scan_adjusted = true;
}
blk_finish_plug(&plug);
sc->nr_reclaimed += nr_reclaimed;
/*
* Even if we did not try to evict anon pages at all, we want to
* rebalance the anon lru active/inactive ratio.
*/
if (can_age_anon_pages(lruvec_pgdat(lruvec), sc) &&
inactive_is_low(lruvec, LRU_INACTIVE_ANON))
shrink_active_list(SWAP_CLUSTER_MAX, lruvec,
sc, LRU_ACTIVE_ANON);
}
v6.12의 보정 뒤 종료는 더 빠를 수 있습니다
v6.12는 시작 시 정하는 proportional_reclaim을 보정 뒤에 참으로 바꾸지 않습니다. 위 조건에서 file 잔여 budget을 98씩으로 계산한 뒤 다음 순회에 32씩 더 처리하면, actual reclaimed는 이미 목표를 넘고 anon 잔여량은 0이므로 종료할 수 있습니다. 이 가정에서는 최종 budget이 [32, 32, 64, 64], 총 192이고 inactive 회수량은 96입니다. v5.18의 324-budget 결과를 v6.12의 보장값으로 옮기면 안 됩니다. v6.18의 classic 함수도 이 비교 기준처럼 읽어야 합니다.
아래에는 MGLRU 분기, block plugging, reschedule, cumulative reclaimed 반영과 suffix aging까지 포함한 v6.12 전체 함수를 실었습니다. loop의 조건에 active anon이 없더라도 body의 for_each_evictable_lru()에는 포함된다는 점도 확인하실 수 있습니다.
// 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:5669-5785.
* Full function definition; surrounding kernel context is required.
* Source: https://github.com/torvalds/linux/blob/v6.12/mm/vmscan.c#L5669
* License text: GPL-2.0.txt (in this directory).
*/
static void shrink_lruvec(struct lruvec *lruvec, struct scan_control *sc)
{
unsigned long nr[NR_LRU_LISTS];
unsigned long targets[NR_LRU_LISTS];
unsigned long nr_to_scan;
enum lru_list lru;
unsigned long nr_reclaimed = 0;
unsigned long nr_to_reclaim = sc->nr_to_reclaim;
bool proportional_reclaim;
struct blk_plug plug;
if (lru_gen_enabled() && !root_reclaim(sc)) {
lru_gen_shrink_lruvec(lruvec, sc);
return;
}
get_scan_count(lruvec, sc, nr);
/* Record the original scan target for proportional adjustments later */
memcpy(targets, nr, sizeof(nr));
/*
* Global reclaiming within direct reclaim at DEF_PRIORITY is a normal
* event that can occur when there is little memory pressure e.g.
* multiple streaming readers/writers. Hence, we do not abort scanning
* when the requested number of pages are reclaimed when scanning at
* DEF_PRIORITY on the assumption that the fact we are direct
* reclaiming implies that kswapd is not keeping up and it is best to
* do a batch of work at once. For memcg reclaim one check is made to
* abort proportional reclaim if either the file or anon lru has already
* dropped to zero at the first pass.
*/
proportional_reclaim = (!cgroup_reclaim(sc) && !current_is_kswapd() &&
sc->priority == DEF_PRIORITY);
blk_start_plug(&plug);
while (nr[LRU_INACTIVE_ANON] || nr[LRU_ACTIVE_FILE] ||
nr[LRU_INACTIVE_FILE]) {
unsigned long nr_anon, nr_file, percentage;
unsigned long nr_scanned;
for_each_evictable_lru(lru) {
if (nr[lru]) {
nr_to_scan = min(nr[lru], SWAP_CLUSTER_MAX);
nr[lru] -= nr_to_scan;
nr_reclaimed += shrink_list(lru, nr_to_scan,
lruvec, sc);
}
}
cond_resched();
if (nr_reclaimed < nr_to_reclaim || proportional_reclaim)
continue;
/*
* For kswapd and memcg, reclaim at least the number of pages
* requested. Ensure that the anon and file LRUs are scanned
* proportionally what was requested by get_scan_count(). We
* stop reclaiming one LRU and reduce the amount scanning
* proportional to the original scan target.
*/
nr_file = nr[LRU_INACTIVE_FILE] + nr[LRU_ACTIVE_FILE];
nr_anon = nr[LRU_INACTIVE_ANON] + nr[LRU_ACTIVE_ANON];
/*
* It's just vindictive to attack the larger once the smaller
* has gone to zero. And given the way we stop scanning the
* smaller below, this makes sure that we only make one nudge
* towards proportionality once we've got nr_to_reclaim.
*/
if (!nr_file || !nr_anon)
break;
if (nr_file > nr_anon) {
unsigned long scan_target = targets[LRU_INACTIVE_ANON] +
targets[LRU_ACTIVE_ANON] + 1;
lru = LRU_BASE;
percentage = nr_anon * 100 / scan_target;
} else {
unsigned long scan_target = targets[LRU_INACTIVE_FILE] +
targets[LRU_ACTIVE_FILE] + 1;
lru = LRU_FILE;
percentage = nr_file * 100 / scan_target;
}
/* Stop scanning the smaller of the LRU */
nr[lru] = 0;
nr[lru + LRU_ACTIVE] = 0;
/*
* Recalculate the other LRU scan count based on its original
* scan target and the percentage scanning already complete
*/
lru = (lru == LRU_FILE) ? LRU_BASE : LRU_FILE;
nr_scanned = targets[lru] - nr[lru];
nr[lru] = targets[lru] * (100 - percentage) / 100;
nr[lru] -= min(nr[lru], nr_scanned);
lru += LRU_ACTIVE;
nr_scanned = targets[lru] - nr[lru];
nr[lru] = targets[lru] * (100 - percentage) / 100;
nr[lru] -= min(nr[lru], nr_scanned);
}
blk_finish_plug(&plug);
sc->nr_reclaimed += nr_reclaimed;
/*
* Even if we did not try to evict anon pages at all, we want to
* rebalance the anon lru active/inactive ratio.
*/
if (can_age_anon_pages(lruvec_pgdat(lruvec), sc) &&
inactive_is_low(lruvec, LRU_INACTIVE_ANON))
shrink_active_list(SWAP_CLUSTER_MAX, lruvec,
sc, LRU_ACTIVE_ANON);
}
shrink_list는 active와 inactive를 갈라 줍니다
개별 list에 도달하면 is_active_lru()로 active 여부를 구분합니다. active이면 family에 맞는 may_deactivate bit를 확인해 shrink_active_list()를 호출합니다. 허용되지 않아 생략하면 skipped_deactivate를 기록하여 caller가 나중에 강제할 수 있도록 합니다.
inactive이면 shrink_inactive_list()를 통해 실제 folio 회수 검사로 내려갑니다. 따라서 함수 이름의 'shrink'가 모든 층에서 같은 일을 뜻하지 않습니다. node를 줄인다는 목표 아래에서도 active shrinking은 후보를 만들고, inactive shrinking은 backing·mapping·reference 조건을 따져 memory를 반환합니다.
active branch는 허용 여부와 관계없이 0을 반환합니다. 1 << is_file_lru(lru)로 anon/file 허용 bit를 선택하며, 비활성 branch만 shrink_inactive_list()의 실제 회수량을 돌려줍니다. suffix의 anon aging은 can_age_anon_pages()와 inactive_is_low() 조건을 확인하므로 swap·demotion 가능성과 후보 공급을 함께 봅니다. 이 검사는 slot을 할당하거나 swap I/O를 완료했다는 뜻이 아닙니다.
// 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:2186-2198.
* Full function definition; surrounding kernel context is required.
* Source: https://github.com/torvalds/linux/blob/v6.12/mm/vmscan.c#L2186
* License text: GPL-2.0.txt (in this directory).
*/
static unsigned long shrink_list(enum lru_list lru, unsigned long nr_to_scan,
struct lruvec *lruvec, struct scan_control *sc)
{
if (is_active_lru(lru)) {
if (sc->may_deactivate & (1 << is_file_lru(lru)))
shrink_active_list(nr_to_scan, lruvec, sc, lru);
else
sc->skipped_deactivate = 1;
return 0;
}
return shrink_inactive_list(nr_to_scan, lruvec, sc, lru);
}
참고 자료
- Lorenzo Stoakes, The Linux Memory Manager, Early Access 2025, Chapter 11, Shrinking LRU Vectors / Shrinking an Individual LRU List.
- Linux v5.18 mm/vmscan.c, v6.12 mm/vmscan.c: 고전 adjusted-scan과 이후 loop의 비교 기준입니다.
- Linux v6.18 mm/vmscan.c:
shrink_node_memcgs(),shrink_lruvec(),shrink_list().