사용 흔적·writeback·cache 제거는 서로 다른 검사입니다

folio를 free하려면 최근에 쓰이지 않았다는 판단만으로는 부족합니다. dirty 내용을 backing에 보존할 수 있어야 하고, cache lookup이 새 reference를 얻는 경쟁도 막아야 합니다. reclaim의 세 helper를 나누어 보면 이 차이가 선명해집니다.

Reference 결과는 네 가지로 나뉩니다

v6.18 classic folio_check_references()는 rmap을 통해 참조 흔적과 VMA flag를 얻습니다. VM_LOCKED면 ordinary 회수 대상에서 빼는 방향으로 처리하고, rmap 검사 경합 등으로 -1이 반환되면 사용되지 않았다고 추측하지 않고 KEEP합니다. MGLRU가 활성화된 경로는 별도입니다.

classic branch에서는 이전 PG_referenced를 test-and-clear하고 현재 PTE 참조를 봅니다. 참조가 있으면 flag를 다시 세우고, 이전에도 referenced였거나 여러 PTE 참조가 있으면 ACTIVATE합니다. executable file은 첫 관찰에도 보호할 수 있습니다. 그 밖의 첫 참조에는 KEEP으로 재검사 기회를 줍니다.

현재 PTE 참조가 없더라도 이전 folio reference가 있었고 file LRU라면 RECLAIM_CLEAN으로 dirty writeback을 미룰 수 있습니다. 따라서 모든 미참조 file folio가 무조건 clean-only인 것은 아닙니다. 이전 software 상태까지 포함한 조건입니다. old enum의 PAGEREF_*와 v6.18의 FOLIOREF_* 이름을 혼동하지 않아야 합니다.

pageout은 먼저 freeable한지 따집니다

v6.12의 pageout()은 기대하는 reference보다 더 많이 붙어 있는 folio를 쓰기 위해 무리하게 진행하지 않습니다. isolation caller와 cache가 가진 reference에 base page 수와 private 상태 등 구현의 accounting을 반영해야 합니다. 외부 pin을 cache reference로 착각하면 아직 사용 중인 page를 잘못 회수할 수 있습니다.

mapping이 없거나 writeback 경로를 사용할 수 없으면 보존 또는 activation 결과를 돌려줍니다. filesystem의 address_space_operations에 필요한 callback이 없으면 dirty 내용을 안전하게 내보낼 방법이 없기 때문입니다. 이 helper의 반환값은 caller가 lock·보존·재검사를 결정하도록 하는 protocol입니다.

dirty인 경우 folio_clear_dirty_for_io()로 dirty 상태를 정리하고 writeback control을 구성한 뒤 callback을 호출합니다. 이 시점의 dirty clear가 PG_writeback 설정과 같은 일이 아닙니다. 실제 backend가 writeback을 시작하며 해당 flag와 completion protocol을 관리합니다. callback 전에 이미 writeback이 시작됐다고 설명하면 순서가 뒤집힙니다.

PG_reclaim은 I/O가 끝나면 reclaim이 다시 빨리 보도록 하는 힌트입니다. callback이 activation을 요구하거나 오류를 반환하는 경우에는 flag·mapping error·lock 상태를 정리합니다. callback이 unlock하는 정상 경로와 caller가 계속 lock을 가진 PAGE_KEEP·PAGE_CLEAN·PAGE_ACTIVATE 경로를 섞으면 double unlock이나 누락이 생깁니다. PAGE_SUCCESS 뒤에도 writeback 완료와 dirty 여부를 다시 확인해야 합니다.

v6.18에서는 일반 filesystem writeback을 이 helper에서 시작하지 않습니다. swap·tmpfs/shmem을 대상으로 writeout()이 하위 작업을 분리하며, 일반 file은 page-writeback에 맡깁니다. 같은 pageout() 이름이 남아 있어도 v6.12 callback 구조와 지원 대상까지 같지는 않습니다.

고전 v6.0 구현에서 freeable의 구체적인 비교식은 folio_ref_count(folio) - folio_test_private(folio) == 1 + folio_nr_pages(folio)입니다. isolation reference 하나, cache가 base page마다 가진 reference, 선택적인 private buffer reference를 구분합니다. 이 값은 pageout 시도의 사전 조건이며, 최종 제거 시의 원자적 freeze를 대체하지는 않습니다.

mapping이 NULL이라고 항상 같은 실패만 있는 것도 아닙니다. orphaned folio의 private buffer를 해제해 clean으로 만들 수 있으면 PAGE_CLEAN으로 진행할 수 있습니다. 일반 mapping의 writepage callback이 없으면 PAGE_ACTIVATE입니다. callback에 주는 WB_SYNC_NONE, nr_to_write=SWAP_CLUSTER_MAX, range_start=0, range_end=LLONG_MAX, for_reclaim=1은 reclaim용 writeback control입니다. 특히 nr_to_write는 한 callback이 반드시 32장을 썼다는 실행 결과가 아닙니다.

결과 Caller가 이어서 할 일
PAGE_KEEP locked 상태를 정리하고 이번 회수를 미룹니다. 이미 writeback 완료라는 뜻이 아닙니다.
PAGE_ACTIVATE locked folio에 대해 activation·보존 경로로 갑니다.
PAGE_CLEAN lock을 유지한 채 다음 free 조건을 검사합니다.
PAGE_SUCCESS backend의 unlock·writeback 상태를 고려해 dirty·writeback을 다시 보고, 필요하면 trylock과 재검사를 수행합니다.

음수 write error는 mapping error 처리로 전달됩니다. AOP_WRITEPAGE_ACTIVATE는 reclaim 표시를 지우고 activation 결과로 바꾸며, writeback flag가 남지 않은 경우에도 reclaim 표시를 정리합니다. I/O error 처리, folio unlock, writeback completion은 하나의 성공 flag로 대신할 수 없습니다.

Mapping은 user PTE와 다른 object입니다

folio_mapping()이 반환하는 address_space는 file page cache나 swap cache의 소속을 뜻합니다. folio_mapped()가 말하는 user page-table mapping과 다릅니다. user PTE를 모두 없앴어도 cache entry와 cache-held reference는 남을 수 있습니다.

file cache라면 해당 i_pages XArray, v6.12 swap cache라면 swapper address space의 XArray에서 object를 제거해야 합니다. v6.18 swap cache는 per-cluster swap table이므로 같은 자료구조라고 설명해서는 안 됩니다. lookup과 삭제의 lock protocol도 version에 맞춰 읽어야 합니다.

Refcount freeze 뒤 dirty를 재검사하는 이유

__remove_mapping() 계열은 folio lock 외에도 cache 자료구조의 lock을 사용합니다. 예상 reference count와 현재 값이 정확히 일치할 때에만 이를 원자적으로 0으로 freeze합니다. cache lookup이나 kernel pin이 추가 reference를 얻었다면 실패하고 이번 회수를 포기합니다. count를 읽고 나중에 0으로 대입하는 방식으로 대체할 수 없습니다.

freeze 뒤에는 dirty를 다시 확인합니다. 순서가 중요합니다. 바깥에서 clean을 먼저 확인했더라도 다른 사용자가 그 사이 수정했을 수 있기 때문입니다. dirty가 발견되면 refcount를 unfreeze하고 제거를 취소합니다.

swap cache라면 entry와 cache accounting을 정리하고, file cache라면 XArray에서 제거하며 working-set shadow를 남길 수 있습니다. filesystem의 free_folio() hook은 filesystem 고유의 후속 정리에 관여합니다. user mapping 수와 무관한 cache lifetime protocol이므로 이를 단순 PTE 삭제로 대신할 수 없습니다.

고전 v6.0에서는 file folio에 inode의 i_lock을 먼저 잡고 mapping->i_pages의 IRQ-safe XArray lock을 잡습니다. swap cache에서는 이 inode lock 없이 swapper XArray를 사용합니다. 제거 후 swap 측은 cache-held backing reference를 put_swap_page()로 정리하고, file 측은 XArray와 inode lock을 놓은 뒤 저장해 둔 free_folio() callback을 호출합니다. callback을 계속 모든 lock 아래 실행한다고 읽으면 lock 순서를 잘못 이해하게 됩니다.

v6.18 swap 측은 이 XArray lock 대신 swap_cluster_get_and_lock_irq()로 얻은 cluster lock을 사용합니다. __swap_cache_del_folio() 뒤 cluster lock을 풀고 put_swap_folio()를 호출합니다. expected count freeze와 dirty 재검사 원칙은 남아 있어도 cache 삭제 API와 lock 범위까지 고전 구현과 같지는 않습니다.

결국 reference 검사는 최근 사용 여부를, pageout은 backing 보존 가능성을, mapping 제거는 object를 더 이상 lookup할 수 없도록 만들 수 있는지를 묻습니다. 하나의 성공을 다른 둘의 성공으로 간주하지 않는 것이 reclaim을 읽는 핵심입니다.

전체 code로 ownership과 종료 경로를 확인합니다

v6.0의 reference 검사·writepage callback·cache 제거와 v6.18의 reference 검사·swap/shmem writeout·swap table 제거를 나누어 제시합니다. 이 code는 kernel context 안에서 읽는 전체 정의이며 standalone 예제가 아닙니다.

v6.0 folio_check_references

공식 source · 전체 code · GPL-2.0

v6_0_folio_check_references.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.0, mm/vmscan.c:1433-1488.
 * Complete function definition; surrounding kernel context is required.
 * Source: https://github.com/torvalds/linux/blob/v6.0/mm/vmscan.c#L1433
 * License text: GPL-2.0.txt (in this directory).
 */
static enum page_references folio_check_references(struct folio *folio,
						  struct scan_control *sc)
{
	int referenced_ptes, referenced_folio;
	unsigned long vm_flags;

	referenced_ptes = folio_referenced(folio, 1, sc->target_mem_cgroup,
					   &vm_flags);
	referenced_folio = folio_test_clear_referenced(folio);

	/*
	 * The supposedly reclaimable folio was found to be in a VM_LOCKED vma.
	 * Let the folio, now marked Mlocked, be moved to the unevictable list.
	 */
	if (vm_flags & VM_LOCKED)
		return PAGEREF_ACTIVATE;

	/* rmap lock contention: rotate */
	if (referenced_ptes == -1)
		return PAGEREF_KEEP;

	if (referenced_ptes) {
		/*
		 * All mapped folios start out with page table
		 * references from the instantiating fault, so we need
		 * to look twice if a mapped file/anon folio is used more
		 * than once.
		 *
		 * Mark it and spare it for another trip around the
		 * inactive list.  Another page table reference will
		 * lead to its activation.
		 *
		 * Note: the mark is set for activated folios as well
		 * so that recently deactivated but used folios are
		 * quickly recovered.
		 */
		folio_set_referenced(folio);

		if (referenced_folio || referenced_ptes > 1)
			return PAGEREF_ACTIVATE;

		/*
		 * Activate file-backed executable folios after first usage.
		 */
		if ((vm_flags & VM_EXEC) && folio_is_file_lru(folio))
			return PAGEREF_ACTIVATE;

		return PAGEREF_KEEP;
	}

	/* Reclaim if clean, defer dirty folios to writeback */
	if (referenced_folio && folio_is_file_lru(folio))
		return PAGEREF_RECLAIM_CLEAN;

	return PAGEREF_RECLAIM;
}

v6.0 is_page_cache_freeable

공식 source · 전체 code · GPL-2.0

v6_0_is_page_cache_freeable.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.0, mm/vmscan.c:1039-1048.
 * Complete function definition; surrounding kernel context is required.
 * Source: https://github.com/torvalds/linux/blob/v6.0/mm/vmscan.c#L1039
 * License text: GPL-2.0.txt (in this directory).
 */
static inline int is_page_cache_freeable(struct folio *folio)
{
	/*
	 * A freeable page cache page is referenced only by the caller
	 * that isolated the page, the page cache and optional buffer
	 * heads at page->private.
	 */
	return folio_ref_count(folio) - folio_test_private(folio) ==
		1 + folio_nr_pages(folio);
}

v6.0 pageout

공식 source · 전체 code · GPL-2.0

v6_0_pageout.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.0, mm/vmscan.c:1215-1283.
 * Complete function definition; surrounding kernel context is required.
 * Source: https://github.com/torvalds/linux/blob/v6.0/mm/vmscan.c#L1215
 * License text: GPL-2.0.txt (in this directory).
 */
static pageout_t pageout(struct folio *folio, struct address_space *mapping,
			 struct swap_iocb **plug)
{
	/*
	 * If the folio is dirty, only perform writeback if that write
	 * will be non-blocking.  To prevent this allocation from being
	 * stalled by pagecache activity.  But note that there may be
	 * stalls if we need to run get_block().  We could test
	 * PagePrivate for that.
	 *
	 * If this process is currently in __generic_file_write_iter() against
	 * this folio's queue, we can perform writeback even if that
	 * will block.
	 *
	 * If the folio is swapcache, write it back even if that would
	 * block, for some throttling. This happens by accident, because
	 * swap_backing_dev_info is bust: it doesn't reflect the
	 * congestion state of the swapdevs.  Easy to fix, if needed.
	 */
	if (!is_page_cache_freeable(folio))
		return PAGE_KEEP;
	if (!mapping) {
		/*
		 * Some data journaling orphaned folios can have
		 * folio->mapping == NULL while being dirty with clean buffers.
		 */
		if (folio_test_private(folio)) {
			if (try_to_free_buffers(folio)) {
				folio_clear_dirty(folio);
				pr_info("%s: orphaned folio\n", __func__);
				return PAGE_CLEAN;
			}
		}
		return PAGE_KEEP;
	}
	if (mapping->a_ops->writepage == NULL)
		return PAGE_ACTIVATE;

	if (folio_clear_dirty_for_io(folio)) {
		int res;
		struct writeback_control wbc = {
			.sync_mode = WB_SYNC_NONE,
			.nr_to_write = SWAP_CLUSTER_MAX,
			.range_start = 0,
			.range_end = LLONG_MAX,
			.for_reclaim = 1,
			.swap_plug = plug,
		};

		folio_set_reclaim(folio);
		res = mapping->a_ops->writepage(&folio->page, &wbc);
		if (res < 0)
			handle_write_error(mapping, folio, res);
		if (res == AOP_WRITEPAGE_ACTIVATE) {
			folio_clear_reclaim(folio);
			return PAGE_ACTIVATE;
		}

		if (!folio_test_writeback(folio)) {
			/* synchronous write or broken a_ops? */
			folio_clear_reclaim(folio);
		}
		trace_mm_vmscan_write_folio(folio);
		node_stat_add_folio(folio, NR_VMSCAN_WRITE);
		return PAGE_SUCCESS;
	}

	return PAGE_CLEAN;
}

v6.0 __remove_mapping

공식 source · 전체 code · GPL-2.0

v6_0___remove_mapping.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.0, mm/vmscan.c:1289-1383.
 * Complete function definition; surrounding kernel context is required.
 * Source: https://github.com/torvalds/linux/blob/v6.0/mm/vmscan.c#L1289
 * License text: GPL-2.0.txt (in this directory).
 */
static int __remove_mapping(struct address_space *mapping, struct folio *folio,
			    bool reclaimed, struct mem_cgroup *target_memcg)
{
	int refcount;
	void *shadow = NULL;

	BUG_ON(!folio_test_locked(folio));
	BUG_ON(mapping != folio_mapping(folio));

	if (!folio_test_swapcache(folio))
		spin_lock(&mapping->host->i_lock);
	xa_lock_irq(&mapping->i_pages);
	/*
	 * The non racy check for a busy page.
	 *
	 * Must be careful with the order of the tests. When someone has
	 * a ref to the page, it may be possible that they dirty it then
	 * drop the reference. So if PageDirty is tested before page_count
	 * here, then the following race may occur:
	 *
	 * get_user_pages(&page);
	 * [user mapping goes away]
	 * write_to(page);
	 *				!PageDirty(page)    [good]
	 * SetPageDirty(page);
	 * put_page(page);
	 *				!page_count(page)   [good, discard it]
	 *
	 * [oops, our write_to data is lost]
	 *
	 * Reversing the order of the tests ensures such a situation cannot
	 * escape unnoticed. The smp_rmb is needed to ensure the page->flags
	 * load is not satisfied before that of page->_refcount.
	 *
	 * Note that if SetPageDirty is always performed via set_page_dirty,
	 * and thus under the i_pages lock, then this ordering is not required.
	 */
	refcount = 1 + folio_nr_pages(folio);
	if (!folio_ref_freeze(folio, refcount))
		goto cannot_free;
	/* note: atomic_cmpxchg in page_ref_freeze provides the smp_rmb */
	if (unlikely(folio_test_dirty(folio))) {
		folio_ref_unfreeze(folio, refcount);
		goto cannot_free;
	}

	if (folio_test_swapcache(folio)) {
		swp_entry_t swap = folio_swap_entry(folio);
		mem_cgroup_swapout(folio, swap);
		if (reclaimed && !mapping_exiting(mapping))
			shadow = workingset_eviction(folio, target_memcg);
		__delete_from_swap_cache(folio, swap, shadow);
		xa_unlock_irq(&mapping->i_pages);
		put_swap_page(&folio->page, swap);
	} else {
		void (*free_folio)(struct folio *);

		free_folio = mapping->a_ops->free_folio;
		/*
		 * Remember a shadow entry for reclaimed file cache in
		 * order to detect refaults, thus thrashing, later on.
		 *
		 * But don't store shadows in an address space that is
		 * already exiting.  This is not just an optimization,
		 * inode reclaim needs to empty out the radix tree or
		 * the nodes are lost.  Don't plant shadows behind its
		 * back.
		 *
		 * We also don't store shadows for DAX mappings because the
		 * only page cache pages found in these are zero pages
		 * covering holes, and because we don't want to mix DAX
		 * exceptional entries and shadow exceptional entries in the
		 * same address_space.
		 */
		if (reclaimed && folio_is_file_lru(folio) &&
		    !mapping_exiting(mapping) && !dax_mapping(mapping))
			shadow = workingset_eviction(folio, target_memcg);
		__filemap_remove_folio(folio, shadow);
		xa_unlock_irq(&mapping->i_pages);
		if (mapping_shrinkable(mapping))
			inode_add_lru(mapping->host);
		spin_unlock(&mapping->host->i_lock);

		if (free_folio)
			free_folio(folio);
	}

	return 1;

cannot_free:
	xa_unlock_irq(&mapping->i_pages);
	if (!folio_test_swapcache(folio))
		spin_unlock(&mapping->host->i_lock);
	return 0;
}

v6.18 folio_check_references

공식 source · 전체 code · GPL-2.0

v6_18_folio_check_references.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:906-974.
 * Complete function definition; surrounding kernel context is required.
 * Source: https://github.com/torvalds/linux/blob/v6.18/mm/vmscan.c#L906
 * License text: GPL-2.0.txt (in this directory).
 */
static enum folio_references folio_check_references(struct folio *folio,
						  struct scan_control *sc)
{
	int referenced_ptes, referenced_folio;
	vm_flags_t vm_flags;

	referenced_ptes = folio_referenced(folio, 1, sc->target_mem_cgroup,
					   &vm_flags);

	/*
	 * The supposedly reclaimable folio was found to be in a VM_LOCKED vma.
	 * Let the folio, now marked Mlocked, be moved to the unevictable list.
	 */
	if (vm_flags & VM_LOCKED)
		return FOLIOREF_ACTIVATE;

	/*
	 * There are two cases to consider.
	 * 1) Rmap lock contention: rotate.
	 * 2) Skip the non-shared swapbacked folio mapped solely by
	 *    the exiting or OOM-reaped process.
	 */
	if (referenced_ptes == -1)
		return FOLIOREF_KEEP;

	if (lru_gen_enabled()) {
		if (!referenced_ptes)
			return FOLIOREF_RECLAIM;

		return lru_gen_set_refs(folio) ? FOLIOREF_ACTIVATE : FOLIOREF_KEEP;
	}

	referenced_folio = folio_test_clear_referenced(folio);

	if (referenced_ptes) {
		/*
		 * All mapped folios start out with page table
		 * references from the instantiating fault, so we need
		 * to look twice if a mapped file/anon folio is used more
		 * than once.
		 *
		 * Mark it and spare it for another trip around the
		 * inactive list.  Another page table reference will
		 * lead to its activation.
		 *
		 * Note: the mark is set for activated folios as well
		 * so that recently deactivated but used folios are
		 * quickly recovered.
		 */
		folio_set_referenced(folio);

		if (referenced_folio || referenced_ptes > 1)
			return FOLIOREF_ACTIVATE;

		/*
		 * Activate file-backed executable folios after first usage.
		 */
		if ((vm_flags & VM_EXEC) && folio_is_file_lru(folio))
			return FOLIOREF_ACTIVATE;

		return FOLIOREF_KEEP;
	}

	/* Reclaim if clean, defer dirty folios to writeback */
	if (referenced_folio && folio_is_file_lru(folio))
		return FOLIOREF_RECLAIM_CLEAN;

	return FOLIOREF_RECLAIM;
}

v6.18 pageout

공식 source · 전체 code · GPL-2.0

v6_18_pageout.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:683-722.
 * Complete function definition; surrounding kernel context is required.
 * Source: https://github.com/torvalds/linux/blob/v6.18/mm/vmscan.c#L683
 * License text: GPL-2.0.txt (in this directory).
 */
static pageout_t pageout(struct folio *folio, struct address_space *mapping,
			 struct swap_iocb **plug, struct list_head *folio_list)
{
	/*
	 * We no longer attempt to writeback filesystem folios here, other
	 * than tmpfs/shmem.  That's taken care of in page-writeback.
	 * If we find a dirty filesystem folio at the end of the LRU list,
	 * typically that means the filesystem is saturating the storage
	 * with contiguous writes and telling it to write a folio here
	 * would only make the situation worse by injecting an element
	 * of random access.
	 *
	 * If the folio is swapcache, write it back even if that would
	 * block, for some throttling. This happens by accident, because
	 * swap_backing_dev_info is bust: it doesn't reflect the
	 * congestion state of the swapdevs.  Easy to fix, if needed.
	 */
	if (!is_page_cache_freeable(folio))
		return PAGE_KEEP;
	if (!mapping) {
		/*
		 * Some data journaling orphaned folios can have
		 * folio->mapping == NULL while being dirty with clean buffers.
		 */
		if (folio_test_private(folio)) {
			if (try_to_free_buffers(folio)) {
				folio_clear_dirty(folio);
				pr_info("%s: orphaned folio\n", __func__);
				return PAGE_CLEAN;
			}
		}
		return PAGE_KEEP;
	}

	if (!shmem_mapping(mapping) && !folio_test_anon(folio))
		return PAGE_ACTIVATE;
	if (!folio_clear_dirty_for_io(folio))
		return PAGE_CLEAN;
	return writeout(folio, mapping, plug, folio_list);
}

v6.18 writeout

공식 source · 전체 code · GPL-2.0

v6_18_writeout.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:647-678.
 * Complete function definition; surrounding kernel context is required.
 * Source: https://github.com/torvalds/linux/blob/v6.18/mm/vmscan.c#L647
 * License text: GPL-2.0.txt (in this directory).
 */
static pageout_t writeout(struct folio *folio, struct address_space *mapping,
		struct swap_iocb **plug, struct list_head *folio_list)
{
	int res;

	folio_set_reclaim(folio);

	/*
	 * The large shmem folio can be split if CONFIG_THP_SWAP is not enabled
	 * or we failed to allocate contiguous swap entries, in which case
	 * the split out folios get added back to folio_list.
	 */
	if (shmem_mapping(mapping))
		res = shmem_writeout(folio, plug, folio_list);
	else
		res = swap_writeout(folio, plug);

	if (res < 0)
		handle_write_error(mapping, folio, res);
	if (res == AOP_WRITEPAGE_ACTIVATE) {
		folio_clear_reclaim(folio);
		return PAGE_ACTIVATE;
	}

	/* synchronous write? */
	if (!folio_test_writeback(folio))
		folio_clear_reclaim(folio);

	trace_mm_vmscan_write_folio(folio);
	node_stat_add_folio(folio, NR_VMSCAN_WRITE);
	return PAGE_SUCCESS;
}

v6.18 __remove_mapping

공식 source · 전체 code · GPL-2.0

v6_18___remove_mapping.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:728-831.
 * Complete function definition; surrounding kernel context is required.
 * Source: https://github.com/torvalds/linux/blob/v6.18/mm/vmscan.c#L728
 * License text: GPL-2.0.txt (in this directory).
 */
static int __remove_mapping(struct address_space *mapping, struct folio *folio,
			    bool reclaimed, struct mem_cgroup *target_memcg)
{
	int refcount;
	void *shadow = NULL;
	struct swap_cluster_info *ci;

	BUG_ON(!folio_test_locked(folio));
	BUG_ON(mapping != folio_mapping(folio));

	if (folio_test_swapcache(folio)) {
		ci = swap_cluster_get_and_lock_irq(folio);
	} else {
		spin_lock(&mapping->host->i_lock);
		xa_lock_irq(&mapping->i_pages);
	}

	/*
	 * The non racy check for a busy folio.
	 *
	 * Must be careful with the order of the tests. When someone has
	 * a ref to the folio, it may be possible that they dirty it then
	 * drop the reference. So if the dirty flag is tested before the
	 * refcount here, then the following race may occur:
	 *
	 * get_user_pages(&page);
	 * [user mapping goes away]
	 * write_to(page);
	 *				!folio_test_dirty(folio)    [good]
	 * folio_set_dirty(folio);
	 * folio_put(folio);
	 *				!refcount(folio)   [good, discard it]
	 *
	 * [oops, our write_to data is lost]
	 *
	 * Reversing the order of the tests ensures such a situation cannot
	 * escape unnoticed. The smp_rmb is needed to ensure the folio->flags
	 * load is not satisfied before that of folio->_refcount.
	 *
	 * Note that if the dirty flag is always set via folio_mark_dirty,
	 * and thus under the i_pages lock, then this ordering is not required.
	 */
	refcount = 1 + folio_nr_pages(folio);
	if (!folio_ref_freeze(folio, refcount))
		goto cannot_free;
	/* note: atomic_cmpxchg in folio_ref_freeze provides the smp_rmb */
	if (unlikely(folio_test_dirty(folio))) {
		folio_ref_unfreeze(folio, refcount);
		goto cannot_free;
	}

	if (folio_test_swapcache(folio)) {
		swp_entry_t swap = folio->swap;

		if (reclaimed && !mapping_exiting(mapping))
			shadow = workingset_eviction(folio, target_memcg);
		__swap_cache_del_folio(ci, folio, swap, shadow);
		memcg1_swapout(folio, swap);
		swap_cluster_unlock_irq(ci);
		put_swap_folio(folio, swap);
	} else {
		void (*free_folio)(struct folio *);

		free_folio = mapping->a_ops->free_folio;
		/*
		 * Remember a shadow entry for reclaimed file cache in
		 * order to detect refaults, thus thrashing, later on.
		 *
		 * But don't store shadows in an address space that is
		 * already exiting.  This is not just an optimization,
		 * inode reclaim needs to empty out the radix tree or
		 * the nodes are lost.  Don't plant shadows behind its
		 * back.
		 *
		 * We also don't store shadows for DAX mappings because the
		 * only page cache folios found in these are zero pages
		 * covering holes, and because we don't want to mix DAX
		 * exceptional entries and shadow exceptional entries in the
		 * same address_space.
		 */
		if (reclaimed && folio_is_file_lru(folio) &&
		    !mapping_exiting(mapping) && !dax_mapping(mapping))
			shadow = workingset_eviction(folio, target_memcg);
		__filemap_remove_folio(folio, shadow);
		xa_unlock_irq(&mapping->i_pages);
		if (mapping_shrinkable(mapping))
			inode_add_lru(mapping->host);
		spin_unlock(&mapping->host->i_lock);

		if (free_folio)
			free_folio(folio);
	}

	return 1;

cannot_free:
	if (folio_test_swapcache(folio)) {
		swap_cluster_unlock_irq(ci);
	} else {
		xa_unlock_irq(&mapping->i_pages);
		spin_unlock(&mapping->host->i_lock);
	}
	return 0;
}

참고 자료