From: Andrea Arcangeli <hidden> Date: 2015-03-05 17:23:52
Hello everyone,
This is a RFC for the userfaultfd syscall API v3 that addresses the
feedback received for the previous v2 submit.
The main change from the v2 is that MADV_USERFAULT/NOUSERFAULT
disappeared (they're replaced by the UFFDIO_REGISTER/UNREGISTER
ioctls). In short userfaults are now only possible through the
userfaultfd. The remap_anon_pages syscall also disappeared replaced by
the UFFDIO_REMAP ioctl which is in turn mostly obsoleted by the newer
UFFDIO_COPY and UFFDIO_ZEROPAGE ioctls that are indeed more efficient
by never having to flush the TLB. The suggestion to copy the data
instead of moving it, in order to resolve the userfault, was
immediately agreed.
The latest code can also be cloned here:
git clone --reference linux -b userfault git://git.kernel.org/pub/scm/linux/kernel/git/andrea/aa.git
Userfaults allow to implement on demand paging from userland and more
generally they allow userland to more efficiently take control on
various types of page faults.
For example userfaults allows a proper and more optimal implementation
of the PROT_NONE+SIGSEGV trick.
There has been interest from multiple users for different use cases:
1) KVM postcopy live migration (one form of cloud memory
externalization). KVM postcopy live migration is the primary driver
of this work:
http://blog.zhaw.ch/icclab/setting-up-post-copy-live-migration-in-openstack/http://lists.gnu.org/archive/html/qemu-devel/2015-02/msg04873.html
)
2) KVM postcopy live snapshotting (allowing to limit/throttle the
memory usage, unlike fork would, plus the avoidance of fork
overhead in the first place).
The syscall API is already contemplating the wrprotect fault
tracking and it's generic enough to allow its later implementation
in a backwards compatible fashion.
3) KVM userfaults on shared memory. The UFFDIO_COPY lowlevel method
should be extended to work also on tmpfs and then the
uffdio_register.ioctls will notify userland that UFFDIO_COPY is
available even when the registered virtual memory range is tmpfs
backed.
4) alternate mechanism to notify web browsers or apps on embedded
devices that volatile pages have been reclaimed. This basically
avoids the need to run a syscall before the app can access with the
CPU the virtual regions marked volatile. This also requires point 3)
to be fulfilled, as volatile pages happily apply to tmpfs.
5) postcopy live migration of binaries inside linux containers.
Even though there wasn't a real use case requesting it yet, the new
API also allows to implement distributed shared memory in a way that
readonly shared mappings can exist simultaneously in different hosts
and they can be become exclusive at the first wrprotect fault.
The UFFDIO_REMAP method is still present in the patchset but it's
provided primarily to remove (add not) memory from the userfault
range. The addition of the UFFDIO_REMAP method is intentionally kept
at the end of the patchset. The postcopy live migration qemu code will
only use UFFDIO_COPY and UFFDIO_ZEROPAGE. UFFDIO_REMAP isn't intended
to be merged upstream in the short term, and it can be dropped later
if there's an agreement it's a bad idea to keep it around in the
patchset.
David run some KVM postcopy live migration benchmarks on a 8-way CPU
system and he measured that using UFFDIO_COPY instead of UFFDIO_REMAP
resulted in a roughly a -20% reduction in latency which is good. The
standard deviation error on the latency measurement decreased
significantly as well (because the number of CPUs that required IPI
delivery was variable, while the copy always takes roughly the same
time). A bigger improvement is expectable if measured on a larger host
with more CPUs.
All UFFDIO_COPY/ZEROPAGE/REMAP methods already support CRIU postcopy
live migration and the UFFD can be passed to a manager process through
unix domain sockets to satisfy point 5).
I look forward to discuss this further next week at the LSF/MM
summit, if you're attending the summit see you soon!
Comments welcome, thanks,
Andrea
Credits: partially funded by the Orbit EU project.
PS. There is one TODO detail worth mentioning for completeness that
affects usage 2) and UFFDIO_REMAP if used to remove memory from the
userfault range: handle_userfault() is only effective if
FAULT_FLAG_ALLOW_RETRY is set... but that is only set at the first
attempted page fault. If by accident some thread was already faulting
in the range and the first page fault attempt returned VM_FAULT_RETRY
and UFFDIO_REMAP or UFFDIO_WP jumps in to arm the userfault just
before the second attempt starts, a SIGBUS would be raised by the page
fault. Stopping all thread access to the userfault ranges during
UFFDIO_REMAP/WP while possible, isn't optimal. Currently (excluding
real filebacked mappings and handle_userfault() itself which is
clearly no problem) only tmpfs or a swapin can return
VM_FAULT_RETRY. To close this SIGBUS window for all usages, the
simplest solution would be that if FAULT_FLAG_TRIED is set
VM_FAULT_RETRY can still be returned (but only by handle_userfault
that has a legitimate reason for insisting a second time in a row with
VM_FAULT_RETRY). That would require some change to the FAULT_FLAG
semantics. Again userland could cope with this detail but it'd be
inefficient to solve it in userland. This would be a fully backwards
compatible change and it's only strictly required by the wrprotect
tracking mode, so it's no problem to solve this later. Because of its
inherent racy nature, nobody could possibly depend on a racy SIGBUS
being raised now, when it won't be raised anymore later.
Andrea Arcangeli (21):
userfaultfd: waitqueue: add nr wake parameter to __wake_up_locked_key
userfaultfd: linux/Documentation/vm/userfaultfd.txt
userfaultfd: uAPI
userfaultfd: linux/userfaultfd_k.h
userfaultfd: add vm_userfaultfd_ctx to the vm_area_struct
userfaultfd: add VM_UFFD_MISSING and VM_UFFD_WP
userfaultfd: call handle_userfault() for userfaultfd_missing() faults
userfaultfd: teach vma_merge to merge across vma->vm_userfaultfd_ctx
userfaultfd: prevent khugepaged to merge if userfaultfd is armed
userfaultfd: add new syscall to provide memory externalization
userfaultfd: buildsystem activation
userfaultfd: activate syscall
userfaultfd: UFFDIO_COPY|UFFDIO_ZEROPAGE uAPI
userfaultfd: mcopy_atomic|mfill_zeropage: UFFDIO_COPY|UFFDIO_ZEROPAGE
preparation
userfaultfd: UFFDIO_COPY and UFFDIO_ZEROPAGE
userfaultfd: remap_pages: rmap preparation
userfaultfd: remap_pages: swp_entry_swapcount() preparation
userfaultfd: UFFDIO_REMAP uABI
userfaultfd: remap_pages: UFFDIO_REMAP preparation
userfaultfd: UFFDIO_REMAP
userfaultfd: add userfaultfd_wp mm helpers
Documentation/ioctl/ioctl-number.txt | 1 +
Documentation/vm/userfaultfd.txt | 97 +++
arch/powerpc/include/asm/systbl.h | 1 +
arch/powerpc/include/asm/unistd.h | 2 +-
arch/powerpc/include/uapi/asm/unistd.h | 1 +
arch/x86/syscalls/syscall_32.tbl | 1 +
arch/x86/syscalls/syscall_64.tbl | 1 +
fs/Makefile | 1 +
fs/userfaultfd.c | 1128 ++++++++++++++++++++++++++++++++
include/linux/mm.h | 4 +-
include/linux/mm_types.h | 11 +
include/linux/swap.h | 6 +
include/linux/syscalls.h | 1 +
include/linux/userfaultfd_k.h | 112 ++++
include/linux/wait.h | 5 +-
include/uapi/linux/userfaultfd.h | 150 +++++
init/Kconfig | 11 +
kernel/fork.c | 3 +-
kernel/sched/wait.c | 7 +-
kernel/sys_ni.c | 1 +
mm/Makefile | 1 +
mm/huge_memory.c | 217 +++++-
mm/madvise.c | 3 +-
mm/memory.c | 16 +
mm/mempolicy.c | 4 +-
mm/mlock.c | 3 +-
mm/mmap.c | 39 +-
mm/mprotect.c | 3 +-
mm/rmap.c | 9 +
mm/swapfile.c | 13 +
mm/userfaultfd.c | 793 ++++++++++++++++++++++
net/sunrpc/sched.c | 2 +-
32 files changed, 2593 insertions(+), 54 deletions(-)
create mode 100644 Documentation/vm/userfaultfd.txt
create mode 100644 fs/userfaultfd.c
create mode 100644 include/linux/userfaultfd_k.h
create mode 100644 include/uapi/linux/userfaultfd.h
create mode 100644 mm/userfaultfd.c
--
To unsubscribe, send a message with 'unsubscribe linux-mm' in
the body to majordomo@kvack.org. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"dont@kvack.org"> email@kvack.org </a>
From: Andrea Arcangeli <hidden> Date: 2015-03-05 17:19:19
As far as the rmap code is concerned, rmap_pages only alters the
page->mapping and page->index. It does it while holding the page
lock. However there are a few places that in presence of anon pages
are allowed to do rmap walks without the page lock (split_huge_page
and page_referenced_anon). Those places that are doing rmap walks
without taking the page lock first, must be updated to re-check that
the page->mapping didn't change after they obtained the anon_vma
lock. remap_pages takes the anon_vma lock for writing before altering
the page->mapping, so if the page->mapping is still the same after
obtaining the anon_vma lock (without the page lock), the rmap walks
can go ahead safely (and remap_pages will wait them to complete before
proceeding).
remap_pages serializes against itself with the page lock.
All other places taking the anon_vma lock while holding the mmap_sem
for writing, don't need to check if the page->mapping has changed
after taking the anon_vma lock, regardless of the page lock, because
remap_pages holds the mmap_sem for reading.
There's one constraint enforced to allow this simplification: the
source pages passed to remap_pages must be mapped only in one vma, but
this is not a limitation when used to handle userland page faults. The
source addresses passed to remap_pages should be set as VM_DONTCOPY
with MADV_DONTFORK to avoid any risk of the mapcount of the pages
increasing, if fork runs in parallel in another thread, before or
while remap_pages runs.
Signed-off-by: Andrea Arcangeli <redacted>
---
mm/huge_memory.c | 23 +++++++++++++++++++----
mm/rmap.c | 9 +++++++++
2 files changed, 28 insertions(+), 4 deletions(-)
From: Andrea Arcangeli <hidden> Date: 2015-03-05 17:19:22
This is where the page faults must be modified to call
handle_userfault() if userfaultfd_missing() is true (so if the
vma->vm_flags had VM_UFFD_MISSING set).
handle_userfault() then takes care of blocking the page fault and
delivering it to userland.
The fault flags must also be passed as parameter so the "read|write"
kind of fault can be passed to userland.
Signed-off-by: Andrea Arcangeli <redacted>
---
mm/huge_memory.c | 68 ++++++++++++++++++++++++++++++++++++++------------------
mm/memory.c | 16 +++++++++++++
2 files changed, 62 insertions(+), 22 deletions(-)
@@ -2585,6 +2586,12 @@ static int do_anonymous_page(struct mm_struct *mm, struct vm_area_struct *vma,page_table=pte_offset_map_lock(mm,pmd,address,&ptl);if(!pte_none(*page_table))gotounlock;+/* Deliver the page fault to userland, check inside PT lock */+if(userfaultfd_missing(vma)){+pte_unmap_unlock(page_table,ptl);+returnhandle_userfault(vma,address,flags,+VM_UFFD_MISSING);+}gotosetpte;}
@@ -2612,6 +2619,15 @@ static int do_anonymous_page(struct mm_struct *mm, struct vm_area_struct *vma,if(!pte_none(*page_table))gotorelease;+/* Deliver the page fault to userland, check inside PT lock */+if(userfaultfd_missing(vma)){+pte_unmap_unlock(page_table,ptl);+mem_cgroup_cancel_charge(page,memcg);+page_cache_release(page);+returnhandle_userfault(vma,address,flags,+VM_UFFD_MISSING);+}+inc_mm_counter_fast(mm,MM_ANONPAGES);page_add_new_anon_rmap(page,vma,address);mem_cgroup_commit_charge(page,memcg,false);--
To unsubscribe, send a message with 'unsubscribe linux-mm' in
the body to majordomo@kvack.org. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"dont@kvack.org"> email@kvack.org </a>
From: Andrea Arcangeli <hidden> Date: 2015-03-05 17:19:37
This implements mcopy_atomic and mfill_zeropage that are the lowlevel
VM methods that are invoked respectively by the UFFDIO_COPY and
UFFDIO_ZEROPAGE userfaultfd commands.
Signed-off-by: Andrea Arcangeli <redacted>
---
include/linux/userfaultfd_k.h | 6 +
mm/Makefile | 1 +
mm/userfaultfd.c | 267 ++++++++++++++++++++++++++++++++++++++++++
3 files changed, 274 insertions(+)
create mode 100644 mm/userfaultfd.c
@@ -0,0 +1,267 @@+/*+*mm/userfaultfd.c+*+*Copyright(C)2015RedHat,Inc.+*+*ThisworkislicensedunderthetermsoftheGNUGPL,version2.See+*theCOPYINGfileinthetop-leveldirectory.+*/++#include<linux/mm.h>+#include<linux/pagemap.h>+#include<linux/rmap.h>+#include<linux/swap.h>+#include<linux/swapops.h>+#include<linux/userfaultfd_k.h>+#include<linux/mmu_notifier.h>+#include<asm/tlbflush.h>+#include"internal.h"++staticintmcopy_atomic_pte(structmm_struct*dst_mm,+pmd_t*dst_pmd,+structvm_area_struct*dst_vma,+unsignedlongdst_addr,+unsignedlongsrc_addr)+{+structmem_cgroup*memcg;+pte_t_dst_pte,*dst_pte;+spinlock_t*ptl;+structpage*page;+void*page_kaddr;+intret;++ret=-ENOMEM;+page=alloc_page_vma(GFP_HIGHUSER_MOVABLE,dst_vma,dst_addr);+if(!page)+gotoout;++page_kaddr=kmap(page);+ret=-EFAULT;+if(copy_from_user(page_kaddr,(constvoid__user*)src_addr,+PAGE_SIZE))+gotoout_kunmap_release;+kunmap(page);++/*+*Thememorybarrierinside__SetPageUptodatemakessurethat+*preceedingstorestothepagecontentsbecomevisiblebefore+*theset_pte_at()write.+*/+__SetPageUptodate(page);++ret=-ENOMEM;+if(mem_cgroup_try_charge(page,dst_mm,GFP_KERNEL,&memcg))+gotoout_release;++_dst_pte=mk_pte(page,dst_vma->vm_page_prot);+if(dst_vma->vm_flags&VM_WRITE)+_dst_pte=pte_mkwrite(pte_mkdirty(_dst_pte));++ret=-EEXIST;+dst_pte=pte_offset_map_lock(dst_mm,dst_pmd,dst_addr,&ptl);+if(!pte_none(*dst_pte))+gotoout_release_uncharge_unlock;++inc_mm_counter(dst_mm,MM_ANONPAGES);+page_add_new_anon_rmap(page,dst_vma,dst_addr);+mem_cgroup_commit_charge(page,memcg,false);+lru_cache_add_active_or_unevictable(page,dst_vma);++set_pte_at(dst_mm,dst_addr,dst_pte,_dst_pte);++/* No need to invalidate - it was non-present before */+update_mmu_cache(dst_vma,dst_addr,dst_pte);++pte_unmap_unlock(dst_pte,ptl);+ret=0;+out:+returnret;+out_release_uncharge_unlock:+pte_unmap_unlock(dst_pte,ptl);+mem_cgroup_cancel_charge(page,memcg);+out_release:+page_cache_release(page);+gotoout;+out_kunmap_release:+kunmap(page);+gotoout_release;+}++staticintmfill_zeropage_pte(structmm_struct*dst_mm,+pmd_t*dst_pmd,+structvm_area_struct*dst_vma,+unsignedlongdst_addr)+{+pte_t_dst_pte,*dst_pte;+spinlock_t*ptl;+intret;++_dst_pte=pte_mkspecial(pfn_pte(my_zero_pfn(dst_addr),+dst_vma->vm_page_prot));+ret=-EEXIST;+dst_pte=pte_offset_map_lock(dst_mm,dst_pmd,dst_addr,&ptl);+if(!pte_none(*dst_pte))+gotoout_unlock;+set_pte_at(dst_mm,dst_addr,dst_pte,_dst_pte);+/* No need to invalidate - it was non-present before */+update_mmu_cache(dst_vma,dst_addr,dst_pte);+ret=0;+out_unlock:+pte_unmap_unlock(dst_pte,ptl);+returnret;+}++staticpmd_t*mm_alloc_pmd(structmm_struct*mm,unsignedlongaddress)+{+pgd_t*pgd;+pud_t*pud;+pmd_t*pmd=NULL;++pgd=pgd_offset(mm,address);+pud=pud_alloc(mm,pgd,address);+if(pud)+/*+*Notethatwedidn'trunthisbecausethepmdwas+*missing,the*pmdmaybealreadyestablishedandin+*turnitmayalsobeatrans_huge_pmd.+*/+pmd=pmd_alloc(mm,pud,address);+returnpmd;+}++staticssize_t__mcopy_atomic(structmm_struct*dst_mm,unsignedlongdst_start,+unsignedlongsrc_start,unsignedlonglen,+boolzeropage)+{+structvm_area_struct*dst_vma;+ssize_terr;+pmd_t*dst_pmd;+unsignedlongsrc_addr,dst_addr;+longcopied=0;++/*+*Sanitizethecommandparameters:+*/+BUG_ON(dst_start&~PAGE_MASK);+BUG_ON(len&~PAGE_MASK);++/* Does the address range wrap, or is the span zero-sized? */+BUG_ON(src_start+len<=src_start);+BUG_ON(dst_start+len<=dst_start);++down_read(&dst_mm->mmap_sem);++/*+*Makesurethevmaisnotshared,thatthedstrangeis+*bothvalidandfullywithinasingleexistingvma.+*/+err=-EINVAL;+dst_vma=find_vma(dst_mm,dst_start);+if(!dst_vma||(dst_vma->vm_flags&VM_SHARED))+gotoout;+if(dst_start<dst_vma->vm_start||+dst_start+len>dst_vma->vm_end)+gotoout;++/*+*Bestrictandonlyallow__mcopy_atomiconuserfaultfd+*registeredrangestopreventuserlanderrorsgoing+*unnoticed.AsfarastheVMconsistencyisconcerned,it+*wouldbeperfectlysafetoremovethischeck,butthere's+*nousefulusagefor__mcopy_atomicousideofuserfaultfd+*registeredranges.Thisisafterallwhytheseareioctls+*belongingtotheuserfaultfdandnotsyscalls.+*/+if(!dst_vma->vm_userfaultfd_ctx.ctx)+gotoout;++/*+*FIXME:onlyallowcopyingonanonymousvmas,tmpfsshould+*beadded.+*/+if(dst_vma->vm_ops)+gotoout;++/*+*Ensurethedst_vmahasaanon_vmaorthispage+*wouldgetaNULLanon_vmawhenmovedinthe+*dst_vma.+*/+err=-ENOMEM;+if(unlikely(anon_vma_prepare(dst_vma)))+gotoout;++for(src_addr=src_start,dst_addr=dst_start;+src_addr<src_start+len;){+pmd_tdst_pmdval;+BUG_ON(dst_addr>=dst_start+len);+dst_pmd=mm_alloc_pmd(dst_mm,dst_addr);+if(unlikely(!dst_pmd)){+err=-ENOMEM;+break;+}++dst_pmdval=pmd_read_atomic(dst_pmd);+/*+*Ifthedst_pmdismappedasTHPdon't+*overrideitandjustbestrict.+*/+if(unlikely(pmd_trans_huge(dst_pmdval))){+err=-EEXIST;+break;+}+if(unlikely(pmd_none(dst_pmdval))&&+unlikely(__pte_alloc(dst_mm,dst_vma,dst_pmd,+dst_addr))){+err=-ENOMEM;+break;+}+/* If an huge pmd materialized from under us fail */+if(unlikely(pmd_trans_huge(*dst_pmd))){+err=-EFAULT;+break;+}++BUG_ON(pmd_none(*dst_pmd));+BUG_ON(pmd_trans_huge(*dst_pmd));++if(!zeropage)+err=mcopy_atomic_pte(dst_mm,dst_pmd,dst_vma,+dst_addr,src_addr);+else+err=mfill_zeropage_pte(dst_mm,dst_pmd,dst_vma,+dst_addr);++cond_resched();++if(!err){+dst_addr+=PAGE_SIZE;+src_addr+=PAGE_SIZE;+copied+=PAGE_SIZE;++if(fatal_signal_pending(current))+err=-EINTR;+}+if(err)+break;+}++out:+up_read(&dst_mm->mmap_sem);+BUG_ON(copied<0);+BUG_ON(err>0);+BUG_ON(!copied&&!err);+returncopied?copied:err;+}++ssize_tmcopy_atomic(structmm_struct*dst_mm,unsignedlongdst_start,+unsignedlongsrc_start,unsignedlonglen)+{+return__mcopy_atomic(dst_mm,dst_start,src_start,len,false);+}++ssize_tmfill_zeropage(structmm_struct*dst_mm,unsignedlongstart,+unsignedlonglen)+{+return__mcopy_atomic(dst_mm,start,0,len,true);+}
From: Andrea Arcangeli <hidden> Date: 2015-03-05 17:20:00
These two ioctl allows to either atomically copy or to map zeropages
into the virtual address space. This is used by the thread that opened
the userfaultfd to resolve the userfaults.
Signed-off-by: Andrea Arcangeli <redacted>
---
fs/userfaultfd.c | 100 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 100 insertions(+)
@@ -798,6 +798,100 @@ out:returnret;}+staticintuserfaultfd_copy(structuserfaultfd_ctx*ctx,+unsignedlongarg)+{+__s64ret;+structuffdio_copyuffdio_copy;+structuffdio_copy__user*user_uffdio_copy;+structuserfaultfd_wake_rangerange;++user_uffdio_copy=(structuffdio_copy__user*)arg;++ret=-EFAULT;+if(copy_from_user(&uffdio_copy,user_uffdio_copy,+/* don't copy "copy" and "wake" last field */+sizeof(uffdio_copy)-sizeof(__s64)*2))+gotoout;++ret=validate_range(ctx->mm,uffdio_copy.dst,uffdio_copy.len);+if(ret)+gotoout;+/*+*doublecheckforwraparoundjustincase.copy_from_user()+*willlatercheckuffdio_copy.src+uffdio_copy.lentofit+*intheuserlandrange.+*/+ret=-EINVAL;+if(uffdio_copy.src+uffdio_copy.len<=uffdio_copy.src)+gotoout;+if(uffdio_copy.mode&~UFFDIO_COPY_MODE_DONTWAKE)+gotoout;++ret=mcopy_atomic(ctx->mm,uffdio_copy.dst,uffdio_copy.src,+uffdio_copy.len);+if(unlikely(put_user(ret,&user_uffdio_copy->copy)))+return-EFAULT;+if(ret<0)+gotoout;+BUG_ON(!ret);+/* len == 0 would wake all */+range.len=ret;+if(!(uffdio_copy.mode&UFFDIO_COPY_MODE_DONTWAKE)){+range.start=uffdio_copy.dst;+ret=wake_userfault(ctx,&range);+if(unlikely(put_user(ret,&user_uffdio_copy->wake)))+return-EFAULT;+}+ret=range.len==uffdio_copy.len?0:-EAGAIN;+out:+returnret;+}++staticintuserfaultfd_zeropage(structuserfaultfd_ctx*ctx,+unsignedlongarg)+{+__s64ret;+structuffdio_zeropageuffdio_zeropage;+structuffdio_zeropage__user*user_uffdio_zeropage;+structuserfaultfd_wake_rangerange;++user_uffdio_zeropage=(structuffdio_zeropage__user*)arg;++ret=-EFAULT;+if(copy_from_user(&uffdio_zeropage,user_uffdio_zeropage,+/* don't copy "zeropage" and "wake" last field */+sizeof(uffdio_zeropage)-sizeof(__s64)*2))+gotoout;++ret=validate_range(ctx->mm,uffdio_zeropage.range.start,+uffdio_zeropage.range.len);+if(ret)+gotoout;+ret=-EINVAL;+if(uffdio_zeropage.mode&~UFFDIO_ZEROPAGE_MODE_DONTWAKE)+gotoout;++ret=mfill_zeropage(ctx->mm,uffdio_zeropage.range.start,+uffdio_zeropage.range.len);+if(unlikely(put_user(ret,&user_uffdio_zeropage->zeropage)))+return-EFAULT;+if(ret<0)+gotoout;+/* len == 0 would wake all */+BUG_ON(!ret);+range.len=ret;+if(!(uffdio_zeropage.mode&UFFDIO_ZEROPAGE_MODE_DONTWAKE)){+range.start=uffdio_zeropage.range.start;+ret=wake_userfault(ctx,&range);+if(unlikely(put_user(ret,&user_uffdio_zeropage->wake)))+return-EFAULT;+}+ret=range.len==uffdio_zeropage.range.len?0:-EAGAIN;+out:+returnret;+}+/**userlandasksforacertainAPIversionandwereturnwhichbits*andioctlcommandsareimplementedinthiskernelforsuchAPI
To unsubscribe, send a message with 'unsubscribe linux-mm' in
the body to majordomo@kvack.org. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"dont@kvack.org"> email@kvack.org </a>
From: Andrea Arcangeli <hidden> Date: 2015-03-05 17:21:01
Once an userfaultfd has been created and certain region of the process
virtual address space have been registered into it, the thread
responsible for doing the memory externalization can manage the page
faults in userland by talking to the kernel using the userfaultfd
protocol.
poll() can be used to know when there are new pending userfaults to be
read (POLLIN).
Signed-off-by: Andrea Arcangeli <redacted>
---
fs/userfaultfd.c | 977 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 977 insertions(+)
create mode 100644 fs/userfaultfd.c
@@ -0,0 +1,977 @@+/*+*fs/userfaultfd.c+*+*Copyright(C)2007DavideLibenzi<davidel@xmailserver.org>+*Copyright(C)2008-2009RedHat,Inc.+*Copyright(C)2015RedHat,Inc.+*+*ThisworkislicensedunderthetermsoftheGNUGPL,version2.See+*theCOPYINGfileinthetop-leveldirectory.+*+*Somepartderivedfromfs/eventfd.c(anoninodesetup)and+*mm/ksm.c(mmhashing).+*/++#include<linux/hashtable.h>+#include<linux/sched.h>+#include<linux/mm.h>+#include<linux/poll.h>+#include<linux/slab.h>+#include<linux/seq_file.h>+#include<linux/file.h>+#include<linux/bug.h>+#include<linux/anon_inodes.h>+#include<linux/syscalls.h>+#include<linux/userfaultfd_k.h>+#include<linux/mempolicy.h>+#include<linux/ioctl.h>+#include<linux/security.h>++enumuserfaultfd_state{+UFFD_STATE_WAIT_API,+UFFD_STATE_RUNNING,+};++structuserfaultfd_ctx{+/* pseudo fd refcounting */+atomic_trefcount;+/* waitqueue head for the userfaultfd page faults */+wait_queue_head_tfault_wqh;+/* waitqueue head for the pseudo fd to wakeup poll/read */+wait_queue_head_tfd_wqh;+/* userfaultfd syscall flags */+unsignedintflags;+/* state machine */+enumuserfaultfd_statestate;+/* released */+boolreleased;+/* mm with one ore more vmas attached to this userfaultfd_ctx */+structmm_struct*mm;+};++structuserfaultfd_wait_queue{+unsignedlongaddress;+wait_queue_twq;+boolpending;+structuserfaultfd_ctx*ctx;+};++structuserfaultfd_wake_range{+unsignedlongstart;+unsignedlonglen;+};++staticintuserfaultfd_wake_function(wait_queue_t*wq,unsignedmode,+intwake_flags,void*key)+{+structuserfaultfd_wake_range*range=key;+intret;+structuserfaultfd_wait_queue*uwq;+unsignedlongstart,len;++uwq=container_of(wq,structuserfaultfd_wait_queue,wq);+ret=0;+/* don't wake the pending ones to avoid reads to block */+if(uwq->pending&&!ACCESS_ONCE(uwq->ctx->released))+gotoout;+/* len == 0 means wake all */+start=range->start;+len=range->len;+if(len&&(start>uwq->address||start+len<=uwq->address))+gotoout;+ret=wake_up_state(wq->private,mode);+if(ret)+/* wake only once, autoremove behavior */+list_del_init(&wq->task_list);+out:+returnret;+}++/**+*userfaultfd_ctx_get-Acquiresareferencetotheinternaluserfaultfd+*context.+*@ctx:[in]Pointertotheuserfaultfdcontext.+*+*Returns:Incaseofsuccess,returnsnotzero.+*/+staticvoiduserfaultfd_ctx_get(structuserfaultfd_ctx*ctx)+{+if(!atomic_inc_not_zero(&ctx->refcount))+BUG();+}++/**+*userfaultfd_ctx_put-Releasesareferencetotheinternaluserfaultfd+*context.+*@ctx:[in]Pointertouserfaultfdcontext.+*+*Theuserfaultfdcontextreferencemusthavebeenpreviouslyacquiredeither+*withuserfaultfd_ctx_get()oruserfaultfd_ctx_fdget().+*/+staticvoiduserfaultfd_ctx_put(structuserfaultfd_ctx*ctx)+{+if(atomic_dec_and_test(&ctx->refcount)){+mmdrop(ctx->mm);+kfree(ctx);+}+}++staticinlineunsignedlonguserfault_address(unsignedlongaddress,+unsignedintflags,+unsignedlongreason)+{+BUILD_BUG_ON(PAGE_SHIFT<UFFD_BITS);+address&=PAGE_MASK;+if(flags&FAULT_FLAG_WRITE)+/*+*Encode"write"faultinformationintheLSBofthe+*addressreadbyuserland,withoutdependingon+*FAULT_FLAG_WRITEkernelinternalvalue.+*/+address|=UFFD_BIT_WRITE;+if(reason&VM_UFFD_WP)+/*+*Encode"reason"faultinformationasbitnumber1+*intheaddressreadbyuserland.Ifbitnumber1is+*clearitmeansthereasonisaVM_FAULT_MISSING+*fault.+*/+address|=UFFD_BIT_WP;+returnaddress;+}++/*+*ThelockingrulesinvolvedinreturningVM_FAULT_RETRYdependingon+*FAULT_FLAG_ALLOW_RETRY,FAULT_FLAG_RETRY_NOWAITand+*FAULT_FLAG_KILLABLEarenotstraightforward.The"Caution"+*recommendationin__lock_page_or_retryisnotanunderstatement.+*+*IfFAULT_FLAG_ALLOW_RETRYisset,themmap_semmustbereleased+*beforereturningVM_FAULT_RETRYonlyifFAULT_FLAG_RETRY_NOWAITis+*notset.+*+*IfFAULT_FLAG_ALLOW_RETRYissetbutFAULT_FLAG_KILLABLEisnot+*set,VM_FAULT_RETRYcanstillbereturnedifandonlyifthereare+*fatal_signal_pending()s,andthemmap_semmustbereleasedbefore+*returningit.+*/+inthandle_userfault(structvm_area_struct*vma,unsignedlongaddress,+unsignedintflags,unsignedlongreason)+{+structmm_struct*mm=vma->vm_mm;+structuserfaultfd_ctx*ctx;+structuserfaultfd_wait_queueuwq;++BUG_ON(!rwsem_is_locked(&mm->mmap_sem));++ctx=vma->vm_userfaultfd_ctx.ctx;+if(!ctx)+returnVM_FAULT_SIGBUS;++BUG_ON(ctx->mm!=mm);++VM_BUG_ON(reason&~(VM_UFFD_MISSING|VM_UFFD_WP));+VM_BUG_ON(!(reason&VM_UFFD_MISSING)^!!(reason&VM_UFFD_WP));++/*+*Ifit'salreadyreleaseddon'tgetit.Thisavoidstoloop+*in__get_user_pagesifuserfaultfd_releasewaitsonthe+*callerofhandle_userfaulttoreleasethemmap_sem.+*/+if(unlikely(ACCESS_ONCE(ctx->released)))+returnVM_FAULT_SIGBUS;++/* check that we can return VM_FAULT_RETRY */+if(unlikely(!(flags&FAULT_FLAG_ALLOW_RETRY))){+/*+*Validatetheinvariantthatnowaitmustallowretry+*tobesurenottoreturnSIGBUSerroneouslyon+*nowaitinvocations.+*/+BUG_ON(flags&FAULT_FLAG_RETRY_NOWAIT);+#ifdef CONFIG_DEBUG_VM+if(printk_ratelimit()){+printk(KERN_WARNING+"FAULT_FLAG_ALLOW_RETRY missing %x\n",flags);+dump_stack();+}+#endif+returnVM_FAULT_SIGBUS;+}++/*+*Handlenowait,notmuchtodootherthantellittoretry+*andwait.+*/+if(flags&FAULT_FLAG_RETRY_NOWAIT)+returnVM_FAULT_RETRY;++/* take the reference before dropping the mmap_sem */+userfaultfd_ctx_get(ctx);++/* be gentle and immediately relinquish the mmap_sem */+up_read(&mm->mmap_sem);++init_waitqueue_func_entry(&uwq.wq,userfaultfd_wake_function);+uwq.wq.private=current;+uwq.address=userfault_address(address,flags,reason);+uwq.pending=true;+uwq.ctx=ctx;++spin_lock(&ctx->fault_wqh.lock);+/*+*Afterthe__add_wait_queuetheuwqisvisibletouserland+*throughpoll/read().+*/+__add_wait_queue(&ctx->fault_wqh,&uwq.wq);+for(;;){+set_current_state(TASK_KILLABLE);+if(!uwq.pending||ACCESS_ONCE(ctx->released)||+fatal_signal_pending(current))+break;+spin_unlock(&ctx->fault_wqh.lock);++wake_up_poll(&ctx->fd_wqh,POLLIN);+schedule();++spin_lock(&ctx->fault_wqh.lock);+}+__remove_wait_queue(&ctx->fault_wqh,&uwq.wq);+__set_current_state(TASK_RUNNING);+spin_unlock(&ctx->fault_wqh.lock);++/*+*ctxmaygoawayafterthisiftheuserfaultpseudofdis+*alreadyreleased.+*/+userfaultfd_ctx_put(ctx);++returnVM_FAULT_RETRY;+}++staticintuserfaultfd_release(structinode*inode,structfile*file)+{+structuserfaultfd_ctx*ctx=file->private_data;+structmm_struct*mm=ctx->mm;+structvm_area_struct*vma,*prev;+/* len == 0 means wake all */+structuserfaultfd_wake_rangerange={.len=0,};+unsignedlongnew_flags;++ACCESS_ONCE(ctx->released)=true;++/*+*FlushpagefaultsoutofallCPUs.NOTE:allpagefaults+*mustberetriedwithoutreturningVM_FAULT_SIGBUSif+*userfaultfd_ctx_get()succeedsbutvma->vma_userfault_ctx+*changeswhilehandle_userfaultreleasedthemmap_sem.So+*it'scriticalthatreleasedissettotrue(above),before+*takingthemmap_semforwriting.+*/+down_write(&mm->mmap_sem);+prev=NULL;+for(vma=mm->mmap;vma;vma=vma->vm_next){+cond_resched();+BUG_ON(!!vma->vm_userfaultfd_ctx.ctx^+!!(vma->vm_flags&(VM_UFFD_MISSING|VM_UFFD_WP)));+if(vma->vm_userfaultfd_ctx.ctx!=ctx){+prev=vma;+continue;+}+new_flags=vma->vm_flags&~(VM_UFFD_MISSING|VM_UFFD_WP);+prev=vma_merge(mm,prev,vma->vm_start,vma->vm_end,+new_flags,vma->anon_vma,+vma->vm_file,vma->vm_pgoff,+vma_policy(vma),+NULL_VM_UFFD_CTX);+if(prev)+vma=prev;+else+prev=vma;+vma->vm_flags=new_flags;+vma->vm_userfaultfd_ctx=NULL_VM_UFFD_CTX;+}+up_write(&mm->mmap_sem);++/*+*Afternonewpagefaultscanwaitonthisfault_wqh,flush+*thelastpagefaultsthatmayhavebeenalreadywaitingon+*thefault_wqh.+*/+spin_lock(&ctx->fault_wqh.lock);+__wake_up_locked_key(&ctx->fault_wqh,TASK_NORMAL,0,&range);+spin_unlock(&ctx->fault_wqh.lock);++wake_up_poll(&ctx->fd_wqh,POLLHUP);+userfaultfd_ctx_put(ctx);+return0;+}++staticinlineunsignedintfind_userfault(structuserfaultfd_ctx*ctx,+structuserfaultfd_wait_queue**uwq)+{+wait_queue_t*wq;+structuserfaultfd_wait_queue*_uwq;+unsignedintret=0;++spin_lock(&ctx->fault_wqh.lock);+list_for_each_entry(wq,&ctx->fault_wqh.task_list,task_list){+_uwq=container_of(wq,structuserfaultfd_wait_queue,wq);+if(_uwq->pending){+ret=POLLIN;+if(uwq)+*uwq=_uwq;+break;+}+}+spin_unlock(&ctx->fault_wqh.lock);++returnret;+}++staticunsignedintuserfaultfd_poll(structfile*file,poll_table*wait)+{+structuserfaultfd_ctx*ctx=file->private_data;++poll_wait(file,&ctx->fd_wqh,wait);++switch(ctx->state){+caseUFFD_STATE_WAIT_API:+returnPOLLERR;+caseUFFD_STATE_RUNNING:+returnfind_userfault(ctx,NULL);+default:+BUG();+}+}++staticssize_tuserfaultfd_ctx_read(structuserfaultfd_ctx*ctx,intno_wait,+__u64*addr)+{+ssize_tret;+DECLARE_WAITQUEUE(wait,current);+structuserfaultfd_wait_queue*uwq=NULL;++/* always take the fd_wqh lock before the fault_wqh lock */+spin_lock(&ctx->fd_wqh.lock);+__add_wait_queue(&ctx->fd_wqh,&wait);+for(;;){+set_current_state(TASK_INTERRUPTIBLE);+if(find_userfault(ctx,&uwq)){+uwq->pending=false;+/* careful to always initialize addr if ret == 0 */+*addr=uwq->address;+ret=0;+break;+}+if(signal_pending(current)){+ret=-ERESTARTSYS;+break;+}+if(no_wait){+ret=-EAGAIN;+break;+}+spin_unlock(&ctx->fd_wqh.lock);+schedule();+spin_lock_irq(&ctx->fd_wqh.lock);+}+__remove_wait_queue(&ctx->fd_wqh,&wait);+__set_current_state(TASK_RUNNING);+spin_unlock_irq(&ctx->fd_wqh.lock);++returnret;+}++staticssize_tuserfaultfd_read(structfile*file,char__user*buf,+size_tcount,loff_t*ppos)+{+structuserfaultfd_ctx*ctx=file->private_data;+ssize_t_ret,ret=0;+/* careful to always initialize addr if ret == 0 */+__u64uninitialized_var(addr);+intno_wait=file->f_flags&O_NONBLOCK;++if(ctx->state==UFFD_STATE_WAIT_API)+return-EINVAL;+BUG_ON(ctx->state!=UFFD_STATE_RUNNING);++for(;;){+if(count<sizeof(addr))+returnret?ret:-EINVAL;+_ret=userfaultfd_ctx_read(ctx,no_wait,&addr);+if(_ret<0)+returnret?ret:_ret;+if(put_user(addr,(__u64__user*)buf))+returnret?ret:-EFAULT;+ret+=sizeof(addr);+buf+=sizeof(addr);+count-=sizeof(addr);+/*+*Allowtoreadmorethanonefaultattimebutonly+*blockifwaitingfortheveryfirstone.+*/+no_wait=O_NONBLOCK;+}+}++staticint__wake_userfault(structuserfaultfd_ctx*ctx,+structuserfaultfd_wake_range*range)+{+wait_queue_t*wq;+structuserfaultfd_wait_queue*uwq;+intret;+unsignedlongstart,end;++start=range->start;+end=range->start+range->len;++ret=-ENOENT;+spin_lock(&ctx->fault_wqh.lock);+list_for_each_entry(wq,&ctx->fault_wqh.task_list,task_list){+uwq=container_of(wq,structuserfaultfd_wait_queue,wq);+if(uwq->pending)+continue;+if(uwq->address>=start&&uwq->address<end){+ret=0;+/* wake all in the range and autoremove */+__wake_up_locked_key(&ctx->fault_wqh,TASK_NORMAL,0,+range);+break;+}+}+spin_unlock(&ctx->fault_wqh.lock);++returnret;+}++static__always_inlineintwake_userfault(structuserfaultfd_ctx*ctx,+structuserfaultfd_wake_range*range)+{+if(!waitqueue_active(&ctx->fault_wqh))+return-ENOENT;++return__wake_userfault(ctx,range);+}++static__always_inlineintvalidate_range(structmm_struct*mm,+__u64start,__u64len)+{+__u64task_size=mm->task_size;++if(start&~PAGE_MASK)+return-EINVAL;+if(len&~PAGE_MASK)+return-EINVAL;+if(!len)+return-EINVAL;+if(start<mmap_min_addr)+return-EINVAL;+if(start>=task_size)+return-EINVAL;+if(len>task_size-start)+return-EINVAL;+return0;+}++staticintuserfaultfd_register(structuserfaultfd_ctx*ctx,+unsignedlongarg)+{+structmm_struct*mm=ctx->mm;+structvm_area_struct*vma,*prev,*cur;+intret;+structuffdio_registeruffdio_register;+structuffdio_register__user*user_uffdio_register;+unsignedlongvm_flags,new_flags;+boolfound;+unsignedlongstart,end,vma_end;++user_uffdio_register=(structuffdio_register__user*)arg;++ret=-EFAULT;+if(copy_from_user(&uffdio_register,user_uffdio_register,+sizeof(uffdio_register)-sizeof(__u64)))+gotoout;++ret=-EINVAL;+if(!uffdio_register.mode)+gotoout;+if(uffdio_register.mode&~(UFFDIO_REGISTER_MODE_MISSING|+UFFDIO_REGISTER_MODE_WP))+gotoout;+vm_flags=0;+if(uffdio_register.mode&UFFDIO_REGISTER_MODE_MISSING)+vm_flags|=VM_UFFD_MISSING;+if(uffdio_register.mode&UFFDIO_REGISTER_MODE_WP){+vm_flags|=VM_UFFD_WP;+/*+*FIXME:removethebelowerrorconstraintby+*implementingthewprotecttrackingmode.+*/+ret=-EINVAL;+gotoout;+}++ret=validate_range(mm,uffdio_register.range.start,+uffdio_register.range.len);+if(ret)+gotoout;++start=uffdio_register.range.start;+end=start+uffdio_register.range.len;++down_write(&mm->mmap_sem);+vma=find_vma_prev(mm,start,&prev);++ret=-ENOMEM;+if(!vma)+gotoout_unlock;++/* check that there's at least one vma in the range */+ret=-EINVAL;+if(vma->vm_start>=end)+gotoout_unlock;++/*+*Searchfornotcompatiblevmas.+*+*FIXME:thisshallberelaxedlatersothatitdoesn'tfail+*ontmpfsbackedvmas(inadditiontothecurrentallowance+*onanonymousvmas).+*/+found=false;+for(cur=vma;cur&&cur->vm_start<end;cur=cur->vm_next){+cond_resched();++BUG_ON(!!cur->vm_userfaultfd_ctx.ctx^+!!(cur->vm_flags&(VM_UFFD_MISSING|VM_UFFD_WP)));++/* check not compatible vmas */+ret=-EINVAL;+if(cur->vm_ops)+gotoout_unlock;++/*+*Checkthatthisvmaisn'talreadyownedbya+*differentuserfaultfd.Wecan'tallowmorethanone+*userfaultfdtoownasinglevmasimultaneouslyorwe+*wouldn'tknowwhichonetodelivertheuserfaultsto.+*/+ret=-EBUSY;+if(cur->vm_userfaultfd_ctx.ctx&&+cur->vm_userfaultfd_ctx.ctx!=ctx)+gotoout_unlock;++found=true;+}+BUG_ON(!found);++/*+*Nowthatwescannedallvmaswecanalreadytelluserlandwhich+*ioctlsmethodsareguaranteedtosucceedonthisrange.+*/+ret=-EFAULT;+if(put_user(UFFD_API_RANGE_IOCTLS,&user_uffdio_register->ioctls))+gotoout_unlock;++if(vma->vm_start<start)+prev=vma;++ret=0;+do{+cond_resched();++BUG_ON(vma->vm_ops);+BUG_ON(vma->vm_userfaultfd_ctx.ctx&&+vma->vm_userfaultfd_ctx.ctx!=ctx);++/*+*Nothingtodo:thisvmaisalreadyregisteredintothis+*userfaultfdandwiththerighttrackingmodetoo.+*/+if(vma->vm_userfaultfd_ctx.ctx==ctx&&+(vma->vm_flags&vm_flags)==vm_flags)+gotoskip;++if(vma->vm_start>start)+start=vma->vm_start;+vma_end=min(end,vma->vm_end);++new_flags=(vma->vm_flags&~vm_flags)|vm_flags;+prev=vma_merge(mm,prev,start,vma_end,new_flags,+vma->anon_vma,vma->vm_file,vma->vm_pgoff,+vma_policy(vma),+((structvm_userfaultfd_ctx){ctx}));+if(prev){+vma=prev;+gotonext;+}+if(vma->vm_start<start){+ret=split_vma(mm,vma,start,1);+if(ret)+break;+}+if(vma->vm_end>end){+ret=split_vma(mm,vma,end,0);+if(ret)+break;+}+next:+/*+*Inthevma_merge()successfulmprotect-likecase8:+*thenextvmawasmergedintothecurrentoneand+*thecurrentonehasnotbeenupdatedyet.+*/+vma->vm_flags=new_flags;+vma->vm_userfaultfd_ctx.ctx=ctx;++skip:+prev=vma;+start=vma->vm_end;+vma=vma->vm_next;+}while(vma&&vma->vm_start<end);+out_unlock:+up_write(&mm->mmap_sem);+out:+returnret;+}++staticintuserfaultfd_unregister(structuserfaultfd_ctx*ctx,+unsignedlongarg)+{+structmm_struct*mm=ctx->mm;+structvm_area_struct*vma,*prev,*cur;+intret;+structuffdio_rangeuffdio_unregister;+unsignedlongnew_flags;+boolfound;+unsignedlongstart,end,vma_end;+constvoid__user*buf=(void__user*)arg;++ret=-EFAULT;+if(copy_from_user(&uffdio_unregister,buf,sizeof(uffdio_unregister)))+gotoout;++ret=validate_range(mm,uffdio_unregister.start,+uffdio_unregister.len);+if(ret)+gotoout;++start=uffdio_unregister.start;+end=start+uffdio_unregister.len;++down_write(&mm->mmap_sem);+vma=find_vma_prev(mm,start,&prev);++ret=-ENOMEM;+if(!vma)+gotoout_unlock;++/* check that there's at least one vma in the range */+ret=-EINVAL;+if(vma->vm_start>=end)+gotoout_unlock;++/*+*Searchfornotcompatiblevmas.+*+*FIXME:thisshallberelaxedlatersothatitdoesn'tfail+*ontmpfsbackedvmas(inadditiontothecurrentallowance+*onanonymousvmas).+*/+found=false;+ret=-EINVAL;+for(cur=vma;cur&&cur->vm_start<end;cur=cur->vm_next){+cond_resched();++BUG_ON(!!cur->vm_userfaultfd_ctx.ctx^+!!(cur->vm_flags&(VM_UFFD_MISSING|VM_UFFD_WP)));++/*+*Checknotcompatiblevmas,notstrictlyrequired+*hereasnotcompatiblevmascannothavean+*userfaultfd_ctxregisteredonthem,butthis+*providesformorestrictbehaviortonotice+*unregistrationerrors.+*/+if(cur->vm_ops)+gotoout_unlock;++found=true;+}+BUG_ON(!found);++if(vma->vm_start<start)+prev=vma;++ret=0;+do{+cond_resched();++BUG_ON(vma->vm_ops);++/*+*Nothingtodo:thisvmaisalreadyregisteredintothis+*userfaultfdandwiththerighttrackingmodetoo.+*/+if(!vma->vm_userfaultfd_ctx.ctx)+gotoskip;++if(vma->vm_start>start)+start=vma->vm_start;+vma_end=min(end,vma->vm_end);++new_flags=vma->vm_flags&~(VM_UFFD_MISSING|VM_UFFD_WP);+prev=vma_merge(mm,prev,start,vma_end,new_flags,+vma->anon_vma,vma->vm_file,vma->vm_pgoff,+vma_policy(vma),+NULL_VM_UFFD_CTX);+if(prev){+vma=prev;+gotonext;+}+if(vma->vm_start<start){+ret=split_vma(mm,vma,start,1);+if(ret)+break;+}+if(vma->vm_end>end){+ret=split_vma(mm,vma,end,0);+if(ret)+break;+}+next:+/*+*Inthevma_merge()successfulmprotect-likecase8:+*thenextvmawasmergedintothecurrentoneand+*thecurrentonehasnotbeenupdatedyet.+*/+vma->vm_flags=new_flags;+vma->vm_userfaultfd_ctx=NULL_VM_UFFD_CTX;++skip:+prev=vma;+start=vma->vm_end;+vma=vma->vm_next;+}while(vma&&vma->vm_start<end);+out_unlock:+up_write(&mm->mmap_sem);+out:+returnret;+}++/*+*Thisismostlyneededtore-wakeupthoseuserfaultsthatwerestill+*pendingwhenuserlandwakethemupthefirsttime.Wedon'twake+*thependingonetoavoidblockingreadstoblock,ornonblocking+*readtoreturn-EAGAIN,ifusedwithPOLLIN,toavoiduserland+*doubtsonwhyPOLLINwasn'treliable.+*/+staticintuserfaultfd_wake(structuserfaultfd_ctx*ctx,+unsignedlongarg)+{+intret;+structuffdio_rangeuffdio_wake;+structuserfaultfd_wake_rangerange;+constvoid__user*buf=(void__user*)arg;++ret=-EFAULT;+if(copy_from_user(&uffdio_wake,buf,sizeof(uffdio_wake)))+gotoout;++ret=validate_range(ctx->mm,uffdio_wake.start,uffdio_wake.len);+if(ret)+gotoout;++range.start=uffdio_wake.start;+range.len=uffdio_wake.len;++/*+*len==0meanswakeallandwedon'twanttowakeallhere,+*socheckitagaintobesure.+*/+VM_BUG_ON(!range.len);++ret=wake_userfault(ctx,&range);++out:+returnret;+}++/*+*userlandasksforacertainAPIversionandwereturnwhichbits+*andioctlcommandsareimplementedinthiskernelforsuchAPI+*versionor-EINVALifunknown.+*/+staticintuserfaultfd_api(structuserfaultfd_ctx*ctx,+unsignedlongarg)+{+structuffdio_apiuffdio_api;+void__user*buf=(void__user*)arg;+intret;++ret=-EINVAL;+if(ctx->state!=UFFD_STATE_WAIT_API)+gotoout;+ret=-EFAULT;+if(copy_from_user(&uffdio_api,buf,sizeof(__u64)))+gotoout;+if(uffdio_api.api!=UFFD_API){+/* careful not to leak info, we only read the first 8 bytes */+memset(&uffdio_api,0,sizeof(uffdio_api));+if(copy_to_user(buf,&uffdio_api,sizeof(uffdio_api)))+gotoout;+ret=-EINVAL;+gotoout;+}+/* careful not to leak info, we only read the first 8 bytes */+uffdio_api.bits=UFFD_API_BITS;+uffdio_api.ioctls=UFFD_API_IOCTLS;+ret=-EFAULT;+if(copy_to_user(buf,&uffdio_api,sizeof(uffdio_api)))+gotoout;+ctx->state=UFFD_STATE_RUNNING;+ret=0;+out:+returnret;+}++staticlonguserfaultfd_ioctl(structfile*file,unsignedcmd,+unsignedlongarg)+{+intret=-EINVAL;+structuserfaultfd_ctx*ctx=file->private_data;++switch(cmd){+caseUFFDIO_API:+ret=userfaultfd_api(ctx,arg);+break;+caseUFFDIO_REGISTER:+ret=userfaultfd_register(ctx,arg);+break;+caseUFFDIO_UNREGISTER:+ret=userfaultfd_unregister(ctx,arg);+break;+caseUFFDIO_WAKE:+ret=userfaultfd_wake(ctx,arg);+break;+}+returnret;+}++#ifdef CONFIG_PROC_FS+staticvoiduserfaultfd_show_fdinfo(structseq_file*m,structfile*f)+{+structuserfaultfd_ctx*ctx=f->private_data;+wait_queue_t*wq;+structuserfaultfd_wait_queue*uwq;+unsignedlongpending=0,total=0;++spin_lock(&ctx->fault_wqh.lock);+list_for_each_entry(wq,&ctx->fault_wqh.task_list,task_list){+uwq=container_of(wq,structuserfaultfd_wait_queue,wq);+if(uwq->pending)+pending++;+total++;+}+spin_unlock(&ctx->fault_wqh.lock);++/*+*Ifmoreprotocolswillbeadded,therewillbeallshown+*separatedbyaspace.Likethis:+*protocols:0xaa0xbb+*/+seq_printf(m,"pending:\t%lu\ntotal:\t%lu\nAPI:\t%Lx:%x:%Lx\n",+pending,total,UFFD_API,UFFD_API_BITS,+UFFD_API_IOCTLS|UFFD_API_RANGE_IOCTLS);+}+#endif++staticconststructfile_operationsuserfaultfd_fops={+#ifdef CONFIG_PROC_FS+.show_fdinfo=userfaultfd_show_fdinfo,+#endif+.release=userfaultfd_release,+.poll=userfaultfd_poll,+.read=userfaultfd_read,+.unlocked_ioctl=userfaultfd_ioctl,+.compat_ioctl=userfaultfd_ioctl,+.llseek=noop_llseek,+};++/**+*userfaultfd_file_create-Createsanuserfaultfdfilepointer.+*@flags:Flagsfortheuserfaultfdfile.+*+*Thisfunctioncreatesanuserfaultfdfilepointer,w/outinstalling+*itintothefdtable.Thisisusefulwhentheuserfaultfdfileis+*usedduringtheinitializationofdatastructuresthatrequire+*extrasetupaftertheuserfaultfdcreation.Sotheuserfaultfd+*creationissplitintothefilepointercreationphase,andthe+*filedescriptorinstallationphase.Inthiswayraceswith+*userspaceclosingthenewlyinstalledfiledescriptorcanbe+*avoided.Returnsanuserfaultfdfilepointer,orapropererror+*pointer.+*/+staticstructfile*userfaultfd_file_create(intflags)+{+structfile*file;+structuserfaultfd_ctx*ctx;++BUG_ON(!current->mm);++/* Check the UFFD_* constants for consistency. */+BUILD_BUG_ON(UFFD_CLOEXEC!=O_CLOEXEC);+BUILD_BUG_ON(UFFD_NONBLOCK!=O_NONBLOCK);++file=ERR_PTR(-EINVAL);+if(flags&~UFFD_SHARED_FCNTL_FLAGS)+gotoout;++file=ERR_PTR(-ENOMEM);+ctx=kmalloc(sizeof(*ctx),GFP_KERNEL);+if(!ctx)+gotoout;++atomic_set(&ctx->refcount,1);+init_waitqueue_head(&ctx->fault_wqh);+init_waitqueue_head(&ctx->fd_wqh);+ctx->flags=flags;+ctx->state=UFFD_STATE_WAIT_API;+ctx->released=false;+ctx->mm=current->mm;+/* prevent the mm struct to be freed */+atomic_inc(&ctx->mm->mm_count);++file=anon_inode_getfile("[userfaultfd]",&userfaultfd_fops,ctx,+O_RDWR|(flags&UFFD_SHARED_FCNTL_FLAGS));+if(IS_ERR(file))+kfree(ctx);+out:+returnfile;+}++SYSCALL_DEFINE1(userfaultfd,int,flags)+{+intfd,error;+structfile*file;++error=get_unused_fd_flags(flags&UFFD_SHARED_FCNTL_FLAGS);+if(error<0)+returnerror;+fd=error;++file=userfaultfd_file_create(flags);+if(IS_ERR(file)){+error=PTR_ERR(file);+gotoerr_put_unused_fd;+}+fd_install(fd,file);++returnfd;++err_put_unused_fd:+put_unused_fd(fd);++returnerror;+}
From: Andrea Arcangeli <hidden> Date: 2015-03-05 17:21:04
Kernel header defining the methods needed by the VM common code to
interact with the userfaultfd.
Signed-off-by: Andrea Arcangeli <redacted>
---
include/linux/userfaultfd_k.h | 79 +++++++++++++++++++++++++++++++++++++++++++
1 file changed, 79 insertions(+)
create mode 100644 include/linux/userfaultfd_k.h
To unsubscribe, send a message with 'unsubscribe linux-mm' in
the body to majordomo@kvack.org. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"dont@kvack.org"> email@kvack.org </a>
@@ -265,3 +265,529 @@ ssize_t mfill_zeropage(struct mm_struct *dst_mm, unsigned long start,{return__mcopy_atomic(dst_mm,start,0,len,true);}++voiddouble_pt_lock(spinlock_t*ptl1,+spinlock_t*ptl2)+__acquires(ptl1)+__acquires(ptl2)+{+spinlock_t*ptl_tmp;++if(ptl1>ptl2){+/* exchange ptl1 and ptl2 */+ptl_tmp=ptl1;+ptl1=ptl2;+ptl2=ptl_tmp;+}+/* lock in virtual address order to avoid lock inversion */+spin_lock(ptl1);+if(ptl1!=ptl2)+spin_lock_nested(ptl2,SINGLE_DEPTH_NESTING);+else+__acquire(ptl2);+}++voiddouble_pt_unlock(spinlock_t*ptl1,+spinlock_t*ptl2)+__releases(ptl1)+__releases(ptl2)+{+spin_unlock(ptl1);+if(ptl1!=ptl2)+spin_unlock(ptl2);+else+__release(ptl2);+}++/*+*Themmap_semforreadingisheldbythecaller.Justmovethepage+*fromsrc_pmdtodst_pmdifpossible,andreturntrueifsucceeded+*inmovingthepage.+*/+staticintremap_pages_pte(structmm_struct*dst_mm,+structmm_struct*src_mm,+pte_t*dst_pte,pte_t*src_pte,pmd_t*src_pmd,+structvm_area_struct*dst_vma,+structvm_area_struct*src_vma,+unsignedlongdst_addr,+unsignedlongsrc_addr,+spinlock_t*dst_ptl,+spinlock_t*src_ptl,+__u64mode)+{+structpage*src_page;+swp_entry_tentry;+pte_torig_src_pte,orig_dst_pte;+structanon_vma*src_anon_vma,*dst_anon_vma;++spin_lock(dst_ptl);+orig_dst_pte=*dst_pte;+spin_unlock(dst_ptl);+if(!pte_none(orig_dst_pte))+return-EEXIST;++spin_lock(src_ptl);+orig_src_pte=*src_pte;+spin_unlock(src_ptl);+if(pte_none(orig_src_pte)){+if(!(mode&UFFDIO_REMAP_MODE_ALLOW_SRC_HOLES))+return-ENOENT;+else+/* nothing to do to remap an hole */+return0;+}++if(pte_present(orig_src_pte)){+/*+*Pinthepagewhileholdingthelocktobesurethe+*pageisn'tfreedunderus+*/+spin_lock(src_ptl);+if(!pte_same(orig_src_pte,*src_pte)){+spin_unlock(src_ptl);+return-EAGAIN;+}+src_page=vm_normal_page(src_vma,src_addr,orig_src_pte);+if(!src_page||!PageAnon(src_page)||+page_mapcount(src_page)!=1){+spin_unlock(src_ptl);+return-EBUSY;+}++get_page(src_page);+spin_unlock(src_ptl);++/* block all concurrent rmap walks */+lock_page(src_page);++/*+*page_referenced_anonwalkstheanon_vmachain+*withoutthepagelock.Serializeagainstitwith+*theanon_vmalock,thepagelockisnotenough.+*/+src_anon_vma=page_get_anon_vma(src_page);+if(!src_anon_vma){+/* page was unmapped from under us */+unlock_page(src_page);+put_page(src_page);+return-EAGAIN;+}+anon_vma_lock_write(src_anon_vma);++double_pt_lock(dst_ptl,src_ptl);++if(!pte_same(*src_pte,orig_src_pte)||+!pte_same(*dst_pte,orig_dst_pte)||+page_mapcount(src_page)!=1){+double_pt_unlock(dst_ptl,src_ptl);+anon_vma_unlock_write(src_anon_vma);+put_anon_vma(src_anon_vma);+unlock_page(src_page);+put_page(src_page);+return-EAGAIN;+}++BUG_ON(!PageAnon(src_page));+/* the PT lock is enough to keep the page pinned now */+put_page(src_page);++dst_anon_vma=(void*)dst_vma->anon_vma+PAGE_MAPPING_ANON;+ACCESS_ONCE(src_page->mapping)=((structaddress_space*)+dst_anon_vma);+ACCESS_ONCE(src_page->index)=linear_page_index(dst_vma,+dst_addr);++if(!pte_same(ptep_clear_flush(src_vma,src_addr,src_pte),+orig_src_pte))+BUG();++orig_dst_pte=mk_pte(src_page,dst_vma->vm_page_prot);+orig_dst_pte=maybe_mkwrite(pte_mkdirty(orig_dst_pte),+dst_vma);++set_pte_at(dst_mm,dst_addr,dst_pte,orig_dst_pte);++if(dst_mm!=src_mm){+inc_mm_counter(dst_mm,MM_ANONPAGES);+dec_mm_counter(src_mm,MM_ANONPAGES);+}++double_pt_unlock(dst_ptl,src_ptl);++anon_vma_unlock_write(src_anon_vma);+put_anon_vma(src_anon_vma);++/* unblock rmap walks */+unlock_page(src_page);++mmu_notifier_invalidate_page(src_mm,src_addr);+}else{+entry=pte_to_swp_entry(orig_src_pte);+if(non_swap_entry(entry)){+if(is_migration_entry(entry)){+migration_entry_wait(src_mm,src_pmd,+src_addr);+return-EAGAIN;+}+return-EFAULT;+}++if(swp_entry_swapcount(entry)!=1)+return-EBUSY;++double_pt_lock(dst_ptl,src_ptl);++if(!pte_same(*src_pte,orig_src_pte)||+!pte_same(*dst_pte,orig_dst_pte)||+swp_entry_swapcount(entry)!=1){+double_pt_unlock(dst_ptl,src_ptl);+return-EAGAIN;+}++if(pte_val(ptep_get_and_clear(src_mm,src_addr,src_pte))!=+pte_val(orig_src_pte))+BUG();+set_pte_at(dst_mm,dst_addr,dst_pte,orig_src_pte);++if(dst_mm!=src_mm){+inc_mm_counter(dst_mm,MM_ANONPAGES);+dec_mm_counter(src_mm,MM_ANONPAGES);+}++double_pt_unlock(dst_ptl,src_ptl);+}++return0;+}++/**+*remap_pages-remaparbitraryanonymouspagesofanexistingvma+*@dst_start:startofthedestinationvirtualmemoryrange+*@src_start:startofthesourcevirtualmemoryrange+*@len:lengthofthevirtualmemoryrange+*+*remap_pages()remapsarbitraryanonymouspagesatomicallyinzero+*copy.Itonlyworksonnonsharedanonymouspagesbecausethosecan+*berelocatedwithoutgeneratingnonlinearanon_vmasinthermap+*code.+*+*Itistheidealmechanismtohandleuserspacepagefaults.Normally+*thedestinationvmawillhaveVM_USERFAULTsetwith+*madvise(MADV_USERFAULT)whilethesourcevmawillhaveVM_DONTCOPY+*setwithmadvise(MADV_DONTFORK).+*+*Thethreadreceivingthepageduringtheuserlandpagefault+*(MADV_USERFAULT)willreceivethefaultingpageinthesourcevma+*throughthenetwork,storageoranyotherI/Odevice(MADV_DONTFORK+*inthesourcevmaavoidsremap_pages()tofailwith-EBUSYifthe+*processforksbeforeremap_pages()iscalled),thenitwillcall+*remap_pages()tomapthepageinthefaultingaddressinthe+*destinationvma.+*+*Thisuserfaultfdcommandworkspurelyviapagetables,soit'sthe+*mostefficientwaytomovephysicalnonsharedanonymouspages+*acrossdifferentvirtualaddresses.Unlikemremap()/mmap()/munmap()+*itdoesnotcreateanynewvmas.Themappinginthedestination+*addressisatomic.+*+*Itonlyworksifthevmaprotectionbitsareidenticalfromthe+*sourceanddestinationvma.+*+*Itcanremapnonsharedanonymouspageswithinthesamevmatoo.+*+*Ifthesourcevirtualmemoryrangehasanyunmappedholes,orif+*thedestinationvirtualmemoryrangeisnotawholeunmappedhole,+*remap_pages()willfailrespectivelywith-ENOENTor-EEXIST.This+*providesaverystrictbehaviortoavoidanychanceofmemory+*corruptiongoingunnoticedifthereareuserlandrace+*conditions.Onlyonethreadshouldresolvetheuserlandpagefault+*atanygiventimeforanygivenfaultingaddress.Thismeansthat+*iftwothreadstrytobothcallremap_pages()onthesame+*destinationaddressatthesametime,thesecondthreadwillgetan+*expliciterrorfromthiscommand.+*+*Thecommandretvalwillreturn"len"issuccesful.Thecommand+*howevercanbeinterruptedbyfatalsignalsorerrors.If+*interrupteditwillreturnthenumberofbytessuccessfully+*remappedbeforetheinterruptionifany,orthenegativeerrorif+*none.Itwillneverreturnzero.Eitheritwillreturnanerroror+*anamountofbytessuccessfullymoved.Iftheretvalreportsa+*"short"remap,theremap_pages()commandshouldberepeatedby+*userlandwithsrc+retval,dst+reval,len-retvalifitwantstoknow+*abouttheerrorthatinterruptedit.+*+*TheUFFDIO_REMAP_MODE_ALLOW_SRC_HOLESflagcanbespecifiedto+*prevent-ENOENTerrorstomaterializeifthereareholesinthe+*sourcevirtualrangethatisbeingremapped.Theholeswillbe+*accountedassuccessfullyremappedintheretvalofthe+*command.Thisismostlyusefultoremaphugepagenaturallyaligned+*virtualregionswithoutknowingiftherearetransparenthugepage+*intheregionsornot,butpreventingtheriskofhavingtosplit+*thehugepmdduringtheremap.+*+*Ifthere'sanyrmapwalkthatistakingtheanon_vmalockswithout+*firstobtainingthepagelock(forexamplesplit_huge_pageand+*page_referenced_anon),theywillhavetoverifyifthe+*page->mappinghaschangedaftertakingtheanon_vmalock.Ifit+*changedtheyshouldreleasethelockandretryobtaininganew+*anon_vma,becauseitmeanstheanon_vmawaschangedby+*remap_pages()beforethelockcouldbeobtained.Thisistheonly+*additionalcomplexityaddedtothermapcodetoprovidethis+*anonymouspageremappingfunctionality.+*/+ssize_tremap_pages(structmm_struct*dst_mm,structmm_struct*src_mm,+unsignedlongdst_start,unsignedlongsrc_start,+unsignedlonglen,__u64mode)+{+structvm_area_struct*src_vma,*dst_vma;+longerr=-EINVAL;+pmd_t*src_pmd,*dst_pmd;+pte_t*src_pte,*dst_pte;+spinlock_t*dst_ptl,*src_ptl;+unsignedlongsrc_addr,dst_addr;+intthp_aligned=-1;+ssize_tmoved=0;++/*+*Sanitizethecommandparameters:+*/+BUG_ON(src_start&~PAGE_MASK);+BUG_ON(dst_start&~PAGE_MASK);+BUG_ON(len&~PAGE_MASK);++/* Does the address range wrap, or is the span zero-sized? */+BUG_ON(src_start+len<=src_start);+BUG_ON(dst_start+len<=dst_start);++/*+*Becausethesearereadsempahoresthere'snoriskoflock+*inversion.+*/+down_read(&dst_mm->mmap_sem);+if(dst_mm!=src_mm)+down_read(&src_mm->mmap_sem);++/*+*Makesurethevmaisnotshared,thatthesrcanddstremap+*rangesarebothvalidandfullywithinasingleexisting+*vma.+*/+src_vma=find_vma(src_mm,src_start);+if(!src_vma||(src_vma->vm_flags&VM_SHARED))+gotoout;+if(src_start<src_vma->vm_start||+src_start+len>src_vma->vm_end)+gotoout;++dst_vma=find_vma(dst_mm,dst_start);+if(!dst_vma||(dst_vma->vm_flags&VM_SHARED))+gotoout;+if(dst_start<dst_vma->vm_start||+dst_start+len>dst_vma->vm_end)+gotoout;++if(pgprot_val(src_vma->vm_page_prot)!=+pgprot_val(dst_vma->vm_page_prot))+gotoout;++/* only allow remapping if both are mlocked or both aren't */+if((src_vma->vm_flags&VM_LOCKED)^(dst_vma->vm_flags&VM_LOCKED))+gotoout;++/*+*Bestrictandonlyallowremap_pagesifeitherthesrcor+*dstrangeisregisteredintheuserfaultfdtoprevent+*userlanderrorsgoingunnoticed.AsfarastheVM+*consistencyisconcerned,itwouldbeperfectlysafeto+*removethischeck,butthere'snousefulusagefor+*remap_pagesousideofuserfaultfdregisteredranges.This+*isafterallwhyitisanioctlbelongingtothe+*userfaultfdandnotasyscall.+*+*Allowbothvmastoberegisteredintheuserfaultfd,just+*incasesomebodyfindsawaytomakesuchacaseuseful.+*Normallyonlyoneofthetwovmaswouldberegisteredin+*theuserfaultfd.+*/+if(!dst_vma->vm_userfaultfd_ctx.ctx&&+!src_vma->vm_userfaultfd_ctx.ctx)+gotoout;++/*+*FIXME:onlyallowremappingacrossanonymousvmas,+*tmpfsshouldbeadded.+*/+if(src_vma->vm_ops||dst_vma->vm_ops)+gotoout;++/*+*Ensurethedst_vmahasaanon_vmaorthispage+*wouldgetaNULLanon_vmawhenmovedinthe+*dst_vma.+*/+err=-ENOMEM;+if(unlikely(anon_vma_prepare(dst_vma)))+gotoout;++for(src_addr=src_start,dst_addr=dst_start;+src_addr<src_start+len;){+spinlock_t*ptl;+pmd_tdst_pmdval;+BUG_ON(dst_addr>=dst_start+len);+src_pmd=mm_find_pmd(src_mm,src_addr);+if(unlikely(!src_pmd)){+if(!(mode&UFFDIO_REMAP_MODE_ALLOW_SRC_HOLES)){+err=-ENOENT;+break;+}else{+src_pmd=mm_alloc_pmd(src_mm,src_addr);+if(unlikely(!src_pmd)){+err=-ENOMEM;+break;+}+}+}+dst_pmd=mm_alloc_pmd(dst_mm,dst_addr);+if(unlikely(!dst_pmd)){+err=-ENOMEM;+break;+}++dst_pmdval=pmd_read_atomic(dst_pmd);+/*+*Ifthedst_pmdismappedasTHPdon't+*overrideitandjustbestrict.+*/+if(unlikely(pmd_trans_huge(dst_pmdval))){+err=-EEXIST;+break;+}+if(pmd_trans_huge_lock(src_pmd,src_vma,&ptl)==1){+/*+*Checkifwecanmovethepmdwithout+*splittingit.Firstchecktheaddress+*alignmenttobethesameinsrc/dst.These+*checksdon'tactuallyneedthePTlockbut+*it'sgoodtodoitheretooptimizethis+*blockawayatbuildtimeif+*CONFIG_TRANSPARENT_HUGEPAGEisnotset.+*/+if(thp_aligned==-1)+thp_aligned=((src_addr&~HPAGE_PMD_MASK)==+(dst_addr&~HPAGE_PMD_MASK));+if(!thp_aligned||(src_addr&~HPAGE_PMD_MASK)||+!pmd_none(dst_pmdval)||+src_start+len-src_addr<HPAGE_PMD_SIZE){+spin_unlock(ptl);+/* Fall through */+split_huge_page_pmd(src_vma,src_addr,+src_pmd);+}else{+BUG_ON(dst_addr&~HPAGE_PMD_MASK);+err=remap_pages_huge_pmd(dst_mm,+src_mm,+dst_pmd,+src_pmd,+dst_pmdval,+dst_vma,+src_vma,+dst_addr,+src_addr);+cond_resched();++if(!err){+dst_addr+=HPAGE_PMD_SIZE;+src_addr+=HPAGE_PMD_SIZE;+moved+=HPAGE_PMD_SIZE;+}++if((!err||err==-EAGAIN)&&+fatal_signal_pending(current))+err=-EINTR;++if(err&&err!=-EAGAIN)+break;++continue;+}+}++if(pmd_none(*src_pmd)){+if(!(mode&UFFDIO_REMAP_MODE_ALLOW_SRC_HOLES)){+err=-ENOENT;+break;+}else{+if(unlikely(__pte_alloc(src_mm,src_vma,+src_pmd,src_addr))){+err=-ENOMEM;+break;+}+}+}++/*+*Weheldthemmap_semforreadingsoMADV_DONTNEED+*canzaptransparenthugepagesunderus,orthe+*transparenthugepagefaultcanestablishnew+*transparenthugepagesunderus.+*/+if(unlikely(pmd_trans_unstable(src_pmd))){+err=-EFAULT;+break;+}++if(unlikely(pmd_none(dst_pmdval))&&+unlikely(__pte_alloc(dst_mm,dst_vma,dst_pmd,+dst_addr))){+err=-ENOMEM;+break;+}+/* If an huge pmd materialized from under us fail */+if(unlikely(pmd_trans_huge(*dst_pmd))){+err=-EFAULT;+break;+}++BUG_ON(pmd_none(*dst_pmd));+BUG_ON(pmd_none(*src_pmd));+BUG_ON(pmd_trans_huge(*dst_pmd));+BUG_ON(pmd_trans_huge(*src_pmd));++dst_pte=pte_offset_map(dst_pmd,dst_addr);+src_pte=pte_offset_map(src_pmd,src_addr);+dst_ptl=pte_lockptr(dst_mm,dst_pmd);+src_ptl=pte_lockptr(src_mm,src_pmd);++err=remap_pages_pte(dst_mm,src_mm,+dst_pte,src_pte,src_pmd,+dst_vma,src_vma,+dst_addr,src_addr,+dst_ptl,src_ptl,mode);++pte_unmap(dst_pte);+pte_unmap(src_pte);+cond_resched();++if(!err){+dst_addr+=PAGE_SIZE;+src_addr+=PAGE_SIZE;+moved+=PAGE_SIZE;+}++if((!err||err==-EAGAIN)&&+fatal_signal_pending(current))+err=-EINTR;++if(err&&err!=-EAGAIN)+break;+}++out:+up_read(&dst_mm->mmap_sem);+if(dst_mm!=src_mm)+up_read(&src_mm->mmap_sem);+BUG_ON(moved<0);+BUG_ON(err>0);+BUG_ON(!moved&&!err);+returnmoved?moved:err;+}--
To unsubscribe, send a message with 'unsubscribe linux-mm' in
the body to majordomo@kvack.org. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"dont@kvack.org"> email@kvack.org </a>
From: Andrea Arcangeli <hidden> Date: 2015-03-05 17:21:10
userfaultfd needs to wake all waitqueues (pass 0 as nr parameter),
instead of the current hardcoded 1 (that would wake just the first
waitqueue in the head list).
Signed-off-by: Andrea Arcangeli <redacted>
---
include/linux/wait.h | 5 +++--
kernel/sched/wait.c | 7 ++++---
net/sunrpc/sched.c | 2 +-
3 files changed, 8 insertions(+), 6 deletions(-)
@@ -179,7 +180,7 @@ wait_queue_head_t *bit_waitqueue(void *, int);#define wake_up_poll(x, m) \__wake_up(x,TASK_NORMAL,1,(void*)(m))#define wake_up_locked_poll(x, m) \-__wake_up_locked_key((x),TASK_NORMAL,(void*)(m))+__wake_up_locked_key((x),TASK_NORMAL,1,(void*)(m))#define wake_up_interruptible_poll(x, m) \__wake_up(x,TASK_INTERRUPTIBLE,1,(void*)(m))#define wake_up_interruptible_sync_poll(x, m) \
@@ -297,7 +297,7 @@ static int rpc_complete_task(struct rpc_task *task)clear_bit(RPC_TASK_ACTIVE,&task->tk_runstate);ret=atomic_dec_and_test(&task->tk_count);if(waitqueue_active(wq))-__wake_up_locked_key(wq,TASK_NORMAL,&k);+__wake_up_locked_key(wq,TASK_NORMAL,1,&k);spin_unlock_irqrestore(&wq->lock,flags);returnret;}--
To unsubscribe, send a message with 'unsubscribe linux-mm' in
the body to majordomo@kvack.org. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"dont@kvack.org"> email@kvack.org </a>
@@ -0,0 +1,97 @@+= Userfaultfd =++== Objective ==++Userfaults allow to implement on demand paging from userland and more+generally they allow userland to take control various memory page+faults, something otherwise only the kernel code could do.++For example userfaults allows a proper and more optimal implementation+of the PROT_NONE+SIGSEGV trick.++== Design ==++Userfaults are delivered and resolved through the userfaultfd syscall.++The userfaultfd (aside from registering and unregistering virtual+memory ranges) provides for two primary functionalities:++1) read/POLLIN protocol to notify an userland thread of the faults+ happening++2) various UFFDIO_* ioctls that can mangle over the virtual memory+ regions registered in the userfaultfd that allows userland to+ efficiently resolve the userfaults it receives via 1) or to mangle+ the virtual memory in the background++The real advantage of userfaults if compared to regular virtual memory+management of mremap/mprotect is that the userfaults in all their+operations never involve heavyweight structures like vmas (in fact the+userfaultfd runtime load never takes the mmap_sem for writing).++Vmas are not suitable for page(or hugepage)-granular fault tracking+when dealing with virtual address spaces that could span+Terabytes. Too many vmas would be needed for that.++The userfaultfd once opened by invoking the syscall, can also be+passed using unix domain sockets to a manager process, so the same+manager process could handle the userfaults of a multitude of+different process without them being aware about what is going on+(well of course unless they later try to use the userfaultfd themself+on the same region the manager is already tracking, which is a corner+case that would currently return -EBUSY).++== API ==++When first opened the userfaultfd must be enabled invoking the+UFFDIO_API ioctl specifying an uffdio_api.api value set to UFFD_API+which will specify the read/POLLIN protocol userland intends to speak+on the UFFD. The UFFDIO_API ioctl if successful (i.e. if the requested+uffdio_api.api is spoken also by the running kernel), will return into+uffdio_api.bits and uffdio_api.ioctls two 64bit bitmasks of+respectively the activated feature bits below PAGE_SHIFT in the+userfault addresses returned by read(2) and the generic ioctl+available.++Once the userfaultfd has been enabled the UFFDIO_REGISTER ioctl should+be invoked (if present in the returned uffdio_api.ioctls bitmask) to+register a memory range in the userfaultfd by setting the+uffdio_register structure accordingly. The uffdio_register.mode+bitmask will specify to the kernel which kind of faults to track for+the range (UFFDIO_REGISTER_MODE_MISSING would track missing+pages). The UFFDIO_REGISTER ioctl will return the+uffdio_register.ioctls bitmask of ioctls that are suitable to resolve+userfaults on the range reigstered. Not all ioctls will necessarily be+supported for all memory types depending on the underlying virtual+memory backend (anonymous memory vs tmpfs vs real filebacked+mappings).++Userland can use the uffdio_register.ioctls to mangle the virtual+address space in the background (to add or potentially also remove+memory from the userfaultfd registered range). This means an userfault+could be triggering just before userland maps in the background the+user-faulted page. To avoid POLLIN resulting in an unexpected blocking+read (if the UFFD is not opened in nonblocking mode in the first+place), we don't allow the background thread to wake userfaults that+haven't been read by userland yet. If we would do that likely the+UFFDIO_WAKE ioctl could be dropped. This may change in the future+(with a UFFD_API protocol bumb combined with the removal of the+UFFDIO_WAKE ioctl) if it'll be demonstrated that it's a valid+optimization and worthy to force userland to use the UFFD always in+nonblocking mode if combined with POLLIN.++userfaultfd is also a generic enough feature, that it allows KVM to+implement postcopy live migration (one form of memory externalization+consisting of a virtual machine running with part or all of its memory+residing on a different node in the cloud) without having to modify a+single line of KVM kernel code. Guest async page faults, FOLL_NOWAIT+and all other GUP features works just fine in combination with+userfaults (userfaults trigger async page faults in the guest+scheduler so those guest processes that aren't waiting for userfaults+can keep running in the guest vcpus).++The primary ioctl to resolve userfaults is UFFDIO_COPY. That+atomically copies a page into the userfault registered range and wakes+up the blocked userfaults (unless uffdio_copy.mode &+UFFDIO_COPY_MODE_DONTWAKE is set). Other ioctl works similarly to+UFFDIO_COPY.--
To unsubscribe, send a message with 'unsubscribe linux-mm' in
the body to majordomo@kvack.org. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"dont@kvack.org"> email@kvack.org </a>
From: Andrea Arcangeli <hidden> Date: 2015-03-05 17:22:40
This implements the uABI of UFFDIO_COPY and UFFDIO_ZEROPAGE.
Signed-off-by: Andrea Arcangeli <redacted>
---
include/uapi/linux/userfaultfd.h | 46 +++++++++++++++++++++++++++++++++++++++-
1 file changed, 45 insertions(+), 1 deletion(-)
To unsubscribe, send a message with 'unsubscribe linux-mm' in
the body to majordomo@kvack.org. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"dont@kvack.org"> email@kvack.org </a>
From: Andrea Arcangeli <hidden> Date: 2015-03-05 17:22:41
These two flags gets set in vma->vm_flags to tell the VM common code
if the userfaultfd is armed and in which mode (only tracking missing
faults, only tracking wrprotect faults or both). If neither flags is
set it means the userfaultfd is not armed on the vma.
Signed-off-by: Andrea Arcangeli <redacted>
---
include/linux/mm.h | 2 ++
kernel/fork.c | 2 +-
2 files changed, 3 insertions(+), 1 deletion(-)
From: Andrea Arcangeli <hidden> Date: 2015-03-05 17:23:49
vma->vm_userfaultfd_ctx is yet another vma parameter that vma_merge
must be aware about so that we can merge vmas back like they were
originally before arming the userfaultfd on some memory range.
Signed-off-by: Andrea Arcangeli <redacted>
---
include/linux/mm.h | 2 +-
mm/madvise.c | 3 ++-
mm/mempolicy.c | 4 ++--
mm/mlock.c | 3 ++-
mm/mmap.c | 39 +++++++++++++++++++++++++++------------
mm/mprotect.c | 3 ++-
6 files changed, 36 insertions(+), 18 deletions(-)
@@ -2760,7 +2774,7 @@ static unsigned long do_brk(unsigned long addr, unsigned long len)/* Can we just expand an old private anonymous mapping? */vma=vma_merge(mm,prev,addr,addr+len,flags,-NULL,NULL,pgoff,NULL);+NULL,NULL,pgoff,NULL,NULL_VM_UFFD_CTX);if(vma)gotoout;
@@ -2916,7 +2930,8 @@ struct vm_area_struct *copy_vma(struct vm_area_struct **vmap,if(find_vma_links(mm,addr,addr+len,&prev,&rb_link,&rb_parent))returnNULL;/* should never get here */new_vma=vma_merge(mm,prev,addr,addr+len,vma->vm_flags,-vma->anon_vma,vma->vm_file,pgoff,vma_policy(vma));+vma->anon_vma,vma->vm_file,pgoff,vma_policy(vma),+vma->vm_userfaultfd_ctx);if(new_vma){/**Sourcevmamayhavebeenmergedintonew_vma
To unsubscribe, send a message with 'unsubscribe linux-mm' in
the body to majordomo@kvack.org. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"dont@kvack.org"> email@kvack.org </a>
From: Andrea Arcangeli <hidden> Date: 2015-03-05 17:25:10
These helpers will be used to know if to call handle_userfault() during
wrprotect faults in order to deliver the wrprotect faults to userland.
Signed-off-by: Andrea Arcangeli <redacted>
---
include/linux/userfaultfd_k.h | 10 ++++++++++
1 file changed, 10 insertions(+)
To unsubscribe, send a message with 'unsubscribe linux-mm' in
the body to majordomo@kvack.org. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"dont@kvack.org"> email@kvack.org </a>
From: Andrea Arcangeli <hidden> Date: 2015-03-05 17:25:13
Provide a new swapfile method for remap_pages() to verify the swap
entry is mapped only in one vma before relocating the swap entry in a
different virtual address. Otherwise if the swap entry is mapped in
multiple vmas, when the page is swapped back in, it could get mapped
in a non linear way in some anon_vma.
Signed-off-by: Andrea Arcangeli <redacted>
---
include/linux/swap.h | 6 ++++++
mm/swapfile.c | 13 +++++++++++++
2 files changed, 19 insertions(+)
@@ -874,6 +874,19 @@ int page_swapcount(struct page *page)returncount;}+intswp_entry_swapcount(swp_entry_tentry)+{+intcount=0;+structswap_info_struct*p;++p=swap_info_get(entry);+if(p){+count=swap_count(p->swap_map[swp_offset(entry)]);+spin_unlock(&p->lock);+}+returncount;+}+/**WecanwritetoananonpagewithoutCOWiftherearenootherreferences*toit.Andasaside-effect,freeupitsswap:becausetheoldcontent--
To unsubscribe, send a message with 'unsubscribe linux-mm' in
the body to majordomo@kvack.org. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"dont@kvack.org"> email@kvack.org </a>
From: Andrea Arcangeli <hidden> Date: 2015-03-05 17:26:10
This allows to select the userfaultfd during configuration to build it.
Signed-off-by: Andrea Arcangeli <redacted>
---
fs/Makefile | 1 +
init/Kconfig | 11 +++++++++++
2 files changed, 12 insertions(+)
To unsubscribe, send a message with 'unsubscribe linux-mm' in
the body to majordomo@kvack.org. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"dont@kvack.org"> email@kvack.org </a>
From: Andrea Arcangeli <hidden> Date: 2015-03-05 17:26:11
This remap ioctl allows to atomically move a page in or out of an
userfaultfd address space. It's more expensive than "copy" (and of
course more expensive than "zerofill") as it requires a TLB flush on
the source range for each ioctl, which is an expensive operation on
SMP. Especially if copying only a few pages at time, copying without
TLB flush is faster.
Signed-off-by: Andrea Arcangeli <redacted>
---
fs/userfaultfd.c | 51 +++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 51 insertions(+)
@@ -892,6 +892,54 @@ out:returnret;}+staticintuserfaultfd_remap(structuserfaultfd_ctx*ctx,+unsignedlongarg)+{+__s64ret;+structuffdio_remapuffdio_remap;+structuffdio_remap__user*user_uffdio_remap;+structuserfaultfd_wake_rangerange;++user_uffdio_remap=(structuffdio_remap__user*)arg;++ret=-EFAULT;+if(copy_from_user(&uffdio_remap,user_uffdio_remap,+/* don't copy "remap" and "wake" last field */+sizeof(uffdio_remap)-sizeof(__s64)*2))+gotoout;++ret=validate_range(ctx->mm,uffdio_remap.dst,uffdio_remap.len);+if(ret)+gotoout;+ret=validate_range(current->mm,uffdio_remap.src,uffdio_remap.len);+if(ret)+gotoout;+ret=-EINVAL;+if(uffdio_remap.mode&~(UFFDIO_REMAP_MODE_ALLOW_SRC_HOLES|+UFFDIO_REMAP_MODE_DONTWAKE))+gotoout;++ret=remap_pages(ctx->mm,current->mm,+uffdio_remap.dst,uffdio_remap.src,+uffdio_remap.len,uffdio_remap.mode);+if(unlikely(put_user(ret,&user_uffdio_remap->remap)))+return-EFAULT;+if(ret<0)+gotoout;+/* len == 0 would wake all */+BUG_ON(!ret);+range.len=ret;+if(!(uffdio_remap.mode&UFFDIO_REMAP_MODE_DONTWAKE)){+range.start=uffdio_remap.dst;+ret=wake_userfault(ctx,&range);+if(unlikely(put_user(ret,&user_uffdio_remap->wake)))+return-EFAULT;+}+ret=range.len==uffdio_remap.len?0:-EAGAIN;+out:+returnret;+}+/**userlandasksforacertainAPIversionandwereturnwhichbits*andioctlcommandsareimplementedinthiskernelforsuchAPI
To unsubscribe, send a message with 'unsubscribe linux-mm' in
the body to majordomo@kvack.org. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"dont@kvack.org"> email@kvack.org </a>
@@ -0,0 +1,81 @@+/*+*include/linux/userfaultfd.h+*+*Copyright(C)2007DavideLibenzi<davidel@xmailserver.org>+*Copyright(C)2015RedHat,Inc.+*+*/++#ifndef _LINUX_USERFAULTFD_H+#define _LINUX_USERFAULTFD_H++#define UFFD_API ((__u64)0xAA)+/* FIXME: add "|UFFD_BIT_WP" to UFFD_API_BITS after implementing it */+#define UFFD_API_BITS (UFFD_BIT_WRITE)+#define UFFD_API_IOCTLS \+((__u64)1<<_UFFDIO_REGISTER|\+(__u64)1<<_UFFDIO_UNREGISTER|\+(__u64)1<<_UFFDIO_API)+#define UFFD_API_RANGE_IOCTLS \+((__u64)1<<_UFFDIO_WAKE)++/*+*ValidioctlcommandnumberrangewiththisAPIisfrom0x00to+*0x3F.UFFDIO_APIisthefixednumber,everythingelsecanbe+*changedbyimplementingadifferentUFFD_API.Ifstickingtothe+*sameUFFD_APImoreioctlcanbeaddedanduserlandwillbeawareof+*whichioctltherunningkernelimplementsthroughtheioctlcommand+*bitmaskwrittenbytheUFFDIO_API.+*/+#define _UFFDIO_REGISTER (0x00)+#define _UFFDIO_UNREGISTER (0x01)+#define _UFFDIO_WAKE (0x02)+#define _UFFDIO_API (0x3F)++/* userfaultfd ioctl ids */+#define UFFDIO 0xAA+#define UFFDIO_API _IOWR(UFFDIO, _UFFDIO_API, \+structuffdio_api)+#define UFFDIO_REGISTER _IOWR(UFFDIO, _UFFDIO_REGISTER, \+structuffdio_register)+#define UFFDIO_UNREGISTER _IOR(UFFDIO, _UFFDIO_UNREGISTER, \+structuffdio_range)+#define UFFDIO_WAKE _IOR(UFFDIO, _UFFDIO_WAKE, \+structuffdio_range)++/*+*ValidbitsbelowPAGE_SHIFTintheuserfaultaddressreadthrough+*theread()syscall.+*/+#define UFFD_BIT_WRITE (1<<0) /* this was a write fault, MISSING or WP */+#define UFFD_BIT_WP (1<<1) /* handle_userfault() reason VM_UFFD_WP */+#define UFFD_BITS 2 /* two above bits used for UFFD_BIT_* mask */++structuffdio_api{+/* userland asks for an API number */+__u64api;++/* kernel answers below with the available features for the API */+__u64bits;+__u64ioctls;+};++structuffdio_range{+__u64start;+__u64len;+};++structuffdio_register{+structuffdio_rangerange;+#define UFFDIO_REGISTER_MODE_MISSING ((__u64)1<<0)+#define UFFDIO_REGISTER_MODE_WP ((__u64)1<<1)+__u64mode;++/*+*kernelanswerswhichioctlcommandsareavailableforthe+*range,keepattheendasthelast8bytesaren'tread.+*/+__u64ioctls;+};++#endif /* _LINUX_USERFAULTFD_H */--
To unsubscribe, send a message with 'unsubscribe linux-mm' in
the body to majordomo@kvack.org. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"dont@kvack.org"> email@kvack.org </a>
From: Andrea Arcangeli <hidden> Date: 2015-03-05 17:33:40
This adds the vm_userfaultfd_ctx to the vm_area_struct.
Signed-off-by: Andrea Arcangeli <redacted>
---
include/linux/mm_types.h | 11 +++++++++++
kernel/fork.c | 1 +
2 files changed, 12 insertions(+)
@@ -313,6 +323,7 @@ struct vm_area_struct {#ifdef CONFIG_NUMAstructmempolicy*vm_policy;/* NUMA policy for the VMA */#endif+structvm_userfaultfd_ctxvm_userfaultfd_ctx;};structcore_thread{
To unsubscribe, send a message with 'unsubscribe linux-mm' in
the body to majordomo@kvack.org. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"dont@kvack.org"> email@kvack.org </a>
On Thu, Mar 5, 2015 at 9:18 AM, Andrea Arcangeli [off-list ref] wrote:
remap_pages is the lowlevel mm helper needed to implement
UFFDIO_REMAP.
This function is nasty nasty nasty.
Is this really worth it? On real loads? That people are expected to use?
Considering how we just got rid of one special magic VM remapping
thing that nobody actually used, I'd really hate to add a new one.
The fact is, almost nobody ever uses anything that isn't standard
POSIX. There are no apps, and even for specialized things like
virtualization hypervisors this kind of thing is often simply not
worth it.
Quite frankly, *if* we ever merge userfaultfd, I would *strongly*
argue for not merging the remap parts. I just don't see the point. It
doesn't seem to add anything that is semantically very important -
it's *potentially* a faster copy, but even that is
(a) questionable in the first place
and
(b) unclear why anybody would ever care about performance of
infrastructure that nobody actually uses today, and future use isn't
even clear or shown to be particualrly performance-sensitive.
So basically I'd like to see better documentation, a few real use
cases (and by real I very much do *not* mean "you can use it for
this", but actual patches to actual projects that matter and that are
expected to care and merge them), and a simplified series that doesn't
do the remap thing.
Because *every* time we add a new clever interface, we end up with
approximately zero users and just pain down the line. Examples:
splice, mremap, yadda yadda.
Linus
--
To unsubscribe, send a message with 'unsubscribe linux-mm' in
the body to majordomo@kvack.org. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"dont@kvack.org"> email@kvack.org </a>
This creates an interesting effect when the userfaultfd is used outside of
the process which created and activated one. If I try to monitor the memory
usage of one task with another, once the first task fork()-s, its child
begins to see zero-pages in the places where the monitor task was supposed
to insert pages with data.
--
To unsubscribe, send a message with 'unsubscribe linux-mm' in
the body to majordomo@kvack.org. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"dont@kvack.org"> email@kvack.org </a>
From: Pavel Emelyanov <hidden> Date: 2015-03-05 17:58:30
+int handle_userfault(struct vm_area_struct *vma, unsigned long address,
+ unsigned int flags, unsigned long reason)
+{
+ struct mm_struct *mm = vma->vm_mm;
+ struct userfaultfd_ctx *ctx;
+ struct userfaultfd_wait_queue uwq;
+
+ BUG_ON(!rwsem_is_locked(&mm->mmap_sem));
+
+ ctx = vma->vm_userfaultfd_ctx.ctx;
+ if (!ctx)
+ return VM_FAULT_SIGBUS;
+
+ BUG_ON(ctx->mm != mm);
+
+ VM_BUG_ON(reason & ~(VM_UFFD_MISSING|VM_UFFD_WP));
+ VM_BUG_ON(!(reason & VM_UFFD_MISSING) ^ !!(reason & VM_UFFD_WP));
+
+ /*
+ * If it's already released don't get it. This avoids to loop
+ * in __get_user_pages if userfaultfd_release waits on the
+ * caller of handle_userfault to release the mmap_sem.
+ */
+ if (unlikely(ACCESS_ONCE(ctx->released)))
+ return VM_FAULT_SIGBUS;
+
+ /* check that we can return VM_FAULT_RETRY */
+ if (unlikely(!(flags & FAULT_FLAG_ALLOW_RETRY))) {
+ /*
+ * Validate the invariant that nowait must allow retry
+ * to be sure not to return SIGBUS erroneously on
+ * nowait invocations.
+ */
+ BUG_ON(flags & FAULT_FLAG_RETRY_NOWAIT);
+#ifdef CONFIG_DEBUG_VM
+ if (printk_ratelimit()) {
+ printk(KERN_WARNING
+ "FAULT_FLAG_ALLOW_RETRY missing %x\n", flags);
+ dump_stack();
+ }
+#endif
+ return VM_FAULT_SIGBUS;
+ }
+
+ /*
+ * Handle nowait, not much to do other than tell it to retry
+ * and wait.
+ */
+ if (flags & FAULT_FLAG_RETRY_NOWAIT)
+ return VM_FAULT_RETRY;
+
+ /* take the reference before dropping the mmap_sem */
+ userfaultfd_ctx_get(ctx);
+
+ /* be gentle and immediately relinquish the mmap_sem */
+ up_read(&mm->mmap_sem);
+
+ init_waitqueue_func_entry(&uwq.wq, userfaultfd_wake_function);
+ uwq.wq.private = current;
+ uwq.address = userfault_address(address, flags, reason);
Since we report only the virtual address of the fault, this will make difficulties
for task monitoring the address space of some other task. Like this:
Let's assume a task creates a userfaultfd, activates one, registers several VMAs
in it and then sends the ufd descriptor to other task. If later the first task will
remap those VMAs and will start touching pages, the monitor will start receiving
fault addresses using which it will not be able to guess the exact vma the
requests come from.
Thanks,
Pavel
--
To unsubscribe, send a message with 'unsubscribe linux-mm' in
the body to majordomo@kvack.org. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"dont@kvack.org"> email@kvack.org </a>
From: Pavel Emelyanov <hidden> Date: 2015-03-05 18:01:55
+ssize_t remap_pages(struct mm_struct *dst_mm, struct mm_struct *src_mm,
+ unsigned long dst_start, unsigned long src_start,
+ unsigned long len, __u64 mode)
+{
+ struct vm_area_struct *src_vma, *dst_vma;
+ long err = -EINVAL;
+ pmd_t *src_pmd, *dst_pmd;
+ pte_t *src_pte, *dst_pte;
+ spinlock_t *dst_ptl, *src_ptl;
+ unsigned long src_addr, dst_addr;
+ int thp_aligned = -1;
+ ssize_t moved = 0;
+
+ /*
+ * Sanitize the command parameters:
+ */
+ BUG_ON(src_start & ~PAGE_MASK);
+ BUG_ON(dst_start & ~PAGE_MASK);
+ BUG_ON(len & ~PAGE_MASK);
+
+ /* Does the address range wrap, or is the span zero-sized? */
+ BUG_ON(src_start + len <= src_start);
+ BUG_ON(dst_start + len <= dst_start);
+
+ /*
+ * Because these are read sempahores there's no risk of lock
+ * inversion.
+ */
+ down_read(&dst_mm->mmap_sem);
+ if (dst_mm != src_mm)
+ down_read(&src_mm->mmap_sem);
+
+ /*
+ * Make sure the vma is not shared, that the src and dst remap
+ * ranges are both valid and fully within a single existing
+ * vma.
+ */
+ src_vma = find_vma(src_mm, src_start);
+ if (!src_vma || (src_vma->vm_flags & VM_SHARED))
+ goto out;
+ if (src_start < src_vma->vm_start ||
+ src_start + len > src_vma->vm_end)
+ goto out;
+
+ dst_vma = find_vma(dst_mm, dst_start);
+ if (!dst_vma || (dst_vma->vm_flags & VM_SHARED))
+ goto out;
I again have a concern about the case when one task monitors the VM of the
other one. If the target task (owning the mm) unmaps a VMA then the monitor
task (holding and operating on the ufd) will get plain EINVAL on UFFDIO_REMAP
request. This is not fatal, but still inconvenient as it will be hard to
find out the reason for failure -- dst VMA is removed and the monitor should
just drop the respective pages with data, or some other error has occurred
and some other actions should be taken.
Thanks,
Pavel
--
To unsubscribe, send a message with 'unsubscribe linux-mm' in
the body to majordomo@kvack.org. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"dont@kvack.org"> email@kvack.org </a>
From: Pavel Emelyanov <hidden> Date: 2015-03-05 18:08:03
+static int mcopy_atomic_pte(struct mm_struct *dst_mm,
+ pmd_t *dst_pmd,
+ struct vm_area_struct *dst_vma,
+ unsigned long dst_addr,
+ unsigned long src_addr)
+{
+ struct mem_cgroup *memcg;
+ pte_t _dst_pte, *dst_pte;
+ spinlock_t *ptl;
+ struct page *page;
+ void *page_kaddr;
+ int ret;
+
+ ret = -ENOMEM;
+ page = alloc_page_vma(GFP_HIGHUSER_MOVABLE, dst_vma, dst_addr);
+ if (!page)
+ goto out;
Not a fatal thing, but still quite inconvenient. If there are two tasks that
have anonymous private VMAs that are still not COW-ed from each other, then
it will be impossible to keep the pages shared with userfault. Thus if we do
post-copy memory migration for tasks, then these guys will have their
memory COW-ed.
Thanks,
Pavel
--
To unsubscribe, send a message with 'unsubscribe linux-mm' in
the body to majordomo@kvack.org. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"dont@kvack.org"> email@kvack.org </a>
From: Pavel Emelyanov <hidden> Date: 2015-03-05 18:16:12
All UFFDIO_COPY/ZEROPAGE/REMAP methods already support CRIU postcopy
live migration and the UFFD can be passed to a manager process through
unix domain sockets to satisfy point 5).
Yup :) That's the best (from my POV) point of ufd -- the ability to delegate
the descriptor to some other task. Though there are several limitations (I've
expressed them in other e-mails), I'm definitely supporting this!
The respective CRIU code is quite sloppy yet, I will try to brush one up and
show soon.
Thanks,
Pavel
--
To unsubscribe, send a message with 'unsubscribe linux-mm' in
the body to majordomo@kvack.org. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"dont@kvack.org"> email@kvack.org </a>
From: Andrea Arcangeli <hidden> Date: 2015-03-05 18:52:18
On Thu, Mar 05, 2015 at 09:39:48AM -0800, Linus Torvalds wrote:
Is this really worth it? On real loads? That people are expected to use?
I fully agree that it's not worth merging upstream UFFDIO_REMAP until
(and if) a real world usage for it will showup. To further clarify:
would this not have been an RFC, the patchset would have stopped at
patch number 15/21 included.
Merging UFFDIO_REMAP with no real life users, would just increase the
attack vector surface of the kernel for no good.
Thanks for your idea that the UFFDIO_COPY is faster, the userland code
we submitted for qemu only uses UFFDIO_COPY|ZEROPAGE, it never uses
UFFDIO_REMAP. I immediately agreed about UFFDIO_COPY being preferable
after you mentioned it during review of the previous RFC.
However this being a RFC with a large audience, and UFFDIO_REMAP
allowing to "remove" memory (think like externalizing memory into to
ceph with deduplication or such), I still added it just in case there
are real world use cases that may justify me keeping it around (even
if I would definitely not have submitted it for merging in the short
term regardless).
In addition of dropping the parts that aren't suitable for merging in
the short term like UFFDIO_REMAP, for any further submits that won't
substantially alter the API like it happened between the v2 to v3
RFCs, I'll also shrink the To/Cc list considerably.
Considering how we just got rid of one special magic VM remapping
thing that nobody actually used, I'd really hate to add a new one.
Having to define an API somehow, I tried to think at all possible
future usages and make sure the API would allow for those if needed.
Quite frankly, *if* we ever merge userfaultfd, I would *strongly*
argue for not merging the remap parts. I just don't see the point. It
doesn't seem to add anything that is semantically very important -
it's *potentially* a faster copy, but even that is
(a) questionable in the first place
Yes, we already measured the UFFDIO_COPY is faster than UFFDIO_REMAP,
the userfault latency decreases -20%.
and
(b) unclear why anybody would ever care about performance of
infrastructure that nobody actually uses today, and future use isn't
even clear or shown to be particualrly performance-sensitive.
The only potential _theoretical_ case that justify the existence of
UFFDIO_REMAP is about "removing" memory from the address space. To
"add" memory UFFDIO_COPY and UFFDIO_ZEROPAGE are always preferable
like you suggested.
So basically I'd like to see better documentation, a few real use
cases (and by real I very much do *not* mean "you can use it for
this", but actual patches to actual projects that matter and that are
expected to care and merge them), and a simplified series that doesn't
do the remap thing.
So far I wrote some doc in 2/21 and in the cover letter, but certainly
more docs are necessary. Trinity is also needed (I got trinity running
on the v2 API but I haven't adapted to the new API yet).
About the real world usages, this is the primary one:
http://lists.gnu.org/archive/html/qemu-devel/2015-02/msg04873.html
And it actually cannot be merged in qemu until userfaultfd is merged
in the kernel. There's simply no safe way to implement postcopy live
migration without something equivalent to the userfaultfd if all Linux
VM features are intended to be retained in the destination node.
Because *every* time we add a new clever interface, we end up with
approximately zero users and just pain down the line. Examples:
splice, mremap, yadda yadda.
Aside from mremap which I think is widely used, I totally agree in
principle.
For now I can quite comfortably guarantee the above real life user for
userfaultfd (qemu), but there are potential 5 of them. And none needs
UFFDIO_REMAP, which is again why I totally agree of not submitting it
for merging and it was intended it only for the initial RFC to share
the idea of "removing" the memory with a larger audience before I
shrink the Cc/To list for further updates.
Thanks,
Andrea
--
To unsubscribe, send a message with 'unsubscribe linux-mm' in
the body to majordomo@kvack.org. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"dont@kvack.org"> email@kvack.org </a>
On Thu, Mar 5, 2015 at 10:51 AM, Andrea Arcangeli [off-list ref] wrote:
Thanks for your idea that the UFFDIO_COPY is faster, the userland code
we submitted for qemu only uses UFFDIO_COPY|ZEROPAGE, it never uses
UFFDIO_REMAP.
Ok. So there's no actual expected use of the remap interface. Good.
That makes this series more palatable, since the rest didn't raise my
hackles much.
(But yeah, the documentation patch didn't really explain the uses very
much or at all, so I think something more is needed in that area).
Linus
From: Andrea Arcangeli <hidden> Date: 2015-03-05 22:02:59
If userfaultfd is armed on a certain vma we can't "fill" the holes
with zeroes or we'll break the userland on demand paging. The holes if
the userfault is armed, are really missing information (not zeroes)
that the userland has to load from network or elsewhere.
The same issue happens for wrprotected ptes that we can't just convert
into a single writable pmd_trans_huge.
We could however in theory still merge across zeropages if only
VM_UFFD_MISSING is set (so if VM_UFFD_WP is not set)... that could be
slightly improved but it'd be much more complex code for a tiny corner
case.
Signed-off-by: Andrea Arcangeli <redacted>
---
mm/huge_memory.c | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
@@ -2145,7 +2145,8 @@ static int __collapse_huge_page_isolate(struct vm_area_struct *vma,_pte++,address+=PAGE_SIZE){pte_tpteval=*_pte;if(pte_none(pteval)||is_zero_pfn(pte_pfn(pteval))){-if(++none_or_zero<=khugepaged_max_ptes_none)+if(!userfaultfd_armed(vma)&&+++none_or_zero<=khugepaged_max_ptes_none)continue;elsegotoout;
@@ -2593,7 +2594,8 @@ static int khugepaged_scan_pmd(struct mm_struct *mm,_pte++,_address+=PAGE_SIZE){pte_tpteval=*_pte;if(pte_none(pteval)||is_zero_pfn(pte_pfn(pteval))){-if(++none_or_zero<=khugepaged_max_ptes_none)+if(!userfaultfd_armed(vma)&&+++none_or_zero<=khugepaged_max_ptes_none)continue;elsegotoout_unmap;--
To unsubscribe, send a message with 'unsubscribe linux-mm' in
the body to majordomo@kvack.org. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"dont@kvack.org"> email@kvack.org </a>
From: Andrea Arcangeli <hidden> Date: 2015-03-05 22:03:49
This implements the uABI of UFFDIO_REMAP.
Notably one mode bitflag is also forwarded (and in turn known) by the
lowlevel remap_pages method.
Signed-off-by: Andrea Arcangeli <redacted>
---
include/uapi/linux/userfaultfd.h | 27 ++++++++++++++++++++++++++-
1 file changed, 26 insertions(+), 1 deletion(-)
To unsubscribe, send a message with 'unsubscribe linux-mm' in
the body to majordomo@kvack.org. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"dont@kvack.org"> email@kvack.org </a>
From: Michael Kerrisk (man-pages) <hidden> Date: 2015-03-06 10:50:05
Hi Andrea,
On 5 March 2015 at 18:17, Andrea Arcangeli [off-list ref] wrote:
Once an userfaultfd has been created and certain region of the process
virtual address space have been registered into it, the thread
responsible for doing the memory externalization can manage the page
faults in userland by talking to the kernel using the userfaultfd
protocol.
Is there someting like a man page for this new syscall?
Thanks,
Michael
quoted hunk
poll() can be used to know when there are new pending userfaults to be
read (POLLIN).
Signed-off-by: Andrea Arcangeli <redacted>
---
fs/userfaultfd.c | 977 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 977 insertions(+)
create mode 100644 fs/userfaultfd.c
@@ -0,0 +1,977 @@+/*+*fs/userfaultfd.c+*+*Copyright(C)2007DavideLibenzi<davidel@xmailserver.org>+*Copyright(C)2008-2009RedHat,Inc.+*Copyright(C)2015RedHat,Inc.+*+*ThisworkislicensedunderthetermsoftheGNUGPL,version2.See+*theCOPYINGfileinthetop-leveldirectory.+*+*Somepartderivedfromfs/eventfd.c(anoninodesetup)and+*mm/ksm.c(mmhashing).+*/++#include<linux/hashtable.h>+#include<linux/sched.h>+#include<linux/mm.h>+#include<linux/poll.h>+#include<linux/slab.h>+#include<linux/seq_file.h>+#include<linux/file.h>+#include<linux/bug.h>+#include<linux/anon_inodes.h>+#include<linux/syscalls.h>+#include<linux/userfaultfd_k.h>+#include<linux/mempolicy.h>+#include<linux/ioctl.h>+#include<linux/security.h>++enumuserfaultfd_state{+UFFD_STATE_WAIT_API,+UFFD_STATE_RUNNING,+};++structuserfaultfd_ctx{+/* pseudo fd refcounting */+atomic_trefcount;+/* waitqueue head for the userfaultfd page faults */+wait_queue_head_tfault_wqh;+/* waitqueue head for the pseudo fd to wakeup poll/read */+wait_queue_head_tfd_wqh;+/* userfaultfd syscall flags */+unsignedintflags;+/* state machine */+enumuserfaultfd_statestate;+/* released */+boolreleased;+/* mm with one ore more vmas attached to this userfaultfd_ctx */+structmm_struct*mm;+};++structuserfaultfd_wait_queue{+unsignedlongaddress;+wait_queue_twq;+boolpending;+structuserfaultfd_ctx*ctx;+};++structuserfaultfd_wake_range{+unsignedlongstart;+unsignedlonglen;+};++staticintuserfaultfd_wake_function(wait_queue_t*wq,unsignedmode,+intwake_flags,void*key)+{+structuserfaultfd_wake_range*range=key;+intret;+structuserfaultfd_wait_queue*uwq;+unsignedlongstart,len;++uwq=container_of(wq,structuserfaultfd_wait_queue,wq);+ret=0;+/* don't wake the pending ones to avoid reads to block */+if(uwq->pending&&!ACCESS_ONCE(uwq->ctx->released))+gotoout;+/* len == 0 means wake all */+start=range->start;+len=range->len;+if(len&&(start>uwq->address||start+len<=uwq->address))+gotoout;+ret=wake_up_state(wq->private,mode);+if(ret)+/* wake only once, autoremove behavior */+list_del_init(&wq->task_list);+out:+returnret;+}++/**+*userfaultfd_ctx_get-Acquiresareferencetotheinternaluserfaultfd+*context.+*@ctx:[in]Pointertotheuserfaultfdcontext.+*+*Returns:Incaseofsuccess,returnsnotzero.+*/+staticvoiduserfaultfd_ctx_get(structuserfaultfd_ctx*ctx)+{+if(!atomic_inc_not_zero(&ctx->refcount))+BUG();+}++/**+*userfaultfd_ctx_put-Releasesareferencetotheinternaluserfaultfd+*context.+*@ctx:[in]Pointertouserfaultfdcontext.+*+*Theuserfaultfdcontextreferencemusthavebeenpreviouslyacquiredeither+*withuserfaultfd_ctx_get()oruserfaultfd_ctx_fdget().+*/+staticvoiduserfaultfd_ctx_put(structuserfaultfd_ctx*ctx)+{+if(atomic_dec_and_test(&ctx->refcount)){+mmdrop(ctx->mm);+kfree(ctx);+}+}++staticinlineunsignedlonguserfault_address(unsignedlongaddress,+unsignedintflags,+unsignedlongreason)+{+BUILD_BUG_ON(PAGE_SHIFT<UFFD_BITS);+address&=PAGE_MASK;+if(flags&FAULT_FLAG_WRITE)+/*+*Encode"write"faultinformationintheLSBofthe+*addressreadbyuserland,withoutdependingon+*FAULT_FLAG_WRITEkernelinternalvalue.+*/+address|=UFFD_BIT_WRITE;+if(reason&VM_UFFD_WP)+/*+*Encode"reason"faultinformationasbitnumber1+*intheaddressreadbyuserland.Ifbitnumber1is+*clearitmeansthereasonisaVM_FAULT_MISSING+*fault.+*/+address|=UFFD_BIT_WP;+returnaddress;+}++/*+*ThelockingrulesinvolvedinreturningVM_FAULT_RETRYdependingon+*FAULT_FLAG_ALLOW_RETRY,FAULT_FLAG_RETRY_NOWAITand+*FAULT_FLAG_KILLABLEarenotstraightforward.The"Caution"+*recommendationin__lock_page_or_retryisnotanunderstatement.+*+*IfFAULT_FLAG_ALLOW_RETRYisset,themmap_semmustbereleased+*beforereturningVM_FAULT_RETRYonlyifFAULT_FLAG_RETRY_NOWAITis+*notset.+*+*IfFAULT_FLAG_ALLOW_RETRYissetbutFAULT_FLAG_KILLABLEisnot+*set,VM_FAULT_RETRYcanstillbereturnedifandonlyifthereare+*fatal_signal_pending()s,andthemmap_semmustbereleasedbefore+*returningit.+*/+inthandle_userfault(structvm_area_struct*vma,unsignedlongaddress,+unsignedintflags,unsignedlongreason)+{+structmm_struct*mm=vma->vm_mm;+structuserfaultfd_ctx*ctx;+structuserfaultfd_wait_queueuwq;++BUG_ON(!rwsem_is_locked(&mm->mmap_sem));++ctx=vma->vm_userfaultfd_ctx.ctx;+if(!ctx)+returnVM_FAULT_SIGBUS;++BUG_ON(ctx->mm!=mm);++VM_BUG_ON(reason&~(VM_UFFD_MISSING|VM_UFFD_WP));+VM_BUG_ON(!(reason&VM_UFFD_MISSING)^!!(reason&VM_UFFD_WP));++/*+*Ifit'salreadyreleaseddon'tgetit.Thisavoidstoloop+*in__get_user_pagesifuserfaultfd_releasewaitsonthe+*callerofhandle_userfaulttoreleasethemmap_sem.+*/+if(unlikely(ACCESS_ONCE(ctx->released)))+returnVM_FAULT_SIGBUS;++/* check that we can return VM_FAULT_RETRY */+if(unlikely(!(flags&FAULT_FLAG_ALLOW_RETRY))){+/*+*Validatetheinvariantthatnowaitmustallowretry+*tobesurenottoreturnSIGBUSerroneouslyon+*nowaitinvocations.+*/+BUG_ON(flags&FAULT_FLAG_RETRY_NOWAIT);+#ifdef CONFIG_DEBUG_VM+if(printk_ratelimit()){+printk(KERN_WARNING+"FAULT_FLAG_ALLOW_RETRY missing %x\n",flags);+dump_stack();+}+#endif+returnVM_FAULT_SIGBUS;+}++/*+*Handlenowait,notmuchtodootherthantellittoretry+*andwait.+*/+if(flags&FAULT_FLAG_RETRY_NOWAIT)+returnVM_FAULT_RETRY;++/* take the reference before dropping the mmap_sem */+userfaultfd_ctx_get(ctx);++/* be gentle and immediately relinquish the mmap_sem */+up_read(&mm->mmap_sem);++init_waitqueue_func_entry(&uwq.wq,userfaultfd_wake_function);+uwq.wq.private=current;+uwq.address=userfault_address(address,flags,reason);+uwq.pending=true;+uwq.ctx=ctx;++spin_lock(&ctx->fault_wqh.lock);+/*+*Afterthe__add_wait_queuetheuwqisvisibletouserland+*throughpoll/read().+*/+__add_wait_queue(&ctx->fault_wqh,&uwq.wq);+for(;;){+set_current_state(TASK_KILLABLE);+if(!uwq.pending||ACCESS_ONCE(ctx->released)||+fatal_signal_pending(current))+break;+spin_unlock(&ctx->fault_wqh.lock);++wake_up_poll(&ctx->fd_wqh,POLLIN);+schedule();++spin_lock(&ctx->fault_wqh.lock);+}+__remove_wait_queue(&ctx->fault_wqh,&uwq.wq);+__set_current_state(TASK_RUNNING);+spin_unlock(&ctx->fault_wqh.lock);++/*+*ctxmaygoawayafterthisiftheuserfaultpseudofdis+*alreadyreleased.+*/+userfaultfd_ctx_put(ctx);++returnVM_FAULT_RETRY;+}++staticintuserfaultfd_release(structinode*inode,structfile*file)+{+structuserfaultfd_ctx*ctx=file->private_data;+structmm_struct*mm=ctx->mm;+structvm_area_struct*vma,*prev;+/* len == 0 means wake all */+structuserfaultfd_wake_rangerange={.len=0,};+unsignedlongnew_flags;++ACCESS_ONCE(ctx->released)=true;++/*+*FlushpagefaultsoutofallCPUs.NOTE:allpagefaults+*mustberetriedwithoutreturningVM_FAULT_SIGBUSif+*userfaultfd_ctx_get()succeedsbutvma->vma_userfault_ctx+*changeswhilehandle_userfaultreleasedthemmap_sem.So+*it'scriticalthatreleasedissettotrue(above),before+*takingthemmap_semforwriting.+*/+down_write(&mm->mmap_sem);+prev=NULL;+for(vma=mm->mmap;vma;vma=vma->vm_next){+cond_resched();+BUG_ON(!!vma->vm_userfaultfd_ctx.ctx^+!!(vma->vm_flags&(VM_UFFD_MISSING|VM_UFFD_WP)));+if(vma->vm_userfaultfd_ctx.ctx!=ctx){+prev=vma;+continue;+}+new_flags=vma->vm_flags&~(VM_UFFD_MISSING|VM_UFFD_WP);+prev=vma_merge(mm,prev,vma->vm_start,vma->vm_end,+new_flags,vma->anon_vma,+vma->vm_file,vma->vm_pgoff,+vma_policy(vma),+NULL_VM_UFFD_CTX);+if(prev)+vma=prev;+else+prev=vma;+vma->vm_flags=new_flags;+vma->vm_userfaultfd_ctx=NULL_VM_UFFD_CTX;+}+up_write(&mm->mmap_sem);++/*+*Afternonewpagefaultscanwaitonthisfault_wqh,flush+*thelastpagefaultsthatmayhavebeenalreadywaitingon+*thefault_wqh.+*/+spin_lock(&ctx->fault_wqh.lock);+__wake_up_locked_key(&ctx->fault_wqh,TASK_NORMAL,0,&range);+spin_unlock(&ctx->fault_wqh.lock);++wake_up_poll(&ctx->fd_wqh,POLLHUP);+userfaultfd_ctx_put(ctx);+return0;+}++staticinlineunsignedintfind_userfault(structuserfaultfd_ctx*ctx,+structuserfaultfd_wait_queue**uwq)+{+wait_queue_t*wq;+structuserfaultfd_wait_queue*_uwq;+unsignedintret=0;++spin_lock(&ctx->fault_wqh.lock);+list_for_each_entry(wq,&ctx->fault_wqh.task_list,task_list){+_uwq=container_of(wq,structuserfaultfd_wait_queue,wq);+if(_uwq->pending){+ret=POLLIN;+if(uwq)+*uwq=_uwq;+break;+}+}+spin_unlock(&ctx->fault_wqh.lock);++returnret;+}++staticunsignedintuserfaultfd_poll(structfile*file,poll_table*wait)+{+structuserfaultfd_ctx*ctx=file->private_data;++poll_wait(file,&ctx->fd_wqh,wait);++switch(ctx->state){+caseUFFD_STATE_WAIT_API:+returnPOLLERR;+caseUFFD_STATE_RUNNING:+returnfind_userfault(ctx,NULL);+default:+BUG();+}+}++staticssize_tuserfaultfd_ctx_read(structuserfaultfd_ctx*ctx,intno_wait,+__u64*addr)+{+ssize_tret;+DECLARE_WAITQUEUE(wait,current);+structuserfaultfd_wait_queue*uwq=NULL;++/* always take the fd_wqh lock before the fault_wqh lock */+spin_lock(&ctx->fd_wqh.lock);+__add_wait_queue(&ctx->fd_wqh,&wait);+for(;;){+set_current_state(TASK_INTERRUPTIBLE);+if(find_userfault(ctx,&uwq)){+uwq->pending=false;+/* careful to always initialize addr if ret == 0 */+*addr=uwq->address;+ret=0;+break;+}+if(signal_pending(current)){+ret=-ERESTARTSYS;+break;+}+if(no_wait){+ret=-EAGAIN;+break;+}+spin_unlock(&ctx->fd_wqh.lock);+schedule();+spin_lock_irq(&ctx->fd_wqh.lock);+}+__remove_wait_queue(&ctx->fd_wqh,&wait);+__set_current_state(TASK_RUNNING);+spin_unlock_irq(&ctx->fd_wqh.lock);++returnret;+}++staticssize_tuserfaultfd_read(structfile*file,char__user*buf,+size_tcount,loff_t*ppos)+{+structuserfaultfd_ctx*ctx=file->private_data;+ssize_t_ret,ret=0;+/* careful to always initialize addr if ret == 0 */+__u64uninitialized_var(addr);+intno_wait=file->f_flags&O_NONBLOCK;++if(ctx->state==UFFD_STATE_WAIT_API)+return-EINVAL;+BUG_ON(ctx->state!=UFFD_STATE_RUNNING);++for(;;){+if(count<sizeof(addr))+returnret?ret:-EINVAL;+_ret=userfaultfd_ctx_read(ctx,no_wait,&addr);+if(_ret<0)+returnret?ret:_ret;+if(put_user(addr,(__u64__user*)buf))+returnret?ret:-EFAULT;+ret+=sizeof(addr);+buf+=sizeof(addr);+count-=sizeof(addr);+/*+*Allowtoreadmorethanonefaultattimebutonly+*blockifwaitingfortheveryfirstone.+*/+no_wait=O_NONBLOCK;+}+}++staticint__wake_userfault(structuserfaultfd_ctx*ctx,+structuserfaultfd_wake_range*range)+{+wait_queue_t*wq;+structuserfaultfd_wait_queue*uwq;+intret;+unsignedlongstart,end;++start=range->start;+end=range->start+range->len;++ret=-ENOENT;+spin_lock(&ctx->fault_wqh.lock);+list_for_each_entry(wq,&ctx->fault_wqh.task_list,task_list){+uwq=container_of(wq,structuserfaultfd_wait_queue,wq);+if(uwq->pending)+continue;+if(uwq->address>=start&&uwq->address<end){+ret=0;+/* wake all in the range and autoremove */+__wake_up_locked_key(&ctx->fault_wqh,TASK_NORMAL,0,+range);+break;+}+}+spin_unlock(&ctx->fault_wqh.lock);++returnret;+}++static__always_inlineintwake_userfault(structuserfaultfd_ctx*ctx,+structuserfaultfd_wake_range*range)+{+if(!waitqueue_active(&ctx->fault_wqh))+return-ENOENT;++return__wake_userfault(ctx,range);+}++static__always_inlineintvalidate_range(structmm_struct*mm,+__u64start,__u64len)+{+__u64task_size=mm->task_size;++if(start&~PAGE_MASK)+return-EINVAL;+if(len&~PAGE_MASK)+return-EINVAL;+if(!len)+return-EINVAL;+if(start<mmap_min_addr)+return-EINVAL;+if(start>=task_size)+return-EINVAL;+if(len>task_size-start)+return-EINVAL;+return0;+}++staticintuserfaultfd_register(structuserfaultfd_ctx*ctx,+unsignedlongarg)+{+structmm_struct*mm=ctx->mm;+structvm_area_struct*vma,*prev,*cur;+intret;+structuffdio_registeruffdio_register;+structuffdio_register__user*user_uffdio_register;+unsignedlongvm_flags,new_flags;+boolfound;+unsignedlongstart,end,vma_end;++user_uffdio_register=(structuffdio_register__user*)arg;++ret=-EFAULT;+if(copy_from_user(&uffdio_register,user_uffdio_register,+sizeof(uffdio_register)-sizeof(__u64)))+gotoout;++ret=-EINVAL;+if(!uffdio_register.mode)+gotoout;+if(uffdio_register.mode&~(UFFDIO_REGISTER_MODE_MISSING|+UFFDIO_REGISTER_MODE_WP))+gotoout;+vm_flags=0;+if(uffdio_register.mode&UFFDIO_REGISTER_MODE_MISSING)+vm_flags|=VM_UFFD_MISSING;+if(uffdio_register.mode&UFFDIO_REGISTER_MODE_WP){+vm_flags|=VM_UFFD_WP;+/*+*FIXME:removethebelowerrorconstraintby+*implementingthewprotecttrackingmode.+*/+ret=-EINVAL;+gotoout;+}++ret=validate_range(mm,uffdio_register.range.start,+uffdio_register.range.len);+if(ret)+gotoout;++start=uffdio_register.range.start;+end=start+uffdio_register.range.len;++down_write(&mm->mmap_sem);+vma=find_vma_prev(mm,start,&prev);++ret=-ENOMEM;+if(!vma)+gotoout_unlock;++/* check that there's at least one vma in the range */+ret=-EINVAL;+if(vma->vm_start>=end)+gotoout_unlock;++/*+*Searchfornotcompatiblevmas.+*+*FIXME:thisshallberelaxedlatersothatitdoesn'tfail+*ontmpfsbackedvmas(inadditiontothecurrentallowance+*onanonymousvmas).+*/+found=false;+for(cur=vma;cur&&cur->vm_start<end;cur=cur->vm_next){+cond_resched();++BUG_ON(!!cur->vm_userfaultfd_ctx.ctx^+!!(cur->vm_flags&(VM_UFFD_MISSING|VM_UFFD_WP)));++/* check not compatible vmas */+ret=-EINVAL;+if(cur->vm_ops)+gotoout_unlock;++/*+*Checkthatthisvmaisn'talreadyownedbya+*differentuserfaultfd.Wecan'tallowmorethanone+*userfaultfdtoownasinglevmasimultaneouslyorwe+*wouldn'tknowwhichonetodelivertheuserfaultsto.+*/+ret=-EBUSY;+if(cur->vm_userfaultfd_ctx.ctx&&+cur->vm_userfaultfd_ctx.ctx!=ctx)+gotoout_unlock;++found=true;+}+BUG_ON(!found);++/*+*Nowthatwescannedallvmaswecanalreadytelluserlandwhich+*ioctlsmethodsareguaranteedtosucceedonthisrange.+*/+ret=-EFAULT;+if(put_user(UFFD_API_RANGE_IOCTLS,&user_uffdio_register->ioctls))+gotoout_unlock;++if(vma->vm_start<start)+prev=vma;++ret=0;+do{+cond_resched();++BUG_ON(vma->vm_ops);+BUG_ON(vma->vm_userfaultfd_ctx.ctx&&+vma->vm_userfaultfd_ctx.ctx!=ctx);++/*+*Nothingtodo:thisvmaisalreadyregisteredintothis+*userfaultfdandwiththerighttrackingmodetoo.+*/+if(vma->vm_userfaultfd_ctx.ctx==ctx&&+(vma->vm_flags&vm_flags)==vm_flags)+gotoskip;++if(vma->vm_start>start)+start=vma->vm_start;+vma_end=min(end,vma->vm_end);++new_flags=(vma->vm_flags&~vm_flags)|vm_flags;+prev=vma_merge(mm,prev,start,vma_end,new_flags,+vma->anon_vma,vma->vm_file,vma->vm_pgoff,+vma_policy(vma),+((structvm_userfaultfd_ctx){ctx}));+if(prev){+vma=prev;+gotonext;+}+if(vma->vm_start<start){+ret=split_vma(mm,vma,start,1);+if(ret)+break;+}+if(vma->vm_end>end){+ret=split_vma(mm,vma,end,0);+if(ret)+break;+}+next:+/*+*Inthevma_merge()successfulmprotect-likecase8:+*thenextvmawasmergedintothecurrentoneand+*thecurrentonehasnotbeenupdatedyet.+*/+vma->vm_flags=new_flags;+vma->vm_userfaultfd_ctx.ctx=ctx;++skip:+prev=vma;+start=vma->vm_end;+vma=vma->vm_next;+}while(vma&&vma->vm_start<end);+out_unlock:+up_write(&mm->mmap_sem);+out:+returnret;+}++staticintuserfaultfd_unregister(structuserfaultfd_ctx*ctx,+unsignedlongarg)+{+structmm_struct*mm=ctx->mm;+structvm_area_struct*vma,*prev,*cur;+intret;+structuffdio_rangeuffdio_unregister;+unsignedlongnew_flags;+boolfound;+unsignedlongstart,end,vma_end;+constvoid__user*buf=(void__user*)arg;++ret=-EFAULT;+if(copy_from_user(&uffdio_unregister,buf,sizeof(uffdio_unregister)))+gotoout;++ret=validate_range(mm,uffdio_unregister.start,+uffdio_unregister.len);+if(ret)+gotoout;++start=uffdio_unregister.start;+end=start+uffdio_unregister.len;++down_write(&mm->mmap_sem);+vma=find_vma_prev(mm,start,&prev);++ret=-ENOMEM;+if(!vma)+gotoout_unlock;++/* check that there's at least one vma in the range */+ret=-EINVAL;+if(vma->vm_start>=end)+gotoout_unlock;++/*+*Searchfornotcompatiblevmas.+*+*FIXME:thisshallberelaxedlatersothatitdoesn'tfail+*ontmpfsbackedvmas(inadditiontothecurrentallowance+*onanonymousvmas).+*/+found=false;+ret=-EINVAL;+for(cur=vma;cur&&cur->vm_start<end;cur=cur->vm_next){+cond_resched();++BUG_ON(!!cur->vm_userfaultfd_ctx.ctx^+!!(cur->vm_flags&(VM_UFFD_MISSING|VM_UFFD_WP)));++/*+*Checknotcompatiblevmas,notstrictlyrequired+*hereasnotcompatiblevmascannothavean+*userfaultfd_ctxregisteredonthem,butthis+*providesformorestrictbehaviortonotice+*unregistrationerrors.+*/+if(cur->vm_ops)+gotoout_unlock;++found=true;+}+BUG_ON(!found);++if(vma->vm_start<start)+prev=vma;++ret=0;+do{+cond_resched();++BUG_ON(vma->vm_ops);++/*+*Nothingtodo:thisvmaisalreadyregisteredintothis+*userfaultfdandwiththerighttrackingmodetoo.+*/+if(!vma->vm_userfaultfd_ctx.ctx)+gotoskip;++if(vma->vm_start>start)+start=vma->vm_start;+vma_end=min(end,vma->vm_end);++new_flags=vma->vm_flags&~(VM_UFFD_MISSING|VM_UFFD_WP);+prev=vma_merge(mm,prev,start,vma_end,new_flags,+vma->anon_vma,vma->vm_file,vma->vm_pgoff,+vma_policy(vma),+NULL_VM_UFFD_CTX);+if(prev){+vma=prev;+gotonext;+}+if(vma->vm_start<start){+ret=split_vma(mm,vma,start,1);+if(ret)+break;+}+if(vma->vm_end>end){+ret=split_vma(mm,vma,end,0);+if(ret)+break;+}+next:+/*+*Inthevma_merge()successfulmprotect-likecase8:+*thenextvmawasmergedintothecurrentoneand+*thecurrentonehasnotbeenupdatedyet.+*/+vma->vm_flags=new_flags;+vma->vm_userfaultfd_ctx=NULL_VM_UFFD_CTX;++skip:+prev=vma;+start=vma->vm_end;+vma=vma->vm_next;+}while(vma&&vma->vm_start<end);+out_unlock:+up_write(&mm->mmap_sem);+out:+returnret;+}++/*+*Thisismostlyneededtore-wakeupthoseuserfaultsthatwerestill+*pendingwhenuserlandwakethemupthefirsttime.Wedon'twake+*thependingonetoavoidblockingreadstoblock,ornonblocking+*readtoreturn-EAGAIN,ifusedwithPOLLIN,toavoiduserland+*doubtsonwhyPOLLINwasn'treliable.+*/+staticintuserfaultfd_wake(structuserfaultfd_ctx*ctx,+unsignedlongarg)+{+intret;+structuffdio_rangeuffdio_wake;+structuserfaultfd_wake_rangerange;+constvoid__user*buf=(void__user*)arg;++ret=-EFAULT;+if(copy_from_user(&uffdio_wake,buf,sizeof(uffdio_wake)))+gotoout;++ret=validate_range(ctx->mm,uffdio_wake.start,uffdio_wake.len);+if(ret)+gotoout;++range.start=uffdio_wake.start;+range.len=uffdio_wake.len;++/*+*len==0meanswakeallandwedon'twanttowakeallhere,+*socheckitagaintobesure.+*/+VM_BUG_ON(!range.len);++ret=wake_userfault(ctx,&range);++out:+returnret;+}++/*+*userlandasksforacertainAPIversionandwereturnwhichbits+*andioctlcommandsareimplementedinthiskernelforsuchAPI+*versionor-EINVALifunknown.+*/+staticintuserfaultfd_api(structuserfaultfd_ctx*ctx,+unsignedlongarg)+{+structuffdio_apiuffdio_api;+void__user*buf=(void__user*)arg;+intret;++ret=-EINVAL;+if(ctx->state!=UFFD_STATE_WAIT_API)+gotoout;+ret=-EFAULT;+if(copy_from_user(&uffdio_api,buf,sizeof(__u64)))+gotoout;+if(uffdio_api.api!=UFFD_API){+/* careful not to leak info, we only read the first 8 bytes */+memset(&uffdio_api,0,sizeof(uffdio_api));+if(copy_to_user(buf,&uffdio_api,sizeof(uffdio_api)))+gotoout;+ret=-EINVAL;+gotoout;+}+/* careful not to leak info, we only read the first 8 bytes */+uffdio_api.bits=UFFD_API_BITS;+uffdio_api.ioctls=UFFD_API_IOCTLS;+ret=-EFAULT;+if(copy_to_user(buf,&uffdio_api,sizeof(uffdio_api)))+gotoout;+ctx->state=UFFD_STATE_RUNNING;+ret=0;+out:+returnret;+}++staticlonguserfaultfd_ioctl(structfile*file,unsignedcmd,+unsignedlongarg)+{+intret=-EINVAL;+structuserfaultfd_ctx*ctx=file->private_data;++switch(cmd){+caseUFFDIO_API:+ret=userfaultfd_api(ctx,arg);+break;+caseUFFDIO_REGISTER:+ret=userfaultfd_register(ctx,arg);+break;+caseUFFDIO_UNREGISTER:+ret=userfaultfd_unregister(ctx,arg);+break;+caseUFFDIO_WAKE:+ret=userfaultfd_wake(ctx,arg);+break;+}+returnret;+}++#ifdef CONFIG_PROC_FS+staticvoiduserfaultfd_show_fdinfo(structseq_file*m,structfile*f)+{+structuserfaultfd_ctx*ctx=f->private_data;+wait_queue_t*wq;+structuserfaultfd_wait_queue*uwq;+unsignedlongpending=0,total=0;++spin_lock(&ctx->fault_wqh.lock);+list_for_each_entry(wq,&ctx->fault_wqh.task_list,task_list){+uwq=container_of(wq,structuserfaultfd_wait_queue,wq);+if(uwq->pending)+pending++;+total++;+}+spin_unlock(&ctx->fault_wqh.lock);++/*+*Ifmoreprotocolswillbeadded,therewillbeallshown+*separatedbyaspace.Likethis:+*protocols:0xaa0xbb+*/+seq_printf(m,"pending:\t%lu\ntotal:\t%lu\nAPI:\t%Lx:%x:%Lx\n",+pending,total,UFFD_API,UFFD_API_BITS,+UFFD_API_IOCTLS|UFFD_API_RANGE_IOCTLS);+}+#endif++staticconststructfile_operationsuserfaultfd_fops={+#ifdef CONFIG_PROC_FS+.show_fdinfo=userfaultfd_show_fdinfo,+#endif+.release=userfaultfd_release,+.poll=userfaultfd_poll,+.read=userfaultfd_read,+.unlocked_ioctl=userfaultfd_ioctl,+.compat_ioctl=userfaultfd_ioctl,+.llseek=noop_llseek,+};++/**+*userfaultfd_file_create-Createsanuserfaultfdfilepointer.+*@flags:Flagsfortheuserfaultfdfile.+*+*Thisfunctioncreatesanuserfaultfdfilepointer,w/outinstalling+*itintothefdtable.Thisisusefulwhentheuserfaultfdfileis+*usedduringtheinitializationofdatastructuresthatrequire+*extrasetupaftertheuserfaultfdcreation.Sotheuserfaultfd+*creationissplitintothefilepointercreationphase,andthe+*filedescriptorinstallationphase.Inthiswayraceswith+*userspaceclosingthenewlyinstalledfiledescriptorcanbe+*avoided.Returnsanuserfaultfdfilepointer,orapropererror+*pointer.+*/+staticstructfile*userfaultfd_file_create(intflags)+{+structfile*file;+structuserfaultfd_ctx*ctx;++BUG_ON(!current->mm);++/* Check the UFFD_* constants for consistency. */+BUILD_BUG_ON(UFFD_CLOEXEC!=O_CLOEXEC);+BUILD_BUG_ON(UFFD_NONBLOCK!=O_NONBLOCK);++file=ERR_PTR(-EINVAL);+if(flags&~UFFD_SHARED_FCNTL_FLAGS)+gotoout;++file=ERR_PTR(-ENOMEM);+ctx=kmalloc(sizeof(*ctx),GFP_KERNEL);+if(!ctx)+gotoout;++atomic_set(&ctx->refcount,1);+init_waitqueue_head(&ctx->fault_wqh);+init_waitqueue_head(&ctx->fd_wqh);+ctx->flags=flags;+ctx->state=UFFD_STATE_WAIT_API;+ctx->released=false;+ctx->mm=current->mm;+/* prevent the mm struct to be freed */+atomic_inc(&ctx->mm->mm_count);++file=anon_inode_getfile("[userfaultfd]",&userfaultfd_fops,ctx,+O_RDWR|(flags&UFFD_SHARED_FCNTL_FLAGS));+if(IS_ERR(file))+kfree(ctx);+out:+returnfile;+}++SYSCALL_DEFINE1(userfaultfd,int,flags)+{+intfd,error;+structfile*file;++error=get_unused_fd_flags(flags&UFFD_SHARED_FCNTL_FLAGS);+if(error<0)+returnerror;+fd=error;++file=userfaultfd_file_create(flags);+if(IS_ERR(file)){+error=PTR_ERR(file);+gotoerr_put_unused_fd;+}+fd_install(fd,file);++returnfd;++err_put_unused_fd:+put_unused_fd(fd);++returnerror;+}--
To unsubscribe from this list: send the line "unsubscribe linux-api" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
--
Michael Kerrisk
Linux man-pages maintainer; http://www.kernel.org/doc/man-pages/
Linux/UNIX System Programming Training: http://man7.org/training/
--
To unsubscribe, send a message with 'unsubscribe linux-mm' in
the body to majordomo@kvack.org. For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"dont@kvack.org"> email@kvack.org </a>
@@ -0,0 +1,97 @@+= Userfaultfd =++== Objective ==++Userfaults allow to implement on demand paging from userland and more
s/to implement/the implementation of/
and maybe: s/on demand/on-demand/
+generally they allow userland to take control various memory page
+faults, something otherwise only the kernel code could do.
+
+For example userfaults allows a proper and more optimal implementation
+of the PROT_NONE+SIGSEGV trick.
+
+== Design ==
+
+Userfaults are delivered and resolved through the userfaultfd syscall.
+
+The userfaultfd (aside from registering and unregistering virtual
+memory ranges) provides for two primary functionalities:
s/provides for/provides/
+
+1) read/POLLIN protocol to notify an userland thread of the faults
s/an userland/a userland/ (remember, 'a unicorn gets an umbrella' - if
the 'u' is pronounced 'you' the correct article is 'a')
+ happening
+
+2) various UFFDIO_* ioctls that can mangle over the virtual memory
+ regions registered in the userfaultfd that allows userland to
+ efficiently resolve the userfaults it receives via 1) or to mangle
+ the virtual memory in the background
maybe: s/mangle/manage/2
+
+The real advantage of userfaults if compared to regular virtual memory
+management of mremap/mprotect is that the userfaults in all their
+operations never involve heavyweight structures like vmas (in fact the
+userfaultfd runtime load never takes the mmap_sem for writing).
+
+Vmas are not suitable for page(or hugepage)-granular fault tracking
+when dealing with virtual address spaces that could span
+Terabytes. Too many vmas would be needed for that.
+
+The userfaultfd once opened by invoking the syscall, can also be
+passed using unix domain sockets to a manager process, so the same
+manager process could handle the userfaults of a multitude of
+different process without them being aware about what is going on
s/process/processes/
+(well of course unless they later try to use the userfaultfd themself
s/themself/themselves/
+on the same region the manager is already tracking, which is a corner
+case that would currently return -EBUSY).
+
+== API ==
+
+When first opened the userfaultfd must be enabled invoking the
+UFFDIO_API ioctl specifying an uffdio_api.api value set to UFFD_API
s/an uffdio/a uffdio/
+which will specify the read/POLLIN protocol userland intends to speak
+on the UFFD. The UFFDIO_API ioctl if successful (i.e. if the requested
+uffdio_api.api is spoken also by the running kernel), will return into
+uffdio_api.bits and uffdio_api.ioctls two 64bit bitmasks of
+respectively the activated feature bits below PAGE_SHIFT in the
+userfault addresses returned by read(2) and the generic ioctl
+available.
+
+Once the userfaultfd has been enabled the UFFDIO_REGISTER ioctl should
+be invoked (if present in the returned uffdio_api.ioctls bitmask) to
+register a memory range in the userfaultfd by setting the
+uffdio_register structure accordingly. The uffdio_register.mode
+bitmask will specify to the kernel which kind of faults to track for
+the range (UFFDIO_REGISTER_MODE_MISSING would track missing
+pages). The UFFDIO_REGISTER ioctl will return the
+uffdio_register.ioctls bitmask of ioctls that are suitable to resolve
+userfaults on the range reigstered. Not all ioctls will necessarily be
s/reigstered/registered/
+supported for all memory types depending on the underlying virtual
+memory backend (anonymous memory vs tmpfs vs real filebacked
+mappings).
+
+Userland can use the uffdio_register.ioctls to mangle the virtual
maybe s/mangle/manage/
+address space in the background (to add or potentially also remove
+memory from the userfaultfd registered range). This means an userfault
s/an/a/
+could be triggering just before userland maps in the background the
+user-faulted page. To avoid POLLIN resulting in an unexpected blocking
+read (if the UFFD is not opened in nonblocking mode in the first
+place), we don't allow the background thread to wake userfaults that
+haven't been read by userland yet. If we would do that likely the
+UFFDIO_WAKE ioctl could be dropped. This may change in the future
+(with a UFFD_API protocol bumb combined with the removal of the
s/bumb/bump/
+UFFDIO_WAKE ioctl) if it'll be demonstrated that it's a valid
+optimization and worthy to force userland to use the UFFD always in
+nonblocking mode if combined with POLLIN.
+
+userfaultfd is also a generic enough feature, that it allows KVM to
+implement postcopy live migration (one form of memory externalization
+consisting of a virtual machine running with part or all of its memory
+residing on a different node in the cloud) without having to modify a
+single line of KVM kernel code. Guest async page faults, FOLL_NOWAIT
+and all other GUP features works just fine in combination with
+userfaults (userfaults trigger async page faults in the guest
+scheduler so those guest processes that aren't waiting for userfaults
+can keep running in the guest vcpus).
+
+The primary ioctl to resolve userfaults is UFFDIO_COPY. That
+atomically copies a page into the userfault registered range and wakes
+up the blocked userfaults (unless uffdio_copy.mode &
+UFFDIO_COPY_MODE_DONTWAKE is set). Other ioctl works similarly to
+UFFDIO_COPY.
--
Eric Blake eblake redhat com +1-919-301-3266
Libvirt virtualization library http://libvirt.org