[PATCH v2 4/5] mm: Refector release_pages()

From: Ryan Roberts
Date: Wed Aug 30 2023 - 15:23:50 EST


In preparation for implementing folios_put_refs() in the next patch,
refactor release_pages() into a set of helper functions, which can be
reused. The primary difference between release_pages() and
folios_put_refs() is how they iterate over the set of folios. The
per-folio actions are identical.

No functional change intended.

Signed-off-by: Ryan Roberts <ryan.roberts@xxxxxxx>
---
mm/swap.c | 167 +++++++++++++++++++++++++++++++-----------------------
1 file changed, 97 insertions(+), 70 deletions(-)

diff --git a/mm/swap.c b/mm/swap.c
index b05cce475202..5d3e35668929 100644
--- a/mm/swap.c
+++ b/mm/swap.c
@@ -945,6 +945,98 @@ void lru_cache_disable(void)
#endif
}

+struct folios_put_refs_ctx {
+ struct list_head pages_to_free;
+ struct lruvec *lruvec;
+ unsigned long flags;
+ unsigned int lock_batch;
+};
+
+static void __folios_put_refs_init(struct folios_put_refs_ctx *ctx)
+{
+ *ctx = (struct folios_put_refs_ctx) {
+ .pages_to_free = LIST_HEAD_INIT(ctx->pages_to_free),
+ .lruvec = NULL,
+ .flags = 0,
+ };
+}
+
+static void __folios_put_refs_complete(struct folios_put_refs_ctx *ctx)
+{
+ if (ctx->lruvec)
+ unlock_page_lruvec_irqrestore(ctx->lruvec, ctx->flags);
+
+ mem_cgroup_uncharge_list(&ctx->pages_to_free);
+ free_unref_page_list(&ctx->pages_to_free);
+}
+
+static void __folios_put_refs_do_one(struct folios_put_refs_ctx *ctx,
+ struct folio *folio, int refs)
+{
+ /*
+ * Make sure the IRQ-safe lock-holding time does not get
+ * excessive with a continuous string of pages from the
+ * same lruvec. The lock is held only if lruvec != NULL.
+ */
+ if (ctx->lruvec && ++ctx->lock_batch == SWAP_CLUSTER_MAX) {
+ unlock_page_lruvec_irqrestore(ctx->lruvec, ctx->flags);
+ ctx->lruvec = NULL;
+ }
+
+ if (is_huge_zero_page(&folio->page))
+ return;
+
+ if (folio_is_zone_device(folio)) {
+ if (ctx->lruvec) {
+ unlock_page_lruvec_irqrestore(ctx->lruvec, ctx->flags);
+ ctx->lruvec = NULL;
+ }
+ if (put_devmap_managed_page_refs(&folio->page, refs))
+ return;
+ if (folio_ref_sub_and_test(folio, refs))
+ free_zone_device_page(&folio->page);
+ return;
+ }
+
+ if (!folio_ref_sub_and_test(folio, refs))
+ return;
+
+ if (folio_test_large(folio)) {
+ if (ctx->lruvec) {
+ unlock_page_lruvec_irqrestore(ctx->lruvec, ctx->flags);
+ ctx->lruvec = NULL;
+ }
+ __folio_put_large(folio);
+ return;
+ }
+
+ if (folio_test_lru(folio)) {
+ struct lruvec *prev_lruvec = ctx->lruvec;
+
+ ctx->lruvec = folio_lruvec_relock_irqsave(folio, ctx->lruvec,
+ &ctx->flags);
+ if (prev_lruvec != ctx->lruvec)
+ ctx->lock_batch = 0;
+
+ lruvec_del_folio(ctx->lruvec, folio);
+ __folio_clear_lru_flags(folio);
+ }
+
+ /*
+ * In rare cases, when truncation or holepunching raced with
+ * munlock after VM_LOCKED was cleared, Mlocked may still be
+ * found set here. This does not indicate a problem, unless
+ * "unevictable_pgs_cleared" appears worryingly large.
+ */
+ if (unlikely(folio_test_mlocked(folio))) {
+ __folio_clear_mlocked(folio);
+ zone_stat_sub_folio(folio, NR_MLOCK);
+ count_vm_event(UNEVICTABLE_PGCLEARED);
+ }
+
+ list_add(&folio->lru, &ctx->pages_to_free);
+}
+
/**
* release_pages - batched put_page()
* @arg: array of pages to release
@@ -959,10 +1051,9 @@ void release_pages(release_pages_arg arg, int nr)
{
int i;
struct page **pages = arg.pages;
- LIST_HEAD(pages_to_free);
- struct lruvec *lruvec = NULL;
- unsigned long flags = 0;
- unsigned int lock_batch;
+ struct folios_put_refs_ctx ctx;
+
+ __folios_put_refs_init(&ctx);

for (i = 0; i < nr; i++) {
struct folio *folio;
@@ -970,74 +1061,10 @@ void release_pages(release_pages_arg arg, int nr)
/* Turn any of the argument types into a folio */
folio = page_folio(pages[i]);

- /*
- * Make sure the IRQ-safe lock-holding time does not get
- * excessive with a continuous string of pages from the
- * same lruvec. The lock is held only if lruvec != NULL.
- */
- if (lruvec && ++lock_batch == SWAP_CLUSTER_MAX) {
- unlock_page_lruvec_irqrestore(lruvec, flags);
- lruvec = NULL;
- }
-
- if (is_huge_zero_page(&folio->page))
- continue;
-
- if (folio_is_zone_device(folio)) {
- if (lruvec) {
- unlock_page_lruvec_irqrestore(lruvec, flags);
- lruvec = NULL;
- }
- if (put_devmap_managed_page(&folio->page))
- continue;
- if (folio_put_testzero(folio))
- free_zone_device_page(&folio->page);
- continue;
- }
-
- if (!folio_put_testzero(folio))
- continue;
-
- if (folio_test_large(folio)) {
- if (lruvec) {
- unlock_page_lruvec_irqrestore(lruvec, flags);
- lruvec = NULL;
- }
- __folio_put_large(folio);
- continue;
- }
-
- if (folio_test_lru(folio)) {
- struct lruvec *prev_lruvec = lruvec;
-
- lruvec = folio_lruvec_relock_irqsave(folio, lruvec,
- &flags);
- if (prev_lruvec != lruvec)
- lock_batch = 0;
-
- lruvec_del_folio(lruvec, folio);
- __folio_clear_lru_flags(folio);
- }
-
- /*
- * In rare cases, when truncation or holepunching raced with
- * munlock after VM_LOCKED was cleared, Mlocked may still be
- * found set here. This does not indicate a problem, unless
- * "unevictable_pgs_cleared" appears worryingly large.
- */
- if (unlikely(folio_test_mlocked(folio))) {
- __folio_clear_mlocked(folio);
- zone_stat_sub_folio(folio, NR_MLOCK);
- count_vm_event(UNEVICTABLE_PGCLEARED);
- }
-
- list_add(&folio->lru, &pages_to_free);
+ __folios_put_refs_do_one(&ctx, folio, 1);
}
- if (lruvec)
- unlock_page_lruvec_irqrestore(lruvec, flags);

- mem_cgroup_uncharge_list(&pages_to_free);
- free_unref_page_list(&pages_to_free);
+ __folios_put_refs_complete(&ctx);
}
EXPORT_SYMBOL(release_pages);

--
2.25.1