[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <164796a6-c9aa-423b-ba2e-5c6093e3d559@lucifer.local>
Date: Mon, 4 Nov 2024 14:17:36 +0000
From: Lorenzo Stoakes <lorenzo.stoakes@...cle.com>
To: Mike Rapoport <rppt@...nel.org>
Cc: Jonathan Corbet <corbet@....net>,
Andrew Morton <akpm@...ux-foundation.org>,
"Liam R . Howlett" <Liam.Howlett@...cle.com>,
Vlastimil Babka <vbabka@...e.cz>, Jann Horn <jannh@...gle.com>,
Alice Ryhl <aliceryhl@...gle.com>, Boqun Feng <boqun.feng@...il.com>,
Matthew Wilcox <willy@...radead.org>, linux-mm@...ck.org,
linux-kernel@...r.kernel.org
Subject: Re: [RFC PATCH] docs/mm: add VMA locks documentation
On Sat, Nov 02, 2024 at 11:00:20AM +0200, Mike Rapoport wrote:
> On Fri, Nov 01, 2024 at 06:50:33PM +0000, Lorenzo Stoakes wrote:
> > Locking around VMAs is complicated and confusing. While we have a number of
> > disparate comments scattered around the place, we seem to be reaching a
> > level of complexity that justifies a serious effort at clearly documenting
> > how locks are expected to be interacted with when it comes to interacting
> > with mm_struct and vm_area_struct objects.
> >
> > This is especially pertinent as regards efforts to find sensible
> > abstractions for these fundamental objects within the kernel rust
> > abstraction whose compiler strictly requires some means of expressing these
> > rules (and through this expression can help self-document these
> > requirements as well as enforce them which is an exciting concept).
> >
> > The document limits scope to mmap and VMA locks and those that are
> > immediately adjacent and relevant to them - so additionally covers page
> > table locking as this is so very closely tied to VMA operations (and relies
> > upon us handling these correctly).
> >
> > The document tries to cover some of the nastier and more confusing edge
> > cases and concerns especially around lock ordering and page table teardown.
> >
> > The document also provides some VMA lock internals, which are up to date
> > and inclusive of recent changes to recent sequence number changes.
> >
> > Signed-off-by: Lorenzo Stoakes <lorenzo.stoakes@...cle.com>
> > ---
> >
> > REVIEWERS NOTES:
> > You can speed up doc builds by running `make SPHINXDIRS=mm htmldocs`. I
> > also uploaded a copy of this to my website at
> > https://ljs.io/output/mm/vma_locks to make it easier to have a quick
> > read through. Thanks!
> >
> >
> > Documentation/mm/index.rst | 1 +
> > Documentation/mm/vma_locks.rst | 527 +++++++++++++++++++++++++++++++++
> > 2 files changed, 528 insertions(+)
> > create mode 100644 Documentation/mm/vma_locks.rst
> >
> > diff --git a/Documentation/mm/index.rst b/Documentation/mm/index.rst
> > index 0be1c7503a01..da5f30acaca5 100644
> > --- a/Documentation/mm/index.rst
> > +++ b/Documentation/mm/index.rst
> > @@ -64,3 +64,4 @@ documentation, or deleted if it has served its purpose.
> > vmemmap_dedup
> > z3fold
> > zsmalloc
> > + vma_locks
>
> Please keep the TOC sorted alphabetically.
Ack, as per response to SJ, will address.
>
> > diff --git a/Documentation/mm/vma_locks.rst b/Documentation/mm/vma_locks.rst
> > new file mode 100644
> > index 000000000000..52b9d484376a
> > --- /dev/null
> > +++ b/Documentation/mm/vma_locks.rst
> > @@ -0,0 +1,527 @@
> > +VMA Locking
> > +===========
> > +
> > +Overview
> > +--------
> > +
> > +Userland memory ranges are tracked by the kernel via Virtual Memory Areas or
> > +'VMA's of type `struct vm_area_struct`.
> > +
> > +Each VMA describes a virtually contiguous memory range with identical
> > +attributes, each of which described by a `struct vm_area_struct`
> > +object. Userland access outside of VMAs is invalid except in the case where an
> > +adjacent stack VMA could be extended to contain the accessed address.
> > +
> > +All VMAs are contained within one and only one virtual address space, described
> > +by a `struct mm_struct` object which is referenced by all tasks (that is,
> > +threads) which share the virtual address space. We refer to this as the `mm`.
> > +
> > +Each mm object contains a maple tree data structure which describes all VMAs
> > +within the virtual address space.
> > +
> > +The kernel is designed to be highly scalable against concurrent access to
> > +userland memory,
>
> "and concurrent changes to the virtual address space layoyt"?
Well, not sure that's quite true to be honest, because we go out of our way to
exclude other users when we change the address space layout. Really I was
getting at the fact you can have simultaneous readers and especially, with VMA
locks, simultaneous fault handlers.
Will update to reference read operations against VMAs.
>
> > so a complicated set of locks are required to ensure no data
> > +races or memory corruption occurs.
> > +
> > +This document explores this locking in detail.
> > +
> > +.. note::
> > +
> > + There are three different things that a user might want to achieve via
> > + locks - the first of which is **stability**. That is - ensuring that the VMA
> > + won't be freed or modified in any way from underneath us.
> > +
> > + All MM and VMA locks ensure stability.
> > +
> > + Secondly we have locks which allow **reads** but not writes (and which might
> > + be held concurrent with other CPUs who also hold the read lock).
>
> I think it should be clarified here that *reads* are from data structures
> rather than user memory.
Ack will update.
>
> > +
> > + Finally, we have locks which permit exclusive access to the VMA to allow for
>
> ^ object
> > + **writes** to the VMA.
Ack
> > +
> > +MM and VMA locks
> > +----------------
> > +
> > +There are two key classes of lock utilised when reading and manipulating VMAs -
> > +the `mmap_lock` which is a read/write semaphore maintained at the `mm_struct`
> > +level of granularity and, if CONFIG_PER_VMA_LOCK is set, a per-VMA lock at the
> > +VMA level of granularity.
> > +
> > +.. note::
> > +
> > + Generally speaking, a read/write semaphore is a class of lock which permits
> > + concurrent readers. However a write lock can only be obtained once all
> > + readers have left the critical region (and pending readers made to wait).
> > +
> > + This renders read locks on a read/write semaphore concurrent with other
> > + readers and write locks exclusive against all others holding the semaphore.
> > +
> > +If CONFIG_PER_VMA_LOCK is not set, then things are relatively simple - a write
> > +mmap lock gives you exclusive write access to a VMA, and a read lock gives you
> > +concurrent read-only access.
> > +
> > +In the presence of CONFIG_PER_VMA_LOCK, i.e. VMA locks, things are more
> > +complicated. In this instance, a write semaphore is no longer enough to gain
> > +exclusive access to a VMA, a VMA write lock is also required.
> > +
> > +The VMA lock is implemented via the use of both a read/write semaphore and
> > +per-VMA and per-mm sequence numbers. We go into detail on this in the VMA lock
> > +internals section below, so for the time being it is important only to note that
> > +we can obtain either a VMA read or write lock.
> > +
> > +.. note::
>
> I don't think the below text should be a "note", I'd just keep at
> continuation of the section
Ack will change.
>
> > +
> > + VMAs under VMA **read** lock are obtained by the `lock_vma_under_rcu()`
> > + function, and **no** existing mmap or VMA lock must be held, This function
> > + either returns a read-locked VMA, or NULL if the lock could not be
> > + acquired. As the name suggests, the VMA will be acquired under RCU, though
> > + once obtained, remains stable.
> > +
> > + This kind of locking is entirely optimistic - if the lock is contended or a
> > + competing write has started, then we do not obtain a read lock.
> > +
> > + The `lock_vma_under_rcu()` function first calls `rcu_read_lock()` to ensure
> > + that the VMA is acquired in an RCU critical section, then attempts to VMA
> > + lock it via `vma_start_read()`, before releasing the RCU lock via
> > + `rcu_read_unlock()`.
> > +
> > + VMA read locks hold the a read lock on the `vma->vm_lock` semaphore for their
>
> ^ no idea if it should be 'a' or 'the', but surely
> not both :)
Sometimes you have to put both _just to be sure_ ;) nah only joking, will drop
the 'a'...
>
> > + duration and the caller of `lock_vma_under_rcu()` must release it via
> > + `vma_end_read()`.
> > +
> > + VMA **write** locks are acquired via `vma_start_write()` in instances where a
> > + VMA is about to be modified, unlike `vma_start_read()` the lock is always
> > + acquired. An mmap write lock **must** be held for the duration of the VMA
> > + write lock, releasing or downgrading the mmap write lock also releases the
> > + VMA write lock so there is no `vma_end_write()` function.
> > +
> > + Note that a semaphore write lock is not held across a VMA lock. Rather, a
> > + sequence number is used for serialisation, and the write semaphore is only
> > + acquired at the point of write lock to update this (we explore this in detail
> > + in the VMA lock internals section below).
> > +
> > + This ensures the semantics we require - VMA write locks provide exclusive
> > + write access to the VMA.
> > +
> > +Examining all valid lock state and what each implies:
> > +
> > +.. list-table::
> > + :header-rows: 1
>
> Can we make it just .. table:
I didn't know you could do tables. I am new to rst... :) Will do!
>
> .. table::
>
> ========= ======== ======= ================ =================
> mmap lock VMA lock Stable? Can read safely? Can write safely?
> ========= ======== ======= ================ =================
> \- \- N N N
> R \- Y Y N
> \- R Y Y N
> W \- Y Y N
> W W Y Y Y
> ========= ======== ======= ================ =================
>
> > +
> > + * - mmap lock
> > + - VMA lock
> > + - Stable?
> > + - Can read safely?
> > + - Can write safely?
> > + * - \-
> > + - \-
> > + - N
> > + - N
> > + - N
> > + * - R
> > + - \-
> > + - Y
> > + - Y
> > + - N
> > + * - \-
> > + - R
> > + - Y
> > + - Y
> > + - N
> > + * - W
> > + - \-
> > + - Y
> > + - Y
> > + - N
> > + * - W
> > + - W
> > + - Y
> > + - Y
> > + - Y
> > +
> > +Note that there are some exceptions to this - the `anon_vma` field is permitted
> > +to be written to under mmap read lock and is instead serialised by the `struct
> > +mm_struct` field `page_table_lock`. In addition the `vm_mm` and all
> > +lock-specific fields are permitted to be read under RCU alone (though stability cannot
> > +be expected in this instance).
> > +
> > +.. note::
> > + The most notable place to use the VMA read lock is on page table faults on
> > + the x86-64 architecture, which importantly means that without a VMA write
> > + lock, page faults can race against you even if you hold an mmap write lock.
> > +
> > +VMA Fields
> > +----------
> > +
> > +We examine each field of the `struct vm_area_struct` type in detail in the table
> > +below.
> > +
> > +Reading of each field requires either an mmap read lock or a VMA read lock to be
> > +held, except where 'unstable RCU read' is specified, in which case unstable
> > +access to the field is permitted under RCU alone.
> > +
> > +The table specifies which write locks must be held to write to the field.
> > +
> > +.. list-table::
> > + :widths: 20 10 22 5 20
> > + :header-rows: 1
>
> And use .. table here as well, e.g
Hm this one is a little less clearly worth it because not only will that take me
ages but it'll be quite difficult to read in a sensible editor. I can if you
insist though?
>
> .. table::
>
> ======== ======== ========================== ================== ==========
> Field Config Description Unstable RCU read? Write lock
> ======== ======== ========================== ================== ==========
> vm_start Inclusive start virtual mmap write,
> address of range VMA VMA write
> describes
>
> vm_end Exclusive end virtual mmap write,
> address of range VMA VMA write
> describes
>
> vm_rcu vma_lock RCU list head, in union N/A N/A
> with vma_start, vma_end.
> RCU implementation detail
> ======== ======== ========================== ================== ==========
>
>
> > +
> > + * - Field
> > + - Config
> > + - Description
> > + - Unstable RCU read?
> > + - Write Lock
> > + * - vm_start
> > + -
> > + - Inclusive start virtual address of range VMA describes.
> > + -
> > + - mmap write, VMA write
> > + * - vm_end
> > + -
> > + - Exclusive end virtual address of range VMA describes.
> > + -
> > + - mmap write, VMA write
> > + * - vm_rcu
> > + - vma lock
> > + - RCU list head, in union with vma_start, vma_end. RCU implementation detail.
> > + - N/A
> > + - N/A
> > + * - vm_mm
> > + -
> > + - Containing mm_struct.
> > + - Y
> > + - (Static)
> > + * - vm_page_prot
> > + -
> > + - Architecture-specific page table protection bits determined from VMA
> > + flags
> > + -
> > + - mmap write, VMA write
> > + * - vm_flags
> > + -
> > + - Read-only access to VMA flags describing attributes of VMA, in union with
> > + private writable `__vm_flags`.
> > + -
> > + - N/A
> > + * - __vm_flags
> > + -
> > + - Private, writable access to VMA flags field, updated by vm_flags_*()
> > + functions.
> > + -
> > + - mmap write, VMA write
> > + * - detached
> > + - vma lock
> > + - VMA lock implementation detail - indicates whether the VMA has been
> > + detached from the tree.
> > + - Y
> > + - mmap write, VMA write
> > + * - vm_lock_seq
> > + - vma lock
> > + - VMA lock implementation detail - A sequence number used to serialise the
> > + VMA lock, see the VMA lock section below.
> > + - Y
> > + - mmap write, VMA write
> > + * - vm_lock
> > + - vma lock
> > + - VMA lock implementation detail - A pointer to the VMA lock read/write
> > + semaphore.
> > + - Y
> > + - None required
> > + * - shared.rb
> > + -
> > + - A red/black tree node used, if the mapping is file-backed, to place the
> > + VMA in the `struct address_space->i_mmap` red/black interval tree.
> > + -
> > + - mmap write, VMA write, i_mmap write
> > + * - shared.rb_subtree_last
> > + -
> > + - Metadata used for management of the interval tree if the VMA is
> > + file-backed.
> > + -
> > + - mmap write, VMA write, i_mmap write
> > + * - anon_vma_chain
> > + -
> > + - List of links to forked/CoW'd `anon_vma` objects.
> > + -
> > + - mmap read or above, anon_vma write lock
> > + * - anon_vma
> > + -
> > + - `anon_vma` object used by anonymous folios mapped exclusively to this VMA.
> > + -
> > + - mmap read or above, page_table_lock
> > + * - vm_ops
> > + -
> > + - If the VMA is file-backed, then either the driver or file-system provides
> > + a `struct vm_operations_struct` object describing callbacks to be invoked
> > + on specific VMA lifetime events.
> > + -
> > + - (Static)
> > + * - vm_pgoff
> > + -
> > + - Describes the page offset into the file, the original page offset within
> > + the virtual address space (prior to any `mremap()`), or PFN if a PFN map.
> > + -
> > + - mmap write, VMA write
> > + * - vm_file
> > + -
> > + - If the VMA is file-backed, points to a `struct file` object describing
> > + the underlying file, if anonymous then `NULL`.
> > + -
> > + - (Static)
> > + * - vm_private_data
> > + -
> > + - A `void *` field for driver-specific metadata.
> > + -
> > + - Driver-mandated.
> > + * - anon_name
> > + - anon name
> > + - A field for storing a `struct anon_vma_name` object providing a name for
> > + anonymous mappings, or `NULL` if none is set or the VMA is file-backed.
> > + -
> > + - mmap write, VMA write
> > + * - swap_readahead_info
> > + - swap
> > + - Metadata used by the swap mechanism to perform readahead.
> > + -
> > + - mmap read
> > + * - vm_region
> > + - nommu
> > + - The containing region for the VMA for architectures which do not
> > + possess an MMU.
> > + - N/A
> > + - N/A
> > + * - vm_policy
> > + - numa
> > + - `mempolicy` object which describes NUMA behaviour of the VMA.
> > + -
> > + - mmap write, VMA write
> > + * - numab_state
> > + - numab
> > + - `vma_numab_state` object which describes the current state of NUMA
> > + balancing in relation to this VMA.
> > + -
> > + - mmap write, VMA write
> > + * - vm_userfaultfd_ctx
> > + -
> > + - Userfaultfd context wrapper object of type `vm_userfaultfd_ctx`, either
> > + of zero size if userfaultfd is disabled, or containing a pointer to an
> > + underlying `userfaultfd_ctx` object which describes userfaultfd metadata.
> > + -
> > + - mmap write, VMA write
> > +
> > +.. note::
> > +
> > + In the config column 'vma lock' configuration means CONFIG_PER_VMA_LOCK,
> > + 'anon name' means CONFIG_ANON_VMA_NAME, 'swap' means CONFIG_SWAP, 'nommu'
> > + means that CONFIG_MMU is not set, 'numa' means CONFIG_NUMA and 'numab' means
> > + CONFIG_NUMA_BALANCING'.
> > +
> > + In the write lock column '(Static)' means that the field is set only once
> > + upon initialisation of the VMA and not changed after this, the VMA would
> > + either have been under an mmap write and VMA write lock at the time or not
> > + yet inserted into any tree.
> > +
> > +Page table locks
> > +----------------
> > +
> > +When allocating a P4D, PUD or PMD and setting the relevant entry in the above
> > +PGD, P4D or PUD, the `mm->page_table_lock` is acquired to do so. This is
> > +acquired in `__p4d_alloc()`, `__pud_alloc()` and `__pmd_alloc()` respectively.
> > +
> > +.. note::
> > + `__pmd_alloc()` actually invokes `pud_lock()` and `pud_lockptr()` in turn,
> > + however at the time of writing it ultimately references the
> > + `mm->page_table_lock`.
> > +
> > +Allocating a PTE will either use the `mm->page_table_lock` or, if
> > +`USE_SPLIT_PMD_PTLOCKS` is defined, used a lock embedded in the PMD physical
> > +page metadata in the form of a `struct ptdesc`, acquired by `pmd_ptdesc()`
> > +called from `pmd_lock()` and ultimately `__pte_alloc()`.
> > +
> > +Finally, modifying the contents of the PTE has special treatment, as this is a
> > +lock that we must acquire whenever we want stable and exclusive access to
> > +entries pointing to data pages within a PTE, especially when we wish to modify
> > +them.
> > +
> > +This is performed via `pte_offset_map_lock()` which carefully checks to ensure
> > +that the PTE hasn't changed from under us, ultimately invoking `pte_lockptr()`
> > +to obtain a spin lock at PTE granularity contained within the `struct ptdesc`
> > +associated with the physical PTE page. The lock must be released via
> > +`pte_unmap_unlock()`.
> > +
> > +.. note::
> > + There are some variants on this, such as `pte_offset_map_rw_nolock()` when we
> > + know we hold the PTE stable but for brevity we do not explore this.
> > + See the comment for `__pte_offset_map_lock()` for more details.
> > +
> > +When modifying data in ranges we typically only wish to allocate higher page
> > +tables as necessary, using these locks to avoid races or overwriting anything,
> > +and set/clear data at the PTE level as required (for instance when page faulting
> > +or zapping).
> > +
> > +Page table teardown
> > +-------------------
> > +
> > +Tearing down page tables themselves is something that requires significant
> > +care. There must be no way that page tables designated for removal can be
> > +traversed or referenced by concurrent tasks.
> > +
> > +It is insufficient to simply hold an mmap write lock and VMA lock (which will
> > +prevent racing faults, and rmap operations), as a file-backed mapping can be
> > +truncated under the `struct address_space` i_mmap_lock alone.
> > +
> > +As a result, no VMA which can be accessed via the reverse mapping (either
> > +anon_vma or the `struct address_space->i_mmap` interval tree) can have its page
> > +tables torn down.
> > +
> > +The operation is typically performed via `free_pgtables()`, which assumes either
> > +the mmap write lock has been taken (as specified by its `mm_wr_locked`
> > +parameter), or that it the VMA is fully detached.
>
> "or that the VMA is..." ?
>
> > +It carefully removes the VMA from all reverse mappings, however it's important
> > +that no new ones overlap these or any route remain to permit access to addresses
> > +within the range whose page tables are being torn down.
> > +
> > +As a result of these careful conditions, note that page table entries are
> > +cleared without page table locks, as it is assumed that all of these precautions
> > +have already been taken.
> > +
> > +mmap write lock downgrading
> > +---------------------------
> > +
> > +While it is possible to obtain an mmap write or read lock using the
> > +`mm->mmap_lock` read/write semaphore, it is also possible to **downgrade** from
> > +a write lock to a read lock via `mmap_write_downgrade()`.
> > +
> > +Similar to `mmap_write_unlock()`, this implicitly terminates all VMA write locks
> > +via `vma_end_write_all()` (more or this behaviour in the VMA lock internals
> > +section below), but importantly does not relinquish the mmap lock while
> > +downgrading, therefore keeping the locked virtual address space stable.
> > +
> > +A subtlety here is that callers can assume, if they invoke an
> > +mmap_write_downgrade() operation, that they still have exclusive access to the
> > +virtual address space (excluding VMA read lock holders), as for another task to
> > +have downgraded they would have had to have exclusive access to the semaphore
> > +which can't be the case until the current task completes what it is doing.
> > +
> > +Stack expansion
> > +---------------
> > +
> > +Stack expansion throws up additional complexities in that we cannot permit there
> > +to be racing page faults, as a result we invoke `vma_start_write()` to prevent
> > +this in `expand_downwards()` or `expand_upwards()`.
> > +
> > +Lock ordering
> > +-------------
> > +
> > +As we have multiple locks across the kernel which may or may not be taken at the
> > +same time as explicit mm or VMA locks, we have to be wary of lock inversion, and
> > +the **order** in which locks are acquired and released becomes very important.
> > +
> > +.. note::
> > +
> > + Lock inversion occurs when two threads need to acquire multiple locks,
> > + but in doing so inadvertently cause a mutual deadlock.
> > +
> > + For example, consider thread 1 which holds lock A and tries to acquire lock B,
> > + while thread 2 holds lock B and tries to acquire lock A.
> > +
> > + Both threads are now deadlocked on each other. However, had they attempted to
> > + acquire locks in the same order, one would have waited for the other to
> > + complete its work and no deadlock would have occurred.
> > +
> > +The opening comment in `mm/rmap.c` describes in detail the required ordering of
> > +locks within memory management code:
> > +
> > +.. code-block::
> > +
> > + inode->i_rwsem (while writing or truncating, not reading or faulting)
> > + mm->mmap_lock
> > + mapping->invalidate_lock (in filemap_fault)
> > + folio_lock
> > + hugetlbfs_i_mmap_rwsem_key (in huge_pmd_share, see hugetlbfs below)
> > + vma_start_write
> > + mapping->i_mmap_rwsem
> > + anon_vma->rwsem
> > + mm->page_table_lock or pte_lock
> > + swap_lock (in swap_duplicate, swap_info_get)
> > + mmlist_lock (in mmput, drain_mmlist and others)
> > + mapping->private_lock (in block_dirty_folio)
> > + i_pages lock (widely used)
> > + lruvec->lru_lock (in folio_lruvec_lock_irq)
> > + inode->i_lock (in set_page_dirty's __mark_inode_dirty)
> > + bdi.wb->list_lock (in set_page_dirty's __mark_inode_dirty)
> > + sb_lock (within inode_lock in fs/fs-writeback.c)
> > + i_pages lock (widely used, in set_page_dirty,
> > + in arch-dependent flush_dcache_mmap_lock,
> > + within bdi.wb->list_lock in __sync_single_inode)
> > +
> > +Please check the current state of this comment which may have changed since the
> > +time of writing of this document.
> > +
> > +VMA lock internals
> > +------------------
> > +
> > +The VMA lock mechanism is designed to be a lightweight means of avoiding the use
> > +of the heavily contended mmap lock. It is implemented using a combination of a
> > +read/write semaphore and sequence numbers belonging to the containing `struct
> > +mm_struct` and the VMA.
> > +
> > +Read locks are acquired via `vma_start_read()`, which is an optimistic
> > +operation, i.e. it tries to acquire a read lock but returns false if it is
> > +unable to do so. At the end of the read operation, `vma_end_read()` is called to
> > +release the VMA read lock. This can be done under RCU alone.
> > +
> > +Writing requires the mmap to be write-locked and the VMA lock to be acquired via
> > +`vma_start_write()`, however the write lock is released by the termination or
> > +downgrade of the mmap write lock so no `vma_end_write()` is required.
> > +
> > +All this is achieved by the use of per-mm and per-VMA sequence counts. This is
> > +used to reduce complexity, and potential especially around operations which
> > +write-lock multiple VMAs at once.
> > +
> > +If the mm sequence count, `mm->mm_lock_seq` is equal to the VMA sequence count
> > +`vma->vm_lock_seq` then the VMA is write-locked. If they differ, then they are
> > +not.
> > +
> > +Each time an mmap write lock is acquired in `mmap_write_lock()`,
> > +`mmap_write_lock_nested()`, `mmap_write_lock_killable()`, the `mm->mm_lock_seq`
> > +sequence number is incremented via `mm_lock_seqcount_begin()`.
> > +
> > +Each time the mmap write lock is released in `mmap_write_unlock()` or
> > +`mmap_write_downgrade()`, `vma_end_write_all()` is invoked which also increments
> > +`mm->mm_lock_seq` via `mm_lock_seqcount_end()`.
> > +
> > +This way, we ensure regardless of the VMA's sequence number count, that a write
> > +lock is not incorrectly indicated (since we increment the sequence counter on
> > +acquiring the mmap write lock, which is required in order to obtain a VMA write
> > +lock), and that when we release an mmap write lock, we efficiently release
> > +**all** VMA write locks contained within the mmap at the same time.
> > +
> > +The exclusivity of the mmap write lock ensures this is what we want, as there
> > +would never be a reason to persist per-VMA write locks across multiple mmap
> > +write lock acquisitions.
> > +
> > +Each time a VMA read lock is acquired, we acquire a read lock on the
> > +`vma->vm_lock` read/write semaphore and hold it, while checking that the
> > +sequence count of the VMA does not match that of the mm.
> > +
> > +If it does, the read lock fails. If it does not, we hold the lock, excluding
> > +writers, but permitting other readers, who will also obtain this lock under RCU.
> > +
> > +Importantly, maple tree operations performed in `lock_vma_under_rcu()` are also
> > +RCU safe, so the whole read lock operation is guaranteed to function correctly.
> > +
> > +On the write side, we acquire a write lock on the `vma->vm_lock` read/write
> > +semaphore, before setting the VMA's sequence number under this lock, also
> > +simultaneously holding the mmap write lock.
> > +
> > +This way, if any read locks are in effect, `vma_start_write()` will sleep until
> > +these are finished and mutual exclusion is achieved.
> > +
> > +After setting the VMA's sequence number, the lock is released, avoiding
> > +complexity with a long-term held write lock.
> > +
> > +This clever combination of a read/write semaphore and sequence count allows for
> > +fast RCU-based per-VMA lock acquisition (especially on x86-64 page fault, though
> > +utilised elsewhere) with minimal complexity around lock ordering.
> > --
> > 2.47.0
>
> --
> Sincerely yours,
> Mike.
Powered by blists - more mailing lists