mm/hmm: make device private reference counts zero based
Subsystems (12)
PATCH v2 1/1 mm/hmm: make device private reference counts zero based
ZONE_DEVICE struct pages have an extra reference count that complicates the code for put_page() and several places in the kernel that need to check the reference count to see that a page is not being used (gup, compaction, migration, etc.). Clean up the code so the reference count doesn't need to be treated specially for device private pages, leaving DAX as still being a special case.
---
I'm sending this as a separate patch since I think it is ready to merge. Originally, this was part of an RFC:
https://lore.kernel.org/linux-mm/[email protected]
and is changed to only make device private struct page reference counts be zero based since DAX needs to detect when a page is not referenced by GUP and not no references at all.
It applies cleanly to linux-5.9.0-rc8-mm1 plus my patch ("ext4/xfs: add page refcount helper")
https://lore.kernel.org/linux-mm/[email protected]
and also
("mm/memcg: fix device private memcg accounting")
https://lore.kernel.org/linux-mm/[email protected]
but doesn't really depend on them, just simple merge conflicts without them.
This is for Andrew Morton after the 5.10.0-rc1 merge window closes.
Changes in v2:
Fixed the call to page_ref_add_unless() when moving a process from one memory cgroup to another thanks to Ira Weiny's comment.
arch/powerpc/kvm/book3s_hv_uvmem.c | 13 +++++++- drivers/gpu/drm/nouveau/nouveau_dmem.c | 2 +- include/linux/memremap.h | 6 ++-- include/linux/mm.h | 9 +----- lib/test_hmm.c | 7 ++++- mm/internal.h | 8 +++++ mm/memcontrol.c | 11 ++----- mm/memremap.c | 42 ++++++++++---------------- mm/migrate.c | 5 --- mm/swap.c | 6 ++-- 10 files changed, 54 insertions(+), 55 deletions(-)
arch/powerpc/kvm/book3s_hv_uvmem.c +12 -1
@@ -711,7 +711,7 @@ static struct page *kvmppc_uvmem_get_page(unsigned long gpa, struct kvm *kvm) dpage =pfn_to_page (uvmem_pfn); dpage->zone_device_data = pvt; -get_page (dpage); +init_page_count (dpage);lock_page (dpage);return dpage; out_clear:
@@ -1151,6 +1151,7 @@ int kvmppc_uvmem_init(void)struct resource *res;void *addr;unsigned long pfn_last, pfn_first; +unsigned long pfn; size =kvmppc_get_secmem_size ();if (!size) {
@@ -1191,6 +1192,16 @@ int kvmppc_uvmem_init(void) goto out_unmap; } +/* +* Pages are created with an initial reference count of one but should +* have a reference count of zero while in the free state. +*/ +for (pfn = pfn_first; pfn < pfn_last; pfn++) { +struct page *dpage =pfn_to_page (pfn); + +set_page_count (dpage,0 ); + } +pr_info ("KVMPPC-UVMEM: Secure Memory size 0x%lx\n" , size);return ret; out_unmap:
drivers/gpu/drm/nouveau/nouveau_dmem.c +1 -1
@@ -324,7 +324,7 @@ nouveau_dmem_page_alloc_locked(struct nouveau_drm *drm)return NULL ; } -get_page (page); +init_page_count (page);lock_page (page);return page; }
include/linux/memremap.h +3 -3
@@ -66,9 +66,9 @@ enum memory_type {struct dev_pagemap_ops {/* -* Called once the page refcount reaches 1. (ZONE_DEVICE pages never -* reach 0 refcount unless there is a refcount bug. This allows the -* device driver to implement its own memory management.) +* Called once the page refcount reaches 0. The reference count should +* be reset to one with init_page_count(page) before reusing the page. +* This allows device drivers to implement their own memory management. */ void (*page_free)(struct page *page);
include/linux/mm.h +1 -8
@@ -1110,14 +1110,7 @@ static inline bool page_is_devmap_managed(struct page *page)return false;if (!is_zone_device_page (page))return false; -switch (page->pgmap->type) { -case MEMORY_DEVICE_PRIVATE : -case MEMORY_DEVICE_FS_DAX : -return true; -default : -break ; - } -return false; +return page->pgmap->type ==MEMORY_DEVICE_FS_DAX ; }void put_devmap_managed_page (struct page *page);
lib/test_hmm.c +6 -1
@@ -509,10 +509,15 @@ static bool dmirror_allocate_chunk(struct dmirror_device *mdevice, mdevice->devmem_count * (DEVMEM_CHUNK_SIZE / (1024 *1024 )), pfn_first, pfn_last); +/* +* Pages are created with an initial reference count of one but should +* have a reference count of zero while in the free state. +*/ spin_lock (&mdevice->lock);for (pfn = pfn_first; pfn < pfn_last; pfn++) {struct page *page =pfn_to_page (pfn); +set_page_count (page,0 ); page->zone_device_data = mdevice->free_pages; mdevice->free_pages = page; }
@@ -561,7 +566,7 @@ static struct page *dmirror_devmem_alloc_page(struct dmirror_device *mdevice) } dpage->zone_device_data = rpage; -get_page (dpage); +init_page_count (dpage);lock_page (dpage);return dpage;
mm/internal.h +8 -0
@@ -618,4 +618,12 @@ struct migration_target_control { gfp_t gfp_mask; }; +#ifdef CONFIG_DEV_PAGEMAP_OPS +void free_zone_device_page (struct page *page); +#else +static inline void free_zone_device_page (struct page *page) +{ +} +#endif +#endif /* __MM_INTERNAL_H */
mm/memcontrol.c +3 -8
@@ -5553,17 +5553,12 @@ static struct page *mc_handle_swap_pte(struct vm_area_struct *vma,return NULL ;/* -* Handle MEMORY_DEVICE_PRIVATE which are ZONE_DEVICE page belonging to -* a device and because they are not accessible by CPU they are store -* as special swap entry in the CPU page table. +* Device private pages are not accessible by the CPU and are stored +* as a special swap entry in the CPU page table. */ if (is_device_private_entry (ent)) { page =device_private_entry_to_page (ent); -/* -* MEMORY_DEVICE_PRIVATE means ZONE_DEVICE page and which have -* a refcount of 1 when free (unlike normal page) -*/ -if (!page_ref_add_unless (page,1 ,1 )) +if (!page_ref_add_unless (page,1 ,0 ))return NULL ;return page; }
mm/memremap.c +16 -26
106 lines not on this page · raw message
mm/migrate.c +0 -5
@@ -380,11 +380,6 @@ static int expected_page_refs(struct address_space *mapping, struct page *page) {int expected_count =1 ; -/* -* Device private pages have an extra refcount as they are -* ZONE_DEVICE pages. -*/ - expected_count +=is_device_private_page (page);if (mapping) expected_count +=thp_nr_pages (page) +page_has_private (page);
mm/swap.c +4 -2
@@ -116,12 +116,11 @@ static void __put_compound_page(struct page *page)void __put_page (struct page *page) {if (is_zone_device_page (page)) { -put_dev_pagemap (page->pgmap); -/* * The page belongs to the device that created pgmap. Do * not return it to page allocator. */ +free_zone_device_page (page);return ; }
@@ -907,6 +906,9 @@ void release_pages(struct page **pages, int nr)put_devmap_managed_page (page);continue ; } +if (put_page_testzero (page)) +__put_page (page); +continue ; }if (!put_page_testzero (page))
Signature · 1 line
2.20.1
Quoted 7 lines · Ralph Campbell
ZONE_DEVICEstruct pages have an extra reference count that complicates the code forput_page()and several places in the kernel that need to check the reference count to see that a page is not being used (gup, compaction, migration, etc.). Clean up the code so the reference count doesn't need to be treated specially for device private pages, leaving DAX as still being a special case.
Please no half-step to removing the special casing...
[..]
mm/memremap.c EXPORT_SYMBOL_GPL(get_dev_pagemap); · line 504
Quoted 4 lines
mm/memremap.c +3
-0+void free_zone_device_page (struct page *page) +{ +if (!is_device_private_page (page))return ;
That seems too subtle to be acceptable to me. All ZONE_DEVICE pages need to have the same relationship with respect to idle-ness and the page reference count.
Quoted 8 lines · Dan Williams
Quoted 6 lines
ZONE_DEVICEstruct pages have an extra reference count that complicates the code forput_page()and several places in the kernel that need to check the reference count to see that a page is not being used (gup, compaction, migration, etc.). Clean up the code so the reference count doesn't need to be treated specially for device private pages, leaving DAX as still being a special case.Please no half-step to removing the special casing...
Agreed. I really like where the refcount changes are heading, but I don't think this half-step is helpful.