Files
Dave Hansen 47e725702b mm: make per-VMA locks available universally
Patch series "mm: Unconditional per-VMA locks and cleanups", v7.

tl;dr: Make per-VMA locks available in all configs.  Simplify some of the
per-VMA lock users now that they can rely on them being always available.

Binder and networking folks: Your code is the target of the cleanups.  I'm
cc'ing you now on v2 because there's emerging consensus on the mm side
that the approach here is sane.  I'm not quite sure how this pile would
get merged, but ack/review tags would be appreciated if this looks good to
you.

Longer version:

When working on some x86 shadow stack code, it was a real pain to avoid
causing recursive locking problems with mmap_lock.  One way to avoid those
was to avoid mmap_lock and use per-VMA locks instead.  They are great, but
they are not available in all configs which makes them unusable in generic
code, or if you want to completely avoid mmap_lock.

Make per-VMA locks available in all configs.  Right now, they are only
available on select architectures when SMP and MMU are enabled.  But all
of the primitives that per-VMA locks are built on (RCU, maple trees,
refcounts) work just fine without SMP or MMU.

The only real downside is that making VMAs a wee bit bigger on !MMU and
!SMP builds.

The upside is much cleaner code, lower complexity and less #ifdeffery.

Clean up a binder VMA locking site now that it can rely on per-VMA locks.

Building on top of universally-available per-VMA locks, introduce a new
helper.  Since the new API does not require callers to have a fallback to
mmap_lock, it's much easier to use.  Callers can potentially replace this
very common kernel idiom:

	mmap_read_lock(mm);
	vma = vma_lookup()
	// fiddle with vma
	mmap_read_unlock(mm);

with:

	vma = vma_start_read_unlocked(mm, address);
	// fiddle with vma
	vma_end_read(vma);

Which avoids mmap_lock entirely in the fast path.

Use that new API for another binder site and one in the TCP code.


This patch (of 7):

The per-VMA locks have been around for several years.  They've had some
bugs worked out of them and have seen quite wide use.  However, they are
still only available when architectures explicitly enable them.  Remove
the conditional compilation around the per-VMA locks, making them
available on all architectures and configs.

The approach up to now seemed to be to add ARCH_SUPPORTS_PER_VMA_LOCK when
the architecture started using per-VMA locks in the fault handler.  But,
contrary to the naming, the Kconfig option does not really indicate
whether the architecture supports per-VMA locks or not.  It is more of a
marker for whether the architecture is likely to benefit from per-VMA
locks.

To me, the most important thing side-effect of universal availability is
letting per-VMA locks be used in SMP=n configs.  This lets us use
per-VMA locking in all x86 code without fallbacks.

Overall, this just generally makes the kernel simpler.  Just look at the
diffstat.  It also opens the door to users that want to use the per-VMA
locks in common code.  Doing *that* brings additional simplifications.

The downside of this is adding some fields to vm_area_struct and
mm_struct.  There are likely ways to optimize this, especially for things
like SMP=n configs.  For now, do the simplest thing: use the same
implementation everywhere.

== Considerations for NOMMU config ==

NOMMU systems do not write-lock VMAs, therefore read-locking a VMA would
always succeed unless VMA is detached.  Therefore for NOMMU config we make
vma_mark_attached() a NOOP, which keeps VMAs always in detached state. 
This causes VMA read-locking to always fail and the caller falls back to
locking mmap_lock.

The following functions will have a different implementation in NOMMU
config:

- vma_mark_attached(), vma_mark_detached() are made NOOPs, keeping VMAs
  always in a detached state and preventing assertions and refcount
  underflows;

- vma_start_write(), vma_start_write_killable() are made NOOPs to avoid
  warnings in __vma_start_write() due to VMAs being detached.  These
  functions are not used in NOMMU code but __vma_start_write() is an
  exported function, therefore might be used by drivers.

- vma_assert_attached() is made NOOP because it's reachable from NOMMU
  code via split_vma()->vma_iter_store_new()->vma_iter_store_overwrite();

- vma_assert_write_locked() is asserting vma->vm_mm is write-locked, as
  was done before this change;

- vma_assert_locked() is asserting vma->vm_mm is locked, as was done
  before this change;

The following functions work for both MMU and NOMMU configs:

- vma_lock_init() performs the same initialization as for MMU config;

- mm_lock_seqcount_init(), mm_lock_seqcount_begin(),
  mm_lock_seqcount_end() are called from mmap_write_{lock|unlock} and
  update mm_lock_seq correctly.

- mmap_lock_speculate_try_begin(), mmap_lock_speculate_retry() work as
  is because mm_lock_seq is updated correctly;

- vma_start_read(), vma_start_read_locked() will always fail because
  VMAs are always detached;

- vma_end_read() will never be called because vma_start_read() never
  succeeds;

- vma_is_attached() always return false because VMAs are always
  detached;

- vma_assert_detached() will never trigger because VMAs are never
  attached;

- vma_start_read_locked() always return false because VMAs are always
  detached;

- lock_vma_under_rcu() will be safe as the attempted read lock will bail;

Changes in the following files are not affecting NOMMU config:

task_mmu.c - not compiled when CONFIG_MMU=n;
pagewalk.c - not compiled when CONFIG_MMU=n;
userfaultfd.c - not compiled when CONFIG_MMU=n (CONFIG_USERFAULTFD depends
on CONFIG_MMU);

The following changes in the BPF code are made to keep NOMMU config
working like before:

stack_map_lock_vma() - keeps mmap_lock in NOMMU config;
bpf_iter_task_vma_new() - bails out in NOMMU config;

Link: https://lore.kernel.org/20260831203056.838265-1-surenb@google.com
Link: https://lore.kernel.org/20260831203056.838265-2-surenb@google.com
Signed-off-by: Dave Hansen <dave.hansen@linux.intel.com>
Signed-off-by: Suren Baghdasaryan <surenb@google.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Reviewed-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>
Acked-by: Vlastimil Babka (SUSE) <vbabka@kernel.org>
Acked-by: David Hildenbrand (Arm) <david@kernel.org>
Cc: Liam R. Howlett <liam@infradead.org>
Cc: Shakeel Butt <shakeel.butt@linux.dev>
Cc: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Cc: Todd Kjos <tkjos@android.com>
Cc: Christian Brauner <christian@brauner.io>
Cc: Carlos Llamas <cmllamas@google.com>
Cc: Alice Ryhl <aliceryhl@google.com>
Cc: David S. Miller <davem@davemloft.net>
Cc: David Ahern <dsahern@kernel.org>
Cc: Arve Hjønnevåg <arve@android.com>
2026-10-06 17:39:10 -07:00

362 lines
8.9 KiB
C

// SPDX-License-Identifier: GPL-2.0
/*
* mm/debug.c
*
* mm/ specific debug routines.
*
*/
#include <linux/kernel.h>
#include <linux/mm.h>
#include <linux/trace_events.h>
#include <linux/memcontrol.h>
#include <trace/events/mmflags.h>
#include <linux/migrate.h>
#include <linux/page_owner.h>
#include <linux/ctype.h>
#include "internal.h"
#include <trace/events/migrate.h>
/*
* Define EM() and EMe() so that MIGRATE_REASON from trace/events/migrate.h can
* be used to populate migrate_reason_names[].
*/
#undef EM
#undef EMe
#define EM(a, b) b,
#define EMe(a, b) b
const char *migrate_reason_names[MR_TYPES] = {
MIGRATE_REASON
};
const struct trace_print_flags pageflag_names[] = {
__def_pageflag_names,
{0, NULL}
};
const struct trace_print_flags gfpflag_names[] = {
__def_gfpflag_names,
{0, NULL}
};
const struct trace_print_flags vmaflag_names[] = {
__def_vmaflag_names,
{0, NULL}
};
#define DEF_PAGETYPE_NAME(_name) [PGTY_##_name - 0xf0] = __stringify(_name)
static const char *page_type_names[] = {
DEF_PAGETYPE_NAME(slab),
DEF_PAGETYPE_NAME(hugetlb),
DEF_PAGETYPE_NAME(offline),
DEF_PAGETYPE_NAME(guard),
DEF_PAGETYPE_NAME(table),
DEF_PAGETYPE_NAME(buddy),
DEF_PAGETYPE_NAME(unaccepted),
};
static const char *page_type_name(unsigned int page_type)
{
unsigned i = (page_type >> 24) - 0xf0;
if (i >= ARRAY_SIZE(page_type_names))
return "unknown";
return page_type_names[i];
}
static void __dump_folio(const struct folio *folio, const struct page *page,
unsigned long pfn, unsigned long idx)
{
struct address_space *mapping = folio_mapping(folio);
int mapcount = atomic_read(&page->_mapcount) + 1;
char *type = "";
if (page_mapcount_is_type(mapcount))
mapcount = 0;
pr_warn("page: refcount:%d mapcount:%d mapping:%p index:%#lx pfn:%#lx\n",
folio_ref_count(folio), mapcount, mapping,
folio->index + idx, pfn);
if (folio_test_large(folio)) {
int pincount = 0;
if (folio_has_pincount(folio))
pincount = atomic_read(&folio->_pincount);
pr_warn("head: order:%u mapcount:%d entire_mapcount:%d nr_pages_mapped:%d pincount:%d\n",
folio_order(folio),
folio_mapcount(folio),
folio_entire_mapcount(folio),
folio_nr_pages_mapped(folio),
pincount);
}
#ifdef CONFIG_MEMCG
if (folio->memcg_data)
pr_warn("memcg:%lx\n", folio->memcg_data);
#endif
if (folio_test_ksm(folio))
type = "ksm ";
else if (folio_test_anon(folio))
type = "anon ";
else if (mapping)
dump_mapping(mapping);
BUILD_BUG_ON(ARRAY_SIZE(pageflag_names) != __NR_PAGEFLAGS + 1);
/*
* Accessing the pageblock without the zone lock. It could change to
* "isolate" again in the meantime, but since we are just dumping the
* state for debugging, it should be fine to accept a bit of
* inaccuracy here due to racing.
*/
pr_warn("%sflags: %pGp%s\n", type, &folio->flags,
is_migrate_cma_folio(folio, pfn) ? " CMA" : "");
if (page_has_type(&folio->page))
pr_warn("page_type: %x(%s)\n", folio->page.page_type >> 24,
page_type_name(folio->page.page_type));
print_hex_dump(KERN_WARNING, "raw: ", DUMP_PREFIX_NONE, 32,
sizeof(unsigned long), page,
sizeof(struct page), false);
if (folio_test_large(folio))
print_hex_dump(KERN_WARNING, "head: ", DUMP_PREFIX_NONE, 32,
sizeof(unsigned long), folio,
2 * sizeof(struct page), false);
}
static void __dump_page(const struct page *page)
{
struct page_snapshot ps;
snapshot_page(&ps, page);
if (!snapshot_page_is_faithful(&ps))
pr_warn("page does not match folio\n");
__dump_folio(&ps.folio_snapshot, &ps.page_snapshot, ps.pfn, ps.idx);
}
void dump_page(const struct page *page, const char *reason)
{
if (PagePoisoned(page))
pr_warn("page:%p is uninitialized and poisoned\n", page);
else
__dump_page(page);
if (reason)
pr_warn("page dumped because: %s\n", reason);
dump_page_owner(page);
}
EXPORT_SYMBOL(dump_page);
#ifdef CONFIG_DEBUG_VM
void dump_vma(const struct vm_area_struct *vma)
{
pr_emerg("vma %px start %px end %px mm %px\n"
"prot %lx anon_vma %px vm_ops %px\n"
"pgoff %lx file %px private_data %px\n"
"refcnt %x\n"
"flags: %#lx(%pGv)\n",
vma, (void *)vma->vm_start, (void *)vma->vm_end, vma->vm_mm,
(unsigned long)pgprot_val(vma->vm_page_prot),
vma->anon_vma, vma->vm_ops, vma_start_pgoff(vma),
vma->vm_file, vma->vm_private_data,
refcount_read(&vma->vm_refcnt),
vma->vm_flags, &vma->vm_flags);
}
EXPORT_SYMBOL(dump_vma);
void dump_mm(const struct mm_struct *mm)
{
pr_emerg("mm %px task_size %lu\n"
"mmap_base %lu mmap_legacy_base %lu\n"
"pgd %px mm_users %d mm_count %d pgtables_bytes %lu map_count %d\n"
"hiwater_rss %lx hiwater_vm %lx total_vm %lx locked_vm %lx\n"
"pinned_vm %llx data_vm %lx exec_vm %lx stack_vm %lx\n"
"start_code %lx end_code %lx start_data %lx end_data %lx\n"
"start_brk %lx brk %lx start_stack %lx\n"
"arg_start %lx arg_end %lx env_start %lx env_end %lx\n"
"binfmt %px flags %*pb\n"
#ifdef CONFIG_AIO
"ioctx_table %px\n"
#endif
#ifdef CONFIG_MEMCG
"owner %px "
#endif
"exe_file %px\n"
#ifdef CONFIG_MMU_NOTIFIER
"notifier_subscriptions %px\n"
#endif
#ifdef CONFIG_NUMA_BALANCING
"numa_next_scan %lu numa_scan_offset %lu numa_scan_seq %d\n"
#endif
"tlb_flush_pending %d\n"
"def_flags: %*pb(%pGv)\n",
mm, mm->task_size,
mm->mmap_base, mm->mmap_legacy_base,
mm->pgd, atomic_read(&mm->mm_users),
atomic_read(&mm->mm_count),
mm_pgtables_bytes(mm),
mm->map_count,
mm->hiwater_rss, mm->hiwater_vm, mm->total_vm, mm->locked_vm,
(u64)atomic64_read(&mm->pinned_vm),
mm->data_vm, mm->exec_vm, mm->stack_vm,
mm->start_code, mm->end_code, mm->start_data, mm->end_data,
mm->start_brk, mm->brk, mm->start_stack,
mm->arg_start, mm->arg_end, mm->env_start, mm->env_end,
mm->binfmt, NUM_MM_FLAG_BITS, __mm_flags_get_bitmap(mm),
#ifdef CONFIG_AIO
mm->ioctx_table,
#endif
#ifdef CONFIG_MEMCG
mm->owner,
#endif
mm->exe_file,
#ifdef CONFIG_MMU_NOTIFIER
mm->notifier_subscriptions,
#endif
#ifdef CONFIG_NUMA_BALANCING
mm->numa_next_scan, mm->numa_scan_offset, mm->numa_scan_seq,
#endif
atomic_read(&mm->tlb_flush_pending),
NUM_VMA_FLAG_BITS, mm->def_vma_flags.__vma_flags,
&mm->def_vma_flags
);
}
EXPORT_SYMBOL(dump_mm);
void dump_vmg(const struct vma_merge_struct *vmg, const char *reason)
{
if (reason)
pr_warn("vmg %px dumped because: %s\n", vmg, reason);
if (!vmg) {
pr_warn("vmg %px state: (NULL)\n", vmg);
return;
}
pr_warn("vmg %px state: mm %px pgoff %lx\n"
"vmi %px [%lx,%lx)\n"
"prev %px middle %px next %px target %px\n"
"start %lx end %lx flags %lx\n"
"file %px anon_vma %px policy %px\n"
"uffd_ctx %px\n"
"anon_name %px\n"
"state %x\n"
"just_expand %d\n"
"__adjust_middle_start %d __adjust_next_start %d\n"
"__remove_middle %d __remove_next %d\n",
vmg, vmg->mm, vmg->pgoff,
vmg->vmi, vmg->vmi ? vma_iter_addr(vmg->vmi) : 0,
vmg->vmi ? vma_iter_end(vmg->vmi) : 0,
vmg->prev, vmg->middle, vmg->next, vmg->target,
vmg->start, vmg->end, vmg->vm_flags,
vmg->file, vmg->anon_vma, vmg->policy,
#ifdef CONFIG_USERFAULTFD
vmg->uffd_ctx.ctx,
#else
(void *)0,
#endif
vmg->anon_name,
(int)vmg->state,
vmg->just_expand,
vmg->__adjust_middle_start, vmg->__adjust_next_start,
vmg->__remove_middle, vmg->__remove_next);
if (vmg->mm) {
pr_warn("vmg %px mm:\n", vmg);
dump_mm(vmg->mm);
} else {
pr_warn("vmg %px mm: (NULL)\n", vmg);
}
if (vmg->prev) {
pr_warn("vmg %px prev:\n", vmg);
dump_vma(vmg->prev);
} else {
pr_warn("vmg %px prev: (NULL)\n", vmg);
}
if (vmg->middle) {
pr_warn("vmg %px middle:\n", vmg);
dump_vma(vmg->middle);
} else {
pr_warn("vmg %px middle: (NULL)\n", vmg);
}
if (vmg->next) {
pr_warn("vmg %px next:\n", vmg);
dump_vma(vmg->next);
} else {
pr_warn("vmg %px next: (NULL)\n", vmg);
}
#ifdef CONFIG_DEBUG_VM_MAPLE_TREE
if (vmg->vmi) {
pr_warn("vmg %px vmi:\n", vmg);
vma_iter_dump_tree(vmg->vmi);
} else {
pr_warn("vmg %px vmi: (NULL)\n", vmg);
}
#endif
}
EXPORT_SYMBOL(dump_vmg);
static bool page_init_poisoning __read_mostly = true;
static int __init setup_vm_debug(char *str)
{
bool __page_init_poisoning = true;
/*
* Calling vm_debug with no arguments is equivalent to requesting
* to enable all debugging options we can control.
*/
if (*str++ != '=' || !*str)
goto out;
__page_init_poisoning = false;
if (*str == '-')
goto out;
while (*str) {
switch (tolower(*str)) {
case 'p':
__page_init_poisoning = true;
break;
default:
pr_err("vm_debug option '%c' unknown. skipped\n",
*str);
}
str++;
}
out:
if (page_init_poisoning && !__page_init_poisoning)
pr_warn("Page struct poisoning disabled by kernel command line option 'vm_debug'\n");
page_init_poisoning = __page_init_poisoning;
return 1;
}
__setup("vm_debug", setup_vm_debug);
void page_init_poison(struct page *page, size_t size)
{
if (page_init_poisoning)
memset(page, PAGE_POISON_PATTERN, size);
}
void vma_iter_dump_tree(const struct vma_iterator *vmi)
{
#if defined(CONFIG_DEBUG_VM_MAPLE_TREE)
mas_dump(&vmi->mas);
mt_dump(vmi->mas.tree, mt_dump_hex);
#endif /* CONFIG_DEBUG_VM_MAPLE_TREE */
}
#endif /* CONFIG_DEBUG_VM */