From: Huang Shijie <huangsj@hygon.cn>
To: Pedro Falcato <pfalcato@suse.de>
Cc: <akpm@linux-foundation.org>, <viro@zeniv.linux.org.uk>,
<brauner@kernel.org>, <jack@suse.cz>, <muchun.song@linux.dev>,
<osalvador@suse.de>, <david@kernel.org>, <surenb@google.com>,
<mjguzik@gmail.com>, <liam@infradead.org>, <ljs@kernel.org>,
<vbabka@kernel.org>, <shakeel.butt@linux.dev>, <rppt@kernel.org>,
<mhocko@suse.com>, <corbet@lwn.net>, <skhan@linuxfoundation.org>,
<linux@armlinux.org.uk>, <dinguyen@kernel.org>,
<schuster.simon@siemens-energy.com>,
<James.Bottomley@hansenpartnership.com>, <deller@gmx.de>,
<djbw@kernel.org>, <willy@infradead.org>, <peterz@infradead.org>,
<mingo@redhat.com>, <acme@kernel.org>, <namhyung@kernel.org>,
<mark.rutland@arm.com>, <alexander.shishkin@linux.intel.com>,
<jolsa@kernel.org>, <irogers@google.com>,
<adrian.hunter@intel.com>, <james.clark@linaro.org>,
<mhiramat@kernel.org>, <oleg@redhat.com>, <ziy@nvidia.com>,
<baolin.wang@linux.alibaba.com>, <npache@redhat.com>,
<ryan.roberts@arm.com>, <dev.jain@arm.com>, <baohua@kernel.org>,
<lance.yang@linux.dev>, <linmiaohe@huawei.com>,
<nao.horiguchi@gmail.com>, <jannh@google.com>, <riel@surriel.com>,
<harry@kernel.org>, <will@kernel.org>,
<brian.ruley@gehealthcare.com>, <rmk+kernel@armlinux.org.uk>,
<dave.anglin@bell.net>, <linux-mm@kvack.org>,
<linux-doc@vger.kernel.org>, <linux-kernel@vger.kernel.org>,
<linux-arm-kernel@lists.infradead.org>,
<linux-parisc@vger.kernel.org>, <linux-fsdevel@vger.kernel.org>,
<nvdimm@lists.linux.dev>, <linux-perf-users@vger.kernel.org>,
<linux-trace-kernel@vger.kernel.org>, <zhongyuan@hygon.cn>,
<fangbaoshun@hygon.cn>, <yingzhiwei@hygon.cn>
Subject: Re: [PATCH v2 3/4] mm/fs: split the file's i_mmap tree
Date: Fri, 12 Jun 2026 14:44:14 +0800 [thread overview]
Message-ID: <aiuqvuK2z5OelVRM@hsj-2U-Workstation> (raw)
In-Reply-To: <aiqFgGbIo1Psy3pI@pedro-suse.lan>
On Thu, Jun 11, 2026 at 12:11:27PM +0100, Pedro Falcato wrote:
> Hi,
>
> On Thu, Jun 11, 2026 at 02:18:59PM +0800, Huang Shijie wrote:
> > In the UnixBench tests, there is a test "execl" which tests
> > the execve system call.
> > For example, a Hygon's server has 12 NUMA nodes, and 384 CPUs.
> > When we test our server with "./Run -c 384 execl",
> > the test result is not good enough. The i_mmap locks contended heavily on
> > "libc.so" and "ld.so". The i_mmap tree for "libc.so" can be
> > over 6000 VMAs, all the VMAs can be in different NUMA mode. The insert/remove
> > operations do not run quickly enough.
>
> I _really_ would have appreciated some coordination here, because I said I was
> going to take a look at it. I have something that I think is much simpler
Okay, no problem.
I waited for more then a month, I thought you are busy at other
things. So I spent more then a week to finish the patch set v2.
> in practice. These patches are also way too complex to be dropped just before
> the merge window.
>
> Some comments:
>
> >
> > In order to reduce the competition of the i_mmap lock, this patch does
> > following:
> > 1.) Split the single i_mmap tree into several sibling trees:
> > Each tree has a lock. The CONFIG_SPLIT_I_MMAP is used to
> > turn on/off this feature.
>
> There is no need for a config option. This needs to Just Work.
>
> > 2.) Introduce a new field "tree_idx" for vm_area_struct to save the
> > sibling tree index for this VMA.
>
> This is possibly contentious, but there are holes in vm_area_struct.
> So I think this is fine.
>
> > 3.) Introduce a new field "vma_count" for address_space.
> > The new mapping_mapped() will use it.
> > 4.) Rewrite the vma_interval_tree_foreach()
> > 5.) Rewrite the lock functions.
> >
> > After this patch, the VMA insert/remove operations will work faster,
> > and we can get over 400% performance improvement with the above test.
> >
> > Signed-off-by: Huang Shijie <huangsj@hygon.cn>
> > ---
> > fs/Kconfig | 8 ++
> > fs/hugetlbfs/inode.c | 20 ++++-
> > fs/inode.c | 75 ++++++++++++++++-
> > include/linux/fs.h | 174 ++++++++++++++++++++++++++++++++++++++-
> > include/linux/mm.h | 80 ++++++++++++++++++
> > include/linux/mm_types.h | 3 +
> > mm/internal.h | 3 +-
> > mm/mmap.c | 11 ++-
> > mm/nommu.c | 23 ++++--
> > mm/pagewalk.c | 2 +-
> > mm/vma.c | 72 +++++++++++-----
> > mm/vma_init.c | 3 +
> > 12 files changed, 436 insertions(+), 38 deletions(-)
> >
> > diff --git a/fs/Kconfig b/fs/Kconfig
> > index 43cb06de297f..e24804f70432 100644
> > --- a/fs/Kconfig
> > +++ b/fs/Kconfig
> > @@ -9,6 +9,14 @@ menu "File systems"
> > config DCACHE_WORD_ACCESS
> > bool
> >
> > +config SPLIT_I_MMAP
> > + bool "Split the file's i_mmap to several trees"
> > + default n
> > + help
> > + Split the file's i_mmap to several trees, each tree has a separate
> > + lock. This will reduce the lock contention of file's i_mmap tree,
> > + but it will cost more memory for per inode.
> > +
> > config VALIDATE_FS_PARSER
> > bool "Validate filesystem parameter description"
> > help
> > diff --git a/fs/hugetlbfs/inode.c b/fs/hugetlbfs/inode.c
> > index da5b41ea5bdd..68d8308418dd 100644
> > --- a/fs/hugetlbfs/inode.c
> > +++ b/fs/hugetlbfs/inode.c
> > @@ -891,6 +891,23 @@ static struct inode *hugetlbfs_get_root(struct super_block *sb,
> > */
> > static struct lock_class_key hugetlbfs_i_mmap_rwsem_key;
> >
> > +#ifdef CONFIG_SPLIT_I_MMAP
> > +static void hugetlbfs_lockdep_set_class(struct address_space *mapping)
> > +{
> > + int i;
> > +
> > + for (i = 0; i < split_tree_num; i++) {
> > + lockdep_set_class(&mapping->i_mmap[i].rwsem,
> > + &hugetlbfs_i_mmap_rwsem_key);
> > + }
> > +}
> > +#else
> > +static void hugetlbfs_lockdep_set_class(struct address_space *mapping)
> > +{
> > + lockdep_set_class(&mapping->i_mmap_rwsem, &hugetlbfs_i_mmap_rwsem_key);
> > +}
> > +#endif
> > +
> > static struct inode *hugetlbfs_get_inode(struct super_block *sb,
> > struct mnt_idmap *idmap,
> > struct inode *dir,
> > @@ -915,8 +932,7 @@ static struct inode *hugetlbfs_get_inode(struct super_block *sb,
> >
> > inode->i_ino = get_next_ino();
> > inode_init_owner(idmap, inode, dir, mode);
> > - lockdep_set_class(&inode->i_mapping->i_mmap_rwsem,
> > - &hugetlbfs_i_mmap_rwsem_key);
> > + hugetlbfs_lockdep_set_class(inode->i_mapping);
> > inode->i_mapping->a_ops = &hugetlbfs_aops;
> > simple_inode_init_ts(inode);
> > info->resv_map = resv_map;
> > diff --git a/fs/inode.c b/fs/inode.c
> > index 62c579a0cf7d..cb67ae83f5b3 100644
> > --- a/fs/inode.c
> > +++ b/fs/inode.c
> > @@ -214,6 +214,70 @@ static int no_open(struct inode *inode, struct file *file)
> > return -ENXIO;
> > }
> >
> > +#ifdef CONFIG_SPLIT_I_MMAP
> > +int split_tree_num;
> > +static int split_tree_align __maybe_unused = 32;
> > +
> > +static void __init init_split_tree_num(void)
> > +{
> > +#ifdef CONFIG_NUMA
> > + split_tree_num = nr_node_ids;
> > +#else
> > + split_tree_num = ALIGN(nr_cpu_ids, split_tree_align);
> > +#endif
> > +}
>
> Again, too configurable. I think you're too stuck up on the NUMA case -
If you do not care about the NUMA. The performance will _NOT_ get improved
in our NUMA server. I had ever tested code which do not care about the NUMA,
and I got a bad performance. Avoid the remote access is a very important
thing for the NUMA server.
> which does not matter for many people - and may actively harm NUMA users. If
> I have a 128 core 2 NUMA node system, what should I shard by?
It is easy to extend the tree number for NUMA. :)
For the 128 core 2 NUMA, we can extend to more trees, such as:
Two trees for each NUMA node.
>
> > +
> > +static void free_mapping_i_mmap(struct address_space *mapping)
> > +{
> > + int i;
> > +
> > + if (!mapping->i_mmap)
> > + return;
> > +
> > + for (i = 0; i < split_tree_num; i++)
> > + kfree(mapping->i_mmap[i]);
> > +
> > + kfree(mapping->i_mmap);
> > + mapping->i_mmap = NULL;
> > +}
> > +
> > +static int init_mapping_i_mmap(struct address_space *mapping, gfp_t gfp)
> > +{
> > + struct i_mmap_tree *tree;
> > + int i;
> > +
> > + /* The extra one is used as terminator in vma_interval_tree_foreach() */
> > + mapping->i_mmap = kzalloc(sizeof(tree) * (split_tree_num + 1), gfp);
> > + if (!mapping->i_mmap)
> > + return -ENOMEM;
> > +
> > + for (i = 0; i < split_tree_num; i++) {
> > + tree = kzalloc_node(sizeof(*tree), gfp, i);
> > + if (!tree)
> > + goto nomem;
> > +
> > + tree->root = RB_ROOT_CACHED;
> > + init_rwsem(&tree->rwsem);
>
> This (as-is) should blow up with lockdep + the locking loops down there.
okay, I will check it later.
thanks a lot.
>
> > +
> > + mapping->i_mmap[i] = tree;
> > + }
> > + return 0;
> > +nomem:
> > + free_mapping_i_mmap(mapping);
> > + return -ENOMEM;
> > +}
>
> Honestly, it's likely that a simple static array in struct address_space
The array size is not fixed, so we cannot add a static array in address_space.
> suffices. I would not go through the trouble of getting everything very
> tight and NUMA correct.
>
> > +#else
> > +static int init_mapping_i_mmap(struct address_space *mapping, gfp_t gfp)
> > +{
> > + mapping->i_mmap = RB_ROOT_CACHED;
> > + init_rwsem(&mapping->i_mmap_rwsem);
> > + return 0;
> > +}
> > +
> > +static void free_mapping_i_mmap(struct address_space *mapping) { }
> > +static void __init init_split_tree_num(void) {}
> > +#endif
> > +
> > /**
> > * inode_init_always_gfp - perform inode structure initialisation
> > * @sb: superblock inode belongs to
> > @@ -302,9 +366,14 @@ int inode_init_always_gfp(struct super_block *sb, struct inode *inode, gfp_t gfp
> > #endif
> > inode->i_flctx = NULL;
> >
> > - if (unlikely(security_inode_alloc(inode, gfp)))
> > + if (init_mapping_i_mmap(mapping, gfp))
> > return -ENOMEM;
> >
> > + if (unlikely(security_inode_alloc(inode, gfp))) {
> > + free_mapping_i_mmap(mapping);
> > + return -ENOMEM;
> > + }
> > +
> > this_cpu_inc(nr_inodes);
> >
> > return 0;
> > @@ -380,6 +449,7 @@ void __destroy_inode(struct inode *inode)
> > if (inode->i_default_acl && !is_uncached_acl(inode->i_default_acl))
> > posix_acl_release(inode->i_default_acl);
> > #endif
> > + free_mapping_i_mmap(&inode->i_data);
> > this_cpu_dec(nr_inodes);
> > }
> > EXPORT_SYMBOL(__destroy_inode);
> > @@ -480,9 +550,7 @@ EXPORT_SYMBOL(inc_nlink);
> > static void __address_space_init_once(struct address_space *mapping)
> > {
> > xa_init_flags(&mapping->i_pages, XA_FLAGS_LOCK_IRQ | XA_FLAGS_ACCOUNT);
> > - init_rwsem(&mapping->i_mmap_rwsem);
> > spin_lock_init(&mapping->i_private_lock);
> > - mapping->i_mmap = RB_ROOT_CACHED;
> > }
> >
> > void address_space_init_once(struct address_space *mapping)
> > @@ -2619,6 +2687,7 @@ void __init inode_init(void)
> > &i_hash_mask,
> > 0,
> > 0);
> > + init_split_tree_num();
> > }
> >
> > void init_special_inode(struct inode *inode, umode_t mode, dev_t rdev)
> > diff --git a/include/linux/fs.h b/include/linux/fs.h
> > index cd46615b8f53..f4b3645b61df 100644
> > --- a/include/linux/fs.h
> > +++ b/include/linux/fs.h
> > @@ -450,6 +450,25 @@ struct mapping_metadata_bhs {
> > struct list_head list; /* The list of bhs (b_assoc_buffers) */
> > };
> >
> > +#ifdef CONFIG_SPLIT_I_MMAP
> > +/*
> > + * struct i_mmap_tree - A single sibling tree of the file's split i_mmap.
> > + * @root: The red/black interval tree root.
> > + * @rwsem: Protects insert/remove operations on this sibling tree.
> > + * @vma_count: Number of VMAs in this sibling tree.
> > + *
> > + * When CONFIG_SPLIT_I_MMAP is enabled, the file's single i_mmap tree is
> > + * split into split_tree_num sibling trees, each with its own lock. This
> > + * reduces lock contention by allowing concurrent VMA insert/remove
> > + * operations on different sibling trees.
> > + */
> > +struct i_mmap_tree {
> > + struct rb_root_cached root;
> > + struct rw_semaphore rwsem;
> > + atomic_t vma_count;
>
> I don't see what you need this vma_count for? I get the one in address_space,
> but this one does not seem useful.
For non-NUMA case, we can use it to determine which tree we should put the new
VMA.
Round-robin is not good enough for a dynamic system.
>
> > +};
> > +#endif
> > +
> > /**
> > * struct address_space - Contents of a cacheable, mappable object.
> > * @host: Owner, either the inode or the block_device.
> > @@ -461,8 +480,13 @@ struct mapping_metadata_bhs {
> > * @gfp_mask: Memory allocation flags to use for allocating pages.
> > * @i_mmap_writable: Number of VM_SHARED, VM_MAYWRITE mappings.
> > * @nr_thps: Number of THPs in the pagecache (non-shmem only).
> > - * @i_mmap: Tree of private and shared mappings.
> > - * @i_mmap_rwsem: Protects @i_mmap and @i_mmap_writable.
> > + * @i_mmap: Tree of private and shared mappings. When CONFIG_SPLIT_I_MMAP
> > + * is enabled, this is an array of split_tree_num struct i_mmap_tree
> > + * pointers (plus a NULL terminator).
>
> NULL terminator wastes more memory, so I would really strongly avoid it as
> well.
any better idea?
>
> > + * @vma_count: Total number of VMAs across all sibling trees (only when
> > + * CONFIG_SPLIT_I_MMAP is enabled). Used by mapping_mapped().
> > + * @i_mmap_rwsem: Protects @i_mmap and @i_mmap_writable (only when
> > + * CONFIG_SPLIT_I_MMAP is disabled; otherwise per-tree rwsem is used).
>
> So, there are very good reasons why you still need an i_mmap_rwsem protecting
> state, even with split mmap trees. Which I'll go into later.
>
> > * @nrpages: Number of page entries, protected by the i_pages lock.
> > * @writeback_index: Writeback starts here.
> > * @a_ops: Methods.
> > @@ -480,14 +504,19 @@ struct address_space {
> > /* number of thp, only for non-shmem files */
> > atomic_t nr_thps;
> > #endif
> > +#ifdef CONFIG_SPLIT_I_MMAP
> > + struct i_mmap_tree **i_mmap;
> > + atomic_t vma_count;
> > +#else
> > struct rb_root_cached i_mmap;
> > + struct rw_semaphore i_mmap_rwsem;
> > +#endif
> > unsigned long nrpages;
> > pgoff_t writeback_index;
> > const struct address_space_operations *a_ops;
> > unsigned long flags;
> > errseq_t wb_err;
> > spinlock_t i_private_lock;
> > - struct rw_semaphore i_mmap_rwsem;
>
> See d3b1a9a778e1 ("fs/address_space: move i_mmap_rwsem to mitigate a false sharing with i_mmap.")
Got it.
>
> > } __attribute__((aligned(sizeof(long)))) __randomize_layout;
> > /*
> > * On most architectures that alignment is already the case; but
> > @@ -508,6 +537,133 @@ static inline bool mapping_tagged(const struct address_space *mapping, xa_mark_t
> > return xa_marked(&mapping->i_pages, tag);
> > }
> >
> > +#ifdef CONFIG_SPLIT_I_MMAP
> > +static inline int mapping_mapped(const struct address_space *mapping)
> > +{
> > + return atomic_read(&mapping->vma_count);
>
> Now that I think of it, I don't think we need atomic_t, only unsigned long +
> READ_ONCE() suffices. Increments can race just fine, we don't expect any
> consistency there - if you want consistency you probably hold the i_mmap lock.
>
okay. I will check it.
> > +}
> > +
> > +static inline void inc_mapping_vma(struct address_space *mapping,
> > + struct vm_area_struct *vma)
> > +{
> > + struct i_mmap_tree *tree = mapping->i_mmap[vma->tree_idx];
> > +
> > + atomic_inc(&tree->vma_count);
> > + atomic_inc(&mapping->vma_count);
> > +}
> > +
> > +static inline void dec_mapping_vma(struct address_space *mapping,
> > + struct vm_area_struct *vma)
> > +{
> > + struct i_mmap_tree *tree = mapping->i_mmap[vma->tree_idx];
> > +
> > + atomic_dec(&tree->vma_count);
> > + atomic_dec(&mapping->vma_count);
> > +}
>
> This probably shouldn't be in linux/fs.h.
>
> > +
> > +static inline struct rb_root_cached *get_i_mmap_root(struct address_space *mapping)
> > +{
> > + return (struct rb_root_cached *)mapping->i_mmap;
> > +}
> > +
> > +static inline void i_mmap_tree_lock_write(struct address_space *mapping,
> > + struct vm_area_struct *vma)
> > +{
> > + struct i_mmap_tree *tree = mapping->i_mmap[vma->tree_idx];
> > +
> > + down_write(&tree->rwsem);
> > +}
> > +
> > +static inline void i_mmap_tree_unlock_write(struct address_space *mapping,
> > + struct vm_area_struct *vma)
> > +{
> > + struct i_mmap_tree *tree = mapping->i_mmap[vma->tree_idx];
> > +
> > + up_write(&tree->rwsem);
> > +}
> > +
> > +#define i_mmap_lock_write_prepare(mapping)
> > +#define i_mmap_unlock_write_complete(mapping)
>
> It's unclear to me why you added write_prepare() and write_complete().
>
> > +
> > +extern int split_tree_num;
> > +static inline void i_mmap_lock_write(struct address_space *mapping)
> > +{
> > + int i;
> > +
> > + for (i = 0; i < split_tree_num; i++)
> > + down_write(&mapping->i_mmap[i]->rwsem);
>
> Oof, this is an incredibly large hammer. This is basically why I think keeping
> i_mmap_rwsem (in a different form) is required. You do not want to take $nr_cpus
> locks (read _or_ write). For my design, I keep i_mmap_rwsem, but I invert its
> meaning - taking it in write = I'm reading from the tree; taking it in read =
> I'm writing to the tree. This provides some lighter-weight exclusion between
> rmap walks and rmap tree manipulation.
okay, it seem your method is better. I am waiting for your patch.
>
> _Technically_, you shouldn't need to always take a lock when manipulating the
> tree. A pattern like mnt_hold_writers()/mnt_get_write_access() can probably
> work well. But it may be too complex ATM.
>
>
> Also, note that you pretty much do not want i_mmap_lock_write() users after
> the conversion is done.
>
> > +}
> > +
> > +static inline int i_mmap_trylock_write(struct address_space *mapping)
> > +{
> > + int i;
> > +
> > + for (i = 0; i < split_tree_num; i++) {
> > + if (!down_write_trylock(&mapping->i_mmap[i]->rwsem)) {
> > + while (i--)
> > + up_write(&mapping->i_mmap[i]->rwsem);
> > + return 0;
> > + }
> > + }
> > + return 1;
> > +}
> > +
> > +static inline void i_mmap_unlock_write(struct address_space *mapping)
> > +{
> > + int i;
> > +
> > + for (i = 0; i < split_tree_num; i++)
> > + up_write(&mapping->i_mmap[i]->rwsem);
> > +}
> > +
> > +static inline int i_mmap_trylock_read(struct address_space *mapping)
> > +{
> > + int i;
> > +
> > + for (i = 0; i < split_tree_num; i++) {
> > + if (!down_read_trylock(&mapping->i_mmap[i]->rwsem)) {
> > + while (i--)
> > + up_read(&mapping->i_mmap[i]->rwsem);
> > + return 0;
> > + }
> > + }
> > + return 1;
> > +}
> > +
> > +static inline void i_mmap_lock_read(struct address_space *mapping)
> > +{
> > + int i;
> > +
> > + for (i = 0; i < split_tree_num; i++)
> > + down_read(&mapping->i_mmap[i]->rwsem);
> > +}
> > +
> > +static inline void i_mmap_unlock_read(struct address_space *mapping)
> > +{
> > + int i;
> > +
> > + for (i = 0; i < split_tree_num; i++)
> > + up_read(&mapping->i_mmap[i]->rwsem);
> > +}
> > +
> > +static inline void i_mmap_assert_locked(struct address_space *mapping)
> > +{
> > + int i;
> > +
> > + for (i = 0; i < split_tree_num; i++)
> > + lockdep_assert_held(&mapping->i_mmap[i]->rwsem);
> > +}
> > +
> > +static inline void i_mmap_assert_write_locked(struct address_space *mapping)
> > +{
> > + int i;
> > +
> > + for (i = 0; i < split_tree_num; i++)
> > + lockdep_assert_held_write(&mapping->i_mmap[i]->rwsem);
> > +}
> > +
> > +#else
> > +
> > static inline void i_mmap_lock_write(struct address_space *mapping)
> > {
> > down_write(&mapping->i_mmap_rwsem);
> > @@ -561,6 +717,18 @@ static inline struct rb_root_cached *get_i_mmap_root(struct address_space *mappi
> > return &mapping->i_mmap;
> > }
> >
> > +static inline void inc_mapping_vma(struct address_space *mapping,
> > + struct vm_area_struct *vma) { }
> > +static inline void dec_mapping_vma(struct address_space *mapping,
> > + struct vm_area_struct *vma) { }
> > +
> > +#define i_mmap_lock_write_prepare(mapping) i_mmap_lock_write(mapping)
> > +#define i_mmap_unlock_write_complete(mapping) i_mmap_unlock_write(mapping)
> > +#define i_mmap_tree_lock_write(mapping, vma)
> > +#define i_mmap_tree_unlock_write(mapping, vma)
> > +
> > +#endif
> > +
> > /*
> > * Might pages of this file have been modified in userspace?
> > * Note that i_mmap_writable counts all VM_SHARED, VM_MAYWRITE vmas: do_mmap
> > diff --git a/include/linux/mm.h b/include/linux/mm.h
> > index 0a45c6a8b9f2..9aa8119fa9bf 100644
> > --- a/include/linux/mm.h
> > +++ b/include/linux/mm.h
> > @@ -4041,11 +4041,91 @@ struct vm_area_struct *vma_interval_tree_iter_first(struct rb_root_cached *root,
> > struct vm_area_struct *vma_interval_tree_iter_next(struct vm_area_struct *node,
> > unsigned long start, unsigned long last);
> >
> > +#ifdef CONFIG_SPLIT_I_MMAP
> > +extern int split_tree_num;
> > +
> > +static inline int smallest_tree_idx(struct file *file)
> > +{
> > + struct address_space *mapping = file->f_mapping;
> > + int tmp = INT_MAX, count;
> > + int i, j = 0;
> > +
> > + /*
> > + * Since a not 100% accurate value is still okay,
> > + * we do not need any lock here.
> > + */
> > + for (i = 0; i < split_tree_num; i++) {
> > + count = atomic_read(&mapping->i_mmap[i]->vma_count);
> > + if (count < tmp) {
> > + j = i;
> > + tmp = count;
> > + if (!tmp)
> > + break;
> > + }
> > + }
>
> Ohh, I see why you want the per-subtree vma_count now. But is this a net-win?
It keep the trees as even as possible.
> I think doing something like vma-pointer-hashing or just smp_processor_id()
> would work a-ok.
>
> > + return j;
> > +}
> > +
> > +static inline void vma_set_tree_idx(struct vm_area_struct *vma)
> > +{
> > +#ifdef CONFIG_NUMA
> > + vma->tree_idx = numa_node_id();
> > +#else
> > + vma->tree_idx = smallest_tree_idx(vma->vm_file);
> > +#endif
> > +}
> > +
> > +static inline struct rb_root_cached *get_rb_root(struct vm_area_struct *vma,
> > + struct address_space *mapping)
> > +{
> > + return &mapping->i_mmap[vma->tree_idx]->root;
> > +}
> > +
> > +/* Find the first valid VMA in the sibling trees */
> > +static inline struct vm_area_struct *first_vma(struct i_mmap_tree ***__r,
> > + unsigned long start, unsigned long last)
> > +{
> > + struct vm_area_struct *vma = NULL;
> > + struct i_mmap_tree **tree = *__r;
> > + struct rb_root_cached *root;
> > +
> > + while (*tree) {
> > + root = &(*tree)->root;
> > + tree++;
> > + vma = vma_interval_tree_iter_first(root, start, last);
> > + if (vma)
> > + break;
> > + }
> > +
> > + /* Save for the next loop */
> > + *__r = tree;
> > + return vma;
> > +}
> > +
> > +/*
> > + * Please use get_i_mmap_root() to get the @root.
> > + * @_tmp is referenced to avoid unused variable warning.
> > + */
> > +#define vma_interval_tree_foreach(vma, root, start, last) \
> > + for (struct i_mmap_tree **_r = (struct i_mmap_tree **)(root), \
> > + **_tmp = (vma = first_vma(&_r, start, last)) ? _r : NULL;\
> > + ((_tmp && vma) || (vma = first_vma(&_r, start, last))); \
> > + vma = vma_interval_tree_iter_next(vma, start, last))
> > +#else
> > /* Please use get_i_mmap_root() to get the @root */
> > #define vma_interval_tree_foreach(vma, root, start, last) \
> > for (vma = vma_interval_tree_iter_first(root, start, last); \
> > vma; vma = vma_interval_tree_iter_next(vma, start, last))
> >
> > +static inline void vma_set_tree_idx(struct vm_area_struct *vma) { }
> > +
> > +static inline struct rb_root_cached *get_rb_root(struct vm_area_struct *vma,
> > + struct address_space *mapping)
> > +{
> > + return &mapping->i_mmap;
> > +}
> > +#endif
> > +
> > void anon_vma_interval_tree_insert(struct anon_vma_chain *node,
> > struct rb_root_cached *root);
> > void anon_vma_interval_tree_remove(struct anon_vma_chain *node,
> > diff --git a/include/linux/mm_types.h b/include/linux/mm_types.h
> > index a308e2c23b82..8d6aab3346ce 100644
> > --- a/include/linux/mm_types.h
> > +++ b/include/linux/mm_types.h
> > @@ -1072,6 +1072,9 @@ struct vm_area_struct {
> > #ifdef __HAVE_PFNMAP_TRACKING
> > struct pfnmap_track_ctx *pfnmap_track_ctx;
> > #endif
> > +#ifdef CONFIG_SPLIT_I_MMAP
> > + int tree_idx; /* The sibling tree index for the VMA */
> > +#endif
>
> FTR the struct hole isn't here, but right after vm_lock_seq or vm_refcnt in
> most configs.
okay, thanks.
I did not notice the struct hole issue.
>
> > } __randomize_layout;
> >
> > /* Clears all bits in the VMA flags bitmap, non-atomically. */
> > diff --git a/mm/internal.h b/mm/internal.h
> > index 5a2ddcf68e0b..2d35cacffd19 100644
> > --- a/mm/internal.h
> > +++ b/mm/internal.h
> > @@ -1888,7 +1888,8 @@ static inline void maybe_rmap_unlock_action(struct vm_area_struct *vma,
> >
> > VM_WARN_ON_ONCE(vma_is_anonymous(vma));
> > file = vma->vm_file;
> > - i_mmap_unlock_write(file->f_mapping);
> > + i_mmap_tree_unlock_write(file->f_mapping, vma);
> > + i_mmap_unlock_write_complete(file->f_mapping);
> > action->hide_from_rmap_until_complete = false;
> > }
> >
> > diff --git a/mm/mmap.c b/mm/mmap.c
> > index d714fdb357e5..70036ec9dcaa 100644
> > --- a/mm/mmap.c
> > +++ b/mm/mmap.c
> > @@ -1825,15 +1825,20 @@ __latent_entropy int dup_mmap(struct mm_struct *mm, struct mm_struct *oldmm)
> > struct address_space *mapping = file->f_mapping;
> >
> > get_file(file);
> > - i_mmap_lock_write(mapping);
> > + i_mmap_lock_write_prepare(mapping);
> > + i_mmap_tree_lock_write(mapping, mpnt);
> > +
> > if (vma_is_shared_maywrite(tmp))
> > mapping_allow_writable(mapping);
> > flush_dcache_mmap_lock(mapping);
> > /* insert tmp into the share list, just after mpnt */
> > vma_interval_tree_insert_after(tmp, mpnt,
> > - get_i_mmap_root(mapping));
> > + get_rb_root(mpnt, mapping));
> > + inc_mapping_vma(mapping, tmp);
>
> Honestly, would prefer to hide all of these details from mmap.
yes, we can.
But we need to change the functions in mm/interval_tree.c
>
> > flush_dcache_mmap_unlock(mapping);
> > - i_mmap_unlock_write(mapping);
> > +
> > + i_mmap_tree_unlock_write(mapping, mpnt);
> > + i_mmap_unlock_write_complete(mapping);
> > }
> >
> > if (!(tmp->vm_flags & VM_WIPEONFORK))
> > diff --git a/mm/nommu.c b/mm/nommu.c
> > index 0f18ffc658e9..1f2c60a220f6 100644
> > --- a/mm/nommu.c
> > +++ b/mm/nommu.c
> > @@ -567,11 +567,16 @@ static void setup_vma_to_mm(struct vm_area_struct *vma, struct mm_struct *mm)
> > if (vma->vm_file) {
> > struct address_space *mapping = vma->vm_file->f_mapping;
> >
> > - i_mmap_lock_write(mapping);
> > + i_mmap_lock_write_prepare(mapping);
> > + i_mmap_tree_lock_write(mapping, vma);
> > +
> > flush_dcache_mmap_lock(mapping);
> > - vma_interval_tree_insert(vma, get_i_mmap_root(mapping));
> > + vma_interval_tree_insert(vma, get_rb_root(vma, mapping));
> > + inc_mapping_vma(mapping, vma);
> > flush_dcache_mmap_unlock(mapping);
> > - i_mmap_unlock_write(mapping);
> > +
> > + i_mmap_tree_unlock_write(mapping, vma);
> > + i_mmap_unlock_write_complete(mapping);
> > }
> > }
> >
> > @@ -583,11 +588,16 @@ static void cleanup_vma_from_mm(struct vm_area_struct *vma)
> > struct address_space *mapping;
> > mapping = vma->vm_file->f_mapping;
> >
> > - i_mmap_lock_write(mapping);
> > + i_mmap_lock_write_prepare(mapping);
> > + i_mmap_tree_lock_write(mapping, vma);
> > +
> > flush_dcache_mmap_lock(mapping);
> > - vma_interval_tree_remove(vma, get_i_mmap_root(mapping));
> > + vma_interval_tree_remove(vma, get_rb_root(vma, mapping));
> > + dec_mapping_vma(mapping, vma);
> > flush_dcache_mmap_unlock(mapping);
> > - i_mmap_unlock_write(mapping);
> > +
> > + i_mmap_tree_unlock_write(mapping, vma);
> > + i_mmap_unlock_write_complete(mapping);
> > }
> > }
> >
> > @@ -1063,6 +1073,7 @@ unsigned long do_mmap(struct file *file,
> > if (file) {
> > region->vm_file = get_file(file);
> > vma->vm_file = get_file(file);
> > + vma_set_tree_idx(vma);
>
> This is unrelated, shouldn't be done here.
>
> > }
> >
> > down_write(&nommu_region_sem);
> > diff --git a/mm/pagewalk.c b/mm/pagewalk.c
> > index 8df1b5077951..d5745519d95a 100644
> > --- a/mm/pagewalk.c
> > +++ b/mm/pagewalk.c
> > @@ -809,7 +809,7 @@ int walk_page_mapping(struct address_space *mapping, pgoff_t first_index,
> > if (!check_ops_safe(ops))
> > return -EINVAL;
> >
> > - lockdep_assert_held(&mapping->i_mmap_rwsem);
> > + i_mmap_assert_locked(mapping);
>
> This kind of conversion should be done in a separate step.
>
> > vma_interval_tree_foreach(vma, get_i_mmap_root(mapping), first_index,
> > first_index + nr - 1) {
> > /* Clip to the vma */
> > diff --git a/mm/vma.c b/mm/vma.c
> > index 6159650c1b42..2055758064a9 100644
> > --- a/mm/vma.c
> > +++ b/mm/vma.c
> > @@ -234,22 +234,23 @@ static void __vma_link_file(struct vm_area_struct *vma,
> > mapping_allow_writable(mapping);
> >
> > flush_dcache_mmap_lock(mapping);
> > - vma_interval_tree_insert(vma, get_i_mmap_root(mapping));
> > + vma_interval_tree_insert(vma, get_rb_root(vma, mapping));
> > + inc_mapping_vma(mapping, vma);
>
> inc_mapping_vma() should probably be done implicitly by insertion?
Yes, we can.
It is more grace to hide it in vma_interval_tree_insert.
>
> > flush_dcache_mmap_unlock(mapping);
> > }
> >
> > -/*
> > - * Requires inode->i_mapping->i_mmap_rwsem
> > - */
> > static void __remove_shared_vm_struct(struct vm_area_struct *vma,
> > struct address_space *mapping)
> > {
> > + i_mmap_tree_lock_write(mapping, vma);
> > if (vma_is_shared_maywrite(vma))
> > mapping_unmap_writable(mapping);
> >
> > flush_dcache_mmap_lock(mapping);
> > - vma_interval_tree_remove(vma, get_i_mmap_root(mapping));
> > + vma_interval_tree_remove(vma, get_rb_root(vma, mapping));
> > + dec_mapping_vma(mapping, vma);
> > flush_dcache_mmap_unlock(mapping);
> > + i_mmap_tree_unlock_write(mapping, vma);
> > }
> >
> > /*
> > @@ -297,8 +298,9 @@ static void vma_prepare(struct vma_prepare *vp)
> > uprobe_munmap(vp->adj_next, vp->adj_next->vm_start,
> > vp->adj_next->vm_end);
> >
> > - i_mmap_lock_write(vp->mapping);
> > + i_mmap_lock_write_prepare(vp->mapping);
> > if (vp->insert && vp->insert->vm_file) {
> > + i_mmap_tree_lock_write(vp->mapping, vp->insert);
> > /*
> > * Put into interval tree now, so instantiated pages
> > * are visible to arm/parisc __flush_dcache_page
> > @@ -307,6 +309,7 @@ static void vma_prepare(struct vma_prepare *vp)
> > */
> > __vma_link_file(vp->insert,
> > vp->insert->vm_file->f_mapping);
> > + i_mmap_tree_unlock_write(vp->mapping, vp->insert);
> > }
> > }
> >
> > @@ -318,12 +321,17 @@ static void vma_prepare(struct vma_prepare *vp)
> > }
> >
> > if (vp->file) {
> > + i_mmap_tree_lock_write(vp->mapping, vp->vma);
> > flush_dcache_mmap_lock(vp->mapping);
> > vma_interval_tree_remove(vp->vma,
> > - get_i_mmap_root(vp->mapping));
> > - if (vp->adj_next)
> > + get_rb_root(vp->vma, vp->mapping));
> > + dec_mapping_vma(vp->mapping, vp->vma);
> > + if (vp->adj_next) {
> > + i_mmap_tree_lock_write(vp->mapping, vp->adj_next);
> > vma_interval_tree_remove(vp->adj_next,
> > - get_i_mmap_root(vp->mapping));
> > + get_rb_root(vp->adj_next, vp->mapping));
> > + dec_mapping_vma(vp->mapping, vp->adj_next);
> > + }
> > }
> >
> > }
> > @@ -340,12 +348,17 @@ static void vma_complete(struct vma_prepare *vp, struct vma_iterator *vmi,
> > struct mm_struct *mm)
> > {
> > if (vp->file) {
> > - if (vp->adj_next)
> > + if (vp->adj_next) {
> > vma_interval_tree_insert(vp->adj_next,
> > - get_i_mmap_root(vp->mapping));
> > + get_rb_root(vp->adj_next, vp->mapping));
> > + inc_mapping_vma(vp->mapping, vp->adj_next);
> > + i_mmap_tree_unlock_write(vp->mapping, vp->adj_next);
> > + }
> > vma_interval_tree_insert(vp->vma,
> > - get_i_mmap_root(vp->mapping));
> > + get_rb_root(vp->vma, vp->mapping));
> > + inc_mapping_vma(vp->mapping, vp->vma);
> > flush_dcache_mmap_unlock(vp->mapping);
> > + i_mmap_tree_unlock_write(vp->mapping, vp->vma);
> > }
> >
> > if (vp->remove && vp->file) {
> > @@ -370,7 +383,7 @@ static void vma_complete(struct vma_prepare *vp, struct vma_iterator *vmi,
> > }
> >
> > if (vp->file) {
> > - i_mmap_unlock_write(vp->mapping);
> > + i_mmap_unlock_write_complete(vp->mapping);
> >
> > if (!vp->skip_vma_uprobe) {
> > uprobe_mmap(vp->vma);
> > @@ -1799,12 +1812,12 @@ static void unlink_file_vma_batch_process(struct unlink_vma_file_batch *vb)
> > int i;
> >
> > mapping = vb->vmas[0]->vm_file->f_mapping;
> > - i_mmap_lock_write(mapping);
> > + i_mmap_lock_write_prepare(mapping);
> > for (i = 0; i < vb->count; i++) {
> > VM_WARN_ON_ONCE(vb->vmas[i]->vm_file->f_mapping != mapping);
> > __remove_shared_vm_struct(vb->vmas[i], mapping);
> > }
> > - i_mmap_unlock_write(mapping);
> > + i_mmap_unlock_write_complete(mapping);
> >
> > unlink_file_vma_batch_init(vb);
> > }
> > @@ -1836,10 +1849,13 @@ static void vma_link_file(struct vm_area_struct *vma, bool hold_rmap_lock)
> >
> > if (file) {
> > mapping = file->f_mapping;
> > - i_mmap_lock_write(mapping);
> > + i_mmap_lock_write_prepare(mapping);
> > + i_mmap_tree_lock_write(mapping, vma);
> > __vma_link_file(vma, mapping);
> > - if (!hold_rmap_lock)
> > - i_mmap_unlock_write(mapping);
> > + if (!hold_rmap_lock) {
> > + i_mmap_tree_unlock_write(mapping, vma);
> > + i_mmap_unlock_write_complete(mapping);
> > + }
> > }
> > }
> >
> > @@ -2164,6 +2180,23 @@ static void vm_lock_anon_vma(struct mm_struct *mm, struct anon_vma *anon_vma)
> > }
> > }
>
> I can but hope that all of the above is quite simplified before we get to the
> "making file rmap more complicated" bit.
:(
If we can do not care about the ARM device, we can make it simple.
Thanks
Huang Shijie
next prev parent reply other threads:[~2026-06-12 6:44 UTC|newest]
Thread overview: 17+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-06-11 6:18 [PATCH v2 0/4] mm: split the file's i_mmap tree for NUMA Huang Shijie
2026-06-11 6:18 ` [PATCH v2 1/4] mm: use mapping_mapped to simplify the code Huang Shijie
2026-06-11 11:13 ` Pedro Falcato
2026-06-11 15:52 ` Lorenzo Stoakes
2026-06-12 6:03 ` Huang Shijie
2026-06-11 6:18 ` [PATCH v2 2/4] mm: use get_i_mmap_root to access the file's i_mmap Huang Shijie
2026-06-11 6:31 ` sashiko-bot
2026-06-11 6:18 ` [PATCH v2 3/4] mm/fs: split the file's i_mmap tree Huang Shijie
2026-06-11 6:37 ` sashiko-bot
2026-06-11 11:11 ` Pedro Falcato
2026-06-11 15:48 ` Lorenzo Stoakes
2026-06-12 6:44 ` Huang Shijie [this message]
2026-06-11 6:19 ` [PATCH v2 4/4] docs/mm: update document for split " Huang Shijie
2026-06-11 6:26 ` sashiko-bot
2026-06-11 16:00 ` [PATCH v2 0/4] mm: split the file's i_mmap tree for NUMA Lorenzo Stoakes
2026-06-12 7:02 ` Huang Shijie
2026-06-11 20:24 ` [syzbot ci] " syzbot ci
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=aiuqvuK2z5OelVRM@hsj-2U-Workstation \
--to=huangsj@hygon.cn \
--cc=James.Bottomley@hansenpartnership.com \
--cc=acme@kernel.org \
--cc=adrian.hunter@intel.com \
--cc=akpm@linux-foundation.org \
--cc=alexander.shishkin@linux.intel.com \
--cc=baohua@kernel.org \
--cc=baolin.wang@linux.alibaba.com \
--cc=brauner@kernel.org \
--cc=brian.ruley@gehealthcare.com \
--cc=corbet@lwn.net \
--cc=dave.anglin@bell.net \
--cc=david@kernel.org \
--cc=deller@gmx.de \
--cc=dev.jain@arm.com \
--cc=dinguyen@kernel.org \
--cc=djbw@kernel.org \
--cc=fangbaoshun@hygon.cn \
--cc=harry@kernel.org \
--cc=irogers@google.com \
--cc=jack@suse.cz \
--cc=james.clark@linaro.org \
--cc=jannh@google.com \
--cc=jolsa@kernel.org \
--cc=lance.yang@linux.dev \
--cc=liam@infradead.org \
--cc=linmiaohe@huawei.com \
--cc=linux-arm-kernel@lists.infradead.org \
--cc=linux-doc@vger.kernel.org \
--cc=linux-fsdevel@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-mm@kvack.org \
--cc=linux-parisc@vger.kernel.org \
--cc=linux-perf-users@vger.kernel.org \
--cc=linux-trace-kernel@vger.kernel.org \
--cc=linux@armlinux.org.uk \
--cc=ljs@kernel.org \
--cc=mark.rutland@arm.com \
--cc=mhiramat@kernel.org \
--cc=mhocko@suse.com \
--cc=mingo@redhat.com \
--cc=mjguzik@gmail.com \
--cc=muchun.song@linux.dev \
--cc=namhyung@kernel.org \
--cc=nao.horiguchi@gmail.com \
--cc=npache@redhat.com \
--cc=nvdimm@lists.linux.dev \
--cc=oleg@redhat.com \
--cc=osalvador@suse.de \
--cc=peterz@infradead.org \
--cc=pfalcato@suse.de \
--cc=riel@surriel.com \
--cc=rmk+kernel@armlinux.org.uk \
--cc=rppt@kernel.org \
--cc=ryan.roberts@arm.com \
--cc=schuster.simon@siemens-energy.com \
--cc=shakeel.butt@linux.dev \
--cc=skhan@linuxfoundation.org \
--cc=surenb@google.com \
--cc=vbabka@kernel.org \
--cc=viro@zeniv.linux.org.uk \
--cc=will@kernel.org \
--cc=willy@infradead.org \
--cc=yingzhiwei@hygon.cn \
--cc=zhongyuan@hygon.cn \
--cc=ziy@nvidia.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.