openEuler_kernel_rk3588/mm/clear_freelist_page.c
2026-01-29 22:25:33 +08:00

170 lines
4.0 KiB
C

// SPDX-License-Identifier: GPL-2.0
/*
* Support for clear free list pages.
*/
#include <linux/mmzone.h>
#include <linux/mm_types.h>
#include <linux/mm.h>
#include <linux/sysctl.h>
#include <linux/highmem.h>
#include <linux/slab.h>
#include <linux/workqueue.h>
#include <linux/sched.h>
#include <linux/atomic.h>
#include <linux/nmi.h>
#include <linux/sched/clock.h>
#include <linux/module.h>
#define CFP_DEFAULT_TIMEOUT 2000
#define for_each_populated_zone_pgdat(pgdat, zone) \
for (zone = pgdat->node_zones; \
zone; \
zone = next_pgdat_zone(zone)) \
if (!populated_zone(zone)) \
; /* do nothing */ \
else
struct pgdat_entry {
struct pglist_data *pgdat;
struct work_struct work;
};
static DECLARE_WAIT_QUEUE_HEAD(clear_freelist_wait);
static DEFINE_MUTEX(clear_freelist_lock);
static atomic_t clear_freelist_workers;
static atomic_t clear_pages_num;
static ulong cfp_timeout_ms = CFP_DEFAULT_TIMEOUT;
/*
* next_pgdat_zone - helper magic for for_each_populated_zone_pgdat()
*/
static struct zone *next_pgdat_zone(struct zone *zone)
{
pg_data_t *pgdat = zone->zone_pgdat;
if (zone < pgdat->node_zones + MAX_NR_ZONES - 1)
zone++;
else
zone = NULL;
return zone;
}
static void clear_pgdat_freelist_pages(struct work_struct *work)
{
struct pgdat_entry *entry = container_of(work, struct pgdat_entry, work);
u64 cfp_timeout_ns = cfp_timeout_ms * NSEC_PER_MSEC;
struct pglist_data *pgdat = entry->pgdat;
unsigned long flags, order, t;
struct page *page;
struct zone *zone;
u64 start, now;
start = sched_clock();
for_each_populated_zone_pgdat(pgdat, zone) {
spin_lock_irqsave(&zone->lock, flags);
for_each_migratetype_order(order, t) {
list_for_each_entry(page, &zone->free_area[order].free_list[t], lru) {
now = sched_clock();
if (unlikely(now - start > cfp_timeout_ns)) {
spin_unlock_irqrestore(&zone->lock, flags);
goto out;
}
memset(page_address(page), 0, (1 << order) * PAGE_SIZE);
touch_nmi_watchdog();
atomic_add(1 << order, &clear_pages_num);
}
}
spin_unlock_irqrestore(&zone->lock, flags);
cond_resched();
}
out:
kfree(entry);
if (atomic_dec_and_test(&clear_freelist_workers))
wake_up(&clear_freelist_wait);
}
static void init_clear_freelist_work(struct pglist_data *pgdat)
{
struct pgdat_entry *entry;
entry = kzalloc(sizeof(struct pgdat_entry), GFP_KERNEL);
if (!entry)
return;
entry->pgdat = pgdat;
INIT_WORK(&entry->work, clear_pgdat_freelist_pages);
queue_work_node(pgdat->node_id, system_unbound_wq, &entry->work);
}
static void clear_freelist_pages(void)
{
struct pglist_data *pgdat;
mutex_lock(&clear_freelist_lock);
drain_all_pages(NULL);
for_each_online_pgdat(pgdat) {
atomic_inc(&clear_freelist_workers);
init_clear_freelist_work(pgdat);
}
wait_event(clear_freelist_wait, atomic_read(&clear_freelist_workers) == 0);
pr_debug("Cleared pages %d\nFree pages %lu\n", atomic_read(&clear_pages_num),
global_zone_page_state(NR_FREE_PAGES));
atomic_set(&clear_pages_num, 0);
mutex_unlock(&clear_freelist_lock);
}
static int sysctl_clear_freelist_handler(struct ctl_table *table, int write,
void __user *buffer, size_t *lenp, loff_t *ppos)
{
int ret;
int val;
table->data = &val;
ret = proc_dointvec_minmax(table, write, buffer, lenp, ppos);
if (!ret && write)
clear_freelist_pages();
return ret;
}
static struct ctl_table clear_freelist_table[] = {
{
.procname = "clear_freelist_pages",
.data = NULL,
.maxlen = sizeof(int),
.mode = 0200,
.proc_handler = &sysctl_clear_freelist_handler,
.extra1 = SYSCTL_ONE,
.extra2 = SYSCTL_ONE,
}
};
static bool clear_freelist_enabled;
static int __init setup_clear_freelist(char *str)
{
clear_freelist_enabled = true;
return 1;
}
__setup("clear_freelist", setup_clear_freelist);
static int __init clear_freelist_init(void)
{
if (clear_freelist_enabled)
register_sysctl_init("vm", clear_freelist_table);
return 0;
}
module_init(clear_freelist_init);
module_param(cfp_timeout_ms, ulong, 0644);