diff mbox series

[v1,3/3] memory: move exclusivity detection in do_wp_page() into wp_can_reuse_anon_folio()

Message ID 20231002142949.235104-4-david@redhat.com (mailing list archive)
State New
Headers show
Series mm/rmap: convert page_move_anon_rmap() to folio_move_anon_rmap() | expand

Commit Message

David Hildenbrand Oct. 2, 2023, 2:29 p.m. UTC
Let's clean up do_wp_page() a bit, removing two labels and making it
a easier to read.

wp_can_reuse_anon_folio() now only operates on the whole folio. Move the
SetPageAnonExclusive() out into do_wp_page(). No need to do this under
page lock -- the page table lock is sufficient.

Signed-off-by: David Hildenbrand <david@redhat.com>
---
 mm/memory.c | 88 +++++++++++++++++++++++++++--------------------------
 1 file changed, 45 insertions(+), 43 deletions(-)

Comments

Suren Baghdasaryan Oct. 3, 2023, 5:05 p.m. UTC | #1
On Mon, Oct 2, 2023 at 7:29 AM David Hildenbrand <david@redhat.com> wrote:
>
> Let's clean up do_wp_page() a bit, removing two labels and making it
> a easier to read.
>
> wp_can_reuse_anon_folio() now only operates on the whole folio. Move the
> SetPageAnonExclusive() out into do_wp_page(). No need to do this under
> page lock -- the page table lock is sufficient.
>
> Signed-off-by: David Hildenbrand <david@redhat.com>
> ---
>  mm/memory.c | 88 +++++++++++++++++++++++++++--------------------------
>  1 file changed, 45 insertions(+), 43 deletions(-)
>
> diff --git a/mm/memory.c b/mm/memory.c
> index 1f0e3317cbdd..512f6f05620e 100644
> --- a/mm/memory.c
> +++ b/mm/memory.c
> @@ -3358,6 +3358,44 @@ static vm_fault_t wp_page_shared(struct vm_fault *vmf, struct folio *folio)
>         return ret;
>  }
>
> +static bool wp_can_reuse_anon_folio(struct folio *folio,
> +                                   struct vm_area_struct *vma)

Since this function is calling folio_move_anon_rmap(), I would suggest
changing its name to wp_reuse_anon_folio(). This would clarify that
it's actually doing that operation instead of just checking if it's
possible. That would also let us keep unconditional
SetPageAnonExclusive() in it and do that under folio lock like it used
to do (keeping rules simple). Other than that, it looks good to me.

> +{
> +       /*
> +        * We have to verify under folio lock: these early checks are
> +        * just an optimization to avoid locking the folio and freeing
> +        * the swapcache if there is little hope that we can reuse.
> +        *
> +        * KSM doesn't necessarily raise the folio refcount.
> +        */
> +       if (folio_test_ksm(folio) || folio_ref_count(folio) > 3)
> +               return false;
> +       if (!folio_test_lru(folio))
> +               /*
> +                * We cannot easily detect+handle references from
> +                * remote LRU caches or references to LRU folios.
> +                */
> +               lru_add_drain();
> +       if (folio_ref_count(folio) > 1 + folio_test_swapcache(folio))
> +               return false;
> +       if (!folio_trylock(folio))
> +               return false;
> +       if (folio_test_swapcache(folio))
> +               folio_free_swap(folio);
> +       if (folio_test_ksm(folio) || folio_ref_count(folio) != 1) {
> +               folio_unlock(folio);
> +               return false;
> +       }
> +       /*
> +        * Ok, we've got the only folio reference from our mapping
> +        * and the folio is locked, it's dark out, and we're wearing
> +        * sunglasses. Hit it.
> +        */
> +       folio_move_anon_rmap(folio, vma);
> +       folio_unlock(folio);
> +       return true;
> +}
> +
>  /*
>   * This routine handles present pages, when
>   * * users try to write to a shared page (FAULT_FLAG_WRITE)
> @@ -3444,49 +3482,14 @@ static vm_fault_t do_wp_page(struct vm_fault *vmf)
>         /*
>          * Private mapping: create an exclusive anonymous page copy if reuse
>          * is impossible. We might miss VM_WRITE for FOLL_FORCE handling.
> +        *
> +        * If we encounter a page that is marked exclusive, we must reuse
> +        * the page without further checks.
>          */
> -       if (folio && folio_test_anon(folio)) {
> -               /*
> -                * If the page is exclusive to this process we must reuse the
> -                * page without further checks.
> -                */
> -               if (PageAnonExclusive(vmf->page))
> -                       goto reuse;
> -
> -               /*
> -                * We have to verify under folio lock: these early checks are
> -                * just an optimization to avoid locking the folio and freeing
> -                * the swapcache if there is little hope that we can reuse.
> -                *
> -                * KSM doesn't necessarily raise the folio refcount.
> -                */
> -               if (folio_test_ksm(folio) || folio_ref_count(folio) > 3)
> -                       goto copy;
> -               if (!folio_test_lru(folio))
> -                       /*
> -                        * We cannot easily detect+handle references from
> -                        * remote LRU caches or references to LRU folios.
> -                        */
> -                       lru_add_drain();
> -               if (folio_ref_count(folio) > 1 + folio_test_swapcache(folio))
> -                       goto copy;
> -               if (!folio_trylock(folio))
> -                       goto copy;
> -               if (folio_test_swapcache(folio))
> -                       folio_free_swap(folio);
> -               if (folio_test_ksm(folio) || folio_ref_count(folio) != 1) {
> -                       folio_unlock(folio);
> -                       goto copy;
> -               }
> -               /*
> -                * Ok, we've got the only folio reference from our mapping
> -                * and the folio is locked, it's dark out, and we're wearing
> -                * sunglasses. Hit it.
> -                */
> -               folio_move_anon_rmap(folio, vma);
> -               SetPageAnonExclusive(vmf->page);
> -               folio_unlock(folio);
> -reuse:
> +       if (folio && folio_test_anon(folio) &&
> +           (PageAnonExclusive(vmf->page) || wp_can_reuse_anon_folio(folio, vma))) {
> +               if (!PageAnonExclusive(vmf->page))
> +                       SetPageAnonExclusive(vmf->page);
>                 if (unlikely(unshare)) {
>                         pte_unmap_unlock(vmf->pte, vmf->ptl);
>                         return 0;
> @@ -3494,7 +3497,6 @@ static vm_fault_t do_wp_page(struct vm_fault *vmf)
>                 wp_page_reuse(vmf);
>                 return 0;
>         }
> -copy:
>         /*
>          * Ok, we need to copy. Oh, well..
>          */
> --
> 2.41.0
>
David Hildenbrand Oct. 9, 2023, 10:03 a.m. UTC | #2
On 03.10.23 19:05, Suren Baghdasaryan wrote:
> On Mon, Oct 2, 2023 at 7:29 AM David Hildenbrand <david@redhat.com> wrote:
>>
>> Let's clean up do_wp_page() a bit, removing two labels and making it
>> a easier to read.
>>
>> wp_can_reuse_anon_folio() now only operates on the whole folio. Move the
>> SetPageAnonExclusive() out into do_wp_page(). No need to do this under
>> page lock -- the page table lock is sufficient.
>>
>> Signed-off-by: David Hildenbrand <david@redhat.com>
>> ---
>>   mm/memory.c | 88 +++++++++++++++++++++++++++--------------------------
>>   1 file changed, 45 insertions(+), 43 deletions(-)
>>
>> diff --git a/mm/memory.c b/mm/memory.c
>> index 1f0e3317cbdd..512f6f05620e 100644
>> --- a/mm/memory.c
>> +++ b/mm/memory.c
>> @@ -3358,6 +3358,44 @@ static vm_fault_t wp_page_shared(struct vm_fault *vmf, struct folio *folio)
>>          return ret;
>>   }
>>

Sorry for the late response.

>> +static bool wp_can_reuse_anon_folio(struct folio *folio,
>> +                                   struct vm_area_struct *vma)
> 
> Since this function is calling folio_move_anon_rmap(), I would suggest
> changing its name to wp_reuse_anon_folio(). This would clarify that

folio_move_anon_rmap() is *not* the reuse part, it's just an rmap 
optimization. You could remove the call here and the whole thing would 
still work :) In fact, we can call folio_move_anon_rmap() whenever we 
sure the folio belongs to a single VMA only and we're holding the page 
lock. ... but we cannot always reuse a folio in that case because there 
might be GUP references from another process.

Reuse is
* Setting PageAnonExclusive
* Write fault: wunprotect the page -> wp_page_reuse()

I went a bit back and forth while cleaning that function up, but calling 
it wp_reuse_anon_folio() would end up being confusing with 
wp_page_reuse() called afterwards [we should probably rename that one to 
wp_page_wunprotect() independently]. So I prefer to leave the actual 
(sub)page reuse part in the caller.

> it's actually doing that operation instead of just checking if it's
> possible. That would also let us keep unconditional
> SetPageAnonExclusive() in it and do that under folio lock like it used
> to do (keeping rules simple). Other than that, it looks good to me.

I really want to avoid passing a "struct page" to that function; once 
we're dealing with PTE-mapped THP, the page might actually be a tail 
page of the folio.

Thanks!
Suren Baghdasaryan Oct. 9, 2023, 4:38 p.m. UTC | #3
On Mon, Oct 9, 2023 at 3:03 AM David Hildenbrand <david@redhat.com> wrote:
>
> On 03.10.23 19:05, Suren Baghdasaryan wrote:
> > On Mon, Oct 2, 2023 at 7:29 AM David Hildenbrand <david@redhat.com> wrote:
> >>
> >> Let's clean up do_wp_page() a bit, removing two labels and making it
> >> a easier to read.
> >>
> >> wp_can_reuse_anon_folio() now only operates on the whole folio. Move the
> >> SetPageAnonExclusive() out into do_wp_page(). No need to do this under
> >> page lock -- the page table lock is sufficient.
> >>
> >> Signed-off-by: David Hildenbrand <david@redhat.com>
> >> ---
> >>   mm/memory.c | 88 +++++++++++++++++++++++++++--------------------------
> >>   1 file changed, 45 insertions(+), 43 deletions(-)
> >>
> >> diff --git a/mm/memory.c b/mm/memory.c
> >> index 1f0e3317cbdd..512f6f05620e 100644
> >> --- a/mm/memory.c
> >> +++ b/mm/memory.c
> >> @@ -3358,6 +3358,44 @@ static vm_fault_t wp_page_shared(struct vm_fault *vmf, struct folio *folio)
> >>          return ret;
> >>   }
> >>
>
> Sorry for the late response.
>
> >> +static bool wp_can_reuse_anon_folio(struct folio *folio,
> >> +                                   struct vm_area_struct *vma)
> >
> > Since this function is calling folio_move_anon_rmap(), I would suggest
> > changing its name to wp_reuse_anon_folio(). This would clarify that
>
> folio_move_anon_rmap() is *not* the reuse part, it's just an rmap
> optimization. You could remove the call here and the whole thing would
> still work :) In fact, we can call folio_move_anon_rmap() whenever we
> sure the folio belongs to a single VMA only and we're holding the page
> lock. ... but we cannot always reuse a folio in that case because there
> might be GUP references from another process.
>
> Reuse is
> * Setting PageAnonExclusive
> * Write fault: wunprotect the page -> wp_page_reuse()

Ok, fair enough. It's not the reuse, only a preparation step. My
concern is that wp_can_reuse_anon_folio() with a bool being returned
looks like a function that only checks for a possibility of an
operation while it's doing more than that. However I don't have a
really good suggestion to improve the naming, so treat it as a nitpick
and feel free to ignore.

>
> I went a bit back and forth while cleaning that function up, but calling
> it wp_reuse_anon_folio() would end up being confusing with
> wp_page_reuse() called afterwards [we should probably rename that one to
> wp_page_wunprotect() independently]. So I prefer to leave the actual
> (sub)page reuse part in the caller.
>
> > it's actually doing that operation instead of just checking if it's
> > possible. That would also let us keep unconditional
> > SetPageAnonExclusive() in it and do that under folio lock like it used
> > to do (keeping rules simple). Other than that, it looks good to me.
>
> I really want to avoid passing a "struct page" to that function; once
> we're dealing with PTE-mapped THP, the page might actually be a tail
> page of the folio.

Oh, I didn't realize that vmf->page and folio->page might differ in
here. Ok, sounds reasonable.
Thanks,
Suren.

>
> Thanks!
>
> --
> Cheers,
>
> David / dhildenb
>
diff mbox series

Patch

diff --git a/mm/memory.c b/mm/memory.c
index 1f0e3317cbdd..512f6f05620e 100644
--- a/mm/memory.c
+++ b/mm/memory.c
@@ -3358,6 +3358,44 @@  static vm_fault_t wp_page_shared(struct vm_fault *vmf, struct folio *folio)
 	return ret;
 }
 
+static bool wp_can_reuse_anon_folio(struct folio *folio,
+				    struct vm_area_struct *vma)
+{
+	/*
+	 * We have to verify under folio lock: these early checks are
+	 * just an optimization to avoid locking the folio and freeing
+	 * the swapcache if there is little hope that we can reuse.
+	 *
+	 * KSM doesn't necessarily raise the folio refcount.
+	 */
+	if (folio_test_ksm(folio) || folio_ref_count(folio) > 3)
+		return false;
+	if (!folio_test_lru(folio))
+		/*
+		 * We cannot easily detect+handle references from
+		 * remote LRU caches or references to LRU folios.
+		 */
+		lru_add_drain();
+	if (folio_ref_count(folio) > 1 + folio_test_swapcache(folio))
+		return false;
+	if (!folio_trylock(folio))
+		return false;
+	if (folio_test_swapcache(folio))
+		folio_free_swap(folio);
+	if (folio_test_ksm(folio) || folio_ref_count(folio) != 1) {
+		folio_unlock(folio);
+		return false;
+	}
+	/*
+	 * Ok, we've got the only folio reference from our mapping
+	 * and the folio is locked, it's dark out, and we're wearing
+	 * sunglasses. Hit it.
+	 */
+	folio_move_anon_rmap(folio, vma);
+	folio_unlock(folio);
+	return true;
+}
+
 /*
  * This routine handles present pages, when
  * * users try to write to a shared page (FAULT_FLAG_WRITE)
@@ -3444,49 +3482,14 @@  static vm_fault_t do_wp_page(struct vm_fault *vmf)
 	/*
 	 * Private mapping: create an exclusive anonymous page copy if reuse
 	 * is impossible. We might miss VM_WRITE for FOLL_FORCE handling.
+	 *
+	 * If we encounter a page that is marked exclusive, we must reuse
+	 * the page without further checks.
 	 */
-	if (folio && folio_test_anon(folio)) {
-		/*
-		 * If the page is exclusive to this process we must reuse the
-		 * page without further checks.
-		 */
-		if (PageAnonExclusive(vmf->page))
-			goto reuse;
-
-		/*
-		 * We have to verify under folio lock: these early checks are
-		 * just an optimization to avoid locking the folio and freeing
-		 * the swapcache if there is little hope that we can reuse.
-		 *
-		 * KSM doesn't necessarily raise the folio refcount.
-		 */
-		if (folio_test_ksm(folio) || folio_ref_count(folio) > 3)
-			goto copy;
-		if (!folio_test_lru(folio))
-			/*
-			 * We cannot easily detect+handle references from
-			 * remote LRU caches or references to LRU folios.
-			 */
-			lru_add_drain();
-		if (folio_ref_count(folio) > 1 + folio_test_swapcache(folio))
-			goto copy;
-		if (!folio_trylock(folio))
-			goto copy;
-		if (folio_test_swapcache(folio))
-			folio_free_swap(folio);
-		if (folio_test_ksm(folio) || folio_ref_count(folio) != 1) {
-			folio_unlock(folio);
-			goto copy;
-		}
-		/*
-		 * Ok, we've got the only folio reference from our mapping
-		 * and the folio is locked, it's dark out, and we're wearing
-		 * sunglasses. Hit it.
-		 */
-		folio_move_anon_rmap(folio, vma);
-		SetPageAnonExclusive(vmf->page);
-		folio_unlock(folio);
-reuse:
+	if (folio && folio_test_anon(folio) &&
+	    (PageAnonExclusive(vmf->page) || wp_can_reuse_anon_folio(folio, vma))) {
+		if (!PageAnonExclusive(vmf->page))
+			SetPageAnonExclusive(vmf->page);
 		if (unlikely(unshare)) {
 			pte_unmap_unlock(vmf->pte, vmf->ptl);
 			return 0;
@@ -3494,7 +3497,6 @@  static vm_fault_t do_wp_page(struct vm_fault *vmf)
 		wp_page_reuse(vmf);
 		return 0;
 	}
-copy:
 	/*
 	 * Ok, we need to copy. Oh, well..
 	 */