2026-01-29 22:25:33 +08:00

269 lines
6.5 KiB
C

// SPDX-License-Identifier: GPL-2.0
#include <linux/mm.h>
#include <linux/swap.h>
#include <linux/pagemap.h>
#include <linux/memcontrol.h>
#include <linux/gfp.h>
#include <linux/mm_inline.h>
#include <linux/sysctl.h>
#include <linux/etmem.h>
#include "internal.h"
static bool enable_kernel_swap __read_mostly = true;
bool kernel_swap_enabled(void)
{
return READ_ONCE(enable_kernel_swap);
}
static ssize_t kernel_swap_enable_show(struct kobject *kobj,
struct kobj_attribute *attr, char *buf)
{
return sprintf(buf, "%s\n", enable_kernel_swap ? "true" : "false");
}
static ssize_t kernel_swap_enable_store(struct kobject *kobj,
struct kobj_attribute *attr,
const char *buf, size_t count)
{
if (!strncmp(buf, "true", 4) || !strncmp(buf, "1", 1))
WRITE_ONCE(enable_kernel_swap, true);
else if (!strncmp(buf, "false", 5) || !strncmp(buf, "0", 1))
WRITE_ONCE(enable_kernel_swap, false);
else
return -EINVAL;
return count;
}
struct kobj_attribute kernel_swap_enable_attr =
__ATTR(kernel_swap_enable, 0644, kernel_swap_enable_show,
kernel_swap_enable_store);
int add_page_for_swap(struct page *page, struct list_head *pagelist)
{
int err = -EBUSY;
struct page *head;
/* If the page is mapped by more than one process, do not swap it */
if (page_mapcount(page) > 1)
return -EACCES;
if (PageHuge(page))
return -EACCES;
head = compound_head(page);
if (!folio_isolate_lru(page_folio(head))) {
put_page(page);
return err;
}
put_page(page);
if (PageUnevictable(page))
putback_lru_page(page);
else
list_add_tail(&head->lru, pagelist);
err = 0;
return err;
}
EXPORT_SYMBOL_GPL(add_page_for_swap);
struct page *get_page_from_vaddr(struct mm_struct *mm, unsigned long vaddr)
{
struct page *page;
struct vm_area_struct *vma;
unsigned int follflags;
mmap_read_lock(mm);
vma = find_vma(mm, vaddr);
if (!vma || vaddr < vma->vm_start || vma->vm_flags & VM_LOCKED) {
mmap_read_unlock(mm);
return NULL;
}
follflags = FOLL_GET | FOLL_DUMP;
page = follow_page(vma, vaddr, follflags);
if (IS_ERR(page) || !page) {
mmap_read_unlock(mm);
return NULL;
}
mmap_read_unlock(mm);
return page;
}
EXPORT_SYMBOL_GPL(get_page_from_vaddr);
#define SWAP_SCAN_NUM_MAX 32
static unsigned long get_swapcache_reclaim_num(unsigned long *swapcache_watermark)
{
return total_swapcache_pages() >
swapcache_watermark[ETMEM_SWAPCACHE_WMARK_LOW] ?
(total_swapcache_pages() - swapcache_watermark[ETMEM_SWAPCACHE_WMARK_LOW]) : 0;
}
static int move_lru_folios_to_list(struct lruvec *lruvec,
struct folio *folio, struct list_head *foliolist)
{
if (!folio_test_large(folio)) {
/* If another process is also mapping this folio */
if (folio_mapcount(folio) > 1)
return -EACCES;
} else if (folio_test_hugetlb(folio)) {
/* Do not reclaim hugetlb folios */
return -EACCES;
} else {
/* Try to reclaim THP unless it is mapped by another process */
if (folio_entire_mapcount(folio) > 1)
return -EACCES;
}
/*
* try to a reference to a folio
* may fail if, the folio has been freed/frozen
*/
if (!(folio_try_get(folio)))
return -1;
/* racing with another isolation */
if (!folio_test_clear_lru(folio)) {
folio_put(folio);
return -1;
}
list_move(&folio->lru, foliolist);
update_lru_size(lruvec,
LRU_INACTIVE_ANON,
folio_zonenum(folio),
-folio_nr_pages(folio));
return 0;
}
/*
* For each node, scan the inactive anon lru, isolate and move
* appropriate candidates to swapcache_list[nid]
*/
static void memcg_reclaim_swapcache(struct list_head *swapcache_list,
unsigned long swapcache_to_reclaim)
{
struct mem_cgroup *memcg = NULL, *target_memcg = NULL;
struct lruvec *lruvec;
int nid;
pg_data_t *pgdat;
unsigned int scan_count = 0;
unsigned long swapcache_total_reclaimable = 0;
struct list_head *src = NULL;
struct folio *folio = NULL, *next = NULL, *pos = NULL;
for_each_node_state(nid, N_MEMORY) {
INIT_LIST_HEAD(&swapcache_list[nid]);
cond_resched();
pgdat = NODE_DATA(nid);
memcg = mem_cgroup_iter(target_memcg, NULL, NULL);
do {
cond_resched();
lruvec = mem_cgroup_lruvec(memcg, pgdat);
src = &(lruvec->lists[LRU_INACTIVE_ANON]);
spin_lock_irq(&lruvec->lru_lock);
pos = list_last_entry(src, struct folio, lru);
spin_unlock_irq(&lruvec->lru_lock);
reverse_scan_lru:
cond_resched();
scan_count = 0;
spin_lock_irq(&lruvec->lru_lock);
if (!pos || list_entry_is_head(pos, src, lru)) {
spin_unlock_irq(&lruvec->lru_lock);
continue;
}
if (!folio_test_lru(pos) || folio_lru_list(pos) != LRU_INACTIVE_ANON) {
spin_unlock_irq(&lruvec->lru_lock);
continue;
}
folio = pos;
list_for_each_entry_safe_reverse_from(folio, next, src, lru) {
pos = next;
scan_count++;
if (scan_count >= SWAP_SCAN_NUM_MAX)
break;
if (!folio_test_swapcache(folio) || folio_mapped(folio))
continue;
if (move_lru_folios_to_list(lruvec,
folio,
&swapcache_list[nid]) != 0)
continue;
swapcache_total_reclaimable += folio_nr_pages(folio);
}
spin_unlock_irq(&lruvec->lru_lock);
if (swapcache_total_reclaimable >= swapcache_to_reclaim)
break;
if (scan_count >= SWAP_SCAN_NUM_MAX)
goto reverse_scan_lru;
} while ((memcg = mem_cgroup_iter(target_memcg, memcg, NULL)));
}
}
static int lru_gen_reclaim_swapcache(struct list_head *swapcache_list,
unsigned long swapcache_to_reclaim)
{
return 0;
}
int do_swapcache_reclaim(unsigned long *swapcache_watermark,
unsigned int watermark_nr)
{
int nid;
unsigned long swapcache_to_reclaim = 0;
struct list_head *swapcache_list = NULL, *folio_list = NULL;
struct folio *folio = NULL;
if (swapcache_watermark == NULL ||
watermark_nr < ETMEM_SWAPCACHE_NR_WMARK)
return -EINVAL;
if (lru_gen_enabled())
return lru_gen_reclaim_swapcache(swapcache_list, swapcache_to_reclaim);
swapcache_to_reclaim = get_swapcache_reclaim_num(swapcache_watermark);
swapcache_list = kcalloc(MAX_NUMNODES, sizeof(struct list_head), GFP_KERNEL);
if (swapcache_list == NULL)
return -ENOMEM;
memcg_reclaim_swapcache(swapcache_list, swapcache_to_reclaim);
/* Reclaim all the swapcache we have scanned */
for_each_node_state(nid, N_MEMORY) {
cond_resched();
reclaim_folio_list(&swapcache_list[nid], NODE_DATA(nid), false);
}
/* Put pack all the pages that are not reclaimed by shrink_folio_list */
for_each_node_state(nid, N_MEMORY) {
cond_resched();
folio_list = &swapcache_list[nid];
while (!list_empty(folio_list)) {
folio = lru_to_folio(folio_list);
list_del(&folio->lru);
folio_putback_lru(folio);
}
}
kfree(swapcache_list);
return 0;
}
EXPORT_SYMBOL_GPL(do_swapcache_reclaim);