mm: protect free_pgtables with mmap_lock write lock in exit_mmap
Subsystems (4)
Suren Baghdasaryan
reviewerPATCH v5 1/3 mm: protect free_pgtables with mmap_lock write lock in exit_mmap
oom-reaper and process_mrelease system call should protect against races with exit_mmap which can destroy page tables while they walk the VMA tree. oom-reaper protects from that race by setting MMF_OOM_VICTIM and by relying on exit_mmap to set MMF_OOM_SKIP before taking and releasing mmap_write_lock. process_mrelease has to elevate mm->mm_users to prevent such race. Both oom-reaper and process_mrelease hold mmap_read_lock when walking the VMA tree. The locking rules and mechanisms could be simpler if exit_mmap takes mmap_write_lock while executing destructive operations such as free_pgtables.
Change exit_mmap to hold the mmap_write_lock when calling unlock_range, free_pgtables and remove_vma. Note also that because oom-reaper checks VM_LOCKED flag, unlock_range() should not be allowed to race with it. Before this patch, remove_vma used to be called with no locks held, however with fput being executed asynchronously and vm_ops->close not being allowed to hold mmap_lock (it is called from __split_vma with mmap_sem held for write), changing that should be fine. In most cases this lock should be uncontended. Previously, Kirill reported ~4% regression caused by a similar change [1]. We reran the same test and although the individual results are quite noisy, the percentiles show lower regression with 1.6% being the worst case [2]. The change allows oom-reaper and process_mrelease to execute safely under mmap_read_lock without worries that exit_mmap might destroy page tables from under them.
[1] https://lore.kernel.org/all/[email protected]/
[2] https://lore.kernel.org/all/CAJuCfpGC9-c9P40x7oy=jy5SphMcd0o0G_6U1-+JAziGKG6dGA@mail.gmail.com/
---
changes in v5
- Corrected patch description, per Michal Hocko
- Added Acked-by
mm/mmap.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-)
mm/mmap.c +8 -8
@@ -3142,25 +3142,27 @@ void exit_mmap(struct mm_struct *mm)* to mmu_notifier_release(mm) ensures mmu notifier callbacks in * __oom_reap_task_mm() will not block. * -* This needs to be done before calling munlock_vma_pages_all(), +* This needs to be done before calling unlock_range(), * which clears VM_LOCKED, otherwise the oom reaper cannot * reliably test it. */ (void )__oom_reap_task_mm (mm);set_bit (MMF_OOM_SKIP , &mm->flags); -mmap_write_lock (mm); -mmap_write_unlock (mm); } +mmap_write_lock (mm);if (mm->locked_vm)unlock_range (mm->mmap,ULONG_MAX );arch_exit_mmap (mm); vma = mm->mmap; -if (!vma)/* Can happen if dup_mmap() received an OOM */ +if (!vma) { +/* Can happen if dup_mmap() received an OOM */ +mmap_write_unlock (mm);return ; + }lru_add_drain ();flush_cache_mm (mm);
@@ -3171,16 +3173,14 @@ void exit_mmap(struct mm_struct *mm)free_pgtables (&tlb, vma,FIRST_USER_ADDRESS ,USER_PGTABLES_CEILING );tlb_finish_mmu (&tlb); -/* -* Walk the list again, actually closing and freeing it, -* with preemption enabled, without holding any MM locks. -*/ +/* Walk the list again, actually closing and freeing it. */ while (vma) {if (vma->vm_flags &VM_ACCOUNT ) nr_accounted +=vma_pages (vma); vma =remove_vma (vma);cond_resched (); } +mmap_write_unlock (mm);vm_unacct_memory (nr_accounted); }
Signature · 1 line
2.34.1.173.g76aa8bc2d0-goog
Suren Baghdasaryan
reviewerPATCH v5 2/3 mm: document locking restrictions for vm_operations_struct::close
Add comments for vm_operations_struct::close documenting locking requirements for this callback and its callers.
---
changes in v5
- Added Acked-by
include/linux/mm.h | 4 ++++ 1 file changed, 4 insertions(+)
include/linux/mm.h +4 -0
@@ -577,6 +577,10 @@ enum page_entry_size {*/ struct vm_operations_struct {void (*open)(struct vm_area_struct * area); +/** +* @close: Called when the VMA is being removed from the MM. +* Context: User context. May sleep. Caller holds mmap_lock. +*/ void (*close)(struct vm_area_struct * area);/* Called any time before splitting to check if it's allowed */ int (*may_split)(struct vm_area_struct *area,unsigned long addr);
Signature · 1 line
2.34.1.173.g76aa8bc2d0-goog
Suren Baghdasaryan
reviewerPATCH v5 3/3 mm/oom_kill: allow process_mrelease to run under mmap_lock protection
In reply to Suren BaghdasaryanWith exit_mmap holding mmap_write_lock during free_pgtables call, process_mrelease does not need to elevate mm->mm_users in order to prevent exit_mmap from destrying pagetables while __oom_reap_task_mm is walking the VMA tree. The change prevents process_mrelease from calling the last mmput, which can lead to waiting for IO completion in exit_aio.
---
changes in v5
- Removed Fixes: tag, per Michal Hocko
- Added Acked-by's
mm/oom_kill.c | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-)
mm/oom_kill.c +15 -12
@@ -1169,15 +1169,15 @@ SYSCALL_DEFINE2(process_mrelease, int, pidfd, unsigned int, flags) goto put_task; } -if (mmget_not_zero (p->mm)) { - mm = p->mm; -if (task_will_free_mem (p)) - reap = true; -else { -/* Error only if the work has not been done already */ -if (!test_bit (MMF_OOM_SKIP , &mm->flags)) - ret = -EINVAL ; - } + mm = p->mm; +mmgrab (mm); + +if (task_will_free_mem (p)) + reap = true; +else { +/* Error only if the work has not been done already */ +if (!test_bit (MMF_OOM_SKIP , &mm->flags)) + ret = -EINVAL ; }task_unlock (p);
@@ -1188,13 +1188,16 @@ SYSCALL_DEFINE2(process_mrelease, int, pidfd, unsigned int, flags) ret = -EINTR ; goto drop_mm; } -if (!__oom_reap_task_mm (mm)) +/* +* Check MMF_OOM_SKIP again under mmap_read_lock protection to ensure +* possible change in exit_mmap is seen +*/ +if (!test_bit (MMF_OOM_SKIP , &mm->flags) && !__oom_reap_task_mm (mm)) ret = -EAGAIN ;mmap_read_unlock (mm); drop_mm: -if (mm) -mmput (mm); +mmdrop (mm); put_task:put_task_struct (task);return ret;
Signature · 1 line
2.34.1.173.g76aa8bc2d0-goog
Re: [PATCH v5 3/3] mm/oom_kill: allow process_mrelease to run under mmap_lock protection
Quoted 9 lines · Suren Baghdasaryan
With
exit_mmapholdingmmap_write_lockduringfree_pgtablescall,process_mreleasedoes not need to elevatemm->mm_usersin order to preventexit_mmapfrom destrying pagetables while__oom_reap_task_mmis walking the VMA tree. The change preventsprocess_mreleasefrom calling the last mmput, which can lead to waiting for IO completion inexit_aio.Signed-off-by Suren Baghdasaryan <[email protected]>Acked-by Michal Hocko <[email protected]>
Quoted 7 lines
---
changes in v5
- Removed Fixes: tag, per Michal Hocko
- Added Acked-by'smm/oom_kill.c | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-)
There are mmget_not_zero's all over the place, can others be cleaned after this series goes ahead too?
It seems like anything doing the mmget just to look at the vma list under the mmap lock is now fine with only a mmgrab?
A few I know about:
drivers/infiniband/core/umem_odp.c: if (!mmget_not_zero(umem->owning_mm)) {
This is because mmu_interval_notifier_insert() might call mm_take_all_locks() which was unsafe with concurrent exit_mmap
drivers/infiniband/core/umem_odp.c: if (!owning_process || !mmget_not_zero(owning_mm)) {
This is because it calls hmm_range_fault() which iterates over the vma list which is safe now
drivers/iommu/iommu-sva-lib.c: return mmget_not_zero(mm); drivers/iommu/iommu-sva-lib.c: return ioasid_find(&iommu_sva_pasid, pasid, __mmget_not_zero);
It calls find_extend_vma() - but also it doesn't seem to have a mmgrab when it does that mmget. The rcu is messed up here too, so humm.
Jason
Suren Baghdasaryan
reviewerRe: [PATCH v5 3/3] mm/oom_kill: allow process_mrelease to run under mmap_lock protection
Quoted 16 lines · Jason Gunthorpe
Quoted 14 lines · Suren Baghdasaryan
With
exit_mmapholdingmmap_write_lockduringfree_pgtablescall,process_mreleasedoes not need to elevatemm->mm_usersin order to preventexit_mmapfrom destrying pagetables while__oom_reap_task_mmis walking the VMA tree. The change preventsprocess_mreleasefrom calling the last mmput, which can lead to waiting for IO completion inexit_aio.Signed-off-by Suren Baghdasaryan <[email protected]>Acked-by Michal Hocko <[email protected]>---
changes in v5
- Removed Fixes: tag, per Michal Hocko
- Added Acked-by's
Quoted 4 lines
Quoted 2 lines
mm/oom_kill.c | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-)Reviewed-by Jason Gunthorpe <[email protected]>
Thanks!
Quoted 6 lines
There are mmget_not_zero's all over the place, can others be cleaned after this series goes ahead too?
It seems like anything doing the mmget just to look at the vma list under the mmap lock is now fine with only a mmgrab?
Sounds reasonable to me. I'll try to carve out some time to look into it but no promises. Lots of loose ends to tie at the end of the year :(
Quoted 20 lines
A few I know about:
drivers/infiniband/core/umem_odp.c: if (!mmget_not_zero(umem->owning_mm)) {This is because
mmu_interval_notifier_insert()might callmm_take_all_locks()which was unsafe with concurrentexit_mmap
drivers/infiniband/core/umem_odp.c: if (!owning_process || !mmget_not_zero(owning_mm)) {This is because it calls
hmm_range_fault()which iterates over the vma list which is safe nowdrivers/iommu/iommu-sva-lib.c: return mmget_not_zero(mm); drivers/iommu/iommu-sva-lib.c: return ioasid_find(&iommu_sva_pasid, pasid, __mmget_not_zero);It calls
find_extend_vma()- but also it doesn't seem to have a mmgrab when it does that mmget. The rcu is messed up here too, so humm.Jason