|
@@ -410,7 +410,7 @@ int migrate_huge_page_move_mapping(struct address_space *mapping,
|
|
|
*/
|
|
|
void migrate_page_copy(struct page *newpage, struct page *page)
|
|
|
{
|
|
|
- if (PageHuge(page))
|
|
|
+ if (PageHuge(page) || PageTransHuge(page))
|
|
|
copy_huge_page(newpage, page);
|
|
|
else
|
|
|
copy_highpage(newpage, page);
|
|
@@ -1491,25 +1491,10 @@ bool migrate_ratelimited(int node)
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
-/*
|
|
|
- * Attempt to migrate a misplaced page to the specified destination
|
|
|
- * node. Caller is expected to have an elevated reference count on
|
|
|
- * the page that will be dropped by this function before returning.
|
|
|
- */
|
|
|
-int migrate_misplaced_page(struct page *page, int node)
|
|
|
+/* Returns true if the node is migrate rate-limited after the update */
|
|
|
+bool numamigrate_update_ratelimit(pg_data_t *pgdat)
|
|
|
{
|
|
|
- pg_data_t *pgdat = NODE_DATA(node);
|
|
|
- int isolated = 0;
|
|
|
- LIST_HEAD(migratepages);
|
|
|
-
|
|
|
- /*
|
|
|
- * Don't migrate pages that are mapped in multiple processes.
|
|
|
- * TODO: Handle false sharing detection instead of this hammer
|
|
|
- */
|
|
|
- if (page_mapcount(page) != 1) {
|
|
|
- put_page(page);
|
|
|
- goto out;
|
|
|
- }
|
|
|
+ bool rate_limited = false;
|
|
|
|
|
|
/*
|
|
|
* Rate-limit the amount of data that is being migrated to a node.
|
|
@@ -1522,13 +1507,18 @@ int migrate_misplaced_page(struct page *page, int node)
|
|
|
pgdat->numabalancing_migrate_next_window = jiffies +
|
|
|
msecs_to_jiffies(migrate_interval_millisecs);
|
|
|
}
|
|
|
- if (pgdat->numabalancing_migrate_nr_pages > ratelimit_pages) {
|
|
|
- spin_unlock(&pgdat->numabalancing_migrate_lock);
|
|
|
- put_page(page);
|
|
|
- goto out;
|
|
|
- }
|
|
|
- pgdat->numabalancing_migrate_nr_pages++;
|
|
|
+ if (pgdat->numabalancing_migrate_nr_pages > ratelimit_pages)
|
|
|
+ rate_limited = true;
|
|
|
+ else
|
|
|
+ pgdat->numabalancing_migrate_nr_pages++;
|
|
|
spin_unlock(&pgdat->numabalancing_migrate_lock);
|
|
|
+
|
|
|
+ return rate_limited;
|
|
|
+}
|
|
|
+
|
|
|
+int numamigrate_isolate_page(pg_data_t *pgdat, struct page *page)
|
|
|
+{
|
|
|
+ int ret = 0;
|
|
|
|
|
|
/* Avoid migrating to a node that is nearly full */
|
|
|
if (migrate_balanced_pgdat(pgdat, 1)) {
|
|
@@ -1536,13 +1526,18 @@ int migrate_misplaced_page(struct page *page, int node)
|
|
|
|
|
|
if (isolate_lru_page(page)) {
|
|
|
put_page(page);
|
|
|
- goto out;
|
|
|
+ return 0;
|
|
|
}
|
|
|
- isolated = 1;
|
|
|
|
|
|
+ /* Page is isolated */
|
|
|
+ ret = 1;
|
|
|
page_lru = page_is_file_cache(page);
|
|
|
- inc_zone_page_state(page, NR_ISOLATED_ANON + page_lru);
|
|
|
- list_add(&page->lru, &migratepages);
|
|
|
+ if (!PageTransHuge(page))
|
|
|
+ inc_zone_page_state(page, NR_ISOLATED_ANON + page_lru);
|
|
|
+ else
|
|
|
+ mod_zone_page_state(page_zone(page),
|
|
|
+ NR_ISOLATED_ANON + page_lru,
|
|
|
+ HPAGE_PMD_NR);
|
|
|
}
|
|
|
|
|
|
/*
|
|
@@ -1555,23 +1550,177 @@ int migrate_misplaced_page(struct page *page, int node)
|
|
|
*/
|
|
|
put_page(page);
|
|
|
|
|
|
- if (isolated) {
|
|
|
- int nr_remaining;
|
|
|
-
|
|
|
- nr_remaining = migrate_pages(&migratepages,
|
|
|
- alloc_misplaced_dst_page,
|
|
|
- node, false, MIGRATE_ASYNC,
|
|
|
- MR_NUMA_MISPLACED);
|
|
|
- if (nr_remaining) {
|
|
|
- putback_lru_pages(&migratepages);
|
|
|
- isolated = 0;
|
|
|
- } else
|
|
|
- count_vm_numa_event(NUMA_PAGE_MIGRATE);
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
+/*
|
|
|
+ * Attempt to migrate a misplaced page to the specified destination
|
|
|
+ * node. Caller is expected to have an elevated reference count on
|
|
|
+ * the page that will be dropped by this function before returning.
|
|
|
+ */
|
|
|
+int migrate_misplaced_page(struct page *page, int node)
|
|
|
+{
|
|
|
+ pg_data_t *pgdat = NODE_DATA(node);
|
|
|
+ int isolated = 0;
|
|
|
+ int nr_remaining;
|
|
|
+ LIST_HEAD(migratepages);
|
|
|
+
|
|
|
+ /*
|
|
|
+ * Don't migrate pages that are mapped in multiple processes.
|
|
|
+ * TODO: Handle false sharing detection instead of this hammer
|
|
|
+ */
|
|
|
+ if (page_mapcount(page) != 1) {
|
|
|
+ put_page(page);
|
|
|
+ goto out;
|
|
|
}
|
|
|
+
|
|
|
+ /*
|
|
|
+ * Rate-limit the amount of data that is being migrated to a node.
|
|
|
+ * Optimal placement is no good if the memory bus is saturated and
|
|
|
+ * all the time is being spent migrating!
|
|
|
+ */
|
|
|
+ if (numamigrate_update_ratelimit(pgdat)) {
|
|
|
+ put_page(page);
|
|
|
+ goto out;
|
|
|
+ }
|
|
|
+
|
|
|
+ isolated = numamigrate_isolate_page(pgdat, page);
|
|
|
+ if (!isolated)
|
|
|
+ goto out;
|
|
|
+
|
|
|
+ list_add(&page->lru, &migratepages);
|
|
|
+ nr_remaining = migrate_pages(&migratepages,
|
|
|
+ alloc_misplaced_dst_page,
|
|
|
+ node, false, MIGRATE_ASYNC,
|
|
|
+ MR_NUMA_MISPLACED);
|
|
|
+ if (nr_remaining) {
|
|
|
+ putback_lru_pages(&migratepages);
|
|
|
+ isolated = 0;
|
|
|
+ } else
|
|
|
+ count_vm_numa_event(NUMA_PAGE_MIGRATE);
|
|
|
BUG_ON(!list_empty(&migratepages));
|
|
|
out:
|
|
|
return isolated;
|
|
|
}
|
|
|
+
|
|
|
+int migrate_misplaced_transhuge_page(struct mm_struct *mm,
|
|
|
+ struct vm_area_struct *vma,
|
|
|
+ pmd_t *pmd, pmd_t entry,
|
|
|
+ unsigned long address,
|
|
|
+ struct page *page, int node)
|
|
|
+{
|
|
|
+ unsigned long haddr = address & HPAGE_PMD_MASK;
|
|
|
+ pg_data_t *pgdat = NODE_DATA(node);
|
|
|
+ int isolated = 0;
|
|
|
+ struct page *new_page = NULL;
|
|
|
+ struct mem_cgroup *memcg = NULL;
|
|
|
+ int page_lru = page_is_file_cache(page);
|
|
|
+
|
|
|
+ /*
|
|
|
+ * Don't migrate pages that are mapped in multiple processes.
|
|
|
+ * TODO: Handle false sharing detection instead of this hammer
|
|
|
+ */
|
|
|
+ if (page_mapcount(page) != 1)
|
|
|
+ goto out_dropref;
|
|
|
+
|
|
|
+ /*
|
|
|
+ * Rate-limit the amount of data that is being migrated to a node.
|
|
|
+ * Optimal placement is no good if the memory bus is saturated and
|
|
|
+ * all the time is being spent migrating!
|
|
|
+ */
|
|
|
+ if (numamigrate_update_ratelimit(pgdat))
|
|
|
+ goto out_dropref;
|
|
|
+
|
|
|
+ new_page = alloc_pages_node(node,
|
|
|
+ (GFP_TRANSHUGE | GFP_THISNODE) & ~__GFP_WAIT, HPAGE_PMD_ORDER);
|
|
|
+ if (!new_page)
|
|
|
+ goto out_dropref;
|
|
|
+ page_xchg_last_nid(new_page, page_last_nid(page));
|
|
|
+
|
|
|
+ isolated = numamigrate_isolate_page(pgdat, page);
|
|
|
+ if (!isolated) {
|
|
|
+ put_page(new_page);
|
|
|
+ goto out_keep_locked;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Prepare a page as a migration target */
|
|
|
+ __set_page_locked(new_page);
|
|
|
+ SetPageSwapBacked(new_page);
|
|
|
+
|
|
|
+ /* anon mapping, we can simply copy page->mapping to the new page: */
|
|
|
+ new_page->mapping = page->mapping;
|
|
|
+ new_page->index = page->index;
|
|
|
+ migrate_page_copy(new_page, page);
|
|
|
+ WARN_ON(PageLRU(new_page));
|
|
|
+
|
|
|
+ /* Recheck the target PMD */
|
|
|
+ spin_lock(&mm->page_table_lock);
|
|
|
+ if (unlikely(!pmd_same(*pmd, entry))) {
|
|
|
+ spin_unlock(&mm->page_table_lock);
|
|
|
+
|
|
|
+ /* Reverse changes made by migrate_page_copy() */
|
|
|
+ if (TestClearPageActive(new_page))
|
|
|
+ SetPageActive(page);
|
|
|
+ if (TestClearPageUnevictable(new_page))
|
|
|
+ SetPageUnevictable(page);
|
|
|
+ mlock_migrate_page(page, new_page);
|
|
|
+
|
|
|
+ unlock_page(new_page);
|
|
|
+ put_page(new_page); /* Free it */
|
|
|
+
|
|
|
+ unlock_page(page);
|
|
|
+ putback_lru_page(page);
|
|
|
+
|
|
|
+ count_vm_events(PGMIGRATE_FAIL, HPAGE_PMD_NR);
|
|
|
+ goto out;
|
|
|
+ }
|
|
|
+
|
|
|
+ /*
|
|
|
+ * Traditional migration needs to prepare the memcg charge
|
|
|
+ * transaction early to prevent the old page from being
|
|
|
+ * uncharged when installing migration entries. Here we can
|
|
|
+ * save the potential rollback and start the charge transfer
|
|
|
+ * only when migration is already known to end successfully.
|
|
|
+ */
|
|
|
+ mem_cgroup_prepare_migration(page, new_page, &memcg);
|
|
|
+
|
|
|
+ entry = mk_pmd(new_page, vma->vm_page_prot);
|
|
|
+ entry = pmd_mknonnuma(entry);
|
|
|
+ entry = maybe_pmd_mkwrite(pmd_mkdirty(entry), vma);
|
|
|
+ entry = pmd_mkhuge(entry);
|
|
|
+
|
|
|
+ page_add_new_anon_rmap(new_page, vma, haddr);
|
|
|
+
|
|
|
+ set_pmd_at(mm, haddr, pmd, entry);
|
|
|
+ update_mmu_cache_pmd(vma, address, entry);
|
|
|
+ page_remove_rmap(page);
|
|
|
+ /*
|
|
|
+ * Finish the charge transaction under the page table lock to
|
|
|
+ * prevent split_huge_page() from dividing up the charge
|
|
|
+ * before it's fully transferred to the new page.
|
|
|
+ */
|
|
|
+ mem_cgroup_end_migration(memcg, page, new_page, true);
|
|
|
+ spin_unlock(&mm->page_table_lock);
|
|
|
+
|
|
|
+ unlock_page(new_page);
|
|
|
+ unlock_page(page);
|
|
|
+ put_page(page); /* Drop the rmap reference */
|
|
|
+ put_page(page); /* Drop the LRU isolation reference */
|
|
|
+
|
|
|
+ count_vm_events(PGMIGRATE_SUCCESS, HPAGE_PMD_NR);
|
|
|
+ count_vm_numa_events(NUMA_PAGE_MIGRATE, HPAGE_PMD_NR);
|
|
|
+
|
|
|
+out:
|
|
|
+ mod_zone_page_state(page_zone(page),
|
|
|
+ NR_ISOLATED_ANON + page_lru,
|
|
|
+ -HPAGE_PMD_NR);
|
|
|
+ return isolated;
|
|
|
+
|
|
|
+out_dropref:
|
|
|
+ put_page(page);
|
|
|
+out_keep_locked:
|
|
|
+ return 0;
|
|
|
+}
|
|
|
#endif /* CONFIG_NUMA_BALANCING */
|
|
|
|
|
|
#endif /* CONFIG_NUMA */
|