v6.18 folio reclaim: swap 준비부터 demotion과 최종 free까지

reclaim을 읽을 때 함수 이름을 최신 이름으로 바꾸는 것만으로는 충분하지 않습니다. v6.18의 shrink_folio_list()는 large folio와 memory tiering, swap table, 비동기 writeback을 함께 다룹니다. 고전 classic LRU의 기본 질문은 남아 있지만, 같은 성공·실패를 전달하는 API와 정리 책임이 달라진 부분을 분리해서 살펴보겠습니다.

먼저 이번 pass에서 처리할 수 있는 후보를 가릅니다

입력은 이미 LRU에서 isolate한 folio list입니다. 함수는 folio trylock에 실패하면 보존하고, lock을 얻으면 base page 수를 scan에 더한 뒤 evictable·unmap 가능 여부를 검사합니다. poisoned base page는 일반 reclaim과 다른 정리 경로를 탑니다. active 상태가 남아 있지 않아야 한다는 assertion도 이 함수가 최초 후보 선정자가 아니라 isolation 뒤 처리자라는 사실을 보여 줍니다.

dirty·writeback 통계는 folio_check_dirty_writeback()으로 얻습니다. 이미 writeback 중인 folio를 다시 만났다고 두 번째 I/O를 시작하지는 않습니다. kswapd가 reclaim 표시와 PGDAT_WRITEBACK을 함께 본 경우 immediate 통계를 올리고 activation 경로로 갑니다. 일반 dirty throttling이 작동하거나, reclaim 표시가 없거나, filesystem 진입이 금지되거나, mapping이 reclaim 중 writeback 대기로 deadlock을 일으킬 수 있으면 reclaim 표시를 남기고 다음 후보를 봅니다.

이 조건들이 모두 맞지 않는 legacy memcg 경로에서는 lock을 풀고 writeback을 기다린 뒤 같은 folio를 입력 list에 다시 넣어 재검사합니다. 따라서 생략된 조건을 모두 단순한 else로 읽으면 대기해도 되는 context와 대기하면 안 되는 context를 구분할 수 없습니다. lock을 내려놓는 시점과 다시 검사한다는 사실이 함께 중요합니다.

Reference 검사 뒤에는 다른 node로 옮길 수도 있습니다

보통 folio_check_references()의 KEEP·ACTIVATE·RECLAIM·RECLAIM_CLEAN 결과를 따릅니다. ignore_references를 요청한 특수 caller는 기본 RECLAIM 값으로 진행하지만, 이것이 mlock·pin·dirty·cache reference 검사까지 생략한다는 뜻은 아닙니다.

demotion이 허용되면 swap-out이나 file eviction 전에 folio를 낮은 tier의 node로 옮길 후보 list에 넣을 수 있습니다. large folio는 해당 migration 지원 여부도 충족해야 합니다. 실제 이동은 주 loop 뒤 demote_folio_list()에서 수행하며, 성공한 base page 수는 nr_demoted와 nr_reclaimed에 들어갑니다. 내용이 다른 RAM node로 옮겨졌을 뿐 시스템 전체 RAM 소비가 그만큼 줄었다는 뜻은 아닙니다.

demotion에 실패한 folio는 ordinary reclaim으로 재시도할 수 있습니다. 다만 proactive reclaim에서는 이 fallback을 하지 않으므로 요청한 작업의 성격까지 함께 보아야 합니다. 매 pass에서 같은 folio를 계속 demotion 후보로 넣지 않도록 재시도 때 demotion pass도 끕니다.

Swap 공간을 예약하는 API는 boolean이 아닙니다

anonymous·swap-backed folio가 아직 swap cache에 없다면 __GFP_IO와 DMA pin 가능성을 확인합니다. v6.18의 folio_alloc_swap()은 0을 성공으로 반환하므로, if (folio_alloc_swap(...)) 안은 실패 처리입니다. 고전 if (!add_to_swap(...))와 조건을 비교할 때 특히 주의해야 합니다.

large folio는 split할 수 있어야 하며, deferred split 대상이면서 partially mapped인 경우에는 먼저 split하여 unmapped subpage를 I/O 없이 회수할 여지를 만듭니다. 연속 swap entry를 확보하지 못한 경우에도 base-page fallback을 시도할 수 있습니다. split된 subpage는 다시 loop를 통과하므로 현재 scan accounting을 중복하지 않도록 보정합니다.

swap cache를 마련한 뒤에는 folio를 dirty로 표시합니다. MADV_FREE와의 경쟁으로 PTE의 dirty가 이미 지워졌지만 swap-backed flag가 아직 남은 순간도 있기 때문입니다. 뒤의 unmap이 dirty를 전파할 것이라고만 믿으면 실제 내용이 swap에 기록되지 않은 채 복구 대상으로 남을 수 있습니다. slot 확보·swap cache 연결은 storage write 완료와 다릅니다.

Unmap 성공 뒤에도 pin을 다시 확인합니다

try_to_unmap()에는 batched TLB flush를 위한 flag를 주고, PMD-mappable folio에는 huge PMD split flag를 더합니다. large folio에는 TTU_SYNC도 사용합니다. 동시 PTE 갱신이 PTE를 일시적으로 비우는 순간 때문에 앞부분을 놓치지 않도록 PTL 획득 범위를 맞추려는 조건입니다.

아직 mapped이면 unmap 실패를 세고 보존합니다. lazy-free folio가 이 사이 다시 swap-backed가 되었다면 lazy-free 실패도 별도로 셉니다. unmap에 성공했어도 DMA pin 가능성을 재검사합니다. pinning 주체가 내용을 바꿀 수 있는 folio나 그 filesystem metadata를 무리하게 회수해서는 안 되기 때문입니다. 이 검사는 마지막 cache reference freeze와 별개입니다.

PAGE_SUCCESS 뒤에는 lock과 상태를 다시 얻습니다

v6.18 pageout()은 ordinary filesystem writeback을 직접 시작하지 않습니다. mapping이 shmem도 아니고 folio가 anon도 아니면 PAGE_ACTIVATE를 반환합니다. swap·shmem은 writeout()에서 각각 swap_writeout()·shmem_writeout()으로 나뉩니다. large shmem writeout 중 split된 folio가 입력 list로 돌아올 수 있어 반환 후 scan accounting도 다시 맞춥니다.

PAGE_KEEP·PAGE_ACTIVATE·PAGE_CLEAN 경로와 backend가 unlock한 PAGE_SUCCESS 경로의 ownership을 구별해야 합니다. PAGE_SUCCESS여도 writeback·dirty가 남으면 보존하고, 모두 없어 보이면 trylock한 뒤 두 상태를 다시 확인합니다. mapping도 다시 읽습니다. 제출 성공을 동기 storage 완료나 folio free와 같은 뜻으로 사용할 수 없습니다.

lazy-free는 isolation reference 하나를 0으로 freeze할 수 있는지 검사합니다. cache folio는 __remove_mapping()에서 예상 reference 수를 freeze하고 dirty를 재검사하여 제거합니다. v6.18 swap cache는 cluster lock과 swap table을 사용하고, file cache는 inode lock·XArray lock을 사용합니다. 이 둘의 lock을 같은 것으로 읽으면 안 됩니다.

Free batch는 중간에도 비울 수 있습니다

v6.18은 free할 folio를 struct folio_batch에 모읍니다. batch가 차면 memcg uncharge와 필요한 TLB flush 뒤 free_unref_folios()로 비우며, deferred split queue에서도 먼저 제거합니다. 끝에 남은 batch도 같은 순서로 정리합니다. 보존한 folio는 반환 list로 옮겨 caller가 LRU에 복귀시킬 수 있도록 합니다.

이 전체 흐름에서 scan량, pageout 제출량, demotion량, reclaimed accounting, 실제 allocator 반환량은 관련 있지만 동일한 값이 아닙니다. 반환 counter 하나만으로 어느 storage I/O가 끝났는지, 원하는 allocation order가 확보됐는지까지 판단할 수는 없습니다.

전체 code로 분기와 정리를 연결합니다

아래는 Linux v6.18의 전체 함수입니다. kernel 내부 자료구조와 configuration에 의존하는 구현이며 standalone 프로그램은 아닙니다. 고전 shrink_page_list()의 전체 구현은 inactive reclaim 글에서 함께 읽을 수 있습니다.

Linux v6.18 원본 · 전체 code · GPL-2.0

v6_18_shrink_folio_list.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.18, mm/vmscan.c:1099-1646.
 * Complete function definition; surrounding kernel context is required.
 * Source: https://github.com/torvalds/linux/blob/v6.18/mm/vmscan.c#L1099
 * License text: GPL-2.0.txt (in this directory).
 */
static unsigned int shrink_folio_list(struct list_head *folio_list,
		struct pglist_data *pgdat, struct scan_control *sc,
		struct reclaim_stat *stat, bool ignore_references,
		struct mem_cgroup *memcg)
{
	struct folio_batch free_folios;
	LIST_HEAD(ret_folios);
	LIST_HEAD(demote_folios);
	unsigned int nr_reclaimed = 0, nr_demoted = 0;
	unsigned int pgactivate = 0;
	bool do_demote_pass;
	struct swap_iocb *plug = NULL;

	folio_batch_init(&free_folios);
	memset(stat, 0, sizeof(*stat));
	cond_resched();
	do_demote_pass = can_demote(pgdat->node_id, sc, memcg);

retry:
	while (!list_empty(folio_list)) {
		struct address_space *mapping;
		struct folio *folio;
		enum folio_references references = FOLIOREF_RECLAIM;
		bool dirty, writeback;
		unsigned int nr_pages;

		cond_resched();

		folio = lru_to_folio(folio_list);
		list_del(&folio->lru);

		if (!folio_trylock(folio))
			goto keep;

		if (folio_contain_hwpoisoned_page(folio)) {
			/*
			 * unmap_poisoned_folio() can't handle large
			 * folio, just skip it. memory_failure() will
			 * handle it if the UCE is triggered again.
			 */
			if (folio_test_large(folio))
				goto keep_locked;

			unmap_poisoned_folio(folio, folio_pfn(folio), false);
			folio_unlock(folio);
			folio_put(folio);
			continue;
		}

		VM_BUG_ON_FOLIO(folio_test_active(folio), folio);

		nr_pages = folio_nr_pages(folio);

		/* Account the number of base pages */
		sc->nr_scanned += nr_pages;

		if (unlikely(!folio_evictable(folio)))
			goto activate_locked;

		if (!sc->may_unmap && folio_mapped(folio))
			goto keep_locked;

		/*
		 * The number of dirty pages determines if a node is marked
		 * reclaim_congested. kswapd will stall and start writing
		 * folios if the tail of the LRU is all dirty unqueued folios.
		 */
		folio_check_dirty_writeback(folio, &dirty, &writeback);
		if (dirty || writeback)
			stat->nr_dirty += nr_pages;

		if (dirty && !writeback)
			stat->nr_unqueued_dirty += nr_pages;

		/*
		 * Treat this folio as congested if folios are cycling
		 * through the LRU so quickly that the folios marked
		 * for immediate reclaim are making it to the end of
		 * the LRU a second time.
		 */
		if (writeback && folio_test_reclaim(folio))
			stat->nr_congested += nr_pages;

		/*
		 * If a folio at the tail of the LRU is under writeback, there
		 * are three cases to consider.
		 *
		 * 1) If reclaim is encountering an excessive number
		 *    of folios under writeback and this folio has both
		 *    the writeback and reclaim flags set, then it
		 *    indicates that folios are being queued for I/O but
		 *    are being recycled through the LRU before the I/O
		 *    can complete. Waiting on the folio itself risks an
		 *    indefinite stall if it is impossible to writeback
		 *    the folio due to I/O error or disconnected storage
		 *    so instead note that the LRU is being scanned too
		 *    quickly and the caller can stall after the folio
		 *    list has been processed.
		 *
		 * 2) Global or new memcg reclaim encounters a folio that is
		 *    not marked for immediate reclaim, or the caller does not
		 *    have __GFP_FS (or __GFP_IO if it's simply going to swap,
		 *    not to fs), or the folio belongs to a mapping where
		 *    waiting on writeback during reclaim may lead to a deadlock.
		 *    In this case mark the folio for immediate reclaim and
		 *    continue scanning.
		 *
		 *    Require may_enter_fs() because we would wait on fs, which
		 *    may not have submitted I/O yet. And the loop driver might
		 *    enter reclaim, and deadlock if it waits on a folio for
		 *    which it is needed to do the write (loop masks off
		 *    __GFP_IO|__GFP_FS for this reason); but more thought
		 *    would probably show more reasons.
		 *
		 * 3) Legacy memcg encounters a folio that already has the
		 *    reclaim flag set. memcg does not have any dirty folio
		 *    throttling so we could easily OOM just because too many
		 *    folios are in writeback and there is nothing else to
		 *    reclaim. Wait for the writeback to complete.
		 *
		 * In cases 1) and 2) we activate the folios to get them out of
		 * the way while we continue scanning for clean folios on the
		 * inactive list and refilling from the active list. The
		 * observation here is that waiting for disk writes is more
		 * expensive than potentially causing reloads down the line.
		 * Since they're marked for immediate reclaim, they won't put
		 * memory pressure on the cache working set any longer than it
		 * takes to write them to disk.
		 */
		if (folio_test_writeback(folio)) {
			mapping = folio_mapping(folio);

			/* Case 1 above */
			if (current_is_kswapd() &&
			    folio_test_reclaim(folio) &&
			    test_bit(PGDAT_WRITEBACK, &pgdat->flags)) {
				stat->nr_immediate += nr_pages;
				goto activate_locked;

			/* Case 2 above */
			} else if (writeback_throttling_sane(sc) ||
			    !folio_test_reclaim(folio) ||
			    !may_enter_fs(folio, sc->gfp_mask) ||
			    (mapping &&
			     mapping_writeback_may_deadlock_on_reclaim(mapping))) {
				/*
				 * This is slightly racy -
				 * folio_end_writeback() might have
				 * just cleared the reclaim flag, then
				 * setting the reclaim flag here ends up
				 * interpreted as the readahead flag - but
				 * that does not matter enough to care.
				 * What we do want is for this folio to
				 * have the reclaim flag set next time
				 * memcg reclaim reaches the tests above,
				 * so it will then wait for writeback to
				 * avoid OOM; and it's also appropriate
				 * in global reclaim.
				 */
				folio_set_reclaim(folio);
				stat->nr_writeback += nr_pages;
				goto activate_locked;

			/* Case 3 above */
			} else {
				folio_unlock(folio);
				folio_wait_writeback(folio);
				/* then go back and try same folio again */
				list_add_tail(&folio->lru, folio_list);
				continue;
			}
		}

		if (!ignore_references)
			references = folio_check_references(folio, sc);

		switch (references) {
		case FOLIOREF_ACTIVATE:
			goto activate_locked;
		case FOLIOREF_KEEP:
			stat->nr_ref_keep += nr_pages;
			goto keep_locked;
		case FOLIOREF_RECLAIM:
		case FOLIOREF_RECLAIM_CLEAN:
			; /* try to reclaim the folio below */
		}

		/*
		 * Before reclaiming the folio, try to relocate
		 * its contents to another node.
		 */
		if (do_demote_pass &&
		    (thp_migration_supported() || !folio_test_large(folio))) {
			list_add(&folio->lru, &demote_folios);
			folio_unlock(folio);
			continue;
		}

		/*
		 * Anonymous process memory has backing store?
		 * Try to allocate it some swap space here.
		 * Lazyfree folio could be freed directly
		 */
		if (folio_test_anon(folio) && folio_test_swapbacked(folio)) {
			if (!folio_test_swapcache(folio)) {
				if (!(sc->gfp_mask & __GFP_IO))
					goto keep_locked;
				if (folio_maybe_dma_pinned(folio))
					goto keep_locked;
				if (folio_test_large(folio)) {
					/* cannot split folio, skip it */
					if (!can_split_folio(folio, 1, NULL))
						goto activate_locked;
					/*
					 * Split partially mapped folios right away.
					 * We can free the unmapped pages without IO.
					 */
					if (data_race(!list_empty(&folio->_deferred_list) &&
					    folio_test_partially_mapped(folio)) &&
					    split_folio_to_list(folio, folio_list))
						goto activate_locked;
				}
				if (folio_alloc_swap(folio, __GFP_HIGH | __GFP_NOWARN)) {
					int __maybe_unused order = folio_order(folio);

					if (!folio_test_large(folio))
						goto activate_locked_split;
					/* Fallback to swap normal pages */
					if (split_folio_to_list(folio, folio_list))
						goto activate_locked;
#ifdef CONFIG_TRANSPARENT_HUGEPAGE
					if (nr_pages >= HPAGE_PMD_NR) {
						count_memcg_folio_events(folio,
							THP_SWPOUT_FALLBACK, 1);
						count_vm_event(THP_SWPOUT_FALLBACK);
					}
#endif
					count_mthp_stat(order, MTHP_STAT_SWPOUT_FALLBACK);
					if (folio_alloc_swap(folio, __GFP_HIGH | __GFP_NOWARN))
						goto activate_locked_split;
				}
				/*
				 * Normally the folio will be dirtied in unmap because its
				 * pte should be dirty. A special case is MADV_FREE page. The
				 * page's pte could have dirty bit cleared but the folio's
				 * SwapBacked flag is still set because clearing the dirty bit
				 * and SwapBacked flag has no lock protected. For such folio,
				 * unmap will not set dirty bit for it, so folio reclaim will
				 * not write the folio out. This can cause data corruption when
				 * the folio is swapped in later. Always setting the dirty flag
				 * for the folio solves the problem.
				 */
				folio_mark_dirty(folio);
			}
		}

		/*
		 * If the folio was split above, the tail pages will make
		 * their own pass through this function and be accounted
		 * then.
		 */
		if ((nr_pages > 1) && !folio_test_large(folio)) {
			sc->nr_scanned -= (nr_pages - 1);
			nr_pages = 1;
		}

		/*
		 * The folio is mapped into the page tables of one or more
		 * processes. Try to unmap it here.
		 */
		if (folio_mapped(folio)) {
			enum ttu_flags flags = TTU_BATCH_FLUSH;
			bool was_swapbacked = folio_test_swapbacked(folio);

			if (folio_test_pmd_mappable(folio))
				flags |= TTU_SPLIT_HUGE_PMD;
			/*
			 * Without TTU_SYNC, try_to_unmap will only begin to
			 * hold PTL from the first present PTE within a large
			 * folio. Some initial PTEs might be skipped due to
			 * races with parallel PTE writes in which PTEs can be
			 * cleared temporarily before being written new present
			 * values. This will lead to a large folio is still
			 * mapped while some subpages have been partially
			 * unmapped after try_to_unmap; TTU_SYNC helps
			 * try_to_unmap acquire PTL from the first PTE,
			 * eliminating the influence of temporary PTE values.
			 */
			if (folio_test_large(folio))
				flags |= TTU_SYNC;

			try_to_unmap(folio, flags);
			if (folio_mapped(folio)) {
				stat->nr_unmap_fail += nr_pages;
				if (!was_swapbacked &&
				    folio_test_swapbacked(folio))
					stat->nr_lazyfree_fail += nr_pages;
				goto activate_locked;
			}
		}

		/*
		 * Folio is unmapped now so it cannot be newly pinned anymore.
		 * No point in trying to reclaim folio if it is pinned.
		 * Furthermore we don't want to reclaim underlying fs metadata
		 * if the folio is pinned and thus potentially modified by the
		 * pinning process as that may upset the filesystem.
		 */
		if (folio_maybe_dma_pinned(folio))
			goto activate_locked;

		mapping = folio_mapping(folio);
		if (folio_test_dirty(folio)) {
			/*
			 * Only kswapd can writeback filesystem folios
			 * to avoid risk of stack overflow. But avoid
			 * injecting inefficient single-folio I/O into
			 * flusher writeback as much as possible: only
			 * write folios when we've encountered many
			 * dirty folios, and when we've already scanned
			 * the rest of the LRU for clean folios and see
			 * the same dirty folios again (with the reclaim
			 * flag set).
			 */
			if (folio_is_file_lru(folio) &&
			    (!current_is_kswapd() ||
			     !folio_test_reclaim(folio) ||
			     !test_bit(PGDAT_DIRTY, &pgdat->flags))) {
				/*
				 * Immediately reclaim when written back.
				 * Similar in principle to folio_deactivate()
				 * except we already have the folio isolated
				 * and know it's dirty
				 */
				node_stat_mod_folio(folio, NR_VMSCAN_IMMEDIATE,
						nr_pages);
				folio_set_reclaim(folio);

				goto activate_locked;
			}

			if (references == FOLIOREF_RECLAIM_CLEAN)
				goto keep_locked;
			if (!may_enter_fs(folio, sc->gfp_mask))
				goto keep_locked;
			if (!sc->may_writepage)
				goto keep_locked;

			/*
			 * Folio is dirty. Flush the TLB if a writable entry
			 * potentially exists to avoid CPU writes after I/O
			 * starts and then write it out here.
			 */
			try_to_unmap_flush_dirty();
			switch (pageout(folio, mapping, &plug, folio_list)) {
			case PAGE_KEEP:
				goto keep_locked;
			case PAGE_ACTIVATE:
				/*
				 * If shmem folio is split when writeback to swap,
				 * the tail pages will make their own pass through
				 * this function and be accounted then.
				 */
				if (nr_pages > 1 && !folio_test_large(folio)) {
					sc->nr_scanned -= (nr_pages - 1);
					nr_pages = 1;
				}
				goto activate_locked;
			case PAGE_SUCCESS:
				if (nr_pages > 1 && !folio_test_large(folio)) {
					sc->nr_scanned -= (nr_pages - 1);
					nr_pages = 1;
				}
				stat->nr_pageout += nr_pages;

				if (folio_test_writeback(folio))
					goto keep;
				if (folio_test_dirty(folio))
					goto keep;

				/*
				 * A synchronous write - probably a ramdisk.  Go
				 * ahead and try to reclaim the folio.
				 */
				if (!folio_trylock(folio))
					goto keep;
				if (folio_test_dirty(folio) ||
				    folio_test_writeback(folio))
					goto keep_locked;
				mapping = folio_mapping(folio);
				fallthrough;
			case PAGE_CLEAN:
				; /* try to free the folio below */
			}
		}

		/*
		 * If the folio has buffers, try to free the buffer
		 * mappings associated with this folio. If we succeed
		 * we try to free the folio as well.
		 *
		 * We do this even if the folio is dirty.
		 * filemap_release_folio() does not perform I/O, but it
		 * is possible for a folio to have the dirty flag set,
		 * but it is actually clean (all its buffers are clean).
		 * This happens if the buffers were written out directly,
		 * with submit_bh(). ext3 will do this, as well as
		 * the blockdev mapping.  filemap_release_folio() will
		 * discover that cleanness and will drop the buffers
		 * and mark the folio clean - it can be freed.
		 *
		 * Rarely, folios can have buffers and no ->mapping.
		 * These are the folios which were not successfully
		 * invalidated in truncate_cleanup_folio().  We try to
		 * drop those buffers here and if that worked, and the
		 * folio is no longer mapped into process address space
		 * (refcount == 1) it can be freed.  Otherwise, leave
		 * the folio on the LRU so it is swappable.
		 */
		if (folio_needs_release(folio)) {
			if (!filemap_release_folio(folio, sc->gfp_mask))
				goto activate_locked;
			if (!mapping && folio_ref_count(folio) == 1) {
				folio_unlock(folio);
				if (folio_put_testzero(folio))
					goto free_it;
				else {
					/*
					 * rare race with speculative reference.
					 * the speculative reference will free
					 * this folio shortly, so we may
					 * increment nr_reclaimed here (and
					 * leave it off the LRU).
					 */
					nr_reclaimed += nr_pages;
					continue;
				}
			}
		}

		if (folio_test_anon(folio) && !folio_test_swapbacked(folio)) {
			/* follow __remove_mapping for reference */
			if (!folio_ref_freeze(folio, 1))
				goto keep_locked;
			/*
			 * The folio has only one reference left, which is
			 * from the isolation. After the caller puts the
			 * folio back on the lru and drops the reference, the
			 * folio will be freed anyway. It doesn't matter
			 * which lru it goes on. So we don't bother checking
			 * the dirty flag here.
			 */
			count_vm_events(PGLAZYFREED, nr_pages);
			count_memcg_folio_events(folio, PGLAZYFREED, nr_pages);
		} else if (!mapping || !__remove_mapping(mapping, folio, true,
							 sc->target_mem_cgroup))
			goto keep_locked;

		folio_unlock(folio);
free_it:
		/*
		 * Folio may get swapped out as a whole, need to account
		 * all pages in it.
		 */
		nr_reclaimed += nr_pages;

		folio_unqueue_deferred_split(folio);
		if (folio_batch_add(&free_folios, folio) == 0) {
			mem_cgroup_uncharge_folios(&free_folios);
			try_to_unmap_flush();
			free_unref_folios(&free_folios);
		}
		continue;

activate_locked_split:
		/*
		 * The tail pages that are failed to add into swap cache
		 * reach here.  Fixup nr_scanned and nr_pages.
		 */
		if (nr_pages > 1) {
			sc->nr_scanned -= (nr_pages - 1);
			nr_pages = 1;
		}
activate_locked:
		/* Not a candidate for swapping, so reclaim swap space. */
		if (folio_test_swapcache(folio) &&
		    (mem_cgroup_swap_full(folio) || folio_test_mlocked(folio)))
			folio_free_swap(folio);
		VM_BUG_ON_FOLIO(folio_test_active(folio), folio);
		if (!folio_test_mlocked(folio)) {
			int type = folio_is_file_lru(folio);
			folio_set_active(folio);
			stat->nr_activate[type] += nr_pages;
			count_memcg_folio_events(folio, PGACTIVATE, nr_pages);
		}
keep_locked:
		folio_unlock(folio);
keep:
		list_add(&folio->lru, &ret_folios);
		VM_BUG_ON_FOLIO(folio_test_lru(folio) ||
				folio_test_unevictable(folio), folio);
	}
	/* 'folio_list' is always empty here */

	/* Migrate folios selected for demotion */
	nr_demoted = demote_folio_list(&demote_folios, pgdat);
	nr_reclaimed += nr_demoted;
	stat->nr_demoted += nr_demoted;
	/* Folios that could not be demoted are still in @demote_folios */
	if (!list_empty(&demote_folios)) {
		/* Folios which weren't demoted go back on @folio_list */
		list_splice_init(&demote_folios, folio_list);

		/*
		 * goto retry to reclaim the undemoted folios in folio_list if
		 * desired.
		 *
		 * Reclaiming directly from top tier nodes is not often desired
		 * due to it breaking the LRU ordering: in general memory
		 * should be reclaimed from lower tier nodes and demoted from
		 * top tier nodes.
		 *
		 * However, disabling reclaim from top tier nodes entirely
		 * would cause ooms in edge scenarios where lower tier memory
		 * is unreclaimable for whatever reason, eg memory being
		 * mlocked or too hot to reclaim. We can disable reclaim
		 * from top tier nodes in proactive reclaim though as that is
		 * not real memory pressure.
		 */
		if (!sc->proactive) {
			do_demote_pass = false;
			goto retry;
		}
	}

	pgactivate = stat->nr_activate[0] + stat->nr_activate[1];

	mem_cgroup_uncharge_folios(&free_folios);
	try_to_unmap_flush();
	free_unref_folios(&free_folios);

	list_splice(&ret_folios, folio_list);
	count_vm_events(PGACTIVATE, pgactivate);

	if (plug)
		swap_write_unplug(plug);
	return nr_reclaimed;
}

참고 자료