From: Pavel Tatashin <pasha.tatashin@soleen.com> Date: 2021-01-19 04:50:55
Changelog
---------
v5
- Added the following patches to the beginning of series, which are fixes
to the other existing problems with CMA migration code:
mm/gup: check every subpage of a compound page during isolation
mm/gup: return an error on migration failure
mm/gup: check for isolation errors also at the beginning of series
mm/gup: do not allow zero page for pinned pages
- remove .gfp_mask/.reclaim_idx changes from mm/vmscan.c
- update movable zone header comment in patch 8 instead of patch 3, fix the comment
- Added acked, sign-offs
- Updated commit logs based on feedback
- Addressed issues reported by Michal and Jason.
- Remove:
#define PINNABLE_MIGRATE_MAX 10
#define PINNABLE_ISOLATE_MAX 100
Instead: fail on the first migration failure, and retry isolation forever as their
failures are transient.
- In self-set addressed some of the comments from John Hubbard, updated commit logs,
and added comments. Renamed gup->flags with gup->test_flags.
v4
- Address page migration comments. New patch:
mm/gup: limit number of gup migration failures, honor failures
Implements the limiting number of retries for migration failures, and
also check for isolation failures.
Added a test case into gup_test to verify that pages never long-term
pinned in a movable zone, and also added tests to fault both in kernel
and in userland.
v3
- Merged with linux-next, which contains clean-up patch from Jason,
therefore this series is reduced by two patches which did the same
thing.
v2
- Addressed all review comments
- Added Reviewed-by's.
- Renamed PF_MEMALLOC_NOMOVABLE to PF_MEMALLOC_PIN
- Added is_pinnable_page() to check if page can be longterm pinned
- Fixed gup fast path by checking is_in_pinnable_zone()
- rename cma_page_list to movable_page_list
- add a admin-guide note about handling pinned pages in ZONE_MOVABLE,
updated caveat about pinned pages from linux/mmzone.h
- Move current_gfp_context() to fast-path
---------
When page is pinned it cannot be moved and its physical address stays
the same until pages is unpinned.
This is useful functionality to allows userland to implementation DMA
access. For example, it is used by vfio in vfio_pin_pages().
However, this functionality breaks memory hotplug/hotremove assumptions
that pages in ZONE_MOVABLE can always be migrated.
This patch series fixes this issue by forcing new allocations during
page pinning to omit ZONE_MOVABLE, and also to migrate any existing
pages from ZONE_MOVABLE during pinning.
It uses the same scheme logic that is currently used by CMA, and extends
the functionality for all allocations.
For more information read the discussion [1] about this problem.
[1] https://lore.kernel.org/lkml/CA+CK2bBffHBxjmb9jmSKacm0fJMinyt3Nhk8Nx6iudcQSj80_w@mail.gmail.com
Previous versions:
v1
https://lore.kernel.org/lkml/20201202052330.474592-1-pasha.tatashin@soleen.com
v2
https://lore.kernel.org/lkml/20201210004335.64634-1-pasha.tatashin@soleen.com
v3
https://lore.kernel.org/lkml/20201211202140.396852-1-pasha.tatashin@soleen.com
v4
https://lore.kernel.org/lkml/20201217185243.3288048-1-pasha.tatashin@soleen.com
Pavel Tatashin (14):
mm/gup: don't pin migrated cma pages in movable zone
mm/gup: check every subpage of a compound page during isolation
mm/gup: return an error on migration failure
mm/gup: check for isolation errors
mm cma: rename PF_MEMALLOC_NOCMA to PF_MEMALLOC_PIN
mm: apply per-task gfp constraints in fast path
mm: honor PF_MEMALLOC_PIN for all movable pages
mm/gup: do not allow zero page for pinned pages
mm/gup: migrate pinned pages out of movable zone
memory-hotplug.rst: add a note about ZONE_MOVABLE and page pinning
mm/gup: change index type to long as it counts pages
mm/gup: longterm pin migration cleaup
selftests/vm: test flag is broken
selftests/vm: test faulting in kernel, and verify pinnable pages
.../admin-guide/mm/memory-hotplug.rst | 9 +
include/linux/migrate.h | 1 +
include/linux/mm.h | 11 ++
include/linux/mmzone.h | 9 +-
include/linux/sched.h | 2 +-
include/linux/sched/mm.h | 27 +--
include/trace/events/migrate.h | 3 +-
mm/gup.c | 178 ++++++++----------
mm/gup_test.c | 29 +--
mm/gup_test.h | 3 +-
mm/hugetlb.c | 4 +-
mm/page_alloc.c | 33 ++--
tools/testing/selftests/vm/gup_test.c | 36 +++-
13 files changed, 185 insertions(+), 160 deletions(-)
--
2.25.1
From: Pavel Tatashin <pasha.tatashin@soleen.com> Date: 2021-01-19 04:45:20
When migration failure occurs, we still pin pages, which means
that we may pin CMA movable pages which should never be the case.
Instead return an error without pinning pages when migration failure
happens.
No need to retry migrating, because migrate_pages() already retries
10 times.
Signed-off-by: Pavel Tatashin <pasha.tatashin@soleen.com>
---
mm/gup.c | 17 +++++++----------
1 file changed, 7 insertions(+), 10 deletions(-)
From: Pavel Tatashin <pasha.tatashin@soleen.com> Date: 2021-01-19 04:46:03
It is still possible that we pin movable CMA pages if there are isolation
errors and cma_page_list stays empty when we check again.
Check for isolation errors, and return success only when there are no
isolation errors, and cma_page_list is empty after checking.
Because isolation errors are transient, we retry indefinitely.
Signed-off-by: Pavel Tatashin <pasha.tatashin@soleen.com>
---
mm/gup.c | 60 ++++++++++++++++++++++++++++++++------------------------
1 file changed, 34 insertions(+), 26 deletions(-)
@@ -1555,8 +1555,8 @@ static long check_and_migrate_cma_pages(struct mm_struct *mm,structvm_area_struct**vmas,unsignedintgup_flags){-unsignedlongi;-booldrain_allow=true;+unsignedlongi,isolation_error_count;+booldrain_allow;LIST_HEAD(cma_page_list);longret=nr_pages;structpage*prev_head,*head;
@@ -1567,6 +1567,8 @@ static long check_and_migrate_cma_pages(struct mm_struct *mm,check_again:prev_head=NULL;+isolation_error_count=0;+drain_allow=true;for(i=0;i<nr_pages;i++){head=compound_head(pages[i]);if(head==prev_head)
@@ -1578,25 +1580,35 @@ static long check_and_migrate_cma_pages(struct mm_struct *mm,*oftheCMAzoneifpossible.*/if(is_migrate_cma_page(head)){-if(PageHuge(head))-isolate_huge_page(head,&cma_page_list);-else{+if(PageHuge(head)){+if(!isolate_huge_page(head,&cma_page_list))+isolation_error_count++;+}else{if(!PageLRU(head)&&drain_allow){lru_add_drain_all();drain_allow=false;}-if(!isolate_lru_page(head)){-list_add_tail(&head->lru,&cma_page_list);-mod_node_page_state(page_pgdat(head),-NR_ISOLATED_ANON+-page_is_file_lru(head),-thp_nr_pages(head));+if(isolate_lru_page(head)){+isolation_error_count++;+continue;}+list_add_tail(&head->lru,&cma_page_list);+mod_node_page_state(page_pgdat(head),+NR_ISOLATED_ANON++page_is_file_lru(head),+thp_nr_pages(head));}}}+/*+*Iflistisempty,andnoisolationerrors,meansthatallpagesare+*inthecorrectzone.+*/+if(list_empty(&cma_page_list)&&!isolation_error_count)+returnret;+if(!list_empty(&cma_page_list)){/**droptheaboveget_user_pagesreference.
@@ -1616,23 +1628,19 @@ static long check_and_migrate_cma_pages(struct mm_struct *mm,returnret>0?-ENOMEM:ret;}-/*-*Wedidmigrateallthepages,Trytogetthepagereferences-*againmigratinganynewCMApageswhichwefailedtoisolate-*earlier.-*/-ret=__get_user_pages_locked(mm,start,nr_pages,-pages,vmas,NULL,-gup_flags);--if(ret>0){-nr_pages=ret;-drain_allow=true;-gotocheck_again;-}+/* We unpinned pages before migration, pin them again */+ret=__get_user_pages_locked(mm,start,nr_pages,pages,vmas,+NULL,gup_flags);+if(ret<=0)+returnret;+nr_pages=ret;}-returnret;+/*+*checkagainbecausepageswereunpinned,andwealsomighthave+*hadisolationerrorsandneedmorepagestomigrate.+*/+gotocheck_again;}#elsestaticlongcheck_and_migrate_cma_pages(structmm_struct*mm,
From: Pavel Tatashin <pasha.tatashin@soleen.com> Date: 2021-01-19 04:47:02
We should not pin pages in ZONE_MOVABLE. Currently, we do not pin only
movable CMA pages. Generalize the function that migrates CMA pages to
migrate all movable pages. Use is_pinnable_page() to check which
pages need to be migrated
Signed-off-by: Pavel Tatashin <pasha.tatashin@soleen.com>
Reviewed-by: John Hubbard <jhubbard@nvidia.com>
---
include/linux/migrate.h | 1 +
include/linux/mmzone.h | 9 +++--
include/trace/events/migrate.h | 3 +-
mm/gup.c | 63 ++++++++++++++--------------------
4 files changed, 36 insertions(+), 40 deletions(-)
From: Pavel Tatashin <pasha.tatashin@soleen.com> Date: 2021-01-19 04:47:29
Function current_gfp_context() is called after fast path. However, soon we
will add more constraints which will also limit zones based on context.
Move this call into fast path, and apply the correct constraints for all
allocations.
Also update .reclaim_idx based on value returned by current_gfp_context()
because it soon will modify the allowed zones.
Note:
With this patch we will do one extra current->flags load during fast path,
but we already load current->flags in fast-path:
__alloc_pages_nodemask()
prepare_alloc_pages()
current_alloc_flags(gfp_mask, *alloc_flags);
Later, when we add the zone constrain logic to current_gfp_context() we
will be able to remove current->flags load from current_alloc_flags, and
therefore return fast-path to the current performance level.
Suggested-by: Michal Hocko <mhocko@kernel.org>
Signed-off-by: Pavel Tatashin <pasha.tatashin@soleen.com>
Acked-by: Michal Hocko <mhocko@suse.com>
---
mm/page_alloc.c | 15 ++++++++-------
1 file changed, 8 insertions(+), 7 deletions(-)
From: Pavel Tatashin <pasha.tatashin@soleen.com> Date: 2021-01-19 04:49:13
Document the special handling of page pinning when ZONE_MOVABLE present.
Signed-off-by: Pavel Tatashin <pasha.tatashin@soleen.com>
Suggested-by: David Hildenbrand <redacted>
Acked-by: Michal Hocko <mhocko@suse.com>
---
Documentation/admin-guide/mm/memory-hotplug.rst | 9 +++++++++
1 file changed, 9 insertions(+)
@@ -357,6 +357,15 @@ creates ZONE_MOVABLE as following. Unfortunately, there is no information to show which memory block belongs to ZONE_MOVABLE. This is TBD.+..note::+ Techniques that rely on long-term pinnings of memory (especially, RDMA and+ vfio) are fundamentally problematic with ZONE_MOVABLE and, therefore, memory+ hot remove. Pinned pages cannot reside on ZONE_MOVABLE, to guarantee that+ memory can still get hot removed - be aware that pinning can fail even if+ there is plenty of free memory in ZONE_MOVABLE. In addition, using+ ZONE_MOVABLE might make page pinning more expensive, because pages have to be+ migrated off that zone first.+.._memory_hotplug_how_to_offline_memory: How to offline memory
From: Pavel Tatashin <pasha.tatashin@soleen.com> Date: 2021-01-19 04:49:13
Zero page should not be used for long term pinned pages. Once pages
are pinned their physical addresses cannot changed until they are unpinned.
Guarantee to always return real pages when they are pinned by adding
FOLL_WRITE.
Signed-off-by: Pavel Tatashin <pasha.tatashin@soleen.com>
---
mm/gup.c | 10 +++++++++-
1 file changed, 9 insertions(+), 1 deletion(-)
From: Pavel Tatashin <pasha.tatashin@soleen.com> Date: 2021-01-19 04:50:55
In order not to fragment CMA the pinned pages are migrated. However,
they are migrated to ZONE_MOVABLE, which also should not have pinned pages.
Remove __GFP_MOVABLE, so pages can be migrated to zones where pinning
is allowed.
Signed-off-by: Pavel Tatashin <pasha.tatashin@soleen.com>
Reviewed-by: David Hildenbrand <redacted>
Reviewed-by: John Hubbard <jhubbard@nvidia.com>
Acked-by: Michal Hocko <mhocko@suse.com>
---
mm/gup.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
From: Pavel Tatashin <pasha.tatashin@soleen.com> Date: 2021-01-19 04:53:22
PF_MEMALLOC_PIN is only honored for CMA pages, extend
this flag to work for any allocations from ZONE_MOVABLE by removing
__GFP_MOVABLE from gfp_mask when this flag is passed in the current
context.
Add is_pinnable_page() to return true if page is in a pinnable page.
A pinnable page is not in ZONE_MOVABLE and not of MIGRATE_CMA type.
Signed-off-by: Pavel Tatashin <pasha.tatashin@soleen.com>
Acked-by: Michal Hocko <mhocko@suse.com>
---
include/linux/mm.h | 11 +++++++++++
include/linux/sched/mm.h | 6 +++++-
mm/hugetlb.c | 2 +-
mm/page_alloc.c | 20 +++++++++-----------
4 files changed, 26 insertions(+), 13 deletions(-)
@@ -3805,16 +3805,13 @@ alloc_flags_nofragment(struct zone *zone, gfp_t gfp_mask)returnalloc_flags;}-staticinlineunsignedintcurrent_alloc_flags(gfp_tgfp_mask,-unsignedintalloc_flags)+/* Must be called after current_gfp_context() which can change gfp_mask */+staticinlineunsignedintgpf_to_alloc_flags(gfp_tgfp_mask,+unsignedintalloc_flags){#ifdef CONFIG_CMA-unsignedintpflags=current->flags;--if(!(pflags&PF_MEMALLOC_PIN)&&-gfp_migratetype(gfp_mask)==MIGRATE_MOVABLE)+if(gfp_migratetype(gfp_mask)==MIGRATE_MOVABLE)alloc_flags|=ALLOC_CMA;-#endifreturnalloc_flags;}
@@ -4772,7 +4769,7 @@ __alloc_pages_slowpath(gfp_t gfp_mask, unsigned int order,reserve_flags=__gfp_pfmemalloc_flags(gfp_mask);if(reserve_flags)-alloc_flags=current_alloc_flags(gfp_mask,reserve_flags);+alloc_flags=gpf_to_alloc_flags(gfp_mask,reserve_flags);/**Resetthenodemaskandzonelistiteratorsifmemorypoliciescanbe
@@ -4941,7 +4938,7 @@ static inline bool prepare_alloc_pages(gfp_t gfp_mask, unsigned int order,if(should_fail_alloc_page(gfp_mask,order))returnfalse;-*alloc_flags=current_alloc_flags(gfp_mask,*alloc_flags);+*alloc_flags=gpf_to_alloc_flags(gfp_mask,*alloc_flags);/* Dirty zone balancing only done in the fast path */ac->spread_dirty_pages=(gfp_mask&__GFP_WRITE);
@@ -4983,7 +4980,8 @@ __alloc_pages_nodemask(gfp_t gfp_mask, unsigned int order, int preferred_nid,*Applyscopedallocationconstraints.ThisismainlyaboutGFP_NOFS*resp.GFP_NOIOwhichhastobeinheritedforallallocationrequests*fromaparticularcontextwhichhasbeenmarkedby-*memalloc_no{fs,io}_{save,restore}.+*memalloc_no{fs,io}_{save,restore}.AndPF_MEMALLOC_PINwhichensures+*movablezonesarenotusedduringallocation.*/gfp_mask=current_gfp_context(gfp_mask);alloc_mask=gfp_mask;
From: Pavel Tatashin <pasha.tatashin@soleen.com> Date: 2021-01-19 04:53:22
In __get_user_pages_locked() i counts number of pages which should be
long, as long is used in all other places to contain number of pages, and
32-bit becomes increasingly small for handling page count proportional
values.
Signed-off-by: Pavel Tatashin <pasha.tatashin@soleen.com>
Acked-by: Michal Hocko <mhocko@suse.com>
---
mm/gup.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
From: Pavel Tatashin <pasha.tatashin@soleen.com> Date: 2021-01-19 04:53:22
When pages are pinned they can be faulted in userland and migrated, and
they can be faulted right in kernel without migration.
In either case, the pinned pages must end-up being pinnable (not movable).
Add a new test to gup_test, to help verify that the gup/pup
(get_user_pages() / pin_user_pages()) behavior with respect to pinnable
and movable pages is reasonable and correct. Specifically, provide a way
to:
1) Verify that only "pinnable" pages are pinned. This is checked
automatically for you.
2) Verify that gup/pup performance is reasonable. This requires
comparing benchmarks between doing gup/pup on pages that have been
pre-faulted in from user space, vs. doing gup/pup on pages that are not
faulted in until gup/pup time (via FOLL_TOUCH). This decision is
controlled with the new -z command line option.
Signed-off-by: Pavel Tatashin <pasha.tatashin@soleen.com>
---
mm/gup_test.c | 6 ++++++
tools/testing/selftests/vm/gup_test.c | 23 +++++++++++++++++++----
2 files changed, 25 insertions(+), 4 deletions(-)
@@ -52,6 +52,12 @@ static void verify_dma_pinned(unsigned int cmd, struct page **pages,dump_page(page,"gup_test failure");break;+}elseif(cmd==PIN_LONGTERM_BENCHMARK&&+WARN(!is_pinnable_page(page),+"pages[%lu] is NOT pinnable but pinned\n",+i)){+dump_page(page,"gup_test failure");+break;}}break;
@@ -13,6 +13,7 @@/* Just the flags we need, copied from mm.h: */#define FOLL_WRITE 0x01 /* check pte is writable */+#define FOLL_TOUCH 0x02 /* mark page accessed */staticchar*cmd_to_str(unsignedlongcmd){
@@ -39,11 +40,11 @@ int main(int argc, char **argv)unsignedlongsize=128*MB;inti,fd,filed,opt,nr_pages=1,thp=-1,repeats=1,write=1;unsignedlongcmd=GUP_FAST_BENCHMARK;-intflags=MAP_PRIVATE;+intflags=MAP_PRIVATE,touch=0;char*file="/dev/zero";char*p;-while((opt=getopt(argc,argv,"m:r:n:F:f:abctTLUuwWSHp"))!=-1){+while((opt=getopt(argc,argv,"m:r:n:F:f:abctTLUuwWSHpz"))!=-1){switch(opt){case'a':cmd=PIN_FAST_BENCHMARK;
@@ -110,6 +111,10 @@ int main(int argc, char **argv)case'H':flags|=(MAP_HUGETLB|MAP_ANONYMOUS);break;+case'z':+/* fault pages in gup, do not fault in userland */+touch=1;+break;default:return-1;}
@@ -167,8 +172,18 @@ int main(int argc, char **argv)elseif(thp==0)madvise(p,size,MADV_NOHUGEPAGE);-for(;(unsignedlong)p<gup.addr+size;p+=PAGE_SIZE)-p[0]=0;+/*+*FOLL_TOUCH,ingup_test,isusedasaneither/orcase:either+*faultpagesinfromthekernelviaFOLL_TOUCH,orfaultthem+*inhere,fromuserspace.Thisallowscomparisonofperformance+*betweenthosetwocases.+*/+if(touch){+gup.gup_flags|=FOLL_TOUCH;+}else{+for(;(unsignedlong)p<gup.addr+size;p+=PAGE_SIZE)+p[0]=0;+}/* Only report timing information on the *_BENCHMARK commands: */if((cmd==PIN_FAST_BENCHMARK)||(cmd==GUP_FAST_BENCHMARK)||
From: Pavel Tatashin <pasha.tatashin@soleen.com> Date: 2021-01-19 04:53:22
In gup_test both gup_flags and test_flags use the same flags field.
This is broken.
Farther, in the actual gup_test.c all the passed gup_flags are erased and
unconditionally replaced with FOLL_WRITE.
Which means that test_flags are ignored, and code like this always
performs pin dump test:
155 if (gup->flags & GUP_TEST_FLAG_DUMP_PAGES_USE_PIN)
156 nr = pin_user_pages(addr, nr, gup->flags,
157 pages + i, NULL);
158 else
159 nr = get_user_pages(addr, nr, gup->flags,
160 pages + i, NULL);
161 break;
Add a new test_flags field, to allow raw gup_flags to work.
Add a new subcommand for DUMP_USER_PAGES_TEST to specify that pin test
should be performed.
Remove unconditional overwriting of gup_flags via FOLL_WRITE. But,
preserve the previous behaviour where FOLL_WRITE was the default flag,
and add a new option "-W" to unset FOLL_WRITE.
Rename flags with gup_flags.
With the fix, dump works like this:
root@virtme:/# gup_test -c
---- page #0, starting from user virt addr: 0x7f8acb9e4000
page:00000000d3d2ee27 refcount:2 mapcount:1 mapping:0000000000000000
index:0x0 pfn:0x100bcf
anon flags: 0x300000000080016(referenced|uptodate|lru|swapbacked)
raw: 0300000000080016 ffffd0e204021608 ffffd0e208df2e88 ffff8ea04243ec61
raw: 0000000000000000 0000000000000000 0000000200000000 0000000000000000
page dumped because: gup_test: dump_pages() test
DUMP_USER_PAGES_TEST: done
root@virtme:/# gup_test -c -p
---- page #0, starting from user virt addr: 0x7fd19701b000
page:00000000baed3c7d refcount:1025 mapcount:1 mapping:0000000000000000
index:0x0 pfn:0x108008
anon flags: 0x300000000080014(uptodate|lru|swapbacked)
raw: 0300000000080014 ffffd0e204200188 ffffd0e205e09088 ffff8ea04243ee71
raw: 0000000000000000 0000000000000000 0000040100000000 0000000000000000
page dumped because: gup_test: dump_pages() test
DUMP_USER_PAGES_TEST: done
Refcount shows the difference between pin vs no-pin case.
Also change type of nr from int to long, as it counts number of pages.
Signed-off-by: Pavel Tatashin <pasha.tatashin@soleen.com>
---
mm/gup_test.c | 23 ++++++++++-------------
mm/gup_test.h | 3 ++-
tools/testing/selftests/vm/gup_test.c | 15 +++++++++++----
3 files changed, 23 insertions(+), 18 deletions(-)
@@ -94,7 +94,7 @@ static int __gup_test_ioctl(unsigned int cmd,{ktime_tstart_time,end_time;unsignedlongi,nr_pages,addr,next;-intnr;+longnr;structpage**pages;intret=0;boolneeds_mmap_lock=
@@ -126,37 +126,34 @@ static int __gup_test_ioctl(unsigned int cmd,nr=(next-addr)/PAGE_SIZE;}-/* Filter out most gup flags: only allow a tiny subset here: */-gup->flags&=FOLL_WRITE;-switch(cmd){caseGUP_FAST_BENCHMARK:-nr=get_user_pages_fast(addr,nr,gup->flags,+nr=get_user_pages_fast(addr,nr,gup->gup_flags,pages+i);break;caseGUP_BASIC_TEST:-nr=get_user_pages(addr,nr,gup->flags,pages+i,+nr=get_user_pages(addr,nr,gup->gup_flags,pages+i,NULL);break;casePIN_FAST_BENCHMARK:-nr=pin_user_pages_fast(addr,nr,gup->flags,+nr=pin_user_pages_fast(addr,nr,gup->gup_flags,pages+i);break;casePIN_BASIC_TEST:-nr=pin_user_pages(addr,nr,gup->flags,pages+i,+nr=pin_user_pages(addr,nr,gup->gup_flags,pages+i,NULL);break;casePIN_LONGTERM_BENCHMARK:nr=pin_user_pages(addr,nr,-gup->flags|FOLL_LONGTERM,+gup->gup_flags|FOLL_LONGTERM,pages+i,NULL);break;caseDUMP_USER_PAGES_TEST:-if(gup->flags&GUP_TEST_FLAG_DUMP_PAGES_USE_PIN)-nr=pin_user_pages(addr,nr,gup->flags,+if(gup->test_flags&GUP_TEST_FLAG_DUMP_PAGES_USE_PIN)+nr=pin_user_pages(addr,nr,gup->gup_flags,pages+i,NULL);else-nr=get_user_pages(addr,nr,gup->flags,+nr=get_user_pages(addr,nr,gup->gup_flags,pages+i,NULL);break;default:
@@ -187,7 +184,7 @@ static int __gup_test_ioctl(unsigned int cmd,start_time=ktime_get();-put_back_pages(cmd,pages,nr_pages,gup->flags);+put_back_pages(cmd,pages,nr_pages,gup->test_flags);end_time=ktime_get();gup->put_delta_usec=ktime_us_delta(end_time,start_time);
@@ -37,13 +37,13 @@ int main(int argc, char **argv){structgup_testgup={0};unsignedlongsize=128*MB;-inti,fd,filed,opt,nr_pages=1,thp=-1,repeats=1,write=0;+inti,fd,filed,opt,nr_pages=1,thp=-1,repeats=1,write=1;unsignedlongcmd=GUP_FAST_BENCHMARK;intflags=MAP_PRIVATE;char*file="/dev/zero";char*p;-while((opt=getopt(argc,argv,"m:r:n:F:f:abctTLUuwSH"))!=-1){+while((opt=getopt(argc,argv,"m:r:n:F:f:abctTLUuwWSHp"))!=-1){switch(opt){case'a':cmd=PIN_FAST_BENCHMARK;
@@ -65,9 +65,13 @@ int main(int argc, char **argv)*/gup.which_pages[0]=1;break;+case'p':+/* works only with DUMP_USER_PAGES_TEST */+gup.test_flags|=GUP_TEST_FLAG_DUMP_PAGES_USE_PIN;+break;case'F':/* strtol, so you can pass flags in hex form */-gup.flags=strtol(optarg,0,0);+gup.gup_flags=strtol(optarg,0,0);break;case'm':size=atoi(optarg)*MB;
@@ -93,6 +97,9 @@ int main(int argc, char **argv)case'w':write=1;break;+case'W':+write=0;+break;case'f':file=optarg;break;
@@ -140,7 +147,7 @@ int main(int argc, char **argv)gup.nr_pages_per_call=nr_pages;if(write)-gup.flags|=FOLL_WRITE;+gup.gup_flags|=FOLL_WRITE;fd=open("/sys/kernel/debug/gup_test",O_RDWR);if(fd==-1){
From: Pavel Tatashin <pasha.tatashin@soleen.com> Date: 2021-01-19 04:53:22
When pages are longterm pinned, we must migrated them out of movable zone.
The function that migrates them has a hidden loop with goto. The loop is
to retry on isolation failures, and after successful migration.
Make this code better by moving this loop to the caller.
Signed-off-by: Pavel Tatashin <pasha.tatashin@soleen.com>
---
mm/gup.c | 101 +++++++++++++++++++++++--------------------------------
1 file changed, 42 insertions(+), 59 deletions(-)
@@ -1606,40 +1607,23 @@ static long check_and_migrate_movable_pages(struct mm_struct *mm,*inthecorrectzone.*/if(list_empty(&movable_page_list)&&!isolation_error_count)-returnret;+returnnr_pages;+if(gup_flags&FOLL_PIN){+unpin_user_pages(pages,nr_pages);+}else{+for(i=0;i<nr_pages;i++)+put_page(pages[i]);+}if(!list_empty(&movable_page_list)){-/*-*droptheaboveget_user_pagesreference.-*/-if(gup_flags&FOLL_PIN)-unpin_user_pages(pages,nr_pages);-else-for(i=0;i<nr_pages;i++)-put_page(pages[i]);-ret=migrate_pages(&movable_page_list,alloc_migration_target,NULL,(unsignedlong)&mtc,MIGRATE_SYNC,MR_LONGTERM_PIN);-if(ret){-if(!list_empty(&movable_page_list))-putback_movable_pages(&movable_page_list);-returnret>0?-ENOMEM:ret;-}--/* We unpinned pages before migration, pin them again */-ret=__get_user_pages_locked(mm,start,nr_pages,pages,vmas,-NULL,gup_flags);-if(ret<=0)-returnret;-nr_pages=ret;+if(ret&&!list_empty(&movable_page_list))+putback_movable_pages(&movable_page_list);}-/*-*checkagainbecausepageswereunpinned,andwealsomighthave-*hadisolationerrorsandneedmorepagestomigrate.-*/-gotocheck_again;+returnret>0?-ENOMEM:ret;}/*
@@ -1653,30 +1637,29 @@ static long __gup_longterm_locked(struct mm_struct *mm,structvm_area_struct**vmas,unsignedintgup_flags){-unsignedlongflags=0;+unsignedintflags;longrc;-if(gup_flags&FOLL_LONGTERM){-/*-*WearelongtermpinningpagesandtheirPA'sshouldnot-*changeuntilunpinned.WithoutFOLL_WRITEwemightgetzero-*pagewhichwedonotwant.Forcecreatingnormal-*pagesbyaddingFOLL_WRITE.-*/-gup_flags|=FOLL_WRITE;-flags=memalloc_pin_save();-}+if(!(gup_flags&FOLL_LONGTERM))+return__get_user_pages_locked(mm,start,nr_pages,pages,vmas,+NULL,gup_flags);+/*+*WearelongtermpinningpagesandtheirPA'sshouldnotchangeuntil+*unpinned.WithoutFOLL_WRITEwemightgetzeropagewhichwedonot+*want.ForcecreatingnormalpagesbyaddingFOLL_WRITE.+*/+gup_flags|=FOLL_WRITE;+flags=memalloc_pin_save();-rc=__get_user_pages_locked(mm,start,nr_pages,pages,vmas,NULL,-gup_flags);+do{+rc=__get_user_pages_locked(mm,start,nr_pages,pages,vmas,+NULL,gup_flags);+if(rc<=0)+break;+rc=check_and_migrate_movable_pages(rc,pages,gup_flags);+}while(!rc);+memalloc_pin_restore(flags);-if(gup_flags&FOLL_LONGTERM){-if(rc>0)-rc=check_and_migrate_movable_pages(mm,start,rc,-pages,vmas,-gup_flags);-memalloc_pin_restore(flags);-}returnrc;}
From: Pavel Tatashin <pasha.tatashin@soleen.com> Date: 2021-01-19 05:08:33
PF_MEMALLOC_NOCMA is used ot guarantee that the allocator will not return
pages that might belong to CMA region. This is currently used for long
term gup to make sure that such pins are not going to be done on any CMA
pages.
When PF_MEMALLOC_NOCMA has been introduced we haven't realized that it is
focusing on CMA pages too much and that there is larger class of pages that
need the same treatment. MOVABLE zone cannot contain any long term pins as
well so it makes sense to reuse and redefine this flag for that usecase as
well. Rename the flag to PF_MEMALLOC_PIN which defines an allocation
context which can only get pages suitable for long-term pins.
Also re-name:
memalloc_nocma_save()/memalloc_nocma_restore
to
memalloc_pin_save()/memalloc_pin_restore()
and make the new functions common.
Signed-off-by: Pavel Tatashin <pasha.tatashin@soleen.com>
Reviewed-by: John Hubbard <jhubbard@nvidia.com>
Acked-by: Michal Hocko <mhocko@suse.com>
---
include/linux/sched.h | 2 +-
include/linux/sched/mm.h | 21 +++++----------------
mm/gup.c | 4 ++--
mm/hugetlb.c | 4 ++--
mm/page_alloc.c | 4 ++--
5 files changed, 12 insertions(+), 23 deletions(-)
@@ -1576,7 +1576,7 @@ extern struct pid *cad_pid;#define PF_SWAPWRITE 0x00800000 /* Allowed to write to swap */#define PF_NO_SETAFFINITY 0x04000000 /* Userland is not allowed to meddle with cpus_mask */#define PF_MCE_EARLY 0x08000000 /* Early kill for mce process policy */-#define PF_MEMALLOC_NOCMA 0x10000000 /* All allocation request will have _GFP_MOVABLE cleared */+#define PF_MEMALLOC_PIN 0x10000000 /* Allocation context constrained to zones which allow long term pinning. */#define PF_FREEZER_SKIP 0x40000000 /* Freezer should not count it as freezable */#define PF_SUSPEND_TASK 0x80000000 /* This thread called freeze_processes() and should not be frozen */
From: Pavel Tatashin <pasha.tatashin@soleen.com> Date: 2021-01-19 05:20:32
When pages are isolated in check_and_migrate_movable_pages() we skip
compound number of pages at a time. However, as Jason noted, it is
not necessary correct that pages[i] corresponds to the pages that
we skipped. This is because it is possible that the addresses in
this range had split_huge_pmd()/split_huge_pud(), and these functions
do not update the compound page metadata.
The problem can be reproduced if something like this occurs:
1. User faulted huge pages.
2. split_huge_pmd() was called for some reason
3. User has unmapped some sub-pages in the range
4. User tries to longterm pin the addresses.
The resulting pages[i] might end-up having pages which are not compound
size page aligned.
Fixes: aa712399c1e8 ("mm/gup: speed up check_and_migrate_cma_pages() on huge page")
Reported-by: Jason Gunthorpe <jgg@nvidia.com>
Signed-off-by: Pavel Tatashin <pasha.tatashin@soleen.com>
---
mm/gup.c | 19 +++++++------------
1 file changed, 7 insertions(+), 12 deletions(-)
From: Jason Gunthorpe <jgg@ziepe.ca> Date: 2021-01-19 18:30:14
On Mon, Jan 18, 2021 at 11:39:10PM -0500, Pavel Tatashin wrote:
It is still possible that we pin movable CMA pages if there are isolation
errors and cma_page_list stays empty when we check again.
Check for isolation errors, and return success only when there are no
isolation errors, and cma_page_list is empty after checking.
Because isolation errors are transient, we retry indefinitely.
Signed-off-by: Pavel Tatashin <pasha.tatashin@soleen.com>
---
mm/gup.c | 60 ++++++++++++++++++++++++++++++++------------------------
1 file changed, 34 insertions(+), 26 deletions(-)
This should have a fixme too, this is a bug.
The patch looks OK, but I keep feeling this logic is all really
overcomplicated...
Reviewed-by: Jason Gunthorpe <jgg@nvidia.com>
Jason
From: Jason Gunthorpe <jgg@ziepe.ca> Date: 2021-01-19 18:32:43
On Mon, Jan 18, 2021 at 11:39:14PM -0500, Pavel Tatashin wrote:
Zero page should not be used for long term pinned pages. Once pages
are pinned their physical addresses cannot changed until they are unpinned.
Guarantee to always return real pages when they are pinned by adding
FOLL_WRITE.
Signed-off-by: Pavel Tatashin <pasha.tatashin@soleen.com>
---
mm/gup.c | 10 +++++++++-
1 file changed, 9 insertions(+), 1 deletion(-)
No, this will definitely break things
Why does the zero page have to be movable?
Jason
From: Pavel Tatashin <pasha.tatashin@soleen.com> Date: 2021-01-19 18:38:02
On Tue, Jan 19, 2021 at 1:30 PM Jason Gunthorpe [off-list ref] wrote:
On Mon, Jan 18, 2021 at 11:39:14PM -0500, Pavel Tatashin wrote:
quoted
Zero page should not be used for long term pinned pages. Once pages
are pinned their physical addresses cannot changed until they are unpinned.
Guarantee to always return real pages when they are pinned by adding
FOLL_WRITE.
Signed-off-by: Pavel Tatashin <pasha.tatashin@soleen.com>
---
mm/gup.c | 10 +++++++++-
1 file changed, 9 insertions(+), 1 deletion(-)
No, this will definitely break things
What will break
Why does the zero page have to be movable?
It is not even about being movable, we can't cow pinned pages returned
by GUP call, how can we use zero page for that?
From: Jason Gunthorpe <jgg@ziepe.ca> Date: 2021-01-19 18:42:51
On Mon, Jan 18, 2021 at 11:39:09PM -0500, Pavel Tatashin wrote:
When migration failure occurs, we still pin pages, which means
that we may pin CMA movable pages which should never be the case.
Instead return an error without pinning pages when migration failure
happens.
No need to retry migrating, because migrate_pages() already retries
10 times.
Signed-off-by: Pavel Tatashin <pasha.tatashin@soleen.com>
mm/gup.c | 17 +++++++----------
1 file changed, 7 insertions(+), 10 deletions(-)
From: Jason Gunthorpe <jgg@ziepe.ca> Date: 2021-01-19 18:57:43
On Mon, Jan 18, 2021 at 11:39:08PM -0500, Pavel Tatashin wrote:
When pages are isolated in check_and_migrate_movable_pages() we skip
compound number of pages at a time. However, as Jason noted, it is
not necessary correct that pages[i] corresponds to the pages that
we skipped. This is because it is possible that the addresses in
this range had split_huge_pmd()/split_huge_pud(), and these functions
do not update the compound page metadata.
The problem can be reproduced if something like this occurs:
1. User faulted huge pages.
2. split_huge_pmd() was called for some reason
3. User has unmapped some sub-pages in the range
4. User tries to longterm pin the addresses.
The resulting pages[i] might end-up having pages which are not compound
size page aligned.
Fixes: aa712399c1e8 ("mm/gup: speed up check_and_migrate_cma_pages() on huge page")
Reported-by: Jason Gunthorpe <jgg@nvidia.com>
From: Pavel Tatashin <pasha.tatashin@soleen.com> Date: 2021-01-19 20:17:00
On Tue, Jan 19, 2021 at 1:47 PM Jason Gunthorpe [off-list ref] wrote:
On Tue, Jan 19, 2021 at 01:34:26PM -0500, Pavel Tatashin wrote:
quoted
On Tue, Jan 19, 2021 at 1:30 PM Jason Gunthorpe [off-list ref] wrote:
quoted
On Mon, Jan 18, 2021 at 11:39:14PM -0500, Pavel Tatashin wrote:
quoted
Zero page should not be used for long term pinned pages. Once pages
are pinned their physical addresses cannot changed until they are unpinned.
Guarantee to always return real pages when they are pinned by adding
FOLL_WRITE.
Signed-off-by: Pavel Tatashin <pasha.tatashin@soleen.com>
mm/gup.c | 10 +++++++++-
1 file changed, 9 insertions(+), 1 deletion(-)
No, this will definitely break things
What will break
Things assuming GUP doesn't break COW, making all GUP WRITE was
already tried and revered for some other reason
quoted
quoted
Why does the zero page have to be movable?
It is not even about being movable, we can't cow pinned pages returned
by GUP call, how can we use zero page for that?
The zero page is always zero, it is never written to. What does cow
matter?
Hi Jason,
I was thinking about a use case where userland would pin an address
without FOLL_WRITE, because the PTE for that address is not going to
be writable, but some device via DMA will write to it. Now, if we got
a zero page we have a problem... If this usecase is not valid then the
fix for movable zero page is make the zero page always come from a
non-movable zone so we do not need to isolate it during migration, and
so the memory can be offlined later.
Pasha
From: Pavel Tatashin <pasha.tatashin@soleen.com> Date: 2021-01-19 21:03:27
I was thinking about a use case where userland would pin an address
without FOLL_WRITE, because the PTE for that address is not going to
be writable, but some device via DMA will write to it. Now, if we got
a zero page we have a problem... If this usecase is not valid then the
fix for movable zero page is make the zero page always come from a
non-movable zone so we do not need to isolate it during migration, and
so the memory can be offlined later.
I looked into making zero_page non-movable, and I am confused here.
huge zero page is already not movable:
get_huge_zero_page()
zero_page = alloc_pages((GFP_TRANSHUGE | __GFP_ZERO) & ~__GFP_MOVABLE, ...
Base zero page can be in a movable zone, which is a bug: if there are
references to zero page, that page cannot be migrated, and we won't be
hot-remove memory area where that page is located. On x86, zero page
should always come from the bottom 4G of physical memory / DMA32 ZONE.
However, I see that sometimes it is not (I reproduce in QEMU emulator):
QEMU instance with 16G of memory and kernelcore=5G
Boot#1:
zero_pfn 48a8d
zero_pfn zone: ZONE_DMA32
Boot#2:
zero_pfn 20168d
zero_pfn zone: ZONE_MOVABLE (???)
The problem is that the x86 zero page comes from the .bss segment:
https://soleen.com/source/xref/linux/arch/x86/kernel/head_64.S?r=31d85460#583
Which, I thought would always be set within the first 4G of physical
memory. What is going on here?
Pasha
From: Jason Gunthorpe <jgg@ziepe.ca> Date: 2021-01-19 21:17:42
On Tue, Jan 19, 2021 at 01:34:26PM -0500, Pavel Tatashin wrote:
On Tue, Jan 19, 2021 at 1:30 PM Jason Gunthorpe [off-list ref] wrote:
quoted
On Mon, Jan 18, 2021 at 11:39:14PM -0500, Pavel Tatashin wrote:
quoted
Zero page should not be used for long term pinned pages. Once pages
are pinned their physical addresses cannot changed until they are unpinned.
Guarantee to always return real pages when they are pinned by adding
FOLL_WRITE.
Signed-off-by: Pavel Tatashin <pasha.tatashin@soleen.com>
mm/gup.c | 10 +++++++++-
1 file changed, 9 insertions(+), 1 deletion(-)
No, this will definitely break things
What will break
Things assuming GUP doesn't break COW, making all GUP WRITE was
already tried and revered for some other reason
quoted
Why does the zero page have to be movable?
It is not even about being movable, we can't cow pinned pages returned
by GUP call, how can we use zero page for that?
The zero page is always zero, it is never written to. What does cow
matter?
Jason
From: Jason Gunthorpe <jgg@ziepe.ca> Date: 2021-01-19 23:43:44
On Tue, Jan 19, 2021 at 03:14:04PM -0500, Pavel Tatashin wrote:
I was thinking about a use case where userland would pin an address
without FOLL_WRITE, because the PTE for that address is not going to
be writable, but some device via DMA will write to it.
That would be a serious bug in the get_user_pages caller to write to a
page without using FOLL_WRITE
Jason