Message ID | 20240125193227.444072-15-david@redhat.com (mailing list archive) |
---|---|
State | New |
Headers | show |
Series | mm/memory: optimize fork() with PTE-mapped THP | expand |
On 25/01/2024 19:32, David Hildenbrand wrote: > Let's always ignore the accessed/young bit: we'll always mark the PTE > as old in our child process during fork, and upcoming users will > similarly not care. > > Ignore the dirty bit only if we don't want to duplicate the dirty bit > into the child process during fork. Maybe, we could just set all PTEs > in the child dirty if any PTE is dirty. For now, let's keep the behavior > unchanged, this can be optimized later if required. > > Ignore the soft-dirty bit only if the bit doesn't have any meaning in > the src vma, and similarly won't have any in the copied dst vma. > > For now, we won't bother with the uffd-wp bit. > > Signed-off-by: David Hildenbrand <david@redhat.com> Reviewed-by: Ryan Roberts <ryan.roberts@arm.com> > --- > mm/memory.c | 36 +++++++++++++++++++++++++++++++----- > 1 file changed, 31 insertions(+), 5 deletions(-) > > diff --git a/mm/memory.c b/mm/memory.c > index 4d1be89a01ee0..b3f035fe54c8d 100644 > --- a/mm/memory.c > +++ b/mm/memory.c > @@ -953,24 +953,44 @@ static __always_inline void __copy_present_ptes(struct vm_area_struct *dst_vma, > set_ptes(dst_vma->vm_mm, addr, dst_pte, pte, nr); > } > > +/* Flags for folio_pte_batch(). */ > +typedef int __bitwise fpb_t; > + > +/* Compare PTEs after pte_mkclean(), ignoring the dirty bit. */ > +#define FPB_IGNORE_DIRTY ((__force fpb_t)BIT(0)) > + > +/* Compare PTEs after pte_clear_soft_dirty(), ignoring the soft-dirty bit. */ > +#define FPB_IGNORE_SOFT_DIRTY ((__force fpb_t)BIT(1)) > + > +static inline pte_t __pte_batch_clear_ignored(pte_t pte, fpb_t flags) > +{ > + if (flags & FPB_IGNORE_DIRTY) > + pte = pte_mkclean(pte); > + if (likely(flags & FPB_IGNORE_SOFT_DIRTY)) > + pte = pte_clear_soft_dirty(pte); > + return pte_mkold(pte); > +} > + > /* > * Detect a PTE batch: consecutive (present) PTEs that map consecutive > * pages of the same folio. > * > - * All PTEs inside a PTE batch have the same PTE bits set, excluding the PFN. > + * All PTEs inside a PTE batch have the same PTE bits set, excluding the PFN, > + * the accessed bit, dirty bit (with FPB_IGNORE_DIRTY) and soft-dirty bit > + * (with FPB_IGNORE_SOFT_DIRTY). > */ > static inline int folio_pte_batch(struct folio *folio, unsigned long addr, > - pte_t *start_ptep, pte_t pte, int max_nr) > + pte_t *start_ptep, pte_t pte, int max_nr, fpb_t flags) > { > unsigned long folio_end_pfn = folio_pfn(folio) + folio_nr_pages(folio); > const pte_t *end_ptep = start_ptep + max_nr; > - pte_t expected_pte = pte_next_pfn(pte); > + pte_t expected_pte = __pte_batch_clear_ignored(pte_next_pfn(pte), flags); > pte_t *ptep = start_ptep + 1; > > VM_WARN_ON_FOLIO(!pte_present(pte), folio); > > while (ptep != end_ptep) { > - pte = ptep_get(ptep); > + pte = __pte_batch_clear_ignored(ptep_get(ptep), flags); > > if (!pte_same(pte, expected_pte)) > break; > @@ -1004,6 +1024,7 @@ copy_present_ptes(struct vm_area_struct *dst_vma, struct vm_area_struct *src_vma > { > struct page *page; > struct folio *folio; > + fpb_t flags = 0; > int err, nr; > > page = vm_normal_page(src_vma, addr, pte); > @@ -1018,7 +1039,12 @@ copy_present_ptes(struct vm_area_struct *dst_vma, struct vm_area_struct *src_vma > * by keeping the batching logic separate. > */ > if (unlikely(!*prealloc && folio_test_large(folio) && max_nr != 1)) { > - nr = folio_pte_batch(folio, addr, src_pte, pte, max_nr); > + if (src_vma->vm_flags & VM_SHARED) > + flags |= FPB_IGNORE_DIRTY; > + if (!vma_soft_dirty_enabled(src_vma)) > + flags |= FPB_IGNORE_SOFT_DIRTY; > + > + nr = folio_pte_batch(folio, addr, src_pte, pte, max_nr, flags); > folio_ref_add(folio, nr); > if (folio_test_anon(folio)) { > if (unlikely(folio_try_dup_anon_rmap_ptes(folio, page,
diff --git a/mm/memory.c b/mm/memory.c index 4d1be89a01ee0..b3f035fe54c8d 100644 --- a/mm/memory.c +++ b/mm/memory.c @@ -953,24 +953,44 @@ static __always_inline void __copy_present_ptes(struct vm_area_struct *dst_vma, set_ptes(dst_vma->vm_mm, addr, dst_pte, pte, nr); } +/* Flags for folio_pte_batch(). */ +typedef int __bitwise fpb_t; + +/* Compare PTEs after pte_mkclean(), ignoring the dirty bit. */ +#define FPB_IGNORE_DIRTY ((__force fpb_t)BIT(0)) + +/* Compare PTEs after pte_clear_soft_dirty(), ignoring the soft-dirty bit. */ +#define FPB_IGNORE_SOFT_DIRTY ((__force fpb_t)BIT(1)) + +static inline pte_t __pte_batch_clear_ignored(pte_t pte, fpb_t flags) +{ + if (flags & FPB_IGNORE_DIRTY) + pte = pte_mkclean(pte); + if (likely(flags & FPB_IGNORE_SOFT_DIRTY)) + pte = pte_clear_soft_dirty(pte); + return pte_mkold(pte); +} + /* * Detect a PTE batch: consecutive (present) PTEs that map consecutive * pages of the same folio. * - * All PTEs inside a PTE batch have the same PTE bits set, excluding the PFN. + * All PTEs inside a PTE batch have the same PTE bits set, excluding the PFN, + * the accessed bit, dirty bit (with FPB_IGNORE_DIRTY) and soft-dirty bit + * (with FPB_IGNORE_SOFT_DIRTY). */ static inline int folio_pte_batch(struct folio *folio, unsigned long addr, - pte_t *start_ptep, pte_t pte, int max_nr) + pte_t *start_ptep, pte_t pte, int max_nr, fpb_t flags) { unsigned long folio_end_pfn = folio_pfn(folio) + folio_nr_pages(folio); const pte_t *end_ptep = start_ptep + max_nr; - pte_t expected_pte = pte_next_pfn(pte); + pte_t expected_pte = __pte_batch_clear_ignored(pte_next_pfn(pte), flags); pte_t *ptep = start_ptep + 1; VM_WARN_ON_FOLIO(!pte_present(pte), folio); while (ptep != end_ptep) { - pte = ptep_get(ptep); + pte = __pte_batch_clear_ignored(ptep_get(ptep), flags); if (!pte_same(pte, expected_pte)) break; @@ -1004,6 +1024,7 @@ copy_present_ptes(struct vm_area_struct *dst_vma, struct vm_area_struct *src_vma { struct page *page; struct folio *folio; + fpb_t flags = 0; int err, nr; page = vm_normal_page(src_vma, addr, pte); @@ -1018,7 +1039,12 @@ copy_present_ptes(struct vm_area_struct *dst_vma, struct vm_area_struct *src_vma * by keeping the batching logic separate. */ if (unlikely(!*prealloc && folio_test_large(folio) && max_nr != 1)) { - nr = folio_pte_batch(folio, addr, src_pte, pte, max_nr); + if (src_vma->vm_flags & VM_SHARED) + flags |= FPB_IGNORE_DIRTY; + if (!vma_soft_dirty_enabled(src_vma)) + flags |= FPB_IGNORE_SOFT_DIRTY; + + nr = folio_pte_batch(folio, addr, src_pte, pte, max_nr, flags); folio_ref_add(folio, nr); if (folio_test_anon(folio)) { if (unlikely(folio_try_dup_anon_rmap_ptes(folio, page,
Let's always ignore the accessed/young bit: we'll always mark the PTE as old in our child process during fork, and upcoming users will similarly not care. Ignore the dirty bit only if we don't want to duplicate the dirty bit into the child process during fork. Maybe, we could just set all PTEs in the child dirty if any PTE is dirty. For now, let's keep the behavior unchanged, this can be optimized later if required. Ignore the soft-dirty bit only if the bit doesn't have any meaning in the src vma, and similarly won't have any in the copied dst vma. For now, we won't bother with the uffd-wp bit. Signed-off-by: David Hildenbrand <david@redhat.com> --- mm/memory.c | 36 +++++++++++++++++++++++++++++++----- 1 file changed, 31 insertions(+), 5 deletions(-)