swapoff of a real swap device iterates its slots through try_to_unuse() and reads each one back into memory. A slot that was written back from an xswap entry has no folio in the swap cache, so the loop skips it and the slot is freed without ever reading the data. While the xswap entry will keep pointing at a slot that no longer holds anything. Now change try_to_unuse() to analyze such a slot through its reverse mapping: bring the folio back, mark it dirty so that reclaim stores it into zswap again, and only then drop the backend. Signed-off-by: Baoquan He --- mm/swapfile.c | 97 ++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 96 insertions(+), 1 deletion(-) diff --git a/mm/swapfile.c b/mm/swapfile.c index 7b44458472a1..9afccdc03845 100644 --- a/mm/swapfile.c +++ b/mm/swapfile.c @@ -44,6 +44,7 @@ #include #include #include +#include #include #include @@ -3138,6 +3139,96 @@ static void xswap_release_slot_backend(struct swap_info_struct *si, xswap_free_phys_slot(psi, entry, phys); } +/* + * A written-back slot has no folio of its own here, so the unuse loop would + * skip it. Resolve it through the reverse mapping and free both sides. + */ +static int xswap_unuse_rmap(struct swap_info_struct *si, unsigned long offset) +{ + struct swap_cluster_info *pci, *xci; + struct swap_info_struct *xsi; + struct swap_io_ctx ctx = {}; + struct mempolicy *mpol; + struct folio *folio; + unsigned long swp_tb, xoff; + swp_entry_t xentry, phys; + + phys = swp_entry(si->type, offset); + + pci = swap_cluster_lock(si, offset); + if (!pci) + return 0; + swp_tb = __swap_table_get(pci, offset % SWAPFILE_CLUSTER); + if (!swp_tb_is_pointer(swp_tb)) { + swap_cluster_unlock(pci); + return 0; + } + xentry = xswap_rmap_to_entry(swp_tb); + swap_cluster_unlock(pci); + + xoff = swp_offset(xentry); + + /* + * Pin the owner before reading its cluster_info: + * free_swap_cluster_info() only unmaps that after killing si->users. + * No owner left means the slot is dropped below. + */ + xsi = swap_type_to_info(swp_type(xentry)); + if (!xsi || !(xsi->flags & SWP_XSWAP) || + xoff >= READ_ONCE(xsi->nr_clusters_mapped) * SWAPFILE_CLUSTER || + !get_swap_device_info(xsi)) + goto out_free; + + /* + * Bring the data back before the slot goes. A dirty folio is what + * reclaim stores into zswap next time. + */ + folio = swap_cache_get_folio(xentry); + if (!folio) { + mpol = get_task_policy(current); + folio = swap_cache_alloc_folio(xentry, GFP_HIGHUSER_MOVABLE, + BIT(0), NULL, mpol, + NO_INTERLEAVE_INDEX); + if (IS_ERR(folio)) { + put_swap_device(xsi); + return PTR_ERR(folio) == -ENOMEM ? -ENOMEM : 0; + } + swap_read_folio(&ctx, folio); + swap_read_submit(&ctx); + folio_lock(folio); + } else { + folio_lock(folio); + } + + if (folio_matches_swap_entry(folio, xentry)) { + folio_wait_writeback(folio); + if (unlikely(!folio_test_uptodate(folio))) + swap_cache_del_folio(folio); + else + folio_mark_dirty(folio); + } + folio_unlock(folio); + folio_put(folio); + + /* The slot is going away, so our record of it is stale either way. */ + xci = swap_cluster_lock(xsi, xoff); + if (xci) { + if (xswap_slot_backend(xci, xoff % SWAPFILE_CLUSTER).val == phys.val) + WRITE_ONCE(xci->xs_table[xoff % SWAPFILE_CLUSTER], 0); + swap_cluster_unlock(xci); + } + put_swap_device(xsi); +out_free: + /* Leaving the mapping in place would make the unuse loop spin. */ + xswap_free_phys_slot(si, xentry, phys); + return 0; +} +#else +static inline int xswap_unuse_rmap(struct swap_info_struct *si, + unsigned long offset) +{ + return 0; +} #endif /* CONFIG_XSWAP */ static int try_to_unuse(unsigned int type) { @@ -3197,8 +3288,12 @@ static int try_to_unuse(unsigned int type) entry = swp_entry(type, i); folio = swap_cache_get_folio(entry); - if (!folio) + if (!folio) { + retval = xswap_unuse_rmap(si, i); + if (retval) + return retval; continue; + } /* * It is conceivable that a racing task removed this folio from -- 2.54.0