Skip to contentSkip to conversation

mm: tweak page cache migration

RFC PATCH In review Dave Hansen 2 participants 4 messages May 1, 2020 – May 5, 2020

Subsystems (2)
lore
Share & tools
lore
#0 lore raw

cover mm: tweak page cache migration

First of all, I think this little slice of code is a bit under-documented. Perhaps this will help clarify things.

I'm pretty confident the page_count() check in the first patch is right, which is why I removed it outright. The xas_load() check is a bit murkier, so I just left a warning in for it.

Cc Nicholas Piggin <[email protected]>
Cc Andrew Morton <[email protected]>
Cc Matthew Wilcox (Oracle) <[email protected]>
Cc Yang Shi <[email protected]>
#1 lore raw

RFC PATCH 1/2 mm/migrate: remove extra page_count() check

From: Dave Hansen <[email protected]>

This is not a bug fix. It was found by inspection, but I believe that it is confusing as it stands.

First, page_ref_freeze() is implemented internally with:

	atomic_cmpxchg(&page->_refcount, expected, 0) == expected

The "cmp" part of cmpxchg is making sure that _refcount==expected which means that there's an implicit check here, equivalent to:

	page_count(page) == expected_count

This appears to have originated in "e286781: mm: speculative page references", which is pretty ancient. This check is also somewhat dangerous to have here because it might lead someone to think that page_ref_freeze() *doesn't* do its own page_count() checking.

Remove the unnecessary check.

Cc Nicholas Piggin <[email protected]>
Cc Andrew Morton <[email protected]>
Cc Matthew Wilcox (Oracle) <[email protected]>
Cc Yang Shi <[email protected]>

---

 b/mm/migrate.c |    3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)
mm/migrate.c renamed from mm/migrate.c~remove_extra_page_count_check +2 -1
diff -puN mm/migrate.c~remove_extra_page_count_check mm/migrate.c
@@ -425,11 +425,12 @@ int migrate_page_move_mapping(struct add
 	newzone = page_zone(newpage);
 
 	xas_lock_irq(&xas);
-	if (page_count(page) != expected_count || xas_load(&xas) != page) {
+	if (xas_load(&xas) != page) {
 		xas_unlock_irq(&xas);
 		return -EAGAIN;
 	}
 
+	/* Freezing will fail if page_count()!=expected_count */
 	if (!page_ref_freeze(page, expected_count)) {
 		xas_unlock_irq(&xas);
 		return -EAGAIN;

_

#2 lore raw

Re: [RFC][PATCH 1/2] mm/migrate: remove extra page_count() check

Quoted 12 lines · Dave Hansen

From: Dave Hansen <[email protected]>

This is not a bug fix. It was found by inspection, but I believe that it is confusing as it stands.

First, page_ref_freeze() is implemented internally with:

	atomic_cmpxchg(&page->_refcount, expected, 0) == expected

The "cmp" part of cmpxchg is making sure that _refcount==expected which means that there's an implicit check here, equivalent to:

Quoted 8 lines
	page_count(page) == expected_count

This appears to have originated in "e286781: mm: speculative page references", which is pretty ancient. This check is also somewhat dangerous to have here because it might lead someone to think that page_ref_freeze() *doesn't* do its own page_count() checking.

Remove the unnecessary check.

Make sense to me. Acked-by: Yang Shi <[email protected]>

Quoted 31 lines
Cc Nicholas Piggin <[email protected]>
Cc Andrew Morton <[email protected]>
Cc Matthew Wilcox (Oracle) <[email protected]>
Cc Yang Shi <[email protected]>

---

  b/mm/migrate.c |    3 ++-
  1 file changed, 2 insertions(+), 1 deletion(-)

diff -puN mm/migrate.c~remove_extra_page_count_check mm/migrate.c

mm/migrate.c renamed from mm/migrate.c~remove_extra_page_count_check +2 -1
@@ -425,11 +425,12 @@ int migrate_page_move_mapping(struct add
  	newzone = page_zone(newpage);
 
  	xas_lock_irq(&xas);
-	if (page_count(page) != expected_count || xas_load(&xas) != page) {
+	if (xas_load(&xas) != page) {
  		xas_unlock_irq(&xas);
  		return -EAGAIN;
  	}
 
+	/* Freezing will fail if page_count()!=expected_count */
  	if (!page_ref_freeze(page, expected_count)) {
  		xas_unlock_irq(&xas);
  		return -EAGAIN;

_

#3 lore raw

RFC PATCH 2/2 mm/migrate: annotate possible unnecessary xas_load()

In reply to Dave Hansen

From: Dave Hansen <[email protected]>

The xas_load() in question also originated in "e286781: mm:
speculative page references" as a radix_tree_deref_slot(), the only one in the tree at the time.

I'm thoroughly confused why it is needed, though. A page's slot in the page cache should be stabilized by lock_page() being held.

So, first of all, add a VM_BUG_ON_ONCE() to make it totally clear that the page is locked.

But, even if the page was truncated, we normally check:

	page_mapping(page) != mapping

to check for truncation. This would seem to imply that we are looking for some kind of state change that can happen to the xarray slot for a page, but without changing page->mapping.

I'm at a loss for that that might be. Stick a WARN_ON_ONCE() in there to see if we ever actually hit this.

Cc Nicholas Piggin <[email protected]>
Cc Andrew Morton <[email protected]>
Cc Matthew Wilcox (Oracle) <[email protected]>
Cc Yang Shi <[email protected]>

---

 b/mm/migrate.c |    8 ++++++++
 1 file changed, 8 insertions(+)
mm/migrate.c renamed from mm/migrate.c~remove_extra_xas_load_check +8 -0
diff -puN mm/migrate.c~remove_extra_xas_load_check mm/migrate.c
@@ -407,6 +407,8 @@ int migrate_page_move_mapping(struct add
 	int dirty;
 	int expected_count = expected_page_refs(mapping, page) + extra_count;
 
+	VM_WARN_ONCE(!PageLocked(page));
+
 	if (!mapping) {
 		/* Anonymous page without mapping */
 		if (page_count(page) != expected_count)
@@ -425,7 +427,13 @@ int migrate_page_move_mapping(struct add
 	newzone = page_zone(newpage);
 
 	xas_lock_irq(&xas);
+	/*
+	 * 'mapping' was established under the page lock, which
+	 * prevents the xarray slot for 'page' from being changed.
+	 * Thus, xas_load() failure here is unexpected.
+	 */
 	if (xas_load(&xas) != page) {
+		WARN_ON_ONCE(1);
 		xas_unlock_irq(&xas);
 		return -EAGAIN;
 	}

_