userfaultfd move option
Subsystems (8)
Suren Baghdasaryan
reviewerv4 cover userfaultfd move option
This patch series introduces UFFDIO_MOVE feature to userfaultfd, which has long been implemented and maintained by Andrea in his local tree [1], but was not upstreamed due to lack of use cases where this approach would be better than allocating a new page and copying the contents. Previous upstraming attempts could be found at [6] and [7].
UFFDIO_COPY performs ~20% better than UFFDIO_MOVE when the application needs pages to be allocated [2]. However, with UFFDIO_MOVE, if pages are available (in userspace) for recycling, as is usually the case in heap compaction algorithms, then we can avoid the page allocation and memcpy (done by UFFDIO_COPY). Also, since the pages are recycled in the userspace, we avoid the need to release (via madvise) the pages back to the kernel [3].
We see over 40% reduction (on a Google pixel 6 device) in the compacting thread’s completion time by using UFFDIO_MOVE vs. UFFDIO_COPY. This was measured using a benchmark that emulates a heap compaction implementation using userfaultfd (to allow concurrent accesses by application threads). More details of the usecase are explained in [3].
Furthermore, UFFDIO_MOVE enables moving swapped-out pages without touching them within the same vma. Today, it can only be done by mremap, however it forces splitting the vma.
TODOs for follow-up improvements:
- cross-mm support. Known differences from single-mm and missing pieces:
- memcg recharging (might need to isolate pages in the process)
- mm counters
- cross-mm deposit table moves
- cross-mm test
- document the address space where src and dest reside in struct uffdio_move
- TLB flush batching. Will require extensive changes to PTL locking in move_pages_pte(). OTOH that might let us reuse parts of mremap code.
Changes since v3 [8]:
- changed retry path in folio_lock_anon_vma_read() to unlock and then relock RCU, per Peter Xu
- removed cross-mm support from initial patchset, per David Hildenbrand
- replaced BUG_ONs with VM_WARN_ON or WARN_ON_ONCE, per David Hildenbrand
- added missing cache flushing, per Lokesh Gidra and Peter Xu
- updated manpage text in the patch description, per Peter Xu
- renamed internal functions from "remap" to "move", per Peter Xu
- added mmap_changing check after taking mmap_lock, per Peter Xu
- changed uffd context check to ensure dst_mm is registered onto uffd we are operating on, Peter Xu and David Hildenbrand
- changed to non-maybe variants of maybe*_mkwrite(), per David Hildenbrand
- fixed warning for CONFIG_TRANSPARENT_HUGEPAGE=n, per kernel test robot
- comments cleanup, per David Hildenbrand and Peter Xu
- checks for VM_IO,VM_PFNMAP,VM_HUGETLB,..., per David Hildenbrand
- prevent moving pinned pages, per Peter Xu
- changed uffd tests to call move uffd_test_ctx_clear() at the end of the test run instead of in the beginning of the next run
- added support for testcase-specific ops
- added test for moving PMD-aligned blocks
Changes since v2 [5]:
- renamed UFFDIO_REMAP to UFFDIO_MOVE, per David Hildenbrand
- rebase over mm-unstable to use folio_move_anon_rmap(),
per David Hildenbrand
- added text for manpage explaining DONTFORK and KSM requirements for this feature, per David Hildenbrand
- check for anon_vma changes in the fast path of folio_lock_anon_vma_read, per Peter Xu
- updated the title and description of the first patch,
per David Hildenbrand
- updating comments in folio_lock_anon_vma_read() explaining the need for anon_vma checks, per David Hildenbrand
- changed all mapcount checks to PageAnonExclusive, per Jann Horn and David Hildenbrand
- changed counters in remap_swap_pte() from MM_ANONPAGES to MM_SWAPENTS, per Jann Horn
- added a check for PTE change after folio is locked in remap_pages_pte(), per Jann Horn
- added handling of PMD migration entries and bailout when pmd_devmap(), per Jann Horn
- added checks to ensure both src and dst VMAs are writable, per Peter Xu
- added UFFD_FEATURE_MOVE, per Peter Xu
- removed obsolete comments, per Peter Xu
- renamed remap_anon_pte to remap_present_pte, per Peter Xu
- added a comment for folio_get_anon_vma() explaining the need for anon_vma checks, per Peter Xu
- changed error handling in remap_pages() to make it more clear, per Peter Xu
- changed EFAULT to EAGAIN to retry when a hugepage appears or disappears from under us, per Peter Xu
- added links to previous upstreaming attempts, per David Hildenbrand
Changes since v1 [4]:
- add mmget_not_zero in userfaultfd_remap, per Jann Horn
- removed extern from function definitions, per Matthew Wilcox
- converted to folios in remap_pages_huge_pmd, per Matthew Wilcox
- use PageAnonExclusive in remap_pages_huge_pmd, per David Hildenbrand
- handle pgtable transfers between MMs, per Jann Horn
- ignore concurrent A/D pte bit changes, per Jann Horn
- split functions into smaller units, per David Hildenbrand
- test for folio_test_large in remap_anon_pte, per Matthew Wilcox
- use pte_swp_exclusive for swapcount check, per David Hildenbrand
- eliminated use of mmu_notifier_invalidate_range_start_nonblock, per Jann Horn
- simplified THP alignment checks, per Jann Horn
- refactored the loop inside remap_pages, per Jann Horn
- additional clarifying comments, per Jann Horn
Main changes since Andrea's last version [1]:
- Trivial translations from page to folio, mmap_sem to mmap_lock
- Replace pmd_trans_unstable() with pte_offset_map_nolock() and handle its possible failure
- Move pte mapping into remap_pages_pte to allow for retries when source page or anon_vma is contended. Since pte_offset_map_nolock() start RCU read section, we can't block anymore after mapping a pte, so have to unmap the ptesm do the locking and retry.
- Add and use anon_vma_trylock_write() to avoid blocking while in RCU read section.
- Accommodate changes in mmu_notifier_range_init() API, switch to mmu_notifier_invalidate_range_start_nonblock() to avoid blocking while in RCU read section.
- Open-code now removed __swp_swapcount()
- Replace pmd_read_atomic() with pmdp_get_lockless()
- Add new selftest for UFFDIO_MOVE
[1] https://gitlab.com/aarcange/aa/-/commit/2aec7aea56b10438a3881a20a411aa4b1fc19e92
[2] https://lore.kernel.org/all/[email protected]/
[3] https://lore.kernel.org/linux-mm/CA+EESO4uO84SSnBhArH4HvLNhaUQ5nZKNKXqxRCyjniNVjp0Aw@mail.gmail.com/
[4] https://lore.kernel.org/all/[email protected]/
[5] https://lore.kernel.org/all/[email protected]/
[6] https://lore.kernel.org/all/[email protected]/
[7] https://lore.kernel.org/all/[email protected]/
[8] https://lore.kernel.org/all/[email protected]/
The patchset applies over mm-unstable.
Andrea Arcangeli (2):
mm/rmap: support move to different root anon_vma in
folio_move_anon_rmap()
userfaultfd: UFFDIO_MOVE uABI
Suren Baghdasaryan (3):
selftests/mm: call uffd_test_ctx_clear at the end of the test selftests/mm: add uffd_test_case_ops to allow test case-specific
operations
selftests/mm: add UFFDIO_MOVE ioctl test
Documentation/admin-guide/mm/userfaultfd.rst | 3 + fs/userfaultfd.c | 72 +++ include/linux/rmap.h | 5 + include/linux/userfaultfd_k.h | 11 + include/uapi/linux/userfaultfd.h | 29 +- mm/huge_memory.c | 122 ++++ mm/khugepaged.c | 3 + mm/rmap.c | 30 + mm/userfaultfd.c | 596 +++++++++++++++++++ tools/testing/selftests/mm/uffd-common.c | 51 +- tools/testing/selftests/mm/uffd-common.h | 11 + tools/testing/selftests/mm/uffd-stress.c | 5 +- tools/testing/selftests/mm/uffd-unit-tests.c | 144 +++++ 13 files changed, 1078 insertions(+), 4 deletions(-)
Signature · 1 line
2.42.0.820.g83a721a137-goog
Suren Baghdasaryan
reviewerPATCH v4 1/5 mm/rmap: support move to different root anon_vma in folio_move_anon_rmap()
From: Andrea Arcangeli <[email protected]>
For now, folio_move_anon_rmap() was only used to move a folio to a different anon_vma after fork(), whereby the root anon_vma stayed unchanged. For that, it was sufficient to hold the folio lock when calling folio_move_anon_rmap().
However, we want to make use of folio_move_anon_rmap() to move folios between VMAs that have a different root anon_vma. As folio_referenced() performs an RMAP walk without holding the folio lock but only holding the anon_vma in read mode, holding the folio lock is insufficient.
When moving to an anon_vma with a different root anon_vma, we'll have to hold both, the folio lock and the anon_vma lock in write mode. Consequently, whenever we succeeded in folio_lock_anon_vma_read() to read-lock the anon_vma, we have to re-check if the mapping was changed in the meantime. If that was the case, we have to retry.
Note that folio_move_anon_rmap() must only be called if the anon page is exclusive to a process, and must not be called on KSM folios.
This is a preparation for UFFDIO_MOVE, which will hold the folio lock, the anon_vma lock in write mode, and the mmap_lock in read mode.
--- mm/rmap.c | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+)
mm/rmap.c +24 -0
@@ -542,6 +542,7 @@ struct anon_vma *folio_lock_anon_vma_read(struct folio *folio,struct anon_vma *root_anon_vma;unsigned long anon_mapping; +retry:rcu_read_lock (); anon_mapping = (unsigned long )READ_ONCE (folio->mapping);if ((anon_mapping &PAGE_MAPPING_FLAGS ) !=PAGE_MAPPING_ANON )
@@ -552,6 +553,17 @@ struct anon_vma *folio_lock_anon_vma_read(struct folio *folio, anon_vma = (struct anon_vma *) (anon_mapping -PAGE_MAPPING_ANON ); root_anon_vma =READ_ONCE (anon_vma->root);if (down_read_trylock (&root_anon_vma->rwsem)) { +/* +* folio_move_anon_rmap() might have changed the anon_vma as we +* might not hold the folio lock here. +*/ +if (unlikely ((unsigned long )READ_ONCE (folio->mapping) != + anon_mapping)) { +up_read (&root_anon_vma->rwsem); +rcu_read_unlock (); + goto retry; + } +/* * If the folio is still mapped, then this anon_vma is still * its anon_vma, and holding the mutex ensures that it will
@@ -586,6 +598,18 @@ struct anon_vma *folio_lock_anon_vma_read(struct folio *folio,rcu_read_unlock ();anon_vma_lock_read (anon_vma); +/* +* folio_move_anon_rmap() might have changed the anon_vma as we might +* not hold the folio lock here. +*/ +if (unlikely ((unsigned long )READ_ONCE (folio->mapping) != + anon_mapping)) { +anon_vma_unlock_read (anon_vma); +put_anon_vma (anon_vma); + anon_vma =NULL ; + goto retry; + } +if (atomic_dec_and_test (&anon_vma->refcount)) {/* * Oops, we held the last refcount, release the lock
Signature · 1 line
2.42.0.820.g83a721a137-goog
Re: [PATCH v4 1/5] mm/rmap: support move to different root anon_vma in folio_move_anon_rmap()
Quoted 18 lines · Suren Baghdasaryan
From: Andrea Arcangeli <[email protected]>
For now,
folio_move_anon_rmap()was only used to move a folio to a differentanon_vmaafterfork(), whereby the rootanon_vmastayed unchanged. For that, it was sufficient to hold the folio lock when callingfolio_move_anon_rmap().However, we want to make use of
folio_move_anon_rmap()to move folios between VMAs that have a different rootanon_vma. Asfolio_referenced()performs an RMAP walk without holding the folio lock but only holding theanon_vmain read mode, holding the folio lock is insufficient.When moving to an
anon_vmawith a different rootanon_vma, we'll have to hold both, the folio lock and theanon_vmalock in write mode. Consequently, whenever we succeeded infolio_lock_anon_vma_read()to read-lock theanon_vma, we have to re-check if the mapping was changed in the meantime. If that was the case, we have to retry.
Quoted 8 lines
Note that
folio_move_anon_rmap()must only be called if the anon page is exclusive to a process, and must not be called on KSM folios.This is a preparation for
UFFDIO_MOVE, which will hold the folio lock, theanon_vmalock in write mode, and themmap_lockin read mode.Signed-off-by Andrea Arcangeli <[email protected]>Signed-off-by Suren Baghdasaryan <[email protected]>
Signature · 1 line
Peter Xu
Suren Baghdasaryan
reviewerPATCH v4 2/5 userfaultfd: UFFDIO_MOVE uABI
In reply to Suren BaghdasaryanFrom: Andrea Arcangeli <[email protected]>
Implement the uABI of UFFDIO_MOVE ioctl.UFFDIO_COPY performs ~20% better than UFFDIO_MOVE when the application needs pages to be allocated [1]. However, with UFFDIO_MOVE, if pages are available (in userspace) for recycling, as is usually the case in heap compaction algorithms, then we can avoid the page allocation and memcpy (done by UFFDIO_COPY). Also, since the pages are recycled in the userspace, we avoid the need to release (via madvise) the pages back to the kernel [2].
We see over 40% reduction (on a Google pixel 6 device) in the compacting thread’s completion time by using UFFDIO_MOVE vs. UFFDIO_COPY. This was measured using a benchmark that emulates a heap compaction implementation using userfaultfd (to allow concurrent accesses by application threads). More details of the usecase are explained in [2]. Furthermore, UFFDIO_MOVE enables moving swapped-out pages without touching them within the same vma. Today, it can only be done by mremap, however it forces splitting the vma.
[1] https://lore.kernel.org/all/[email protected]/
[2] https://lore.kernel.org/linux-mm/CA+EESO4uO84SSnBhArH4HvLNhaUQ5nZKNKXqxRCyjniNVjp0Aw@mail.gmail.com/
Update for the ioctl_userfaultfd(2) manpage:
UFFDIO_MOVE
(Since Linux xxx) Move a continuous memory chunk into the userfault registered range and optionally wake up the blocked thread. The source and destination addresses and the number of bytes to move are specified by the src, dst, and len fields of the uffdio_move structure pointed to by argp:
struct uffdio_move {
__u64 dst; /* Destination of move */
__u64 src; /* Source of move */
__u64 len; /* Number of bytes to move */
__u64 mode; /* Flags controlling behavior of move */
__s64 move; /* Number of bytes moved, or negated error */
};
The following value may be bitwise ORed in mode to change the behavior of the UFFDIO_MOVE operation:
UFFDIO_MOVE_MODE_DONTWAKE
Do not wake up the thread that waits for page-fault resolution
UFFDIO_MOVE_MODE_ALLOW_SRC_HOLES
Allow holes in the source virtual range that is being moved. When not specified, the holes will result in ENOENT error. When specified, the holes will be accounted as successfully moved memory. This is mostly useful to move hugepage aligned virtual regions without knowing if there are transparent hugepages in the regions or not, but preventing the risk of having to split the hugepage during the operation.
The move field is used by the kernel to return the number of bytes that was actually moved, or an error (a negated errno- style value). If the value returned in move doesn't match the value that was specified in len, the operation fails with the error EAGAIN. The move field is output-only; it is not read by the UFFDIO_MOVE operation.
The operation may fail for various reasons. Usually, remapping of pages that are not exclusive to the given process fail; once KSM might deduplicate pages or fork() COW-shares pages during fork() with child processes, they are no longer exclusive. Further, the kernel might only perform lightweight checks for detecting whether the pages are exclusive, and return -EBUSY in case that check fails. To make the operation more likely to succeed, KSM should be disabled, fork() should be avoided or MADV_DONTFORK should be configured for the source VMA before fork().
This ioctl(2) operation returns 0 on success. In this case, the entire area was moved. On error, -1 is returned and errno is set to indicate the error. Possible errors include:
EAGAIN The number of bytes moved (i.e., the value returned in
the move field) does not equal the value that was specified in the len field.
EINVAL Either dst or len was not a multiple of the system page
size, or the range specified by src and len or dst and len was invalid.
EINVAL An invalid bit was specified in the mode field.
ENOENT
The source virtual memory range has unmapped holes and UFFDIO_MOVE_MODE_ALLOW_SRC_HOLES is not set.
EEXIST
The destination virtual memory range is fully or partially mapped.
EBUSY
The pages in the source virtual memory range are either pinned or not exclusive to the process. The kernel might only perform lightweight checks for detecting whether the pages are exclusive. To make the operation more likely to succeed, KSM should be disabled, fork() should be avoided or MADV_DONTFORK should be configured for the source virtual memory area before fork().
ENOMEM Allocating memory needed for the operation failed.
ESRCH
The target process has exited at the time of a UFFDIO_MOVE operation.
--- Documentation/admin-guide/mm/userfaultfd.rst | 3 + fs/userfaultfd.c | 72 +++ include/linux/rmap.h | 5 + include/linux/userfaultfd_k.h | 11 + include/uapi/linux/userfaultfd.h | 29 +- mm/huge_memory.c | 122 ++++ mm/khugepaged.c | 3 + mm/rmap.c | 6 + mm/userfaultfd.c | 596 +++++++++++++++++++ 9 files changed, 846 insertions(+), 1 deletion(-)
Documentation/admin-guide/mm/userfaultfd.rst +3 -0
@@ -113,6 +113,9 @@ events, except page fault notifications, may be generated: areas. ``UFFD_FEATURE_MINOR_SHMEM`` is the analogous feature indicating support for shmem virtual memory areas. +- ``UFFD_FEATURE_MOVE`` indicates that the kernel supports moving an + existing page contents from userspace. + The userland application should set the feature flags it intends to use when invoking the ``UFFDIO_API`` ioctl, to request that those features be enabled if supported.
fs/userfaultfd.c +72 -0
84 lines not on this page · raw message
include/linux/rmap.h +5 -0
@@ -121,6 +121,11 @@ static inline void anon_vma_lock_write(struct anon_vma *anon_vma)down_write (&anon_vma->root->rwsem); } +static inline int anon_vma_trylock_write (struct anon_vma *anon_vma) +{ +return down_write_trylock (&anon_vma->root->rwsem); +} +static inline void anon_vma_unlock_write (struct anon_vma *anon_vma) {up_write (&anon_vma->root->rwsem);
include/linux/userfaultfd_k.h +11 -0
@@ -93,6 +93,17 @@ extern int mwriteprotect_range(struct mm_struct *dst_mm,extern long uffd_wp_range (struct vm_area_struct *vma,unsigned long start,unsigned long len,bool enable_wp); +/* move_pages */ +void double_pt_lock (spinlock_t *ptl1,spinlock_t *ptl2); +void double_pt_unlock (spinlock_t *ptl1,spinlock_t *ptl2); +ssize_t move_pages (struct userfaultfd_ctx *ctx,struct mm_struct *mm, +unsigned long dst_start,unsigned long src_start, +unsigned long len,__u64 flags); +int move_pages_huge_pmd (struct mm_struct *mm,pmd_t *dst_pmd,pmd_t *src_pmd,pmd_t dst_pmdval, +struct vm_area_struct *dst_vma, +struct vm_area_struct *src_vma, +unsigned long dst_addr,unsigned long src_addr); +/* mm helpers */ static inline bool is_mergeable_vm_userfaultfd_ctx (struct vm_area_struct *vma,struct vm_userfaultfd_ctx vm_ctx)
include/uapi/linux/userfaultfd.h +28 -1
@@ -41,7 +41,8 @@ UFFD_FEATURE_WP_HUGETLBFS_SHMEM | \ UFFD_FEATURE_WP_UNPOPULATED | \ UFFD_FEATURE_POISON | \ - UFFD_FEATURE_WP_ASYNC) + UFFD_FEATURE_WP_ASYNC | \ + UFFD_FEATURE_MOVE)#define UFFD_API_IOCTLS \ ((__u64)1 << _UFFDIO_REGISTER | \ (__u64)1 << _UFFDIO_UNREGISTER | \
@@ -50,6 +51,7 @@ ((__u64 )1 << _UFFDIO_WAKE | \ (__u64 )1 << _UFFDIO_COPY | \ (__u64 )1 << _UFFDIO_ZEROPAGE | \ + (__u64 )1 << _UFFDIO_MOVE | \ (__u64 )1 << _UFFDIO_WRITEPROTECT | \ (__u64 )1 << _UFFDIO_CONTINUE | \ (__u64 )1 << _UFFDIO_POISON)
@@ -73,6 +75,7 @@#define _UFFDIO_WAKE (0x02)#define _UFFDIO_COPY (0x03)#define _UFFDIO_ZEROPAGE (0x04) +#define _UFFDIO_MOVE (0x05)#define _UFFDIO_WRITEPROTECT (0x06)#define _UFFDIO_CONTINUE (0x07)#define _UFFDIO_POISON (0x08)
@@ -92,6 +95,8 @@ struct uffdio_copy)#define UFFDIO_ZEROPAGE _IOWR(UFFDIO, _UFFDIO_ZEROPAGE, \ struct uffdio_zeropage) +#define UFFDIO_MOVE _IOWR(UFFDIO, _UFFDIO_MOVE, \ + struct uffdio_move)#define UFFDIO_WRITEPROTECT _IOWR(UFFDIO, _UFFDIO_WRITEPROTECT, \ struct uffdio_writeprotect)#define UFFDIO_CONTINUE _IOWR(UFFDIO, _UFFDIO_CONTINUE, \
@@ -222,6 +227,9 @@ struct uffdio_api {* asynchronous mode is supported in which the write fault is * automatically resolved and write-protection is un-set. * It implies UFFD_FEATURE_WP_UNPOPULATED. +* +* UFFD_FEATURE_MOVE indicates that the kernel supports moving an +* existing page contents from userspace. */ #define UFFD_FEATURE_PAGEFAULT_FLAG_WP (1<<0)#define UFFD_FEATURE_EVENT_FORK (1<<1)
@@ -239,6 +247,7 @@ struct uffdio_api {#define UFFD_FEATURE_WP_UNPOPULATED (1<<13)#define UFFD_FEATURE_POISON (1<<14)#define UFFD_FEATURE_WP_ASYNC (1<<15) +#define UFFD_FEATURE_MOVE (1<<16)__u64 features;__u64 ioctls;
@@ -347,6 +356,24 @@ struct uffdio_poison {__s64 updated; }; +struct uffdio_move { +__u64 dst; +__u64 src; +__u64 len; +/* +* Especially if used to atomically remove memory from the +* address space the wake on the dst range is not needed. +*/ +#define UFFDIO_MOVE_MODE_DONTWAKE ((__u64)1<<0) +#define UFFDIO_MOVE_MODE_ALLOW_SRC_HOLES ((__u64)1<<1) +__u64 mode; +/* +* "move" is written by the ioctl and must be at the end: the +* copy_from_user will not read the last 8 bytes. +*/ +__s64 move; +}; +/* * Flags for the userfaultfd(2) system call itself. */
mm/huge_memory.c +122 -0
128 lines not on this page · raw message
mm/khugepaged.c +3 -0
@@ -1139,6 +1139,9 @@ static int collapse_huge_page(struct mm_struct *mm, unsigned long address,* Prevent all access to pagetables with the exception of * gup_fast later handled by the ptep_clear_flush and the VM * handled by the anon_vma lock + PG_lock. +* +* UFFDIO_MOVE is prevented to race as well thanks to the +* mmap_lock. */ mmap_write_lock (mm); result =hugepage_vma_revalidate (mm, address, true, &vma, cc);
mm/rmap.c +6 -0
@@ -490,6 +490,12 @@ void __init anon_vma_init(void)* page_remove_rmap() that the anon_vma pointer from page->mapping is valid * if there is a mapcount, we can dereference the anon_vma after observing * those. +* +* NOTE: the caller should normally hold folio lock when calling this. If +* not, the caller needs to double check the anon_vma didn't change after +* taking the anon_vma lock for either read or write (UFFDIO_MOVE can modify it +* concurrently without folio lock protection). See folio_lock_anon_vma_read() +* which has already covered that, and comment above remap_pages(). */ struct anon_vma *folio_get_anon_vma (struct folio *folio) {
mm/userfaultfd.c +596 -0
599 lines not on this page · raw message
Signature · 1 line
2.42.0.820.g83a721a137-goog
kernel test robot
build botRe: [PATCH v4 2/5] userfaultfd: UFFDIO_MOVE uABI
kernel test robot noticed the following build warnings:
Hi Suren,
kernel test robot noticed the following build warnings:
[auto build test WARNING on akpm-mm/mm-everything]
[also build test WARNING on next-20231027]
[cannot apply to linus/master v6.6-rc7]
[If your patch is applied to the wrong git tree, kindly drop us a note. And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]
url: https://github.com/intel-lab-lkp/linux/commits/Suren-Baghdasaryan/mm-rmap-support-move-to-different-root-anon_vma-in-folio_move_anon_rmap/20231028-084120 base: https://git.kernel.org/pub/scm/linux/kernel/git/akpm/mm.git mm-everything patch link: https://lore.kernel.org/r/20231028003819.652322-3-surenb%40google.com patch subject: [PATCH v4 2/5] userfaultfd: UFFDIO_MOVE uABI config: m68k-allyesconfig (https://download.01.org/0day-ci/archive/20231028/[email protected]/config) compiler: m68k-linux-gcc (GCC) 13.2.0 reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20231028/[email protected]/reproduce)
If you fix the issue in a separate patch/commit (i.e. not just a new version of the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <[email protected]>
| Closes: https://lore.kernel.org/oe-kbuild-all/[email protected]/
All warnings (new ones prefixed by >>):
>> mm/userfaultfd.c:1289: warning: Function parameter or member 'ctx' not described in 'move_pages' >> mm/userfaultfd.c:1289: warning: Function parameter or member 'mm' not described in 'move_pages' >> mm/userfaultfd.c:1289: warning: Function parameter or member 'mode' not described in 'move_pages'
vim +1289 mm/userfaultfd.c
1211
1212 /**
1213 * move_pages - move arbitrary anonymous pages of an existing vma
1214 * @dst_start: start of the destination virtual memory range
1215 * @src_start: start of the source virtual memory range
1216 * @len: length of the virtual memory range
1217 *
1218 * Must be called with mmap_lock held for read.
1219 *
1220 * move_pages() remaps arbitrary anonymous pages atomically in zero
1221 * copy. It only works on non shared anonymous pages because those can
1222 * be relocated without generating non linear anon_vmas in the rmap
1223 * code.
1224 *
1225 * It provides a zero copy mechanism to handle userspace page faults.
1226 * The source vma pages should have mapcount == 1, which can be
1227 * enforced by using madvise(MADV_DONTFORK) on src vma.
1228 *
1229 * The thread receiving the page during the userland page fault
1230 * will receive the faulting page in the source vma through the network,
1231 * storage or any other I/O device (MADV_DONTFORK in the source vma
1232 * avoids move_pages() to fail with -EBUSY if the process forks before
1233 * move_pages() is called), then it will call move_pages() to map the
1234 * page in the faulting address in the destination vma.
1235 *
1236 * This userfaultfd command works purely via pagetables, so it's the
1237 * most efficient way to move physical non shared anonymous pages
1238 * across different virtual addresses. Unlike mremap()/mmap()/munmap()
1239 * it does not create any new vmas. The mapping in the destination
1240 * address is atomic.
1241 *
1242 * It only works if the vma protection bits are identical from the
1243 * source and destination vma.
1244 *
1245 * It can remap non shared anonymous pages within the same vma too.
1246 *
1247 * If the source virtual memory range has any unmapped holes, or if
1248 * the destination virtual memory range is not a whole unmapped hole,
1249 * move_pages() will fail respectively with -ENOENT or -EEXIST. This
1250 * provides a very strict behavior to avoid any chance of memory
1251 * corruption going unnoticed if there are userland race conditions.
1252 * Only one thread should resolve the userland page fault at any given
1253 * time for any given faulting address. This means that if two threads
1254 * try to both call move_pages() on the same destination address at the
1255 * same time, the second thread will get an explicit error from this
1256 * command.
1257 *
1258 * The command retval will return "len" is successful. The command
1259 * however can be interrupted by fatal signals or errors. If
1260 * interrupted it will return the number of bytes successfully
1261 * remapped before the interruption if any, or the negative error if
1262 * none. It will never return zero. Either it will return an error or
1263 * an amount of bytes successfully moved. If the retval reports a
1264 * "short" remap, the move_pages() command should be repeated by
1265 * userland with src+retval, dst+reval, len-retval if it wants to know
1266 * about the error that interrupted it.
1267 *
1268 * The UFFDIO_MOVE_MODE_ALLOW_SRC_HOLES flag can be specified to
1269 * prevent -ENOENT errors to materialize if there are holes in the
1270 * source virtual range that is being remapped. The holes will be
1271 * accounted as successfully remapped in the retval of the
1272 * command. This is mostly useful to remap hugepage naturally aligned
1273 * virtual regions without knowing if there are transparent hugepage
1274 * in the regions or not, but preventing the risk of having to split
1275 * the hugepmd during the remap.
1276 *
1277 * If there's any rmap walk that is taking the anon_vma locks without
1278 * first obtaining the folio lock (the only current instance is
1279 * folio_referenced), they will have to verify if the folio->mapping
1280 * has changed after taking the anon_vma lock. If it changed they
1281 * should release the lock and retry obtaining a new anon_vma, because
1282 * it means the anon_vma was changed by move_pages() before the lock
1283 * could be obtained. This is the only additional complexity added to
1284 * the rmap code to provide this anonymous page remapping functionality.
1285 */
1286 ssize_t move_pages(struct userfaultfd_ctx *ctx, struct mm_struct *mm,
1287 unsigned long dst_start, unsigned long src_start,
1288 unsigned long len, __u64 mode)
> 1289 {Signature · 2 lines
0-DAY CI Kernel Test Service https://github.com/intel/lkp-tests/wiki
Suren Baghdasaryan
reviewerPATCH v4 3/5 selftests/mm: call uffd_test_ctx_clear at the end of the test
In reply to Suren Baghdasaryanuffd_test_ctx_clear() is being called from uffd_test_ctx_init() to unmap areas used in the previous test run. This approach is problematic because while unmapping areas uffd_test_ctx_clear() uses page_size and nr_pages which might differ from one test run to another.
Fix this by calling uffd_test_ctx_clear() after each test is done.
--- tools/testing/selftests/mm/uffd-common.c | 4 +--- tools/testing/selftests/mm/uffd-common.h | 1 + tools/testing/selftests/mm/uffd-stress.c | 5 ++++- tools/testing/selftests/mm/uffd-unit-tests.c | 1 + 4 files changed, 7 insertions(+), 4 deletions(-)
tools/testing/selftests/mm/uffd-common.c +1 -3
@@ -262,7 +262,7 @@ static inline void munmap_area(void **area) *area =NULL ; } -static void uffd_test_ctx_clear (void ) +void uffd_test_ctx_clear (void ) {size_t i;
@@ -298,8 +298,6 @@ int uffd_test_ctx_init(uint64_t features, const char **errmsg)unsigned long nr, cpu;int ret; -uffd_test_ctx_clear (); - ret = uffd_test_ops->allocate_area ((void **)&area_src, true); ret |= uffd_test_ops->allocate_area ((void **)&area_dst, false);if (ret) {
tools/testing/selftests/mm/uffd-common.h +1 -0
@@ -105,6 +105,7 @@ extern uffd_test_ops_t *uffd_test_ops;void uffd_stats_report (struct uffd_args *args,int n_cpus);int uffd_test_ctx_init (uint64_t features,const char **errmsg); +void uffd_test_ctx_clear (void );int userfaultfd_open (uint64_t *features);int uffd_read_msg (int ufd,struct uffd_msg *msg);void wp_range (int ufd,__u64 start,__u64 len,bool wp);
tools/testing/selftests/mm/uffd-stress.c +4 -1
@@ -323,8 +323,10 @@ static int userfaultfd_stress(void)uffd_stats_reset (args, nr_cpus);/* bounce pass */ -if (stress (args)) +if (stress (args)) { +uffd_test_ctx_clear ();return 1 ; + }/* Clear all the write protections if there is any */ if (test_uffdio_wp)
@@ -354,6 +356,7 @@ static int userfaultfd_stress(void)uffd_stats_report (args, nr_cpus); } +uffd_test_ctx_clear ();return 0 ; }
tools/testing/selftests/mm/uffd-unit-tests.c +1 -0
@@ -1319,6 +1319,7 @@ int main(int argc, char *argv[])continue ; } test->uffd_fn (&args); +uffd_test_ctx_clear (); } }
Signature · 1 line
2.42.0.820.g83a721a137-goog
Re: [PATCH v4 3/5] selftests/mm: call uffd_test_ctx_clear at the end of the test
Quoted 7 lines · Suren Baghdasaryan
uffd_test_ctx_clear()is being called fromuffd_test_ctx_init()to unmap areas used in the previous test run. This approach is problematic because while unmapping areasuffd_test_ctx_clear()usespage_sizeandnr_pageswhich might differ from one test run to another.
Fix this by callinguffd_test_ctx_clear()after each test is done.Signed-off-by Suren Baghdasaryan <[email protected]>
Signature · 1 line
Peter Xu
Axel Rasmussen
Reviewed-byRe: [PATCH v4 3/5] selftests/mm: call uffd_test_ctx_clear at the end of the test
Quoted 11 lines · Peter Xu
Quoted 7 lines · Suren Baghdasaryan
uffd_test_ctx_clear()is being called fromuffd_test_ctx_init()to unmap areas used in the previous test run. This approach is problematic because while unmapping areasuffd_test_ctx_clear()usespage_sizeandnr_pageswhich might differ from one test run to another.
Fix this by callinguffd_test_ctx_clear()after each test is done.Signed-off-by Suren Baghdasaryan <[email protected]>Reviewed-by Peter Xu <[email protected]>
Looks correct to me as well. Thanks for fixing this, Suren!
Quoted 4 lines
--
Peter Xu
Suren Baghdasaryan
reviewerPATCH v4 4/5 selftests/mm: add uffd_test_case_ops to allow test case-specific operations
In reply to Suren BaghdasaryanCurrently each test can specify unique operations using uffd_test_ops, however these operations are per-memory type and not per-test. Add uffd_test_case_ops which each test case can customize for its own needs regardless of the memory type being used. Post- and pre- allocation and release operations are added, some of which will be used in the next patch to implement test-specific customizations like area re-alignment, page size overrides and madvise after memory allocations but before memory is accessed.
--- tools/testing/selftests/mm/uffd-common.c | 25 ++++++++++++++++++++ tools/testing/selftests/mm/uffd-common.h | 9 +++++++ tools/testing/selftests/mm/uffd-unit-tests.c | 2 ++ 3 files changed, 36 insertions(+)
tools/testing/selftests/mm/uffd-common.c +25 -0
@@ -17,6 +17,7 @@ bool map_shared;bool test_uffdio_wp = true;unsigned long long *count_verify;uffd_test_ops_t *uffd_test_ops; +uffd_test_case_ops_t *uffd_test_case_ops;static int uffd_mem_fd_create (off_t mem_size,bool hugetlb) {
@@ -286,11 +287,17 @@ void uffd_test_ctx_clear(void) uffd =-1 ; } +if (uffd_test_case_ops && uffd_test_case_ops->pre_release) + uffd_test_case_ops->pre_release (); +munmap_area ((void **)&area_src);munmap_area ((void **)&area_src_alias);munmap_area ((void **)&area_dst);munmap_area ((void **)&area_dst_alias);munmap_area ((void **)&area_remap); + +if (uffd_test_case_ops && uffd_test_case_ops->post_release) + uffd_test_case_ops->post_release (); }int uffd_test_ctx_init (uint64_t features,const char **errmsg)
@@ -298,6 +305,15 @@ int uffd_test_ctx_init(uint64_t features, const char **errmsg)unsigned long nr, cpu;int ret; +if (uffd_test_case_ops && uffd_test_case_ops->pre_alloc) { + ret = uffd_test_case_ops->pre_alloc (); +if (ret) { +if (errmsg) + *errmsg ="pre-allocation operation failed" ; +return ret; + } + } + ret = uffd_test_ops->allocate_area ((void **)&area_src, true); ret |= uffd_test_ops->allocate_area ((void **)&area_dst, false);if (ret) {
@@ -306,6 +322,15 @@ int uffd_test_ctx_init(uint64_t features, const char **errmsg)return ret; } +if (uffd_test_case_ops && uffd_test_case_ops->post_alloc) { + ret = uffd_test_case_ops->post_alloc (); +if (ret) { +if (errmsg) + *errmsg ="post-allocation operation failed" ; +return ret; + } + } + ret =userfaultfd_open (&features);if (ret) {if (errmsg)
tools/testing/selftests/mm/uffd-common.h +9 -0
@@ -90,6 +90,14 @@ struct uffd_test_ops { };typedef struct uffd_test_ops uffd_test_ops_t ; +struct uffd_test_case_ops { +int (*pre_alloc)(void ); +int (*post_alloc)(void ); +void (*pre_release)(void ); +void (*post_release)(void ); +}; +typedef struct uffd_test_case_ops uffd_test_case_ops_t ; +extern unsigned long nr_cpus, nr_pages, nr_pages_per_cpu, page_size;extern char *area_src, *area_src_alias, *area_dst, *area_dst_alias, *area_remap;extern int uffd, uffd_flags, finished, *pipefd, test_type;
@@ -102,6 +110,7 @@ extern uffd_test_ops_t anon_uffd_test_ops;extern uffd_test_ops_t shmem_uffd_test_ops;extern uffd_test_ops_t hugetlb_uffd_test_ops;extern uffd_test_ops_t *uffd_test_ops; +extern uffd_test_case_ops_t *uffd_test_case_ops;void uffd_stats_report (struct uffd_args *args,int n_cpus);int uffd_test_ctx_init (uint64_t features,const char **errmsg);
tools/testing/selftests/mm/uffd-unit-tests.c +2 -0
@@ -78,6 +78,7 @@ typedef struct {uffd_test_fn uffd_fn;unsigned int mem_targets;uint64_t uffd_feature_required; +uffd_test_case_ops_t *test_case_ops; } uffd_test_case_t;static void uffd_test_report (void )
@@ -185,6 +186,7 @@ uffd_setup_environment(uffd_test_args_t *args, uffd_test_case_t *test, { map_shared = mem_type->shared; uffd_test_ops = mem_type->mem_ops; + uffd_test_case_ops = test->test_case_ops;if (mem_type->mem_flag & (MEM_HUGETLB_PRIVATE |MEM_HUGETLB )) page_size =default_huge_page_size ();
Signature · 1 line
2.42.0.820.g83a721a137-goog
Suren Baghdasaryan
reviewerPATCH v4 5/5 selftests/mm: add UFFDIO_MOVE ioctl test
In reply to Suren BaghdasaryanAdd tests for new UFFDIO_MOVE ioctl which uses uffd to move source into destination buffer while checking the contents of both after the move. After the operation the content of the destination buffer should match the original source buffer's content while the source buffer should be zeroed. Separate tests are designed for PMD aligned and unaligned cases because they utilize different code paths in the kernel.
--- tools/testing/selftests/mm/uffd-common.c | 24 ++++ tools/testing/selftests/mm/uffd-common.h | 1 + tools/testing/selftests/mm/uffd-unit-tests.c | 141 +++++++++++++++++++ 3 files changed, 166 insertions(+)
tools/testing/selftests/mm/uffd-common.c +24 -0
@@ -643,6 +643,30 @@ int copy_page(int ufd, unsigned long offset, bool wp)return __copy_page (ufd, offset, false, wp); } +int move_page (int ufd,unsigned long offset) +{ +struct uffdio_move uffdio_move; + +if (offset >= nr_pages * page_size) +err ("unexpected offset %lu\n" , offset); + uffdio_move.dst = (unsigned long ) area_dst + offset; + uffdio_move.src = (unsigned long ) area_src + offset; + uffdio_move.len = page_size; + uffdio_move.mode =UFFDIO_MOVE_MODE_ALLOW_SRC_HOLES ; + uffdio_move.move =0 ; +if (ioctl (ufd,UFFDIO_MOVE , &uffdio_move)) { +/* real retval in uffdio_move.move */ +if (uffdio_move.move != -EEXIST ) +err ("UFFDIO_MOVE error: %" PRId64, + (int64_t )uffdio_move.move); +wake_range (ufd, uffdio_move.dst, page_size); + }else if (uffdio_move.move != page_size) { +err ("UFFDIO_MOVE error: %" PRId64, (int64_t )uffdio_move.move); + }else +return 1 ; +return 0 ; +} +int uffd_open_dev (unsigned int flags) {int fd, uffd;
tools/testing/selftests/mm/uffd-common.h +1 -0
@@ -121,6 +121,7 @@ void wp_range(int ufd, __u64 start, __u64 len, bool wp);void uffd_handle_page_fault (struct uffd_msg *msg,struct uffd_args *args);int __copy_page (int ufd,unsigned long offset,bool retry,bool wp);int copy_page (int ufd,unsigned long offset,bool wp); +int move_page (int ufd,unsigned long offset);void *uffd_poll_thread (void *arg);int uffd_open_dev (unsigned int flags);
tools/testing/selftests/mm/uffd-unit-tests.c +141 -0
153 lines not on this page · raw message
Signature · 1 line
2.42.0.820.g83a721a137-goog
Peter Xu
reviewerRe: [PATCH v4 5/5] selftests/mm: add UFFDIO_MOVE ioctl test
Quoted 171 lines · Suren Baghdasaryan
Add tests for new
UFFDIO_MOVEioctl which uses uffd to move source into destination buffer while checking the contents of both after the move. After the operation the content of the destination buffer should match the original source buffer's content while the source buffer should be zeroed. Separate tests are designed for PMD aligned and unaligned cases because they utilize different code paths in the kernel.Signed-off-by Suren Baghdasaryan <[email protected]>--- tools/testing/selftests/mm/uffd-common.c | 24 ++++ tools/testing/selftests/mm/uffd-common.h | 1 + tools/testing/selftests/mm/uffd-unit-tests.c | 141 +++++++++++++++++++ 3 files changed, 166 insertions(+)tools/testing/selftests/mm/uffd-common.c +24
-0@@ -643,6 +643,30 @@ int copy_page(int ufd, unsigned long offset, bool wp)return __copy_page (ufd, offset, false, wp); } +int move_page (int ufd,unsigned long offset) +{ +struct uffdio_move uffdio_move; + +if (offset >= nr_pages * page_size) +err ("unexpected offset %lu\n" , offset); + uffdio_move.dst = (unsigned long ) area_dst + offset; + uffdio_move.src = (unsigned long ) area_src + offset; + uffdio_move.len = page_size; + uffdio_move.mode =UFFDIO_MOVE_MODE_ALLOW_SRC_HOLES ; + uffdio_move.move =0 ; +if (ioctl (ufd,UFFDIO_MOVE , &uffdio_move)) { +/* real retval in uffdio_move.move */ +if (uffdio_move.move != -EEXIST ) +err ("UFFDIO_MOVE error: %" PRId64, + (int64_t )uffdio_move.move); +wake_range (ufd, uffdio_move.dst, page_size); + }else if (uffdio_move.move != page_size) { +err ("UFFDIO_MOVE error: %" PRId64, (int64_t )uffdio_move.move); + }else +return 1 ; +return 0 ; +} +int uffd_open_dev (unsigned int flags) {int fd, uffd;tools/testing/selftests/mm/uffd-common.h +1
-0@@ -121,6 +121,7 @@ void wp_range(int ufd, __u64 start, __u64 len, bool wp);void uffd_handle_page_fault (struct uffd_msg *msg,struct uffd_args *args);int __copy_page (int ufd,unsigned long offset,bool retry,bool wp);int copy_page (int ufd,unsigned long offset,bool wp); +int move_page (int ufd,unsigned long offset);void *uffd_poll_thread (void *arg);int uffd_open_dev (unsigned int flags);tools/testing/selftests/mm/uffd-unit-tests.c +102
-0@@ -1064,6 +1064,133 @@ static void uffd_poison_test(uffd_test_args_t *targs)uffd_test_pass (); } +static void uffd_move_handle_fault ( +struct uffd_msg *msg,struct uffd_args *args) +{ +unsigned long offset; + +if (msg->event !=UFFD_EVENT_PAGEFAULT ) +err ("unexpected msg event %u" , msg->event); + +if (msg->arg.pagefault.flags & + (UFFD_PAGEFAULT_FLAG_WP |UFFD_PAGEFAULT_FLAG_MINOR |UFFD_PAGEFAULT_FLAG_WRITE )) +err ("unexpected fault type %llu" , msg->arg.pagefault.flags); + + offset = (char *)(unsigned long )msg->arg.pagefault.address - area_dst; + offset &= ~(page_size-1 ); + +if (move_page (uffd, offset)) + args->missing_faults++; +} + +static void uffd_move_test (uffd_test_args_t *targs) +{ +unsigned long nr; +pthread_t uffd_mon; +char c; +unsigned long long count; +struct uffd_args args = {0 }; + +/* Prevent source pages from being mapped more than once */ +if (madvise (area_src, nr_pages * page_size,MADV_DONTFORK )) +err ("madvise(MADV_DONTFORK) failure" ); + +if (uffd_register (uffd, area_dst, nr_pages * page_size, + true, false, false)) +err ("register failure" ); + + args.handle_fault = uffd_move_handle_fault; +if (pthread_create (&uffd_mon,NULL , uffd_poll_thread, &args)) +err ("uffd_poll_thread create" ); + +/* +* Read each of the pages back using the UFFD-registered mapping. We +* expect that the first time we touch a page, it will result in a missing +* fault. uffd_poll_thread will resolve the fault by moving source +* page to destination. +*/ +for (nr =0 ; nr < nr_pages; nr++) { +/* Check area_src content */ + count = *area_count (area_src, nr); +if (count != count_verify[nr]) +err ("nr %lu source memory invalid %llu %llu\n" , + nr, count, count_verify[nr]); + +/* Faulting into area_dst should move the page */ + count = *area_count (area_dst, nr); +if (count != count_verify[nr]) +err ("nr %lu memory corruption %llu %llu\n" , + nr, count, count_verify[nr]); + +/* Re-check area_src content which should be empty */ + count = *area_count (area_src, nr); +if (count !=0 ) +err ("nr %lu move failed %llu %llu\n" , + nr, count, count_verify[nr]); + } + +if (write (pipefd[1 ], &c,sizeof (c)) !=sizeof (c)) +err ("pipe write" ); +if (pthread_join (uffd_mon,NULL )) +err ("join() failed" ); + +if (args.missing_faults != nr_pages || args.minor_faults !=0 ) +uffd_test_fail ("stats check error" ); +else +uffd_test_pass (); +} + +static int prevent_hugepages (void ) +{ +/* This should be done before source area is populated */ +if (madvise (area_src, nr_pages * page_size,MADV_NOHUGEPAGE )) { +/* Ignore if CONFIG_TRANSPARENT_HUGEPAGE=n */ +if (errno !=EINVAL ) +return -errno; + } +return 0 ; +} + +struct uffd_test_case_ops uffd_move_test_case_ops = { + .post_alloc = prevent_hugepages, +}; + +#define ALIGN_UP (x, align_to) \ + (__typeof__(x))((((unsigned long)(x)) + ((align_to)-1)) & ~((align_to)-1)) + +static char *orig_area_src, *orig_area_dst; +static int pmd_align_areas (void ) +{ + orig_area_src = area_src; + orig_area_dst = area_dst; + area_src =ALIGN_UP (area_src, page_size); + area_dst =ALIGN_UP (area_dst, page_size); + nr_pages--;
tools/testing/selftests/mm/uffd-unit-tests.c static void uffd_poison_test(uffd_test_args_t *targs) · line 1178
Quoted 10 lines
tools/testing/selftests/mm/uffd-unit-tests.c +10
-0+ +return 0 ; +} + +static void pmd_restore_areas (void ) +{ + area_src = orig_area_src; + area_dst = orig_area_dst; + nr_pages++; +}
Please stop using more global variables.. uffd tests are even less maintainable.
Maybe you can consider add a flag for uffd_test_ctx_init()? For allocating either small/thp/default?
tools/testing/selftests/mm/uffd-unit-tests.c static void uffd_poison_test(uffd_test_args_t *targs) · line 1182
Quoted 4 lines
tools/testing/selftests/mm/uffd-unit-tests.c +4
-0+ +static int adjust_page_size (void ) +{ + page_size =default_huge_page_size ();
This is hacky too, currently page_size is the real page_size backing the memory.
To make thp test simple, maybe just add one more test to MOVE a large chunk to replace the thp test, which may contain a few thps? It also doesn't need to be fault based.
Quoted 38 lines
tools/testing/selftests/mm/uffd-unit-tests.c +25
-0+ nr_pages =UFFD_TEST_MEM_SIZE / page_size; + +return 0 ; +} + +struct uffd_test_case_ops uffd_move_pmd_test_case_ops = { + .pre_alloc = adjust_page_size, + .post_alloc = pmd_align_areas, + .pre_release = pmd_restore_areas, +}; +/* * Test the returned uffdio_register.ioctls with different register modes. * Note that _UFFDIO_ZEROPAGE is tested separately in the zeropage test. @@ -1141,6 +1268,20 @@ uffd_test_case_t uffd_tests[] = { .mem_targets = MEM_ALL, .uffd_feature_required = 0, }, + { + .name = "move", + .uffd_fn = uffd_move_test, + .mem_targets = MEM_ANON, + .uffd_feature_required = UFFD_FEATURE_MOVE, + .test_case_ops = &uffd_move_test_case_ops, + }, + { + .name = "move-pmd", + .uffd_fn = uffd_move_test, + .mem_targets = MEM_ANON, + .uffd_feature_required = UFFD_FEATURE_MOVE, + .test_case_ops = &uffd_move_pmd_test_case_ops, + }, { .name = "wp-fork", .uffd_fn = uffd_wp_fork_test,Signature · 1 line
2.42.0.820.g83a721a137-goog
Signature · 1 line
Peter Xu
Suren Baghdasaryan
reviewerRe: [PATCH v4 5/5] selftests/mm: add UFFDIO_MOVE ioctl test
Quoted 187 lines · Peter Xu
Quoted 171 lines · Suren Baghdasaryan
Add tests for new
UFFDIO_MOVEioctl which uses uffd to move source into destination buffer while checking the contents of both after the move. After the operation the content of the destination buffer should match the original source buffer's content while the source buffer should be zeroed. Separate tests are designed for PMD aligned and unaligned cases because they utilize different code paths in the kernel.Signed-off-by Suren Baghdasaryan <[email protected]>--- tools/testing/selftests/mm/uffd-common.c | 24 ++++ tools/testing/selftests/mm/uffd-common.h | 1 + tools/testing/selftests/mm/uffd-unit-tests.c | 141 +++++++++++++++++++ 3 files changed, 166 insertions(+)tools/testing/selftests/mm/uffd-common.c +24
-0@@ -643,6 +643,30 @@ int copy_page(int ufd, unsigned long offset, bool wp)return __copy_page (ufd, offset, false, wp); } +int move_page (int ufd,unsigned long offset) +{ +struct uffdio_move uffdio_move; + +if (offset >= nr_pages * page_size) +err ("unexpected offset %lu\n" , offset); + uffdio_move.dst = (unsigned long ) area_dst + offset; + uffdio_move.src = (unsigned long ) area_src + offset; + uffdio_move.len = page_size; + uffdio_move.mode =UFFDIO_MOVE_MODE_ALLOW_SRC_HOLES ; + uffdio_move.move =0 ; +if (ioctl (ufd,UFFDIO_MOVE , &uffdio_move)) { +/* real retval in uffdio_move.move */ +if (uffdio_move.move != -EEXIST ) +err ("UFFDIO_MOVE error: %" PRId64, + (int64_t )uffdio_move.move); +wake_range (ufd, uffdio_move.dst, page_size); + }else if (uffdio_move.move != page_size) { +err ("UFFDIO_MOVE error: %" PRId64, (int64_t )uffdio_move.move); + }else +return 1 ; +return 0 ; +} +int uffd_open_dev (unsigned int flags) {int fd, uffd;tools/testing/selftests/mm/uffd-common.h +1
-0@@ -121,6 +121,7 @@ void wp_range(int ufd, __u64 start, __u64 len, bool wp);void uffd_handle_page_fault (struct uffd_msg *msg,struct uffd_args *args);int __copy_page (int ufd,unsigned long offset,bool retry,bool wp);int copy_page (int ufd,unsigned long offset,bool wp); +int move_page (int ufd,unsigned long offset);void *uffd_poll_thread (void *arg);int uffd_open_dev (unsigned int flags);tools/testing/selftests/mm/uffd-unit-tests.c +102
-0@@ -1064,6 +1064,133 @@ static void uffd_poison_test(uffd_test_args_t *targs)uffd_test_pass (); } +static void uffd_move_handle_fault ( +struct uffd_msg *msg,struct uffd_args *args) +{ +unsigned long offset; + +if (msg->event !=UFFD_EVENT_PAGEFAULT ) +err ("unexpected msg event %u" , msg->event); + +if (msg->arg.pagefault.flags & + (UFFD_PAGEFAULT_FLAG_WP |UFFD_PAGEFAULT_FLAG_MINOR |UFFD_PAGEFAULT_FLAG_WRITE )) +err ("unexpected fault type %llu" , msg->arg.pagefault.flags); + + offset = (char *)(unsigned long )msg->arg.pagefault.address - area_dst; + offset &= ~(page_size-1 ); + +if (move_page (uffd, offset)) + args->missing_faults++; +} + +static void uffd_move_test (uffd_test_args_t *targs) +{ +unsigned long nr; +pthread_t uffd_mon; +char c; +unsigned long long count; +struct uffd_args args = {0 }; + +/* Prevent source pages from being mapped more than once */ +if (madvise (area_src, nr_pages * page_size,MADV_DONTFORK )) +err ("madvise(MADV_DONTFORK) failure" ); + +if (uffd_register (uffd, area_dst, nr_pages * page_size, + true, false, false)) +err ("register failure" ); + + args.handle_fault = uffd_move_handle_fault; +if (pthread_create (&uffd_mon,NULL , uffd_poll_thread, &args)) +err ("uffd_poll_thread create" ); + +/* +* Read each of the pages back using the UFFD-registered mapping. We +* expect that the first time we touch a page, it will result in a missing +* fault. uffd_poll_thread will resolve the fault by moving source +* page to destination. +*/ +for (nr =0 ; nr < nr_pages; nr++) { +/* Check area_src content */ + count = *area_count (area_src, nr); +if (count != count_verify[nr]) +err ("nr %lu source memory invalid %llu %llu\n" , + nr, count, count_verify[nr]); + +/* Faulting into area_dst should move the page */ + count = *area_count (area_dst, nr); +if (count != count_verify[nr]) +err ("nr %lu memory corruption %llu %llu\n" , + nr, count, count_verify[nr]); + +/* Re-check area_src content which should be empty */ + count = *area_count (area_src, nr); +if (count !=0 ) +err ("nr %lu move failed %llu %llu\n" , + nr, count, count_verify[nr]); + } + +if (write (pipefd[1 ], &c,sizeof (c)) !=sizeof (c)) +err ("pipe write" ); +if (pthread_join (uffd_mon,NULL )) +err ("join() failed" ); + +if (args.missing_faults != nr_pages || args.minor_faults !=0 ) +uffd_test_fail ("stats check error" ); +else +uffd_test_pass (); +} + +static int prevent_hugepages (void ) +{ +/* This should be done before source area is populated */ +if (madvise (area_src, nr_pages * page_size,MADV_NOHUGEPAGE )) { +/* Ignore if CONFIG_TRANSPARENT_HUGEPAGE=n */ +if (errno !=EINVAL ) +return -errno; + } +return 0 ; +} + +struct uffd_test_case_ops uffd_move_test_case_ops = { + .post_alloc = prevent_hugepages, +}; + +#define ALIGN_UP (x, align_to) \ + (__typeof__(x))((((unsigned long)(x)) + ((align_to)-1)) & ~((align_to)-1)) + +static char *orig_area_src, *orig_area_dst; +static int pmd_align_areas (void ) +{ + orig_area_src = area_src; + orig_area_dst = area_dst; + area_src =ALIGN_UP (area_src, page_size); + area_dst =ALIGN_UP (area_dst, page_size); + nr_pages--;tools/testing/selftests/mm/uffd-unit-tests.c static void uffd_poison_test(uffd_test_args_t *targs) · line 1178
Quoted 10 lines
tools/testing/selftests/mm/uffd-unit-tests.c +10
-0+ +return 0 ; +} + +static void pmd_restore_areas (void ) +{ + area_src = orig_area_src; + area_dst = orig_area_dst; + nr_pages++; +}Please stop using more global variables.. uffd tests are even less maintainable.
Quoted 2 lines
Maybe you can consider add a flag for
uffd_test_ctx_init()? For allocating either small/thp/default?
I was considering that but was not sure which way would be more preferable - using these new callbacks or adding new page size requirements. I'll change to the latter.
Quoted 10 lines
tools/testing/selftests/mm/uffd-unit-tests.c static void uffd_poison_test(uffd_test_args_t *targs) · line 1182
Quoted 6 lines
tools/testing/selftests/mm/uffd-unit-tests.c +4
-0+ +static int adjust_page_size (void ) +{ + page_size =default_huge_page_size ();This is hacky too, currently
page_sizeis the realpage_sizebacking the memory.
Quoted 3 lines
To make thp test simple, maybe just add one more test to MOVE a large chunk to replace the thp test, which may contain a few thps? It also doesn't need to be fault based.
Sorry, I didn't get your suggestion. Could you please clarify? Which thp test are you referring to?
Thanks,
Suren.
Quoted 43 lines
Quoted 37 lines
tools/testing/selftests/mm/uffd-unit-tests.c +25
-1+ nr_pages =UFFD_TEST_MEM_SIZE / page_size; + +return 0 ; +} + +struct uffd_test_case_ops uffd_move_pmd_test_case_ops = { + .pre_alloc = adjust_page_size, + .post_alloc = pmd_align_areas, + .pre_release = pmd_restore_areas, +}; +/* * Test the returned uffdio_register.ioctls with different register modes. * Note that _UFFDIO_ZEROPAGE is tested separately in the zeropage test. @@ -1141,6 +1268,20 @@ uffd_test_case_t uffd_tests[] = { .mem_targets = MEM_ALL, .uffd_feature_required = 0, }, + { + .name = "move", + .uffd_fn = uffd_move_test, + .mem_targets = MEM_ANON, + .uffd_feature_required = UFFD_FEATURE_MOVE, + .test_case_ops = &uffd_move_test_case_ops, + }, + { + .name = "move-pmd", + .uffd_fn = uffd_move_test, + .mem_targets = MEM_ANON, + .uffd_feature_required = UFFD_FEATURE_MOVE, + .test_case_ops = &uffd_move_pmd_test_case_ops, + }, { .name = "wp-fork", .uffd_fn = uffd_wp_fork_test, --Quoted 2 lines
2.42.0.820.g83a721a137-goog
--
Peter Xu
Peter Xu
reviewerRe: [PATCH v4 5/5] selftests/mm: add UFFDIO_MOVE ioctl test
Quoted 7 lines · Suren Baghdasaryan
Quoted 7 lines
tools/testing/selftests/mm/uffd-unit-tests.c static void uffd_poison_test(uffd_test_args_t *targs) · line 1182
Quoted 3 lines
tools/testing/selftests/mm/uffd-unit-tests.c +3
-0+static int adjust_page_size (void ) +{ + page_size =default_huge_page_size ();This is hacky too, currently
page_sizeis the realpage_sizebacking the memory.
Quoted 6 lines
Quoted 3 lines
To make thp test simple, maybe just add one more test to MOVE a large chunk to replace the thp test, which may contain a few thps? It also doesn't need to be fault based.
Sorry, I didn't get your suggestion. Could you please clarify? Which thp test are you referring to?
The new "move-pmd" test.
I meant maybe it makes sense to have one separate MOVE test for when one ioctl(MOVE) covers a large range which can cover some thps. Then that will trigger thp paths. Assuming the fault paths are already covered in the generic "move" test.
Thanks,
Signature · 1 line
Peter Xu
Suren Baghdasaryan
reviewerRe: [PATCH v4 5/5] selftests/mm: add UFFDIO_MOVE ioctl test
Quoted 16 lines · Peter Xu
Quoted 7 lines · Suren Baghdasaryan
Quoted 7 lines
tools/testing/selftests/mm/uffd-unit-tests.c static void uffd_poison_test(uffd_test_args_t *targs) · line 1182
Quoted 3 lines
tools/testing/selftests/mm/uffd-unit-tests.c +3
-0+static int adjust_page_size (void ) +{ + page_size =default_huge_page_size ();This is hacky too, currently
page_sizeis the realpage_sizebacking the memory.Quoted 6 lines
Quoted 3 lines
To make thp test simple, maybe just add one more test to MOVE a large chunk to replace the thp test, which may contain a few thps? It also doesn't need to be fault based.
Sorry, I didn't get your suggestion. Could you please clarify? Which thp test are you referring to?
Quoted 6 lines
The new "move-pmd" test.
I meant maybe it makes sense to have one separate MOVE test for when one ioctl(MOVE) covers a large range which can cover some thps. Then that will trigger thp paths. Assuming the fault paths are already covered in the generic "move" test.
Oh, you mean I should not share uffd_move_test() between move and move-pmd test and have separate logic instead that does not rely on the page_size overrides? If so then I think that's doable. Some more code but probably cleaner.
Quoted 6 lines
Thanks,
--
Peter Xu