diff mbox series

[03/12] mempolicy: fix migrate_pages(2) syscall return nr_failed

Message ID ddad2cee-cbad-7b5d-935a-59f961b7c3a@google.com (mailing list archive)
State New
Headers show
Series mempolicy: cleanups leading to NUMA mpol without vma | expand

Commit Message

Hugh Dickins Sept. 25, 2023, 8:24 a.m. UTC
"man 2 migrate_pages" says "On success migrate_pages() returns the number
of pages that could not be moved".  Although 5.3 and 5.4 commits fixed
mbind(MPOL_MF_STRICT|MPOL_MF_MOVE*) to fail with EIO when not all pages
could be moved (because some could not be isolated for migration),
migrate_pages(2) was left still reporting only those pages failing at the
migration stage, forgetting those failing at the earlier isolation stage.

Fix that by accumulating a long nr_failed count in struct queue_pages,
returned by queue_pages_range() when it's not returning an error, for
adding on to the nr_failed count from migrate_pages() in mm/migrate.c.
A count of pages?  It's more a count of folios, but changing it to pages
would entail more work (also in mm/migrate.c): does not seem justified.

queue_pages_range() itself should only return -EIO in the "strictly
unmovable" case (STRICT without any MOVEs): in that case it's best to
break out as soon as nr_failed gets set; but otherwise it should continue
to isolate pages for MOVing even when nr_failed - as the mbind(2) manpage
promises.  This fixes mbind(MPOL_MF_STRICT|MPOL_MF_MOVE*) behavior left
over from 5.3 and 5.4, and the recent syzbot need for vma_start_write()
before mbind_range(): but both of those may be fixed by smaller patches.

There's a case when nr_failed should be incremented when it was missed:
queue_folios_pte_range() and queue_folios_hugetlb() count the transient
migration entries, like queue_folios_pmd() already did.  And there's a
case when nr_failed should not be incremented when it would have been:
in meeting later PTEs of the same large folio, which can only be isolated
once: fixed by recording the current large folio in struct queue_pages.

Clean up the affected functions, fixing or updating many comments.  Bool
migrate_folio_add(), without -EIO: true if adding, or if skipping shared
(but its arguable folio_estimated_sharers() heuristic left unchanged).
Use MPOL_MF_WRLOCK flag to queue_pages_range(), instead of bool lock_vma.
Use explicit STRICT|MOVE* flags where queue_pages_test_walk() checks for
skipping, instead of hiding them behind MPOL_MF_VALID.

Signed-off-by: Hugh Dickins <hughd@google.com>
---
 mm/mempolicy.c | 322 +++++++++++++++++++++++--------------------------
 1 file changed, 149 insertions(+), 173 deletions(-)

Comments

Matthew Wilcox Sept. 25, 2023, 10:22 p.m. UTC | #1
On Mon, Sep 25, 2023 at 01:24:02AM -0700, Hugh Dickins wrote:
> "man 2 migrate_pages" says "On success migrate_pages() returns the number
> of pages that could not be moved".  Although 5.3 and 5.4 commits fixed
> mbind(MPOL_MF_STRICT|MPOL_MF_MOVE*) to fail with EIO when not all pages
> could be moved (because some could not be isolated for migration),
> migrate_pages(2) was left still reporting only those pages failing at the
> migration stage, forgetting those failing at the earlier isolation stage.
> 
> Fix that by accumulating a long nr_failed count in struct queue_pages,
> returned by queue_pages_range() when it's not returning an error, for
> adding on to the nr_failed count from migrate_pages() in mm/migrate.c.
> A count of pages?  It's more a count of folios, but changing it to pages
> would entail more work (also in mm/migrate.c): does not seem justified.

I certainly see what you're saying.  If a folio is only partially mapped
(in an extreme case, the VMA is PAGE_SIZE and maps one page of a 512-page
folio), then setting nr_failed to folio_nr_pages() is misleading at best.

> +static void queue_folios_pmd(pmd_t *pmd, spinlock_t *ptl, unsigned long addr,
>  				unsigned long end, struct mm_walk *walk)
> -	__releases(ptl)
>  {
> -	int ret = 0;
>  	struct folio *folio;
>  	struct queue_pages *qp = walk->private;
> -	unsigned long flags;
>  
>  	if (unlikely(is_pmd_migration_entry(*pmd))) {
> -		ret = -EIO;
> -		goto unlock;
> +		qp->nr_failed++;
> +		return;
>  	}
>  	folio = pfn_folio(pmd_pfn(*pmd));
>  	if (is_huge_zero_page(&folio->page)) {
>  		walk->action = ACTION_CONTINUE;
> -		goto unlock;
> +		return;
>  	}
>  	if (!queue_folio_required(folio, qp))
> -		goto unlock;
> -
> -	flags = qp->flags;
> -	/* go to folio migration */
> -	if (flags & (MPOL_MF_MOVE | MPOL_MF_MOVE_ALL)) {
> -		if (!vma_migratable(walk->vma) ||
> -		    migrate_folio_add(folio, qp->pagelist, flags)) {
> -			ret = 1;
> -			goto unlock;
> -		}
> -	} else
> -		ret = -EIO;
> -unlock:
> -	spin_unlock(ptl);
> -	return ret;
> +		return;
> +	if (!(qp->flags & (MPOL_MF_MOVE | MPOL_MF_MOVE_ALL)) ||
> +	    !vma_migratable(walk->vma) ||
> +	    !migrate_folio_add(folio, qp->pagelist, qp->flags))
> +		qp->nr_failed++;

However, I think here, we would do well to increment by HPAGE_PMD_NR.
Or whatever equivalent is flavour of the week.

Bravo to the other changes.

Reviewed-by: Matthew Wilcox (Oracle) <willy@infradead.org>
Hugh Dickins Sept. 26, 2023, 8:47 p.m. UTC | #2
On Mon, 25 Sep 2023, Matthew Wilcox wrote:
> On Mon, Sep 25, 2023 at 01:24:02AM -0700, Hugh Dickins wrote:
> > "man 2 migrate_pages" says "On success migrate_pages() returns the number
> > of pages that could not be moved".  Although 5.3 and 5.4 commits fixed
> > mbind(MPOL_MF_STRICT|MPOL_MF_MOVE*) to fail with EIO when not all pages
> > could be moved (because some could not be isolated for migration),
> > migrate_pages(2) was left still reporting only those pages failing at the
> > migration stage, forgetting those failing at the earlier isolation stage.
> > 
> > Fix that by accumulating a long nr_failed count in struct queue_pages,
> > returned by queue_pages_range() when it's not returning an error, for
> > adding on to the nr_failed count from migrate_pages() in mm/migrate.c.
> > A count of pages?  It's more a count of folios, but changing it to pages
> > would entail more work (also in mm/migrate.c): does not seem justified.
> 
> I certainly see what you're saying.  If a folio is only partially mapped
> (in an extreme case, the VMA is PAGE_SIZE and maps one page of a 512-page
> folio), then setting nr_failed to folio_nr_pages() is misleading at best.

Actually, that wasn't what I was thinking when I said that: but thank you
for the comment, you've helped me to see that what I'm actually doing is
not what is claimed there.

What I was thinking, something I'm taking as an axiom, is that the units
of failure when isolating must match the units of failure when migrating,
whatever they are.

And migrate_pages(), the internal one, has this helpfully explicit comment:
 * Returns the number of {normal folio, large folio, hugetlb} that were not
 * migrated, or an error code. The number of large folio splits will be
 * considered as the number of non-migrated large folio, no matter how many
 * split folios of the large folio are migrated successfully.

(TBH I haven't spent long enough to actually understand what the second
sentence is saying: I do realize that splits complicate the issue, but the
function wouldn't be expected to return a "number of large folio splits"
anyway.  One day, I should work out what the code is actually doing, and
try to reword that sentence better.)

So above I was trying to say that migrate_pages(), the syscall, returns
that quantity: totalling the failed-isolation and failed-migration folios.

But you've alerted me to how in fact I'm doing an nr_failed++ for each
PTE of a failing-to-isolate folio, not as claimed.  It looks like I need
to record "qp->large" in the case of failure as well as success.  (And
then bother about when isolation fails on the first PTE, but succeeds
by the time of a later PTE? maybe, or maybe that just gets silly.)
I must fix that in v2.

> 
> > +static void queue_folios_pmd(pmd_t *pmd, spinlock_t *ptl, unsigned long addr,
> >  				unsigned long end, struct mm_walk *walk)
...
> > +	if (!(qp->flags & (MPOL_MF_MOVE | MPOL_MF_MOVE_ALL)) ||
> > +	    !vma_migratable(walk->vma) ||
> > +	    !migrate_folio_add(folio, qp->pagelist, qp->flags))
> > +		qp->nr_failed++;
> 
> However, I think here, we would do well to increment by HPAGE_PMD_NR.
> Or whatever equivalent is flavour of the week.

I *really* wanted to do that (and increment nr_failed PTE by PTE as I'm
doing, rather than as I claimed), and gave it some thought: but I don't
think it can be done - or not without abandoning the axiom (in which
case it's impossible to say what migrate_pages(2) is counting), or
adding a layer of complication which simply isn't justifiable.

Certainly we could change the definition of what migrate_pages(internal)
returns (though I haven't researched who depends on it: IIRC-long-ago
there's maybe only one other caller who cares, to update a stat); but
that still would not help.  Because whether migrate_pages(internal)
returns 1 or HPAGE_PMD_NR for an unmigratable and unsplittable THP, it
has no idea whether that THP got into the pagelist via a PMD or via one
or some number more of PTEs.  More info would have to be passed down
separately, folio by folio: an auxiliary xarray perhaps, but let's not.

If it turns out that I'm deluded, and it can be easily done, please
clarify one point: you made this comment on queue_folios_pmd(), but
what about queue_folios_hugetlb()?  Would you nowadays prefer hugetlb
to count 1 or folio_nr_pages()?  I think the latter.

> 
> Bravo to the other changes.

Thanks - I'm guessing your enthusiasm is mainly due to that "qp->large"
realization, which we ought to have thought of before.  I'm afraid it's
going to get more complicated, once COWs are feeding on Ryan's ALFalfA -
might need large[MAX_ORDER], or some better way.  But no great hurry,
nothing will crash if it's occasionally not-quite-right.

> 
> Reviewed-by: Matthew Wilcox (Oracle) <willy@infradead.org>

Many thanks for all these rapid and encouraging reviews.

Hugh
Huang, Ying Sept. 27, 2023, 8:02 a.m. UTC | #3
Hugh Dickins <hughd@google.com> writes:

> "man 2 migrate_pages" says "On success migrate_pages() returns the number
> of pages that could not be moved".  Although 5.3 and 5.4 commits fixed
> mbind(MPOL_MF_STRICT|MPOL_MF_MOVE*) to fail with EIO when not all pages
> could be moved (because some could not be isolated for migration),
> migrate_pages(2) was left still reporting only those pages failing at the
> migration stage, forgetting those failing at the earlier isolation stage.
>
> Fix that by accumulating a long nr_failed count in struct queue_pages,
> returned by queue_pages_range() when it's not returning an error, for
> adding on to the nr_failed count from migrate_pages() in mm/migrate.c.
> A count of pages?  It's more a count of folios, but changing it to pages
> would entail more work (also in mm/migrate.c): does not seem justified.
>
> queue_pages_range() itself should only return -EIO in the "strictly
> unmovable" case (STRICT without any MOVEs): in that case it's best to
> break out as soon as nr_failed gets set; but otherwise it should continue
> to isolate pages for MOVing even when nr_failed - as the mbind(2) manpage
> promises.  This fixes mbind(MPOL_MF_STRICT|MPOL_MF_MOVE*) behavior left
> over from 5.3 and 5.4, and the recent syzbot need for vma_start_write()
> before mbind_range(): but both of those may be fixed by smaller patches.
>
> There's a case when nr_failed should be incremented when it was missed:
> queue_folios_pte_range() and queue_folios_hugetlb() count the transient
> migration entries, like queue_folios_pmd() already did.  And there's a
> case when nr_failed should not be incremented when it would have been:
> in meeting later PTEs of the same large folio, which can only be isolated
> once: fixed by recording the current large folio in struct queue_pages.
>
> Clean up the affected functions, fixing or updating many comments.  Bool
> migrate_folio_add(), without -EIO: true if adding, or if skipping shared
> (but its arguable folio_estimated_sharers() heuristic left unchanged).
> Use MPOL_MF_WRLOCK flag to queue_pages_range(), instead of bool lock_vma.
> Use explicit STRICT|MOVE* flags where queue_pages_test_walk() checks for
> skipping, instead of hiding them behind MPOL_MF_VALID.
>
> Signed-off-by: Hugh Dickins <hughd@google.com>
> ---
>  mm/mempolicy.c | 322 +++++++++++++++++++++++--------------------------
>  1 file changed, 149 insertions(+), 173 deletions(-)
>
> diff --git a/mm/mempolicy.c b/mm/mempolicy.c
> index 42b5567e3773..937386409c28 100644
> --- a/mm/mempolicy.c
> +++ b/mm/mempolicy.c
> @@ -111,7 +111,8 @@
>  
>  /* Internal flags */
>  #define MPOL_MF_DISCONTIG_OK (MPOL_MF_INTERNAL << 0)	/* Skip checks for continuous vmas */
> -#define MPOL_MF_INVERT (MPOL_MF_INTERNAL << 1)		/* Invert check for nodemask */
> +#define MPOL_MF_INVERT       (MPOL_MF_INTERNAL << 1)	/* Invert check for nodemask */
> +#define MPOL_MF_WRLOCK       (MPOL_MF_INTERNAL << 2)	/* Write-lock walked vmas */
>  
>  static struct kmem_cache *policy_cache;
>  static struct kmem_cache *sn_cache;
> @@ -416,9 +417,19 @@ static const struct mempolicy_operations mpol_ops[MPOL_MAX] = {
>  	},
>  };
>  
> -static int migrate_folio_add(struct folio *folio, struct list_head *foliolist,
> +static bool migrate_folio_add(struct folio *folio, struct list_head *foliolist,
>  				unsigned long flags);
>  
> +static bool strictly_unmovable(unsigned long flags)
> +{
> +	/*
> +	 * STRICT without MOVE flags lets do_mbind() fail immediately with -EIO
> +	 * if any misplaced page is found.
> +	 */
> +	return (flags & (MPOL_MF_STRICT | MPOL_MF_MOVE | MPOL_MF_MOVE_ALL)) ==
> +			 MPOL_MF_STRICT;
> +}
> +
>  struct queue_pages {
>  	struct list_head *pagelist;
>  	unsigned long flags;
> @@ -426,6 +437,8 @@ struct queue_pages {
>  	unsigned long start;
>  	unsigned long end;
>  	struct vm_area_struct *first;
> +	struct folio *large;		/* note last large folio on pagelist */
> +	long nr_failed;			/* could not be isolated at this time */
>  };
>  
>  /*
> @@ -443,50 +456,27 @@ static inline bool queue_folio_required(struct folio *folio,
>  	return node_isset(nid, *qp->nmask) == !(flags & MPOL_MF_INVERT);
>  }
>  
> -/*
> - * queue_folios_pmd() has three possible return values:
> - * 0 - folios are placed on the right node or queued successfully, or
> - *     special page is met, i.e. huge zero page.
> - * 1 - there is unmovable folio, and MPOL_MF_MOVE* & MPOL_MF_STRICT were
> - *     specified.
> - * -EIO - is migration entry or only MPOL_MF_STRICT was specified and an
> - *        existing folio was already on a node that does not follow the
> - *        policy.
> - */
> -static int queue_folios_pmd(pmd_t *pmd, spinlock_t *ptl, unsigned long addr,
> +static void queue_folios_pmd(pmd_t *pmd, spinlock_t *ptl, unsigned long addr,

I don't find that "ptl" is used in the function now.  So, remove it?

>  				unsigned long end, struct mm_walk *walk)
> -	__releases(ptl)
>  {
> -	int ret = 0;
>  	struct folio *folio;
>  	struct queue_pages *qp = walk->private;
> -	unsigned long flags;
>  
>  	if (unlikely(is_pmd_migration_entry(*pmd))) {
> -		ret = -EIO;
> -		goto unlock;
> +		qp->nr_failed++;
> +		return;
>  	}
>  	folio = pfn_folio(pmd_pfn(*pmd));
>  	if (is_huge_zero_page(&folio->page)) {
>  		walk->action = ACTION_CONTINUE;
> -		goto unlock;
> +		return;
>  	}
>  	if (!queue_folio_required(folio, qp))
> -		goto unlock;
> -
> -	flags = qp->flags;
> -	/* go to folio migration */
> -	if (flags & (MPOL_MF_MOVE | MPOL_MF_MOVE_ALL)) {
> -		if (!vma_migratable(walk->vma) ||
> -		    migrate_folio_add(folio, qp->pagelist, flags)) {
> -			ret = 1;
> -			goto unlock;
> -		}
> -	} else
> -		ret = -EIO;
> -unlock:
> -	spin_unlock(ptl);
> -	return ret;
> +		return;
> +	if (!(qp->flags & (MPOL_MF_MOVE | MPOL_MF_MOVE_ALL)) ||
> +	    !vma_migratable(walk->vma) ||
> +	    !migrate_folio_add(folio, qp->pagelist, qp->flags))
> +		qp->nr_failed++;
>  }
>  
>  /*
> @@ -496,8 +486,6 @@ static int queue_folios_pmd(pmd_t *pmd, spinlock_t *ptl, unsigned long addr,
>   * queue_folios_pte_range() has three possible return values:
>   * 0 - folios are placed on the right node or queued successfully, or
>   *     special page is met, i.e. zero page.
> - * 1 - there is unmovable folio, and MPOL_MF_MOVE* & MPOL_MF_STRICT were
> - *     specified.
>   * -EIO - only MPOL_MF_STRICT was specified and an existing folio was already
>   *        on a node that does not follow the policy.
>   */
> @@ -508,14 +496,16 @@ static int queue_folios_pte_range(pmd_t *pmd, unsigned long addr,
>  	struct folio *folio;
>  	struct queue_pages *qp = walk->private;
>  	unsigned long flags = qp->flags;
> -	bool has_unmovable = false;
>  	pte_t *pte, *mapped_pte;
>  	pte_t ptent;
>  	spinlock_t *ptl;
>  
>  	ptl = pmd_trans_huge_lock(pmd, vma);
> -	if (ptl)
> -		return queue_folios_pmd(pmd, ptl, addr, end, walk);
> +	if (ptl) {
> +		queue_folios_pmd(pmd, ptl, addr, end, walk);
> +		spin_unlock(ptl);
> +		goto out;
> +	}
>  
>  	mapped_pte = pte = pte_offset_map_lock(walk->mm, pmd, addr, &ptl);
>  	if (!pte) {
> @@ -524,8 +514,13 @@ static int queue_folios_pte_range(pmd_t *pmd, unsigned long addr,
>  	}
>  	for (; addr != end; pte++, addr += PAGE_SIZE) {
>  		ptent = ptep_get(pte);
> -		if (!pte_present(ptent))
> +		if (pte_none(ptent))
>  			continue;
> +		if (!pte_present(ptent)) {
> +			if (is_migration_entry(pte_to_swp_entry(ptent)))
> +				qp->nr_failed++;
> +			continue;
> +		}
>  		folio = vm_normal_folio(vma, addr, ptent);
>  		if (!folio || folio_is_zone_device(folio))
>  			continue;
> @@ -537,97 +532,82 @@ static int queue_folios_pte_range(pmd_t *pmd, unsigned long addr,
>  			continue;
>  		if (!queue_folio_required(folio, qp))
>  			continue;
> -		if (flags & (MPOL_MF_MOVE | MPOL_MF_MOVE_ALL)) {
> -			/* MPOL_MF_STRICT must be specified if we get here */
> -			if (!vma_migratable(vma)) {
> -				has_unmovable = true;
> +		if (!(flags & (MPOL_MF_MOVE | MPOL_MF_MOVE_ALL)) ||
> +		    !vma_migratable(vma)) {
> +			qp->nr_failed++;
> +			if (strictly_unmovable(flags))
>  				break;
> -			}
> -
> +		}

IIUC, even if !(flags & (MPOL_MF_MOVE | MPOL_MF_MOVE_ALL)) or
!vma_migratable(vma), the folio will be isolated in migrate_folio_add()
below.  Is this the expected behavior?

> +		if (migrate_folio_add(folio, qp->pagelist, flags)) {
>  			/*
> -			 * Do not abort immediately since there may be
> -			 * temporary off LRU pages in the range.  Still
> -			 * need migrate other LRU pages.
> +			 * A large folio can only be isolated from LRU once,
> +			 * but may be mapped by many PTEs (and Copy-On-Write may
> +			 * intersperse PTEs of other folios).  This is a common
> +			 * case, so don't mistake it for failure (but of course
> +			 * there can be other cases of multi-mapped pages which
> +			 * this quick check does not help to filter out - and a
> +			 * search of the pagelist might grow to be prohibitive).
>  			 */
> -			if (migrate_folio_add(folio, qp->pagelist, flags))
> -				has_unmovable = true;
> -		} else
> -			break;
> +			if (folio_test_large(folio))
> +				qp->large = folio;
> +		} else if (folio != qp->large) {
> +			qp->nr_failed++;
> +			if (strictly_unmovable(flags))
> +				break;
> +		}
>  	}
>  	pte_unmap_unlock(mapped_pte, ptl);
>  	cond_resched();
> -
> -	if (has_unmovable)
> -		return 1;
> -
> -	return addr != end ? -EIO : 0;
> +out:
> +	if (qp->nr_failed && strictly_unmovable(flags))
> +		return -EIO;
> +	return 0;
>  }
>  
>  static int queue_folios_hugetlb(pte_t *pte, unsigned long hmask,
>  			       unsigned long addr, unsigned long end,
>  			       struct mm_walk *walk)
>  {
> -	int ret = 0;
>  #ifdef CONFIG_HUGETLB_PAGE
>  	struct queue_pages *qp = walk->private;
> -	unsigned long flags = (qp->flags & MPOL_MF_VALID);
> +	unsigned long flags = qp->flags;
>  	struct folio *folio;
>  	spinlock_t *ptl;
>  	pte_t entry;
>  
>  	ptl = huge_pte_lock(hstate_vma(walk->vma), walk->mm, pte);
>  	entry = huge_ptep_get(pte);
> -	if (!pte_present(entry))
> +	if (!pte_present(entry)) {
> +		if (unlikely(is_hugetlb_entry_migration(entry)))
> +			qp->nr_failed++;
>  		goto unlock;
> +	}
>  	folio = pfn_folio(pte_pfn(entry));
>  	if (!queue_folio_required(folio, qp))
>  		goto unlock;
> -
> -	if (flags == MPOL_MF_STRICT) {
> -		/*
> -		 * STRICT alone means only detecting misplaced folio and no
> -		 * need to further check other vma.
> -		 */
> -		ret = -EIO;
> +	if (!(flags & (MPOL_MF_MOVE | MPOL_MF_MOVE_ALL)) ||
> +	    !vma_migratable(walk->vma)) {
> +		qp->nr_failed++;
>  		goto unlock;
>  	}
> -
> -	if (!vma_migratable(walk->vma)) {
> -		/*
> -		 * Must be STRICT with MOVE*, otherwise .test_walk() have
> -		 * stopped walking current vma.
> -		 * Detecting misplaced folio but allow migrating folios which
> -		 * have been queued.
> -		 */
> -		ret = 1;
> -		goto unlock;
> -	}
> -
>  	/*
> -	 * With MPOL_MF_MOVE, we try to migrate only unshared folios. If it
> -	 * is shared it is likely not worth migrating.
> +	 * Unless MPOL_MF_MOVE_ALL, we try to avoid migrating a shared folio.
> +	 * Choosing not to migrate a shared folio is not counted as a failure.
>  	 *
>  	 * To check if the folio is shared, ideally we want to make sure
>  	 * every page is mapped to the same process. Doing that is very
> -	 * expensive, so check the estimated mapcount of the folio instead.
> +	 * expensive, so check the estimated sharers of the folio instead.
>  	 */
> -	if (flags & (MPOL_MF_MOVE_ALL) ||
> -	    (flags & MPOL_MF_MOVE && folio_estimated_sharers(folio) == 1 &&
> -	     !hugetlb_pmd_shared(pte))) {
> -		if (!isolate_hugetlb(folio, qp->pagelist) &&
> -			(flags & MPOL_MF_STRICT))
> -			/*
> -			 * Failed to isolate folio but allow migrating pages
> -			 * which have been queued.
> -			 */
> -			ret = 1;
> -	}
> +	if ((flags & MPOL_MF_MOVE_ALL) ||
> +	    (folio_estimated_sharers(folio) == 1 && !hugetlb_pmd_shared(pte)))
> +		if (!isolate_hugetlb(folio, qp->pagelist))
> +			qp->nr_failed++;
>  unlock:
>  	spin_unlock(ptl);
> -#else
> -	BUG();
> +	if (qp->nr_failed && strictly_unmovable(flags))
> +		return -EIO;
>  #endif
> -	return ret;
> +	return 0;
>  }
>  
>  #ifdef CONFIG_NUMA_BALANCING
> @@ -708,8 +688,11 @@ static int queue_pages_test_walk(unsigned long start, unsigned long end,
>  		return 1;
>  	}
>  
> -	/* queue pages from current vma */
> -	if (flags & MPOL_MF_VALID)
> +	/*
> +	 * Check page nodes, and queue pages to move, in the current vma.
> +	 * But if no moving, and no strict checking, the scan can be skipped.
> +	 */
> +	if (flags & (MPOL_MF_STRICT | MPOL_MF_MOVE | MPOL_MF_MOVE_ALL))
>  		return 0;
>  	return 1;
>  }
> @@ -731,22 +714,22 @@ static const struct mm_walk_ops queue_pages_lock_vma_walk_ops = {
>  /*
>   * Walk through page tables and collect pages to be migrated.
>   *
> - * If pages found in a given range are on a set of nodes (determined by
> - * @nodes and @flags,) it's isolated and queued to the pagelist which is
> - * passed via @private.
> + * If pages found in a given range are not on the required set of @nodes,
> + * and migration is allowed, they are isolated and queued to the pagelist
> + * which is passed via @private.

s/@private/@pagelist/

>   *
> - * queue_pages_range() has three possible return values:
> - * 1 - there is unmovable page, but MPOL_MF_MOVE* & MPOL_MF_STRICT were
> - *     specified.
> - * 0 - queue pages successfully or no misplaced page.
> - * errno - i.e. misplaced pages with MPOL_MF_STRICT specified (-EIO) or
> - *         memory range specified by nodemask and maxnode points outside
> - *         your accessible address space (-EFAULT)
> + * queue_pages_range() may return:
> + * 0 - all pages already on the right node, or successfully queued for moving
> + *     (or neither strict checking nor moving requested: only range checking).
> + * >0 - this number of misplaced folios could not be queued for moving
> + *      (a hugetlbfs page or a transparent huge page being counted as 1).
> + * -EIO - a misplaced page found, when MPOL_MF_STRICT specified without MOVEs.
> + * -EFAULT - a hole in the memory range, when MPOL_MF_DISCONTIG_OK unspecified.
>   */
> -static int
> +static long
>  queue_pages_range(struct mm_struct *mm, unsigned long start, unsigned long end,
>  		nodemask_t *nodes, unsigned long flags,
> -		struct list_head *pagelist, bool lock_vma)
> +		struct list_head *pagelist)
>  {
>  	int err;
>  	struct queue_pages qp = {
> @@ -757,7 +740,7 @@ queue_pages_range(struct mm_struct *mm, unsigned long start, unsigned long end,
>  		.end = end,
>  		.first = NULL,
>  	};
> -	const struct mm_walk_ops *ops = lock_vma ?
> +	const struct mm_walk_ops *ops = (flags & MPOL_MF_WRLOCK) ?
>  			&queue_pages_lock_vma_walk_ops : &queue_pages_walk_ops;
>  
>  	err = walk_page_range(mm, start, end, ops, &qp);
> @@ -766,7 +749,7 @@ queue_pages_range(struct mm_struct *mm, unsigned long start, unsigned long end,
>  		/* whole range in hole */
>  		err = -EFAULT;
>  
> -	return err;
> +	return err ? : qp.nr_failed;
>  }
>  
>  /*
> @@ -1029,16 +1012,16 @@ static long do_get_mempolicy(int *policy, nodemask_t *nmask,
>  }
>  
>  #ifdef CONFIG_MIGRATION
> -static int migrate_folio_add(struct folio *folio, struct list_head *foliolist,
> +static bool migrate_folio_add(struct folio *folio, struct list_head *foliolist,
>  				unsigned long flags)
>  {
>  	/*
> -	 * We try to migrate only unshared folios. If it is shared it
> -	 * is likely not worth migrating.
> +	 * Unless MPOL_MF_MOVE_ALL, we try to avoid migrating a shared folio.
> +	 * Choosing not to migrate a shared folio is not counted as a failure.
>  	 *
>  	 * To check if the folio is shared, ideally we want to make sure
>  	 * every page is mapped to the same process. Doing that is very
> -	 * expensive, so check the estimated mapcount of the folio instead.
> +	 * expensive, so check the estimated sharers of the folio instead.
>  	 */
>  	if ((flags & MPOL_MF_MOVE_ALL) || folio_estimated_sharers(folio) == 1) {
>  		if (folio_isolate_lru(folio)) {
> @@ -1046,32 +1029,31 @@ static int migrate_folio_add(struct folio *folio, struct list_head *foliolist,
>  			node_stat_mod_folio(folio,
>  				NR_ISOLATED_ANON + folio_is_file_lru(folio),
>  				folio_nr_pages(folio));
> -		} else if (flags & MPOL_MF_STRICT) {
> +		} else {
>  			/*
>  			 * Non-movable folio may reach here.  And, there may be
>  			 * temporary off LRU folios or non-LRU movable folios.
>  			 * Treat them as unmovable folios since they can't be
> -			 * isolated, so they can't be moved at the moment.  It
> -			 * should return -EIO for this case too.
> +			 * isolated, so they can't be moved at the moment.
>  			 */
> -			return -EIO;
> +			return false;
>  		}
>  	}
> -
> -	return 0;
> +	return true;
>  }
>  
>  /*
>   * Migrate pages from one node to a target node.
>   * Returns error or the number of pages not migrated.
>   */
> -static int migrate_to_node(struct mm_struct *mm, int source, int dest,
> -			   int flags)
> +static long migrate_to_node(struct mm_struct *mm, int source, int dest,
> +			    int flags)
>  {
>  	nodemask_t nmask;
>  	struct vm_area_struct *vma;
>  	LIST_HEAD(pagelist);
> -	int err = 0;
> +	long nr_failed;
> +	long err = 0;
>  	struct migration_target_control mtc = {
>  		.nid = dest,
>  		.gfp_mask = GFP_HIGHUSER_MOVABLE | __GFP_THISNODE,
> @@ -1080,23 +1062,27 @@ static int migrate_to_node(struct mm_struct *mm, int source, int dest,
>  	nodes_clear(nmask);
>  	node_set(source, nmask);
>  
> -	/*
> -	 * This does not "check" the range but isolates all pages that
> -	 * need migration.  Between passing in the full user address
> -	 * space range and MPOL_MF_DISCONTIG_OK, this call can not fail.
> -	 */
> -	vma = find_vma(mm, 0);
>  	VM_BUG_ON(!(flags & (MPOL_MF_MOVE | MPOL_MF_MOVE_ALL)));
> -	queue_pages_range(mm, vma->vm_start, mm->task_size, &nmask,
> -			flags | MPOL_MF_DISCONTIG_OK, &pagelist, false);
> +	vma = find_vma(mm, 0);
> +
> +	/*
> +	 * This does not migrate the range, but isolates all pages that
> +	 * need migration.  Between passing in the full user address
> +	 * space range and MPOL_MF_DISCONTIG_OK, this call cannot fail,
> +	 * but passes back the count of pages which could not be isolated.
> +	 */
> +	nr_failed = queue_pages_range(mm, vma->vm_start, mm->task_size, &nmask,
> +				      flags | MPOL_MF_DISCONTIG_OK, &pagelist);
>  
>  	if (!list_empty(&pagelist)) {
>  		err = migrate_pages(&pagelist, alloc_migration_target, NULL,
> -				(unsigned long)&mtc, MIGRATE_SYNC, MR_SYSCALL, NULL);
> +			(unsigned long)&mtc, MIGRATE_SYNC, MR_SYSCALL, NULL);
>  		if (err)
>  			putback_movable_pages(&pagelist);
>  	}
>  
> +	if (err >= 0)
> +		err += nr_failed;
>  	return err;
>  }
>  
> @@ -1109,8 +1095,8 @@ static int migrate_to_node(struct mm_struct *mm, int source, int dest,
>  int do_migrate_pages(struct mm_struct *mm, const nodemask_t *from,
>  		     const nodemask_t *to, int flags)
>  {
> -	int busy = 0;
> -	int err = 0;
> +	long nr_failed = 0;
> +	long err = 0;
>  	nodemask_t tmp;
>  
>  	lru_cache_disable();
> @@ -1192,7 +1178,7 @@ int do_migrate_pages(struct mm_struct *mm, const nodemask_t *from,
>  		node_clear(source, tmp);
>  		err = migrate_to_node(mm, source, dest, flags);
>  		if (err > 0)
> -			busy += err;
> +			nr_failed += err;
>  		if (err < 0)
>  			break;
>  	}
> @@ -1201,8 +1187,7 @@ int do_migrate_pages(struct mm_struct *mm, const nodemask_t *from,
>  	lru_cache_enable();
>  	if (err < 0)
>  		return err;
> -	return busy;
> -
> +	return (nr_failed < INT_MAX) ? nr_failed : INT_MAX;

        return min_t(long, nr_failed, INT_MAX);
?

>  }
>  
>  /*
> @@ -1241,10 +1226,10 @@ static struct folio *new_folio(struct folio *src, unsigned long start)
>  }
>  #else
>  
> -static int migrate_folio_add(struct folio *folio, struct list_head *foliolist,
> +static bool migrate_folio_add(struct folio *folio, struct list_head *foliolist,
>  				unsigned long flags)
>  {
> -	return -EIO;
> +	return false;
>  }
>  
>  int do_migrate_pages(struct mm_struct *mm, const nodemask_t *from,
> @@ -1268,8 +1253,8 @@ static long do_mbind(unsigned long start, unsigned long len,
>  	struct vma_iterator vmi;
>  	struct mempolicy *new;
>  	unsigned long end;
> -	int err;
> -	int ret;
> +	long err;
> +	long nr_failed;
>  	LIST_HEAD(pagelist);
>  
>  	if (flags & ~(unsigned long)MPOL_MF_VALID)
> @@ -1309,10 +1294,8 @@ static long do_mbind(unsigned long start, unsigned long len,
>  		 start, start + len, mode, mode_flags,
>  		 nmask ? nodes_addr(*nmask)[0] : NUMA_NO_NODE);
>  
> -	if (flags & (MPOL_MF_MOVE | MPOL_MF_MOVE_ALL)) {
> -
> +	if (flags & (MPOL_MF_MOVE | MPOL_MF_MOVE_ALL))
>  		lru_cache_disable();
> -	}
>  	{
>  		NODEMASK_SCRATCH(scratch);
>  		if (scratch) {
> @@ -1328,44 +1311,37 @@ static long do_mbind(unsigned long start, unsigned long len,
>  		goto mpol_out;
>  
>  	/*
> -	 * Lock the VMAs before scanning for pages to migrate, to ensure we don't
> -	 * miss a concurrently inserted page.
> +	 * Lock the VMAs before scanning for pages to migrate,
> +	 * to ensure we don't miss a concurrently inserted page.
>  	 */
> -	ret = queue_pages_range(mm, start, end, nmask,
> -			  flags | MPOL_MF_INVERT, &pagelist, true);
> +	nr_failed = queue_pages_range(mm, start, end, nmask,
> +			flags | MPOL_MF_INVERT | MPOL_MF_WRLOCK, &pagelist);
>  
> -	if (ret < 0) {
> -		err = ret;
> -		goto up_out;
> -	}
> -
> -	vma_iter_init(&vmi, mm, start);
> -	prev = vma_prev(&vmi);
> -	for_each_vma_range(vmi, vma, end) {
> -		err = mbind_range(&vmi, vma, &prev, start, end, new);
> -		if (err)
> -			break;
> +	if (nr_failed < 0) {
> +		err = nr_failed;
> +	} else {
> +		vma_iter_init(&vmi, mm, start);
> +		prev = vma_prev(&vmi);
> +		for_each_vma_range(vmi, vma, end) {
> +			err = mbind_range(&vmi, vma, &prev, start, end, new);
> +			if (err)
> +				break;
> +		}
>  	}
>  
>  	if (!err) {
> -		int nr_failed = 0;
> -
>  		if (!list_empty(&pagelist)) {
>  			WARN_ON_ONCE(flags & MPOL_MF_LAZY);
> -			nr_failed = migrate_pages(&pagelist, new_folio, NULL,
> +			nr_failed |= migrate_pages(&pagelist, new_folio, NULL,
>  				start, MIGRATE_SYNC, MR_MEMPOLICY_MBIND, NULL);
> -			if (nr_failed)
> -				putback_movable_pages(&pagelist);
>  		}
> -
> -		if ((ret > 0) || (nr_failed && (flags & MPOL_MF_STRICT)))
> +		if (nr_failed && (flags & MPOL_MF_STRICT))
>  			err = -EIO;
> -	} else {
> -up_out:
> -		if (!list_empty(&pagelist))
> -			putback_movable_pages(&pagelist);
>  	}
>  
> +	if (!list_empty(&pagelist))
> +		putback_movable_pages(&pagelist);
> +
>  	mmap_write_unlock(mm);
>  mpol_out:
>  	mpol_put(new);

--
Best Regards,
Huang, Ying
Hugh Dickins Sept. 30, 2023, 4:20 a.m. UTC | #4
On Wed, 27 Sep 2023, Huang, Ying wrote:
> Hugh Dickins <hughd@google.com> writes:
> > -static int queue_folios_pmd(pmd_t *pmd, spinlock_t *ptl, unsigned long addr,
> > +static void queue_folios_pmd(pmd_t *pmd, spinlock_t *ptl, unsigned long addr,
> 
> I don't find that "ptl" is used in the function now.  So, remove it?

Yes indeed, thanks for spotting that, I shall remove it.
And I think addr, end are also unused: I'll remove them too.

...
> > +		if (!(flags & (MPOL_MF_MOVE | MPOL_MF_MOVE_ALL)) ||
> > +		    !vma_migratable(vma)) {
> > +			qp->nr_failed++;
> > +			if (strictly_unmovable(flags))
> >  				break;
> > -			}
> > -
> > +		}
> 
> IIUC, even if !(flags & (MPOL_MF_MOVE | MPOL_MF_MOVE_ALL)) or
> !vma_migratable(vma), the folio will be isolated in migrate_folio_add()
> below.  Is this the expected behavior?

Yikes, I think you're right, thanks a lot: it was okay up until I realized
the "qp->large" issue, and had to separate out the migrate_folio_add()
failure case.  Late changes...

> 
> > +		if (migrate_folio_add(folio, qp->pagelist, flags)) {
...
> > @@ -731,22 +714,22 @@ static const struct mm_walk_ops queue_pages_lock_vma_walk_ops = {
> >  /*
> >   * Walk through page tables and collect pages to be migrated.
> >   *
> > - * If pages found in a given range are on a set of nodes (determined by
> > - * @nodes and @flags,) it's isolated and queued to the pagelist which is
> > - * passed via @private.
> > + * If pages found in a given range are not on the required set of @nodes,
> > + * and migration is allowed, they are isolated and queued to the pagelist
> > + * which is passed via @private.
> 
> s/@private/@pagelist/

Right: or even better, simply "queued to @pagelist."

...
> > @@ -1201,8 +1187,7 @@ int do_migrate_pages(struct mm_struct *mm, const nodemask_t *from,
> >  	lru_cache_enable();
> >  	if (err < 0)
> >  		return err;
> > -	return busy;
> > -
> > +	return (nr_failed < INT_MAX) ? nr_failed : INT_MAX;
> 
>         return min_t(long, nr_failed, INT_MAX);
> ?

I may be the odd man out, but I read it more easily how it is:
so will stick with that, unless others object.

Very helpful comments: thank you,

Hugh
diff mbox series

Patch

diff --git a/mm/mempolicy.c b/mm/mempolicy.c
index 42b5567e3773..937386409c28 100644
--- a/mm/mempolicy.c
+++ b/mm/mempolicy.c
@@ -111,7 +111,8 @@ 
 
 /* Internal flags */
 #define MPOL_MF_DISCONTIG_OK (MPOL_MF_INTERNAL << 0)	/* Skip checks for continuous vmas */
-#define MPOL_MF_INVERT (MPOL_MF_INTERNAL << 1)		/* Invert check for nodemask */
+#define MPOL_MF_INVERT       (MPOL_MF_INTERNAL << 1)	/* Invert check for nodemask */
+#define MPOL_MF_WRLOCK       (MPOL_MF_INTERNAL << 2)	/* Write-lock walked vmas */
 
 static struct kmem_cache *policy_cache;
 static struct kmem_cache *sn_cache;
@@ -416,9 +417,19 @@  static const struct mempolicy_operations mpol_ops[MPOL_MAX] = {
 	},
 };
 
-static int migrate_folio_add(struct folio *folio, struct list_head *foliolist,
+static bool migrate_folio_add(struct folio *folio, struct list_head *foliolist,
 				unsigned long flags);
 
+static bool strictly_unmovable(unsigned long flags)
+{
+	/*
+	 * STRICT without MOVE flags lets do_mbind() fail immediately with -EIO
+	 * if any misplaced page is found.
+	 */
+	return (flags & (MPOL_MF_STRICT | MPOL_MF_MOVE | MPOL_MF_MOVE_ALL)) ==
+			 MPOL_MF_STRICT;
+}
+
 struct queue_pages {
 	struct list_head *pagelist;
 	unsigned long flags;
@@ -426,6 +437,8 @@  struct queue_pages {
 	unsigned long start;
 	unsigned long end;
 	struct vm_area_struct *first;
+	struct folio *large;		/* note last large folio on pagelist */
+	long nr_failed;			/* could not be isolated at this time */
 };
 
 /*
@@ -443,50 +456,27 @@  static inline bool queue_folio_required(struct folio *folio,
 	return node_isset(nid, *qp->nmask) == !(flags & MPOL_MF_INVERT);
 }
 
-/*
- * queue_folios_pmd() has three possible return values:
- * 0 - folios are placed on the right node or queued successfully, or
- *     special page is met, i.e. huge zero page.
- * 1 - there is unmovable folio, and MPOL_MF_MOVE* & MPOL_MF_STRICT were
- *     specified.
- * -EIO - is migration entry or only MPOL_MF_STRICT was specified and an
- *        existing folio was already on a node that does not follow the
- *        policy.
- */
-static int queue_folios_pmd(pmd_t *pmd, spinlock_t *ptl, unsigned long addr,
+static void queue_folios_pmd(pmd_t *pmd, spinlock_t *ptl, unsigned long addr,
 				unsigned long end, struct mm_walk *walk)
-	__releases(ptl)
 {
-	int ret = 0;
 	struct folio *folio;
 	struct queue_pages *qp = walk->private;
-	unsigned long flags;
 
 	if (unlikely(is_pmd_migration_entry(*pmd))) {
-		ret = -EIO;
-		goto unlock;
+		qp->nr_failed++;
+		return;
 	}
 	folio = pfn_folio(pmd_pfn(*pmd));
 	if (is_huge_zero_page(&folio->page)) {
 		walk->action = ACTION_CONTINUE;
-		goto unlock;
+		return;
 	}
 	if (!queue_folio_required(folio, qp))
-		goto unlock;
-
-	flags = qp->flags;
-	/* go to folio migration */
-	if (flags & (MPOL_MF_MOVE | MPOL_MF_MOVE_ALL)) {
-		if (!vma_migratable(walk->vma) ||
-		    migrate_folio_add(folio, qp->pagelist, flags)) {
-			ret = 1;
-			goto unlock;
-		}
-	} else
-		ret = -EIO;
-unlock:
-	spin_unlock(ptl);
-	return ret;
+		return;
+	if (!(qp->flags & (MPOL_MF_MOVE | MPOL_MF_MOVE_ALL)) ||
+	    !vma_migratable(walk->vma) ||
+	    !migrate_folio_add(folio, qp->pagelist, qp->flags))
+		qp->nr_failed++;
 }
 
 /*
@@ -496,8 +486,6 @@  static int queue_folios_pmd(pmd_t *pmd, spinlock_t *ptl, unsigned long addr,
  * queue_folios_pte_range() has three possible return values:
  * 0 - folios are placed on the right node or queued successfully, or
  *     special page is met, i.e. zero page.
- * 1 - there is unmovable folio, and MPOL_MF_MOVE* & MPOL_MF_STRICT were
- *     specified.
  * -EIO - only MPOL_MF_STRICT was specified and an existing folio was already
  *        on a node that does not follow the policy.
  */
@@ -508,14 +496,16 @@  static int queue_folios_pte_range(pmd_t *pmd, unsigned long addr,
 	struct folio *folio;
 	struct queue_pages *qp = walk->private;
 	unsigned long flags = qp->flags;
-	bool has_unmovable = false;
 	pte_t *pte, *mapped_pte;
 	pte_t ptent;
 	spinlock_t *ptl;
 
 	ptl = pmd_trans_huge_lock(pmd, vma);
-	if (ptl)
-		return queue_folios_pmd(pmd, ptl, addr, end, walk);
+	if (ptl) {
+		queue_folios_pmd(pmd, ptl, addr, end, walk);
+		spin_unlock(ptl);
+		goto out;
+	}
 
 	mapped_pte = pte = pte_offset_map_lock(walk->mm, pmd, addr, &ptl);
 	if (!pte) {
@@ -524,8 +514,13 @@  static int queue_folios_pte_range(pmd_t *pmd, unsigned long addr,
 	}
 	for (; addr != end; pte++, addr += PAGE_SIZE) {
 		ptent = ptep_get(pte);
-		if (!pte_present(ptent))
+		if (pte_none(ptent))
 			continue;
+		if (!pte_present(ptent)) {
+			if (is_migration_entry(pte_to_swp_entry(ptent)))
+				qp->nr_failed++;
+			continue;
+		}
 		folio = vm_normal_folio(vma, addr, ptent);
 		if (!folio || folio_is_zone_device(folio))
 			continue;
@@ -537,97 +532,82 @@  static int queue_folios_pte_range(pmd_t *pmd, unsigned long addr,
 			continue;
 		if (!queue_folio_required(folio, qp))
 			continue;
-		if (flags & (MPOL_MF_MOVE | MPOL_MF_MOVE_ALL)) {
-			/* MPOL_MF_STRICT must be specified if we get here */
-			if (!vma_migratable(vma)) {
-				has_unmovable = true;
+		if (!(flags & (MPOL_MF_MOVE | MPOL_MF_MOVE_ALL)) ||
+		    !vma_migratable(vma)) {
+			qp->nr_failed++;
+			if (strictly_unmovable(flags))
 				break;
-			}
-
+		}
+		if (migrate_folio_add(folio, qp->pagelist, flags)) {
 			/*
-			 * Do not abort immediately since there may be
-			 * temporary off LRU pages in the range.  Still
-			 * need migrate other LRU pages.
+			 * A large folio can only be isolated from LRU once,
+			 * but may be mapped by many PTEs (and Copy-On-Write may
+			 * intersperse PTEs of other folios).  This is a common
+			 * case, so don't mistake it for failure (but of course
+			 * there can be other cases of multi-mapped pages which
+			 * this quick check does not help to filter out - and a
+			 * search of the pagelist might grow to be prohibitive).
 			 */
-			if (migrate_folio_add(folio, qp->pagelist, flags))
-				has_unmovable = true;
-		} else
-			break;
+			if (folio_test_large(folio))
+				qp->large = folio;
+		} else if (folio != qp->large) {
+			qp->nr_failed++;
+			if (strictly_unmovable(flags))
+				break;
+		}
 	}
 	pte_unmap_unlock(mapped_pte, ptl);
 	cond_resched();
-
-	if (has_unmovable)
-		return 1;
-
-	return addr != end ? -EIO : 0;
+out:
+	if (qp->nr_failed && strictly_unmovable(flags))
+		return -EIO;
+	return 0;
 }
 
 static int queue_folios_hugetlb(pte_t *pte, unsigned long hmask,
 			       unsigned long addr, unsigned long end,
 			       struct mm_walk *walk)
 {
-	int ret = 0;
 #ifdef CONFIG_HUGETLB_PAGE
 	struct queue_pages *qp = walk->private;
-	unsigned long flags = (qp->flags & MPOL_MF_VALID);
+	unsigned long flags = qp->flags;
 	struct folio *folio;
 	spinlock_t *ptl;
 	pte_t entry;
 
 	ptl = huge_pte_lock(hstate_vma(walk->vma), walk->mm, pte);
 	entry = huge_ptep_get(pte);
-	if (!pte_present(entry))
+	if (!pte_present(entry)) {
+		if (unlikely(is_hugetlb_entry_migration(entry)))
+			qp->nr_failed++;
 		goto unlock;
+	}
 	folio = pfn_folio(pte_pfn(entry));
 	if (!queue_folio_required(folio, qp))
 		goto unlock;
-
-	if (flags == MPOL_MF_STRICT) {
-		/*
-		 * STRICT alone means only detecting misplaced folio and no
-		 * need to further check other vma.
-		 */
-		ret = -EIO;
+	if (!(flags & (MPOL_MF_MOVE | MPOL_MF_MOVE_ALL)) ||
+	    !vma_migratable(walk->vma)) {
+		qp->nr_failed++;
 		goto unlock;
 	}
-
-	if (!vma_migratable(walk->vma)) {
-		/*
-		 * Must be STRICT with MOVE*, otherwise .test_walk() have
-		 * stopped walking current vma.
-		 * Detecting misplaced folio but allow migrating folios which
-		 * have been queued.
-		 */
-		ret = 1;
-		goto unlock;
-	}
-
 	/*
-	 * With MPOL_MF_MOVE, we try to migrate only unshared folios. If it
-	 * is shared it is likely not worth migrating.
+	 * Unless MPOL_MF_MOVE_ALL, we try to avoid migrating a shared folio.
+	 * Choosing not to migrate a shared folio is not counted as a failure.
 	 *
 	 * To check if the folio is shared, ideally we want to make sure
 	 * every page is mapped to the same process. Doing that is very
-	 * expensive, so check the estimated mapcount of the folio instead.
+	 * expensive, so check the estimated sharers of the folio instead.
 	 */
-	if (flags & (MPOL_MF_MOVE_ALL) ||
-	    (flags & MPOL_MF_MOVE && folio_estimated_sharers(folio) == 1 &&
-	     !hugetlb_pmd_shared(pte))) {
-		if (!isolate_hugetlb(folio, qp->pagelist) &&
-			(flags & MPOL_MF_STRICT))
-			/*
-			 * Failed to isolate folio but allow migrating pages
-			 * which have been queued.
-			 */
-			ret = 1;
-	}
+	if ((flags & MPOL_MF_MOVE_ALL) ||
+	    (folio_estimated_sharers(folio) == 1 && !hugetlb_pmd_shared(pte)))
+		if (!isolate_hugetlb(folio, qp->pagelist))
+			qp->nr_failed++;
 unlock:
 	spin_unlock(ptl);
-#else
-	BUG();
+	if (qp->nr_failed && strictly_unmovable(flags))
+		return -EIO;
 #endif
-	return ret;
+	return 0;
 }
 
 #ifdef CONFIG_NUMA_BALANCING
@@ -708,8 +688,11 @@  static int queue_pages_test_walk(unsigned long start, unsigned long end,
 		return 1;
 	}
 
-	/* queue pages from current vma */
-	if (flags & MPOL_MF_VALID)
+	/*
+	 * Check page nodes, and queue pages to move, in the current vma.
+	 * But if no moving, and no strict checking, the scan can be skipped.
+	 */
+	if (flags & (MPOL_MF_STRICT | MPOL_MF_MOVE | MPOL_MF_MOVE_ALL))
 		return 0;
 	return 1;
 }
@@ -731,22 +714,22 @@  static const struct mm_walk_ops queue_pages_lock_vma_walk_ops = {
 /*
  * Walk through page tables and collect pages to be migrated.
  *
- * If pages found in a given range are on a set of nodes (determined by
- * @nodes and @flags,) it's isolated and queued to the pagelist which is
- * passed via @private.
+ * If pages found in a given range are not on the required set of @nodes,
+ * and migration is allowed, they are isolated and queued to the pagelist
+ * which is passed via @private.
  *
- * queue_pages_range() has three possible return values:
- * 1 - there is unmovable page, but MPOL_MF_MOVE* & MPOL_MF_STRICT were
- *     specified.
- * 0 - queue pages successfully or no misplaced page.
- * errno - i.e. misplaced pages with MPOL_MF_STRICT specified (-EIO) or
- *         memory range specified by nodemask and maxnode points outside
- *         your accessible address space (-EFAULT)
+ * queue_pages_range() may return:
+ * 0 - all pages already on the right node, or successfully queued for moving
+ *     (or neither strict checking nor moving requested: only range checking).
+ * >0 - this number of misplaced folios could not be queued for moving
+ *      (a hugetlbfs page or a transparent huge page being counted as 1).
+ * -EIO - a misplaced page found, when MPOL_MF_STRICT specified without MOVEs.
+ * -EFAULT - a hole in the memory range, when MPOL_MF_DISCONTIG_OK unspecified.
  */
-static int
+static long
 queue_pages_range(struct mm_struct *mm, unsigned long start, unsigned long end,
 		nodemask_t *nodes, unsigned long flags,
-		struct list_head *pagelist, bool lock_vma)
+		struct list_head *pagelist)
 {
 	int err;
 	struct queue_pages qp = {
@@ -757,7 +740,7 @@  queue_pages_range(struct mm_struct *mm, unsigned long start, unsigned long end,
 		.end = end,
 		.first = NULL,
 	};
-	const struct mm_walk_ops *ops = lock_vma ?
+	const struct mm_walk_ops *ops = (flags & MPOL_MF_WRLOCK) ?
 			&queue_pages_lock_vma_walk_ops : &queue_pages_walk_ops;
 
 	err = walk_page_range(mm, start, end, ops, &qp);
@@ -766,7 +749,7 @@  queue_pages_range(struct mm_struct *mm, unsigned long start, unsigned long end,
 		/* whole range in hole */
 		err = -EFAULT;
 
-	return err;
+	return err ? : qp.nr_failed;
 }
 
 /*
@@ -1029,16 +1012,16 @@  static long do_get_mempolicy(int *policy, nodemask_t *nmask,
 }
 
 #ifdef CONFIG_MIGRATION
-static int migrate_folio_add(struct folio *folio, struct list_head *foliolist,
+static bool migrate_folio_add(struct folio *folio, struct list_head *foliolist,
 				unsigned long flags)
 {
 	/*
-	 * We try to migrate only unshared folios. If it is shared it
-	 * is likely not worth migrating.
+	 * Unless MPOL_MF_MOVE_ALL, we try to avoid migrating a shared folio.
+	 * Choosing not to migrate a shared folio is not counted as a failure.
 	 *
 	 * To check if the folio is shared, ideally we want to make sure
 	 * every page is mapped to the same process. Doing that is very
-	 * expensive, so check the estimated mapcount of the folio instead.
+	 * expensive, so check the estimated sharers of the folio instead.
 	 */
 	if ((flags & MPOL_MF_MOVE_ALL) || folio_estimated_sharers(folio) == 1) {
 		if (folio_isolate_lru(folio)) {
@@ -1046,32 +1029,31 @@  static int migrate_folio_add(struct folio *folio, struct list_head *foliolist,
 			node_stat_mod_folio(folio,
 				NR_ISOLATED_ANON + folio_is_file_lru(folio),
 				folio_nr_pages(folio));
-		} else if (flags & MPOL_MF_STRICT) {
+		} else {
 			/*
 			 * Non-movable folio may reach here.  And, there may be
 			 * temporary off LRU folios or non-LRU movable folios.
 			 * Treat them as unmovable folios since they can't be
-			 * isolated, so they can't be moved at the moment.  It
-			 * should return -EIO for this case too.
+			 * isolated, so they can't be moved at the moment.
 			 */
-			return -EIO;
+			return false;
 		}
 	}
-
-	return 0;
+	return true;
 }
 
 /*
  * Migrate pages from one node to a target node.
  * Returns error or the number of pages not migrated.
  */
-static int migrate_to_node(struct mm_struct *mm, int source, int dest,
-			   int flags)
+static long migrate_to_node(struct mm_struct *mm, int source, int dest,
+			    int flags)
 {
 	nodemask_t nmask;
 	struct vm_area_struct *vma;
 	LIST_HEAD(pagelist);
-	int err = 0;
+	long nr_failed;
+	long err = 0;
 	struct migration_target_control mtc = {
 		.nid = dest,
 		.gfp_mask = GFP_HIGHUSER_MOVABLE | __GFP_THISNODE,
@@ -1080,23 +1062,27 @@  static int migrate_to_node(struct mm_struct *mm, int source, int dest,
 	nodes_clear(nmask);
 	node_set(source, nmask);
 
-	/*
-	 * This does not "check" the range but isolates all pages that
-	 * need migration.  Between passing in the full user address
-	 * space range and MPOL_MF_DISCONTIG_OK, this call can not fail.
-	 */
-	vma = find_vma(mm, 0);
 	VM_BUG_ON(!(flags & (MPOL_MF_MOVE | MPOL_MF_MOVE_ALL)));
-	queue_pages_range(mm, vma->vm_start, mm->task_size, &nmask,
-			flags | MPOL_MF_DISCONTIG_OK, &pagelist, false);
+	vma = find_vma(mm, 0);
+
+	/*
+	 * This does not migrate the range, but isolates all pages that
+	 * need migration.  Between passing in the full user address
+	 * space range and MPOL_MF_DISCONTIG_OK, this call cannot fail,
+	 * but passes back the count of pages which could not be isolated.
+	 */
+	nr_failed = queue_pages_range(mm, vma->vm_start, mm->task_size, &nmask,
+				      flags | MPOL_MF_DISCONTIG_OK, &pagelist);
 
 	if (!list_empty(&pagelist)) {
 		err = migrate_pages(&pagelist, alloc_migration_target, NULL,
-				(unsigned long)&mtc, MIGRATE_SYNC, MR_SYSCALL, NULL);
+			(unsigned long)&mtc, MIGRATE_SYNC, MR_SYSCALL, NULL);
 		if (err)
 			putback_movable_pages(&pagelist);
 	}
 
+	if (err >= 0)
+		err += nr_failed;
 	return err;
 }
 
@@ -1109,8 +1095,8 @@  static int migrate_to_node(struct mm_struct *mm, int source, int dest,
 int do_migrate_pages(struct mm_struct *mm, const nodemask_t *from,
 		     const nodemask_t *to, int flags)
 {
-	int busy = 0;
-	int err = 0;
+	long nr_failed = 0;
+	long err = 0;
 	nodemask_t tmp;
 
 	lru_cache_disable();
@@ -1192,7 +1178,7 @@  int do_migrate_pages(struct mm_struct *mm, const nodemask_t *from,
 		node_clear(source, tmp);
 		err = migrate_to_node(mm, source, dest, flags);
 		if (err > 0)
-			busy += err;
+			nr_failed += err;
 		if (err < 0)
 			break;
 	}
@@ -1201,8 +1187,7 @@  int do_migrate_pages(struct mm_struct *mm, const nodemask_t *from,
 	lru_cache_enable();
 	if (err < 0)
 		return err;
-	return busy;
-
+	return (nr_failed < INT_MAX) ? nr_failed : INT_MAX;
 }
 
 /*
@@ -1241,10 +1226,10 @@  static struct folio *new_folio(struct folio *src, unsigned long start)
 }
 #else
 
-static int migrate_folio_add(struct folio *folio, struct list_head *foliolist,
+static bool migrate_folio_add(struct folio *folio, struct list_head *foliolist,
 				unsigned long flags)
 {
-	return -EIO;
+	return false;
 }
 
 int do_migrate_pages(struct mm_struct *mm, const nodemask_t *from,
@@ -1268,8 +1253,8 @@  static long do_mbind(unsigned long start, unsigned long len,
 	struct vma_iterator vmi;
 	struct mempolicy *new;
 	unsigned long end;
-	int err;
-	int ret;
+	long err;
+	long nr_failed;
 	LIST_HEAD(pagelist);
 
 	if (flags & ~(unsigned long)MPOL_MF_VALID)
@@ -1309,10 +1294,8 @@  static long do_mbind(unsigned long start, unsigned long len,
 		 start, start + len, mode, mode_flags,
 		 nmask ? nodes_addr(*nmask)[0] : NUMA_NO_NODE);
 
-	if (flags & (MPOL_MF_MOVE | MPOL_MF_MOVE_ALL)) {
-
+	if (flags & (MPOL_MF_MOVE | MPOL_MF_MOVE_ALL))
 		lru_cache_disable();
-	}
 	{
 		NODEMASK_SCRATCH(scratch);
 		if (scratch) {
@@ -1328,44 +1311,37 @@  static long do_mbind(unsigned long start, unsigned long len,
 		goto mpol_out;
 
 	/*
-	 * Lock the VMAs before scanning for pages to migrate, to ensure we don't
-	 * miss a concurrently inserted page.
+	 * Lock the VMAs before scanning for pages to migrate,
+	 * to ensure we don't miss a concurrently inserted page.
 	 */
-	ret = queue_pages_range(mm, start, end, nmask,
-			  flags | MPOL_MF_INVERT, &pagelist, true);
+	nr_failed = queue_pages_range(mm, start, end, nmask,
+			flags | MPOL_MF_INVERT | MPOL_MF_WRLOCK, &pagelist);
 
-	if (ret < 0) {
-		err = ret;
-		goto up_out;
-	}
-
-	vma_iter_init(&vmi, mm, start);
-	prev = vma_prev(&vmi);
-	for_each_vma_range(vmi, vma, end) {
-		err = mbind_range(&vmi, vma, &prev, start, end, new);
-		if (err)
-			break;
+	if (nr_failed < 0) {
+		err = nr_failed;
+	} else {
+		vma_iter_init(&vmi, mm, start);
+		prev = vma_prev(&vmi);
+		for_each_vma_range(vmi, vma, end) {
+			err = mbind_range(&vmi, vma, &prev, start, end, new);
+			if (err)
+				break;
+		}
 	}
 
 	if (!err) {
-		int nr_failed = 0;
-
 		if (!list_empty(&pagelist)) {
 			WARN_ON_ONCE(flags & MPOL_MF_LAZY);
-			nr_failed = migrate_pages(&pagelist, new_folio, NULL,
+			nr_failed |= migrate_pages(&pagelist, new_folio, NULL,
 				start, MIGRATE_SYNC, MR_MEMPOLICY_MBIND, NULL);
-			if (nr_failed)
-				putback_movable_pages(&pagelist);
 		}
-
-		if ((ret > 0) || (nr_failed && (flags & MPOL_MF_STRICT)))
+		if (nr_failed && (flags & MPOL_MF_STRICT))
 			err = -EIO;
-	} else {
-up_out:
-		if (!list_empty(&pagelist))
-			putback_movable_pages(&pagelist);
 	}
 
+	if (!list_empty(&pagelist))
+		putback_movable_pages(&pagelist);
+
 	mmap_write_unlock(mm);
 mpol_out:
 	mpol_put(new);