Merge "mem-offline: improve the effective utilization of movable zone"

This commit is contained in:
qctecmdr 2020-09-07 01:24:47 -07:00 • committed by Gerrit - the friendly Code Review server
commit 7765a70ffe
3 changed files with 289 additions and 2 deletions

View file

@ -15,6 +15,10 @@
#include <linux/kobject.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/rmap.h>
#include <linux/swap.h>
#include <linux/migrate.h>
#include <linux/vmstat.h>
#include <linux/mailbox_client.h>
#include <linux/mailbox/qmp.h>
#include <asm/tlbflush.h>
@ -29,9 +33,13 @@
static unsigned long start_section_nr, end_section_nr;
static struct kobject *kobj;
static unsigned int sections_per_block;
static atomic_t target_migrate_pages = ATOMIC_INIT(0);
static u32 offline_granule;
static bool is_rpm_controller;
static bool has_pend_offline_req;
#define MODULE_CLASS_NAME "mem-offline"
#define MIGRATE_TIMEOUT_SEC (20)
struct section_stat {
unsigned long success_count;
@ -69,6 +77,21 @@ struct memory_refresh_request {
static struct section_stat *mem_info;
struct movable_zone_fill_control {
struct list_head freepages;
unsigned long start_pfn;
unsigned long end_pfn;
unsigned long nr_migrate_pages;
unsigned long nr_free_pages;
unsigned long limit;
int target;
struct zone *zone;
};
static void fill_movable_zone_fn(struct work_struct *work);
static DECLARE_WORK(fill_movable_zone_work, fill_movable_zone_fn);
static DEFINE_MUTEX(page_migrate_lock);
static void clear_pgtable_mapping(phys_addr_t start, phys_addr_t end)
{
unsigned long size = end - start;
@ -355,6 +378,238 @@ out:
return 0;
}
static inline void reset_page_order(struct page *page)
{
__ClearPageBuddy(page);
set_page_private(page, 0);
}
static int isolate_free_page(struct page *page, unsigned int order)
{
struct zone *zone;
zone = page_zone(page);
list_del(&page->lru);
zone->free_area[order].nr_free--;
reset_page_order(page);
return 1UL << order;
}
static void isolate_free_pages(struct movable_zone_fill_control *fc)
{
struct page *page;
unsigned long flags;
unsigned int order;
unsigned long start_pfn = fc->start_pfn;
unsigned long end_pfn = fc->end_pfn;
spin_lock_irqsave(&fc->zone->lock, flags);
for (; start_pfn < end_pfn; start_pfn++) {
unsigned long isolated;
if (!pfn_valid(start_pfn))
continue;
page = pfn_to_page(start_pfn);
if (!page)
continue;
if (PageCompound(page)) {
struct page *head = compound_head(page);
int skip;
skip = (1 << compound_order(head)) - (page - head);
start_pfn += skip - 1;
continue;
}
if (!PageBuddy(page))
continue;
order = page_private(page);
isolated = isolate_free_page(page, order);
set_page_private(page, order);
list_add_tail(&page->lru, &fc->freepages);
fc->nr_free_pages += isolated;
__mod_zone_page_state(fc->zone, NR_FREE_PAGES, -isolated);
start_pfn += isolated - 1;
/*
* Make sure that the zone->lock is not held for long by
* returning once we have SWAP_CLUSTER_MAX pages in the
* free list for migration.
*/
if (fc->nr_free_pages >= SWAP_CLUSTER_MAX ||
has_pend_offline_req)
break;
}
fc->start_pfn = start_pfn + 1;
spin_unlock_irqrestore(&fc->zone->lock, flags);
split_map_pages(&fc->freepages);
}
static struct page *movable_page_alloc(struct page *page, unsigned long data)
{
struct movable_zone_fill_control *fc;
struct page *freepage;
fc = (struct movable_zone_fill_control *)data;
if (list_empty(&fc->freepages)) {
isolate_free_pages(fc);
if (list_empty(&fc->freepages))
return NULL;
}
freepage = list_entry(fc->freepages.next, struct page, lru);
list_del(&freepage->lru);
fc->nr_free_pages--;
return freepage;
}
static void movable_page_free(struct page *page, unsigned long data)
{
struct movable_zone_fill_control *fc;
fc = (struct movable_zone_fill_control *)data;
list_add(&page->lru, &fc->freepages);
fc->nr_free_pages++;
}
static unsigned long get_anon_movable_pages(
struct movable_zone_fill_control *fc,
unsigned long start_pfn,
unsigned long end_pfn, struct list_head *list)
{
int found = 0, pfn, ret;
int limit = min_t(int, fc->target, (int)pageblock_nr_pages);
fc->nr_migrate_pages = 0;
for (pfn = start_pfn; pfn < end_pfn && found < limit; ++pfn) {
struct page *page = pfn_to_page(pfn);
if (!pfn_valid(pfn))
continue;
if (PageCompound(page)) {
struct page *head = compound_head(page);
int skip;
skip = (1 << compound_order(head)) - (page - head);
pfn += skip - 1;
continue;
}
if (PageBuddy(page)) {
unsigned long freepage_order;
freepage_order = READ_ONCE(page_private(page));
if (freepage_order > 0 && freepage_order < MAX_ORDER)
pfn += (1 << page_private(page)) - 1;
continue;
}
if (!(pfn % pageblock_nr_pages) &&
get_pageblock_migratetype(page) == MIGRATE_CMA) {
pfn += pageblock_nr_pages - 1;
continue;
}
if (!PageLRU(page) || !PageAnon(page))
continue;
if (!get_page_unless_zero(page))
continue;
found++;
ret = isolate_lru_page(page);
if (!ret) {
list_add_tail(&page->lru, list);
++fc->nr_migrate_pages;
}
put_page(page);
}
return pfn;
}
static void prepare_fc(struct movable_zone_fill_control *fc)
{
struct zone *zone;
zone = &(NODE_DATA(0)->node_zones[ZONE_MOVABLE]);
fc->zone = zone;
fc->start_pfn = zone->zone_start_pfn;
fc->end_pfn = zone_end_pfn(zone);
fc->limit = atomic64_read(&zone->managed_pages);
INIT_LIST_HEAD(&fc->freepages);
}
static void fill_movable_zone_fn(struct work_struct *work)
{
unsigned long start_pfn, end_pfn;
unsigned long movable_highmark;
struct zone *normal_zone = &(NODE_DATA(0)->node_zones[ZONE_NORMAL]);
struct zone *movable_zone = &(NODE_DATA(0)->node_zones[ZONE_MOVABLE]);
LIST_HEAD(source);
int ret, free;
struct movable_zone_fill_control fc = { {0} };
unsigned long timeout = MIGRATE_TIMEOUT_SEC * HZ, expire;
start_pfn = normal_zone->zone_start_pfn;
end_pfn = zone_end_pfn(normal_zone);
movable_highmark = high_wmark_pages(movable_zone);
if (has_pend_offline_req)
return;
lru_add_drain_all();
drain_all_pages(normal_zone);
if (!mutex_trylock(&page_migrate_lock))
return;
prepare_fc(&fc);
if (!fc.limit)
goto out;
expire = jiffies + timeout;
restart:
fc.target = atomic_xchg(&target_migrate_pages, 0);
if (!fc.target)
goto out;
repeat:
cond_resched();
if (time_after(jiffies, expire))
goto out;
free = zone_page_state(movable_zone, NR_FREE_PAGES);
if (free - fc.target <= movable_highmark)
fc.target = free - movable_highmark;
if (fc.target <= 0)
goto out;
start_pfn = get_anon_movable_pages(&fc, start_pfn, end_pfn, &source);
if (list_empty(&source) && start_pfn < end_pfn)
goto repeat;
ret = migrate_pages(&source, movable_page_alloc, movable_page_free,
(unsigned long) &fc,
MIGRATE_ASYNC, MR_MEMORY_HOTPLUG);
if (ret)
putback_movable_pages(&source);
fc.target -= fc.nr_migrate_pages;
if (ret == -ENOMEM || start_pfn >= end_pfn || has_pend_offline_req)
goto out;
else if (fc.target <= 0)
goto restart;
goto repeat;
out:
mutex_unlock(&page_migrate_lock);
if (fc.nr_free_pages > 0)
release_freepages(&fc.freepages);
}
static int mem_event_callback(struct notifier_block *self,
unsigned long action, void *arg)
{
@ -415,6 +670,8 @@ static int mem_event_callback(struct notifier_block *self,
start_addr, end_addr);
++mem_info[(sec_nr - start_section_nr + MEMORY_OFFLINE *
idx) / sections_per_block].fail_count;
has_pend_offline_req = true;
cancel_work_sync(&fill_movable_zone_work);
cur = ktime_get();
break;
case MEM_OFFLINE:
@ -431,6 +688,7 @@ static int mem_event_callback(struct notifier_block *self,
delay = ktime_ms_delta(ktime_get(), cur);
record_stat(sec_nr, delay, MEMORY_OFFLINE);
cur = 0;
has_pend_offline_req = false;
pr_info("mem-offline: Offlined memory block mem%pK\n",
(void *)sec_nr);
break;
@ -665,12 +923,34 @@ static ssize_t show_mem_stats(struct kobject *kobj,
static struct kobj_attribute stats_attr =
__ATTR(stats, 0444, show_mem_stats, NULL);
static ssize_t store_anon_migrate(struct kobject *kobj,
struct kobj_attribute *attr, const char *buf,
size_t size)
{
int val = 0, ret;
ret = kstrtoint(buf, 0, &val);
if (ret < 0)
return ret;
atomic_add(val, &target_migrate_pages);
if (!work_pending(&fill_movable_zone_work))
queue_work(system_unbound_wq, &fill_movable_zone_work);
return size;
}
static struct kobj_attribute offline_granule_attr =
__ATTR(offline_granule, 0444, show_mem_offline_granule, NULL);
static struct kobj_attribute anon_migration_size_attr =
__ATTR(anon_migrate, 0220, NULL, store_anon_migrate);
static struct attribute *mem_root_attrs[] = {
&stats_attr.attr,
&offline_granule_attr.attr,
&anon_migration_size_attr.attr,
NULL,
};

View file

@ -78,6 +78,8 @@ extern int migrate_huge_page_move_mapping(struct address_space *mapping,
struct page *newpage, struct page *page);
extern int migrate_page_move_mapping(struct address_space *mapping,
struct page *newpage, struct page *page, int extra_count);
extern void split_map_pages(struct list_head *list);
extern unsigned long release_freepages(struct list_head *freelist);
#else
static inline void putback_movable_pages(struct list_head *l) {}
@ -104,6 +106,11 @@ static inline int migrate_huge_page_move_mapping(struct address_space *mapping,
return -ENOSYS;
}
static inline void split_map_pages(struct list_head *list) { }
static inline unsigned long release_freepages(struct list_head *freelist)
{
return 0;
}
#endif /* CONFIG_MIGRATION */
#ifdef CONFIG_COMPACTION

View file

@ -50,7 +50,7 @@ static inline void count_compact_events(enum vm_event_item item, long delta)
#define pageblock_start_pfn(pfn) block_start_pfn(pfn, pageblock_order)
#define pageblock_end_pfn(pfn) block_end_pfn(pfn, pageblock_order)
static unsigned long release_freepages(struct list_head *freelist)
unsigned long release_freepages(struct list_head *freelist)
{
struct page *page, *next;
unsigned long high_pfn = 0;
@ -66,7 +66,7 @@ static unsigned long release_freepages(struct list_head *freelist)
return high_pfn;
}
static void split_map_pages(struct list_head *list)
void split_map_pages(struct list_head *list)
{
unsigned int i, order, nr_pages;
struct page *page, *next;