From: Andrea Arcangeli <hidden> Date: 2014-10-03 17:11:23
Hello everyone,
There's a large To/Cc list for this RFC because this adds two new
syscalls (userfaultfd and remap_anon_pages) and
MADV_USERFAULT/MADV_NOUSERFAULT, so suggestions on changes are welcome
sooner than later.
The major change compared to the previous RFC I sent a few months ago
is that the userfaultfd protocol now supports dynamic range
registration. So you can have an unlimited number of userfaults for
each process, so each shared library can use its own userfaultfd on
its own memory independently from other shared libraries or the main
program. This functionality was suggested from Andy Lutomirski (more
details on this are in the commit header of the last patch of this
patchset).
In addition the mmap_sem complexities has been sorted out. In fact the
real userfault patchset starts from patch number 7. Patches 1-6 will
be submitted separately for merging and if applied standalone they
provide a scalability improvement by reducing the mmap_sem hold times
during I/O. I included patch 1-6 here too because they're an hard
dependency for the userfault patchset. The userfaultfd syscall depends
on the first fault to always have FAULT_FLAG_ALLOW_RETRY set (the
later retry faults don't matter, it's fine to clear
FAULT_FLAG_ALLOW_RETRY with the retry faults, following the current
model).
The combination of these features are what I would propose to
implement postcopy live migration in qemu, and in general demand
paging of remote memory, hosted in different cloud nodes.
If the access could ever happen in kernel context through syscalls
(not not just from userland context), then userfaultfd has to be used
on top of MADV_USERFAULT, to make the userfault unnoticeable to the
syscall (no error will be returned). This latter feature is more
advanced than what volatile ranges alone could do with SIGBUS so far
(but it's optional, if the process doesn't register the memory in a
userfaultfd, the regular SIGBUS will fire, if the fd is closed SIGBUS
will also fire for any blocked userfault that was waiting a
userfaultfd_write ack).
userfaultfd is also a generic enough feature, that it allows KVM to
implement postcopy live migration 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).
remap_anon_pages is the syscall to use to resolve the userfaults (it's
not mandatory, vmsplice will likely still be used in the case of local
postcopy live migration just to upgrade the qemu binary, but
remap_anon_pages is faster and ideal for transferring memory across
the network, it's zerocopy and doesn't touch the vma: it only holds
the mmap_sem for reading).
The current behavior of remap_anon_pages is very strict to avoid any
chance of memory corruption going unnoticed. mremap is not strict like
that: if there's a synchronization bug it would drop the destination
range silently resulting in subtle memory corruption for
example. remap_anon_pages would return -EEXIST in that case. If there
are holes in the source range remap_anon_pages will return -ENOENT.
If remap_anon_pages is used always with 2M naturally aligned
addresses, transparent hugepages will not be splitted. In there could
be 4k (or any size) holes in the 2M (or any size) source range,
remap_anon_pages should be used with the RAP_ALLOW_SRC_HOLES flag to
relax some of its strict checks (-ENOENT won't be returned if
RAP_ALLOW_SRC_HOLES is set, remap_anon_pages then will just behave as
a noop on any hole in the source range). This flag is generally useful
when implementing userfaults with THP granularity, but it shouldn't be
set if doing the userfaults with PAGE_SIZE granularity if the
developer wants to benefit from the strict -ENOENT behavior.
The remap_anon_pages syscall API is not vectored, as I expect it to be
used mainly for demand paging (where there can be just one faulting
range per userfault) or for large ranges (with the THP model as an
alternative to zapping re-dirtied pages with MADV_DONTNEED with 4k
granularity before starting the guest in the destination node) where
vectoring isn't going to provide much performance advantages (thanks
to the THP coarser granularity).
On the rmap side remap_anon_pages doesn't add much complexity: there's
no need of nonlinear anon vmas to support it because I added the
constraint that it will fail if the mapcount is more than 1. So in
general the source range of remap_anon_pages should be marked
MADV_DONTFORK to prevent any risk of failure if the process ever
forks (like qemu can in some case).
The MADV_USERFAULT feature should be generic enough that it can
provide the userfaults to the Android volatile range feature too, on
access of reclaimed volatile pages. Or it could be used for other
similar things with tmpfs in the future. I've been discussing how to
extend it to tmpfs for example. Currently if MADV_USERFAULT is set on
a non-anonymous vma, it will return -EINVAL and that's enough to
provide backwards compatibility once MADV_USERFAULT will be extended
to tmpfs. An orthogonal problem then will be to identify the optimal
mechanism to atomically resolve a tmpfs backed userfault (like
remap_anon_pages does it optimally for anonymous memory) but that's
beyond the scope of the userfault functionality (in theory
remap_anon_pages is also orthogonal and I could split it off in a
separate patchset if somebody prefers). Of course remap_file_pages
should do it fine too, but it would create rmap nonlinearity which
isn't optimal.
The code can be found here:
git clone --reference linux git://git.kernel.org/pub/scm/linux/kernel/git/andrea/aa.git -b userfault
The branch is rebased so you can get updates for example with:
git fetch && git checkout -f origin/userfault
Comments welcome, thanks!
Andrea
Andrea Arcangeli (15):
mm: gup: add get_user_pages_locked and get_user_pages_unlocked
mm: gup: use get_user_pages_unlocked within get_user_pages_fast
mm: gup: make get_user_pages_fast and __get_user_pages_fast latency
conscious
mm: gup: use get_user_pages_fast and get_user_pages_unlocked
mm: madvise MADV_USERFAULT: prepare vm_flags to allow more than 32bits
mm: madvise MADV_USERFAULT
mm: PT lock: export double_pt_lock/unlock
mm: rmap preparation for remap_anon_pages
mm: swp_entry_swapcount
mm: sys_remap_anon_pages
waitqueue: add nr wake parameter to __wake_up_locked_key
userfaultfd: add new syscall to provide memory externalization
userfaultfd: make userfaultfd_write non blocking
powerpc: add remap_anon_pages and userfaultfd
userfaultfd: implement USERFAULTFD_RANGE_REGISTER|UNREGISTER
Andres Lagar-Cavilla (2):
mm: gup: add FOLL_TRIED
kvm: Faults which trigger IO release the mmap_sem
arch/alpha/include/uapi/asm/mman.h | 3 +
arch/mips/include/uapi/asm/mman.h | 3 +
arch/mips/mm/gup.c | 8 +-
arch/parisc/include/uapi/asm/mman.h | 3 +
arch/powerpc/include/asm/systbl.h | 2 +
arch/powerpc/include/asm/unistd.h | 2 +-
arch/powerpc/include/uapi/asm/unistd.h | 2 +
arch/powerpc/mm/gup.c | 6 +-
arch/s390/kvm/kvm-s390.c | 4 +-
arch/s390/mm/gup.c | 6 +-
arch/sh/mm/gup.c | 6 +-
arch/sparc/mm/gup.c | 6 +-
arch/x86/mm/gup.c | 235 +++++++----
arch/x86/syscalls/syscall_32.tbl | 2 +
arch/x86/syscalls/syscall_64.tbl | 2 +
arch/xtensa/include/uapi/asm/mman.h | 3 +
drivers/dma/iovlock.c | 10 +-
drivers/iommu/amd_iommu_v2.c | 6 +-
drivers/media/pci/ivtv/ivtv-udma.c | 6 +-
drivers/scsi/st.c | 10 +-
drivers/video/fbdev/pvr2fb.c | 5 +-
fs/Makefile | 1 +
fs/proc/task_mmu.c | 5 +-
fs/userfaultfd.c | 722 +++++++++++++++++++++++++++++++++
include/linux/huge_mm.h | 11 +-
include/linux/ksm.h | 4 +-
include/linux/mm.h | 15 +-
include/linux/mm_types.h | 13 +-
include/linux/swap.h | 6 +
include/linux/syscalls.h | 5 +
include/linux/userfaultfd.h | 55 +++
include/linux/wait.h | 5 +-
include/uapi/asm-generic/mman-common.h | 3 +
init/Kconfig | 11 +
kernel/sched/wait.c | 7 +-
kernel/sys_ni.c | 2 +
mm/fremap.c | 506 +++++++++++++++++++++++
mm/gup.c | 182 ++++++++-
mm/huge_memory.c | 208 ++++++++--
mm/ksm.c | 2 +-
mm/madvise.c | 22 +-
mm/memory.c | 14 +
mm/mempolicy.c | 4 +-
mm/mlock.c | 3 +-
mm/mmap.c | 39 +-
mm/mprotect.c | 3 +-
mm/mremap.c | 2 +-
mm/nommu.c | 23 ++
mm/process_vm_access.c | 7 +-
mm/rmap.c | 9 +
mm/swapfile.c | 13 +
mm/util.c | 10 +-
net/ceph/pagevec.c | 9 +-
net/sunrpc/sched.c | 2 +-
virt/kvm/async_pf.c | 4 +-
virt/kvm/kvm_main.c | 4 +-
56 files changed, 2025 insertions(+), 236 deletions(-)
create mode 100644 fs/userfaultfd.c
create mode 100644 include/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: 2014-10-03 17:09:00
Provide a new swapfile method for remap_anon_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(+)
@@ -301,11 +301,9 @@ slow_irqon:start+=nr<<PAGE_SHIFT;pages+=nr;-down_read(&mm->mmap_sem);-ret=get_user_pages(current,mm,start,-(end-start)>>PAGE_SHIFT,-write,0,pages,NULL);-up_read(&mm->mmap_sem);+ret=get_user_pages_unlocked(current,mm,start,+(end-start)>>PAGE_SHIFT,+write,0,pages);/* Have to be a bit careful with return values */if(nr>0){
@@ -215,10 +215,8 @@ int get_user_pages_fast(unsigned long start, int nr_pages, int write,start+=nr<<PAGE_SHIFT;pages+=nr;-down_read(&mm->mmap_sem);-ret=get_user_pages(current,mm,start,-nr_pages-nr,write,0,pages,NULL);-up_read(&mm->mmap_sem);+ret=get_user_pages_unlocked(current,mm,start,+nr_pages-nr,write,0,pages);/* Have to be a bit careful with return values */if(nr>0){
@@ -235,10 +235,8 @@ int get_user_pages_fast(unsigned long start, int nr_pages, int write,/* Try to get the remaining pages with get_user_pages */start+=nr<<PAGE_SHIFT;pages+=nr;-down_read(&mm->mmap_sem);-ret=get_user_pages(current,mm,start,-nr_pages-nr,write,0,pages,NULL);-up_read(&mm->mmap_sem);+ret=get_user_pages_unlocked(current,mm,start,+nr_pages-nr,write,0,pages);/* Have to be a bit careful with return values */if(nr>0)ret=(ret<0)?nr:ret+nr;
@@ -257,10 +257,8 @@ slow_irqon:start+=nr<<PAGE_SHIFT;pages+=nr;-down_read(&mm->mmap_sem);-ret=get_user_pages(current,mm,start,-(end-start)>>PAGE_SHIFT,write,0,pages,NULL);-up_read(&mm->mmap_sem);+ret=get_user_pages_unlocked(current,mm,start,+(end-start)>>PAGE_SHIFT,write,0,pages);/* Have to be a bit careful with return values */if(nr>0){
@@ -219,10 +219,8 @@ slow:start+=nr<<PAGE_SHIFT;pages+=nr;-down_read(&mm->mmap_sem);-ret=get_user_pages(current,mm,start,-(end-start)>>PAGE_SHIFT,write,0,pages,NULL);-up_read(&mm->mmap_sem);+ret=get_user_pages_unlocked(current,mm,start,+(end-start)>>PAGE_SHIFT,write,0,pages);/* Have to be a bit careful with return values */if(nr>0){
@@ -388,10 +388,9 @@ slow_irqon:start+=nr<<PAGE_SHIFT;pages+=nr;-down_read(&mm->mmap_sem);-ret=get_user_pages(current,mm,start,-(end-start)>>PAGE_SHIFT,write,0,pages,NULL);-up_read(&mm->mmap_sem);+ret=get_user_pages_unlocked(current,mm,start,+(end-start)>>PAGE_SHIFT,+write,0,pages);/* Have to be a bit careful with return values */if(nr>0){--
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: 2014-10-03 17:09:26
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(-)
@@ -175,7 +176,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>
From: Andrea Arcangeli <hidden> Date: 2014-10-03 17:09:58
It is generally inefficient to ask the wakeup of userfault ranges
where there's not a single userfault address read through
userfaultfd_read earlier and in turn waiting a wakeup. However it may
come handy to wakeup the same userfault range twice in case of
multiple thread faulting on the same address. But we should still
return an error so if the application thinks this occurrence can never
happen it will know it hit a bug. So just return -ENOENT instead of
blocking.
Signed-off-by: Andrea Arcangeli <redacted>
---
fs/userfaultfd.c | 34 +++++-----------------------------
1 file changed, 5 insertions(+), 29 deletions(-)
@@ -488,34 +486,12 @@ static ssize_t userfaultfd_write(struct file *file, const char __user *buf,if(range[0]>=range[1])return-ERANGE;-spin_lock(&ctx->fd_wqh.lock);-__add_wait_queue(&ctx->fd_wqh,&wait);-for(;;){-set_current_state(TASK_INTERRUPTIBLE);-/* always take the fd_wqh lock before the fault_wqh lock */-if(find_userfault(ctx,NULL,POLLOUT)){-if(!wake_userfault(ctx,range)){-res=sizeof(range);-break;-}-}-if(signal_pending(current)){-res=-ERESTARTSYS;-break;-}-if(file->f_flags&O_NONBLOCK){-res=-EAGAIN;-break;-}-spin_unlock(&ctx->fd_wqh.lock);-schedule();-spin_lock(&ctx->fd_wqh.lock);-}-__remove_wait_queue(&ctx->fd_wqh,&wait);-__set_current_state(TASK_RUNNING);-spin_unlock(&ctx->fd_wqh.lock);+/* always take the fd_wqh lock before the fault_wqh lock */+if(find_userfault(ctx,NULL,POLLOUT))+if(!wake_userfault(ctx,range))+returnsizeof(range);-returnres;+return-ENOENT;}#ifdef CONFIG_PROC_FS--
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: 2014-10-03 17:10:01
Once an userfaultfd is created MADV_USERFAULT regions talks through
the userfaultfd protocol with the thread responsible for doing the
memory externalization of the process.
The protocol starts by userland writing the requested/preferred
USERFAULT_PROTOCOL version into the userfault fd (64bit write), if
kernel knows it, it will ack it by allowing userland to read 64bit
from the userfault fd that will contain the same 64bit
USERFAULT_PROTOCOL version that userland asked. Otherwise userfault
will read __u64 value -1ULL (aka USERFAULTFD_UNKNOWN_PROTOCOL) and it
will have to try again by writing an older protocol version if
suitable for its usage too, and read it back again until it stops
reading -1ULL. After that the userfaultfd protocol starts.
The protocol consists in the userfault fd reads 64bit in size
providing userland the fault addresses. After a userfault address has
been read and the fault is resolved by userland, the application must
write back 128bits in the form of [ start, end ] range (64bit each)
that will tell the kernel such a range has been mapped. Multiple read
userfaults can be resolved in a single range write. poll() can be used
to know when there are new userfaults to read (POLLIN) and when there
are threads waiting a wakeup through a range write (POLLOUT).
Signed-off-by: Andrea Arcangeli <redacted>
---
arch/x86/syscalls/syscall_32.tbl | 1 +
arch/x86/syscalls/syscall_64.tbl | 1 +
fs/Makefile | 1 +
fs/userfaultfd.c | 643 +++++++++++++++++++++++++++++++++++++++
include/linux/syscalls.h | 1 +
include/linux/userfaultfd.h | 42 +++
init/Kconfig | 11 +
kernel/sys_ni.c | 1 +
mm/huge_memory.c | 24 +-
mm/memory.c | 5 +-
10 files changed, 720 insertions(+), 10 deletions(-)
create mode 100644 fs/userfaultfd.c
create mode 100644 include/linux/userfaultfd.h
@@ -328,6 +328,7 @@ 319 common memfd_create sys_memfd_create 320 common kexec_file_load sys_kexec_file_load 321 common remap_anon_pages sys_remap_anon_pages+322 common userfaultfd sys_userfaultfd # # x32-specific system call numbers start at 512 to avoid cache impact
@@ -0,0 +1,643 @@+/*+*fs/userfaultfd.c+*+*Copyright(C)2007DavideLibenzi<davidel@xmailserver.org>+*Copyright(C)2008-2009RedHat,Inc.+*Copyright(C)2014RedHat,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.h>++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 */+unsignedintstate;+/* released */+boolreleased;+};++structuserfaultfd_wait_queue{+unsignedlongaddress;+wait_queue_twq;+boolpending;+structuserfaultfd_ctx*ctx;+};++#define USERFAULTFD_PROTOCOL ((__u64) 0xaa)+#define USERFAULTFD_UNKNOWN_PROTOCOL ((__u64) -1ULL)++enum{+USERFAULTFD_STATE_ASK_PROTOCOL,+USERFAULTFD_STATE_ACK_PROTOCOL,+USERFAULTFD_STATE_ACK_UNKNOWN_PROTOCOL,+USERFAULTFD_STATE_RUNNING,+};++/**+*structmm_slot-userlandfdinformationpermmthatisbeingscanned+*@link:linktothemm_slotshashlist+*@mm:themmthatthisinformationisvalidfor+*@ctx:userfaultfdcontextforthismm+*/+structmm_slot{+structhlist_nodelink;+structmm_struct*mm;+structuserfaultfd_ctxctx;+structrcu_headrcu_head;+};++#define MM_USERLANDFD_HASH_BITS 10+staticDEFINE_HASHTABLE(mm_userlandfd_hash,MM_USERLANDFD_HASH_BITS);++staticDEFINE_MUTEX(mm_userlandfd_mutex);++staticstructmm_slot*get_mm_slot(structmm_struct*mm)+{+structmm_slot*slot;++hash_for_each_possible_rcu(mm_userlandfd_hash,slot,link,+(unsignedlong)mm)+if(slot->mm==mm)+returnslot;++returnNULL;+}++staticvoidinsert_to_mm_userlandfd_hash(structmm_struct*mm,+structmm_slot*mm_slot)+{+mm_slot->mm=mm;+hash_add_rcu(mm_userlandfd_hash,&mm_slot->link,(unsignedlong)mm);+}++staticintuserfaultfd_wake_function(wait_queue_t*wq,unsignedmode,+intwake_flags,void*key)+{+unsignedlong*range=key;+intret;+structuserfaultfd_wait_queue*uwq;++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;+if(range[0]>uwq->address||range[1]<=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.+*/+staticintuserfaultfd_ctx_get(structuserfaultfd_ctx*ctx)+{+/*+*Ifit'salreadyreleaseddon'tgetit.Thiscanrace+*againstuserfaultfd_release,iftheracetriggersit'llbe+*handledsafelybythehandle_userfaultmainloop+*(userfaultfd_releasewilltakethemmap_semforwritingto+*flushoutallin-flightuserfaults).Thischeckisonlyan+*optimization.+*/+if(unlikely(ACCESS_ONCE(ctx->released)))+return0;+returnatomic_inc_not_zero(&ctx->refcount);+}++staticvoiduserfaultfd_free(structuserfaultfd_ctx*ctx)+{+structmm_slot*mm_slot=container_of(ctx,structmm_slot,ctx);++mutex_lock(&mm_userlandfd_mutex);+hash_del_rcu(&mm_slot->link);+mutex_unlock(&mm_userlandfd_mutex);++kfree_rcu(mm_slot,rcu_head);+}++/**+*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))+userfaultfd_free(ctx);+}++/*+*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)+{+structmm_struct*mm=vma->vm_mm;+structmm_slot*slot;+structuserfaultfd_ctx*ctx;+structuserfaultfd_wait_queueuwq;+intret;++BUG_ON(!rwsem_is_locked(&mm->mmap_sem));++rcu_read_lock();+slot=get_mm_slot(mm);+if(!slot){+rcu_read_unlock();+returnVM_FAULT_SIGBUS;+}+ctx=&slot->ctx;+if(!userfaultfd_ctx_get(ctx)){+rcu_read_unlock();+returnVM_FAULT_SIGBUS;+}+rcu_read_unlock();++init_waitqueue_func_entry(&uwq.wq,userfaultfd_wake_function);+uwq.wq.private=current;+uwq.address=address;+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_INTERRUPTIBLE);+if(fatal_signal_pending(current)){+/*+*Ifwehavetofailbecausethetaskis+*killedjustretrythefaulteitherby+*returningtouserlandorthrough+*VM_FAULT_RETRYifwecomefromapagefault+*andafatalsignalispending.+*/+ret=0;+if(flags&FAULT_FLAG_KILLABLE){+/*+*IfFAULT_FLAG_KILLABLEissetwe+*andthere'safatalsignalpending+*canreturnVM_FAULT_RETRY+*regardlessif+*FAULT_FLAG_ALLOW_RETRYissetor+*notaslongaswereleasethe+*mmap_sem.Thepagefaultwill+*returnstrighttouserlandthento+*handlethefatalsignal.+*/+up_read(&mm->mmap_sem);+ret=VM_FAULT_RETRY;+}+break;+}+if(!uwq.pending||ACCESS_ONCE(ctx->released)){+ret=0;+if(flags&FAULT_FLAG_ALLOW_RETRY){+ret=VM_FAULT_RETRY;+if(!(flags&FAULT_FLAG_RETRY_NOWAIT))+up_read(&mm->mmap_sem);+}+break;+}+if(((FAULT_FLAG_ALLOW_RETRY|FAULT_FLAG_RETRY_NOWAIT)&+flags)==+(FAULT_FLAG_ALLOW_RETRY|FAULT_FLAG_RETRY_NOWAIT)){+ret=VM_FAULT_RETRY;+/*+*Themmap_semmustnotbereleasedif+*FAULT_FLAG_RETRY_NOWAITissetdespitewe+*returnVM_FAULT_RETRY(FOLL_NOWAITcase).+*/+break;+}+spin_unlock(&ctx->fault_wqh.lock);+up_read(&mm->mmap_sem);++wake_up_poll(&ctx->fd_wqh,POLLIN);+schedule();++down_read(&mm->mmap_sem);+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+*releasedbyanotherCPU.+*/+userfaultfd_ctx_put(ctx);++returnret;+}++staticintuserfaultfd_release(structinode*inode,structfile*file)+{+structuserfaultfd_ctx*ctx=file->private_data;+structmm_slot*mm_slot=container_of(ctx,structmm_slot,ctx);+__u64range[2]={0ULL,-1ULL};++ACCESS_ONCE(ctx->released)=true;++/*+*FlushpagefaultsoutofallCPUstoavoidraceconditions+*againstctx->released.Allpagefaultsmustberetried+*withoutreturningVM_FAULT_SIGBUSiftheget_mm_slotand+*userfaultfd_ctx_getbothsucceedsbutctx->releasedisset.+*/+down_write(&mm_slot->mm->mmap_sem);+up_write(&mm_slot->mm->mmap_sem);++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;+}++staticinlineunsignedlongfind_userfault(structuserfaultfd_ctx*ctx,+structuserfaultfd_wait_queue**uwq,+unsignedintevents_filter)+{+wait_queue_t*wq;+structuserfaultfd_wait_queue*_uwq;+unsignedintevents=0;++BUG_ON(!events_filter);++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){+if(!(events&POLLIN)&&(events_filter&POLLIN)){+events|=POLLIN;+if(uwq)+*uwq=_uwq;+}+}elseif(events_filter&POLLOUT)+events|=POLLOUT;+if(events==events_filter)+break;+}+spin_unlock(&ctx->fault_wqh.lock);++returnevents;+}++staticunsignedintuserfaultfd_poll(structfile*file,poll_table*wait)+{+structuserfaultfd_ctx*ctx=file->private_data;++poll_wait(file,&ctx->fd_wqh,wait);++switch(ctx->state){+caseUSERFAULTFD_STATE_ASK_PROTOCOL:+returnPOLLOUT;+caseUSERFAULTFD_STATE_ACK_PROTOCOL:+returnPOLLIN;+caseUSERFAULTFD_STATE_ACK_UNKNOWN_PROTOCOL:+returnPOLLIN;+caseUSERFAULTFD_STATE_RUNNING:+returnfind_userfault(ctx,NULL,POLLIN|POLLOUT);+default:+BUG();+}+}++staticssize_tuserfaultfd_ctx_read(structuserfaultfd_ctx*ctx,intno_wait,+__u64*addr)+{+ssize_tret;+DECLARE_WAITQUEUE(wait,current);+structuserfaultfd_wait_queue*uwq=NULL;++if(ctx->state==USERFAULTFD_STATE_ASK_PROTOCOL){+return-EINVAL;+}elseif(ctx->state==USERFAULTFD_STATE_ACK_PROTOCOL){+*addr=USERFAULTFD_PROTOCOL;+ctx->state=USERFAULTFD_STATE_RUNNING;+return0;+}elseif(ctx->state==USERFAULTFD_STATE_ACK_UNKNOWN_PROTOCOL){+*addr=USERFAULTFD_UNKNOWN_PROTOCOL;+ctx->state=USERFAULTFD_STATE_ASK_PROTOCOL;+return0;+}+BUG_ON(ctx->state!=USERFAULTFD_STATE_RUNNING);++spin_lock(&ctx->fd_wqh.lock);+__add_wait_queue(&ctx->fd_wqh,&wait);+for(;;){+set_current_state(TASK_INTERRUPTIBLE);+/* always take the fd_wqh lock before the fault_wqh lock */+if(find_userfault(ctx,&uwq,POLLIN)){+uwq->pending=false;+*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);+if(ret==0){+if(waitqueue_active(&ctx->fd_wqh))+wake_up_locked_poll(&ctx->fd_wqh,POLLOUT);+}+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_tret;+/* careful to always initialize addr if ret == 0 */+__u64uninitialized_var(addr);++if(count<sizeof(addr))+return-EINVAL;+ret=userfaultfd_ctx_read(ctx,file->f_flags&O_NONBLOCK,&addr);+if(ret<0)+returnret;++returnput_user(addr,(__u64__user*)buf)?-EFAULT:sizeof(addr);+}++staticintwake_userfault(structuserfaultfd_ctx*ctx,__u64*range)+{+wait_queue_t*wq;+structuserfaultfd_wait_queue*uwq;+intret=-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>=range[0]&&+uwq->address<range[1]){+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;+}++staticssize_tuserfaultfd_write(structfile*file,constchar__user*buf,+size_tcount,loff_t*ppos)+{+structuserfaultfd_ctx*ctx=file->private_data;+ssize_tres;+__u64range[2];+DECLARE_WAITQUEUE(wait,current);++if(ctx->state==USERFAULTFD_STATE_ASK_PROTOCOL){+__u64protocol;+if(count<sizeof(__u64))+return-EINVAL;+if(copy_from_user(&protocol,buf,sizeof(protocol)))+return-EFAULT;+if(protocol!=USERFAULTFD_PROTOCOL){+/* we'll offer the supported protocol in the ack */+printk_once(KERN_INFO+"userfaultfd protocol not available\n");+ctx->state=USERFAULTFD_STATE_ACK_UNKNOWN_PROTOCOL;+}else+ctx->state=USERFAULTFD_STATE_ACK_PROTOCOL;+returnsizeof(protocol);+}elseif(ctx->state==USERFAULTFD_STATE_ACK_PROTOCOL)+return-EINVAL;++BUG_ON(ctx->state!=USERFAULTFD_STATE_RUNNING);++if(count<sizeof(range))+return-EINVAL;+if(copy_from_user(&range,buf,sizeof(range)))+return-EFAULT;+if(range[0]>=range[1])+return-ERANGE;++spin_lock(&ctx->fd_wqh.lock);+__add_wait_queue(&ctx->fd_wqh,&wait);+for(;;){+set_current_state(TASK_INTERRUPTIBLE);+/* always take the fd_wqh lock before the fault_wqh lock */+if(find_userfault(ctx,NULL,POLLOUT)){+if(!wake_userfault(ctx,range)){+res=sizeof(range);+break;+}+}+if(signal_pending(current)){+res=-ERESTARTSYS;+break;+}+if(file->f_flags&O_NONBLOCK){+res=-EAGAIN;+break;+}+spin_unlock(&ctx->fd_wqh.lock);+schedule();+spin_lock(&ctx->fd_wqh.lock);+}+__remove_wait_queue(&ctx->fd_wqh,&wait);+__set_current_state(TASK_RUNNING);+spin_unlock(&ctx->fd_wqh.lock);++returnres;+}++#ifdef CONFIG_PROC_FS+staticintuserfaultfd_show_fdinfo(structseq_file*m,structfile*f)+{+structuserfaultfd_ctx*ctx=f->private_data;+intret;+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+*/+ret=seq_printf(m,"pending:\t%lu\ntotal:\t%lu\nprotocols:\t%Lx\n",+pending,total,USERFAULTFD_PROTOCOL);++returnret;+}+#endif++staticconststructfile_operationsuserfaultfd_fops={+#ifdef CONFIG_PROC_FS+.show_fdinfo=userfaultfd_show_fdinfo,+#endif+.release=userfaultfd_release,+.poll=userfaultfd_poll,+.read=userfaultfd_read,+.write=userfaultfd_write,+.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;+structmm_slot*mm_slot;++/* 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;++mm_slot=kmalloc(sizeof(*mm_slot),GFP_KERNEL);+file=ERR_PTR(-ENOMEM);+if(!mm_slot)+gotoout;++mutex_lock(&mm_userlandfd_mutex);+file=ERR_PTR(-EBUSY);+if(get_mm_slot(current->mm))+gotoout_free_unlock;++atomic_set(&mm_slot->ctx.refcount,1);+init_waitqueue_head(&mm_slot->ctx.fault_wqh);+init_waitqueue_head(&mm_slot->ctx.fd_wqh);+mm_slot->ctx.flags=flags;+mm_slot->ctx.state=USERFAULTFD_STATE_ASK_PROTOCOL;+mm_slot->ctx.released=false;++file=anon_inode_getfile("[userfaultfd]",&userfaultfd_fops,+&mm_slot->ctx,+O_RDWR|(flags&UFFD_SHARED_FCNTL_FLAGS));+if(IS_ERR(file))+out_free_unlock:+kfree(mm_slot);+else+insert_to_mm_userlandfd_hash(current->mm,+mm_slot);+mutex_unlock(&mm_userlandfd_mutex);+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, 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>
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>
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: 2014-10-03 17:11:27
This teaches gup_fast and __gup_fast to re-enable irqs and
cond_resched() if possible every BATCH_PAGES.
This must be implemented by other archs as well and it's a requirement
before converting more get_user_pages() to get_user_pages_fast() as an
optimization (instead of using get_user_pages_unlocked which would be
slower).
Signed-off-by: Andrea Arcangeli <redacted>
---
arch/x86/mm/gup.c | 234 ++++++++++++++++++++++++++++++++++--------------------
1 file changed, 149 insertions(+), 85 deletions(-)
@@ -250,6 +256,40 @@ static int gup_pud_range(pgd_t pgd, unsigned long addr, unsigned long end,return1;}+staticinlineint__get_user_pages_fast_batch(unsignedlongstart,+unsignedlongend,+intwrite,structpage**pages)+{+structmm_struct*mm=current->mm;+unsignedlongnext;+unsignedlongflags;+pgd_t*pgdp;+intnr=0;++/*+*Thisdoesn'tpreventpagetableteardown,butdoesprevent+*thepagetablesandpagesfrombeingfreedonx86.+*+*Solongasweatomicallyloadpagetablepointersversusteardown+*(whichwedoonx86,withtheabovePAEexception),wecanfollowthe+*addressdowntothethepageandtakearefonit.+*/+local_irq_save(flags);+pgdp=pgd_offset(mm,start);+do{+pgd_tpgd=*pgdp;++next=pgd_addr_end(start,end);+if(pgd_none(pgd))+break;+if(!gup_pud_range(pgd,start,next,write,pages,&nr))+break;+}while(pgdp++,start=next,start!=end);+local_irq_restore(flags);++returnnr;+}+/**Likeget_user_pages_fast()exceptitsIRQ-safeinthatitwon'tfall*backtotheregularGUP.
@@ -257,31 +297,55 @@ static int gup_pud_range(pgd_t pgd, unsigned long addr, unsigned long end,int__get_user_pages_fast(unsignedlongstart,intnr_pages,intwrite,structpage**pages){-structmm_struct*mm=current->mm;-unsignedlongaddr,len,end;-unsignedlongnext;-unsignedlongflags;-pgd_t*pgdp;-intnr=0;+unsignedlonglen,end,batch_pages;+intnr,ret;start&=PAGE_MASK;-addr=start;len=(unsignedlong)nr_pages<<PAGE_SHIFT;end=start+len;+/*+*get_user_pages()handlesnr_pages==0gracefully,but+*gup_faststartswalkingthefirstpagetableinado{}+*while()fashionsoit'snotrobusttohandlenr_pages==+*0.There'snopointinbeingpermissiveaboutend<start+*either.Sothischeckverifiesbothnr_pagesbeingnon+*zero,andthat"end"didn'toverflow.+*/+VM_BUG_ON(end<=start);if(unlikely(!access_ok(write?VERIFY_WRITE:VERIFY_READ,(void__user*)start,len)))return0;-/*-*XXX:batch/limit'nr',toavoidlargeirqofflatency-*needssomeinstrumentingtodeterminethecommonsizesusedby-*importantworkloads(eg.DB2),andwhetherlimitingthebatchsize-*willdecreaseperformance.-*-*Itseemslikewe'reintheclearforthemoment.Direct-IOis-*themainguythatbatchesuplotsofget_user_pages,andeven-*theyarelimitedto64-at-a-timewhichisnotsomany.-*/+ret=0;+for(;;){+batch_pages=nr_pages;+if(batch_pages>BATCH_PAGES&&!irqs_disabled())+batch_pages=BATCH_PAGES;+len=(unsignedlong)batch_pages<<PAGE_SHIFT;+end=start+len;+nr=__get_user_pages_fast_batch(start,end,write,pages);+VM_BUG_ON(nr>batch_pages);+nr_pages-=nr;+ret+=nr;+if(!nr_pages||nr!=batch_pages)+break;+start+=len;+pages+=batch_pages;+}++returnret;+}++staticinlineintget_user_pages_fast_batch(unsignedlongstart,+unsignedlongend,+intwrite,structpage**pages)+{+structmm_struct*mm=current->mm;+unsignedlongnext;+pgd_t*pgdp;+intnr=0;+unsignedlongorig_start=start;+/**Thisdoesn'tpreventpagetableteardown,butdoesprevent*thepagetablesandpagesfrombeingfreedonx86.
@@ -290,18 +354,24 @@ int __get_user_pages_fast(unsigned long start, int nr_pages, int write,*(whichwedoonx86,withtheabovePAEexception),wecanfollowthe*addressdowntothethepageandtakearefonit.*/-local_irq_save(flags);-pgdp=pgd_offset(mm,addr);+local_irq_disable();+pgdp=pgd_offset(mm,start);do{pgd_tpgd=*pgdp;-next=pgd_addr_end(addr,end);-if(pgd_none(pgd))+next=pgd_addr_end(start,end);+if(pgd_none(pgd)){+VM_BUG_ON(nr>=(end-orig_start)>>PAGE_SHIFT);break;-if(!gup_pud_range(pgd,addr,next,write,pages,&nr))+}+if(!gup_pud_range(pgd,start,next,write,pages,&nr)){+VM_BUG_ON(nr>=(end-orig_start)>>PAGE_SHIFT);break;-}while(pgdp++,addr=next,addr!=end);-local_irq_restore(flags);+}+}while(pgdp++,start=next,start!=end);+local_irq_enable();++cond_resched();returnnr;}
@@ -326,80 +396,74 @@ int get_user_pages_fast(unsigned long start, int nr_pages, int write,structpage**pages){structmm_struct*mm=current->mm;-unsignedlongaddr,len,end;-unsignedlongnext;-pgd_t*pgdp;-intnr=0;+unsignedlonglen,end,batch_pages;+intnr,ret;+unsignedlongorig_start;start&=PAGE_MASK;-addr=start;+orig_start=start;len=(unsignedlong)nr_pages<<PAGE_SHIFT;end=start+len;-if(end<start)-gotoslow_irqon;+/*+*get_user_pages()handlesnr_pages==0gracefully,but+*gup_faststartswalkingthefirstpagetableinado{}+*while()fashionsoit'snotrobusttohandlenr_pages==+*0.There'snopointinbeingpermissiveaboutend<start+*either.Sothischeckverifiesbothnr_pagesbeingnon+*zero,andthat"end"didn'toverflow.+*/+VM_BUG_ON(end<=start);+nr=ret=0;#ifdef CONFIG_X86_64if(end>>__VIRTUAL_MASK_SHIFT)gotoslow_irqon;#endif+for(;;){+batch_pages=min(nr_pages,BATCH_PAGES);+len=(unsignedlong)batch_pages<<PAGE_SHIFT;+end=start+len;+nr=get_user_pages_fast_batch(start,end,write,pages);+VM_BUG_ON(nr>batch_pages);+nr_pages-=nr;+ret+=nr;+if(!nr_pages)+break;+if(nr<batch_pages)+gotoslow_irqon;+start+=len;+pages+=batch_pages;+}-/*-*XXX:batch/limit'nr',toavoidlargeirqofflatency-*needssomeinstrumentingtodeterminethecommonsizesusedby-*importantworkloads(eg.DB2),andwhetherlimitingthebatchsize-*willdecreaseperformance.-*-*Itseemslikewe'reintheclearforthemoment.Direct-IOis-*themainguythatbatchesuplotsofget_user_pages,andeven-*theyarelimitedto64-at-a-timewhichisnotsomany.-*/-/*-*Thisdoesn'tpreventpagetableteardown,butdoesprevent-*thepagetablesandpagesfrombeingfreedonx86.-*-*Solongasweatomicallyloadpagetablepointersversusteardown-*(whichwedoonx86,withtheabovePAEexception),wecanfollowthe-*addressdowntothethepageandtakearefonit.-*/-local_irq_disable();-pgdp=pgd_offset(mm,addr);-do{-pgd_tpgd=*pgdp;--next=pgd_addr_end(addr,end);-if(pgd_none(pgd))-gotoslow;-if(!gup_pud_range(pgd,addr,next,write,pages,&nr))-gotoslow;-}while(pgdp++,addr=next,addr!=end);-local_irq_enable();--VM_BUG_ON(nr!=(end-start)>>PAGE_SHIFT);-returnnr;--{-intret;+VM_BUG_ON(ret!=(end-orig_start)>>PAGE_SHIFT);+returnret;-slow:-local_irq_enable();slow_irqon:-/* Try to get the remaining pages with get_user_pages */-start+=nr<<PAGE_SHIFT;-pages+=nr;--ret=get_user_pages_unlocked(current,mm,start,-(end-start)>>PAGE_SHIFT,-write,0,pages);--/* Have to be a bit careful with return values */-if(nr>0){-if(ret<0)-ret=nr;-else-ret+=nr;-}+/* Try to get the remaining pages with get_user_pages */+start+=nr<<PAGE_SHIFT;+pages+=nr;-returnret;+/*+*"nr"wastheget_user_pages_fast_batchlastretval,"ret"+*wasthesumofallget_user_pages_fast_batchretvals,now+*"nr"becomesthesumofallget_user_pages_fast_batch+*retvalsand"ret"willbecometheget_user_pages_unlocked+*retval.+*/+nr=ret;++ret=get_user_pages_unlocked(current,mm,start,+(end-start)>>PAGE_SHIFT,+write,0,pages);++/* Have to be a bit careful with return values */+if(nr>0){+if(ret<0)+ret=nr;+else+ret+=nr;}++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>
From: Andrea Arcangeli <hidden> Date: 2014-10-03 17:11:32
From: Andres Lagar-Cavilla <redacted>
When KVM handles a tdp fault it uses FOLL_NOWAIT. If the guest memory
has been swapped out or is behind a filemap, this will trigger async
readahead and return immediately. The rationale is that KVM will kick
back the guest with an "async page fault" and allow for some other
guest process to take over.
If async PFs are enabled the fault is retried asap from an async
workqueue. If not, it's retried immediately in the same code path. In
either case the retry will not relinquish the mmap semaphore and will
block on the IO. This is a bad thing, as other mmap semaphore users
now stall as a function of swap or filemap latency.
This patch ensures both the regular and async PF path re-enter the
fault allowing for the mmap semaphore to be relinquished in the case
of IO wait.
Reviewed-by: Radim Krčmář <redacted>
Signed-off-by: Andres Lagar-Cavilla <redacted>
Signed-off-by: Andrea Arcangeli <redacted>
---
virt/kvm/async_pf.c | 4 +---
virt/kvm/kvm_main.c | 4 ++--
2 files changed, 3 insertions(+), 5 deletions(-)
@@ -1170,8 +1170,8 @@ static int hva_to_pfn_slow(unsigned long addr, bool *async, bool write_fault,addr,write_fault,page);up_read(¤t->mm->mmap_sem);}else-npages=get_user_pages_fast(addr,1,write_fault,-page);+npages=get_user_pages_unlocked(current,current->mm,addr,1,+write_fault,0,page);if(npages!=1)returnnpages;--
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: 2014-10-03 17:22:19
Those two helpers are needed by remap_anon_pages.
Signed-off-by: Andrea Arcangeli <redacted>
---
include/linux/mm.h | 4 ++++
mm/fremap.c | 29 +++++++++++++++++++++++++++++
2 files changed, 33 insertions(+)
@@ -281,3 +281,32 @@ out_freed:returnerr;}++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);+}++voiddouble_pt_unlock(spinlock_t*ptl1,+spinlock_t*ptl2)+__releases(ptl1)+__releases(ptl2)+{+spin_unlock(ptl1);+if(ptl1!=ptl2)+spin_unlock(ptl2);+}--
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>
@@ -121,7 +121,7 @@ extern void split_huge_page_pmd_mm(struct mm_struct *mm, unsigned long address,#error "hugepages can't be allocated by the buddy allocator"#endifexterninthugepage_madvise(structvm_area_struct*vma,-unsignedlong*vm_flags,intadvice);+vm_flags_t*vm_flags,intadvice);externvoid__vma_adjust_trans_huge(structvm_area_struct*vma,unsignedlongstart,unsignedlongend,
@@ -239,7 +239,7 @@ static unsigned long move_vma(struct vm_area_struct *vma,{structmm_struct*mm=vma->vm_mm;structvm_area_struct*new_vma;-unsignedlongvm_flags=vma->vm_flags;+vm_flags_tvm_flags=vma->vm_flags;unsignedlongnew_pgoff;unsignedlongmoved_len;unsignedlongexcess=0;--
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: 2014-10-03 18:01:35
remap_anon_pages (unlike remap_file_pages) tries to be non intrusive
in the rmap code.
As far as the rmap code is concerned, rmap_anon_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_anon_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_anon_pages will wait them to
complete before proceeding).
remap_anon_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_anon_pages holds the mmap_sem for reading.
Overall this looks a fairly small change to the rmap code, notably
less intrusive than the nonlinear vmas created by remap_file_pages.
There's one constraint enforced to allow this simplification: the
source pages passed to remap_anon_pages must be mapped only in one
vma, but this is not a limitation when used to handle userland page
faults with MADV_USERFAULT. The source addresses passed to
remap_anon_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_anon_pages runs.
Signed-off-by: Andrea Arcangeli <redacted>
---
mm/huge_memory.c | 24 ++++++++++++++++++++----
mm/rmap.c | 9 +++++++++
2 files changed, 29 insertions(+), 4 deletions(-)
@@ -488,6 +489,14 @@ struct anon_vma *page_lock_anon_vma_read(struct page *page)rcu_read_unlock();anon_vma_lock_read(anon_vma);+/* check if remap_anon_pages changed the anon_vma */+if(unlikely((unsignedlong)ACCESS_ONCE(page->mapping)!=anon_mapping)){+anon_vma_unlock_read(anon_vma);+put_anon_vma(anon_vma);+anon_vma=NULL;+gotorepeat;+}+if(atomic_dec_and_test(&anon_vma->refcount)){/**Oops,weheldthelastrefcount,releasethelock--
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>
@@ -124,10 +124,8 @@ int ivtv_udma_setup(struct ivtv *itv, unsigned long ivtv_dest_addr,}/* Get user pages for DMA Xfer */-down_read(¤t->mm->mmap_sem);-err=get_user_pages(current,current->mm,-user_dma.uaddr,user_dma.page_count,0,1,dma->map,NULL);-up_read(¤t->mm->mmap_sem);+err=get_user_pages_unlocked(current,current->mm,+user_dma.uaddr,user_dma.page_count,0,1,dma->map);if(user_dma.page_count!=err){IVTV_DEBUG_WARN("failed to map user pages, returned %d instead of %d\n",
@@ -4536,18 +4536,12 @@ static int sgl_map_user_pages(struct st_buffer *STbp,return-ENOMEM;/* Try to fault in all of the necessary pages */-down_read(¤t->mm->mmap_sem);/* rw==READ means read from drive, write into memory area */-res=get_user_pages(-current,-current->mm,+res=get_user_pages_fast(uaddr,nr_pages,rw==READ,-0,/* don't force */-pages,-NULL);-up_read(¤t->mm->mmap_sem);+pages);/* Errors and no page mapped should return here */if(res<nr_pages)
@@ -99,11 +99,8 @@ static int process_vm_rw_single_vec(unsigned long addr,size_tbytes;/* Get the pages we're interested in */-down_read(&mm->mmap_sem);-pages=get_user_pages(task,mm,pa,pages,-vm_write,0,process_pages,NULL);-up_read(&mm->mmap_sem);-+pages=get_user_pages_unlocked(task,mm,pa,pages,+vm_write,0,process_pages);if(pages<=0)return-EFAULT;
@@ -247,14 +247,8 @@ int __weak get_user_pages_fast(unsigned long start,intnr_pages,intwrite,structpage**pages){structmm_struct*mm=current->mm;-intret;--down_read(&mm->mmap_sem);-ret=get_user_pages(current,mm,start,nr_pages,-write,0,pages,NULL);-up_read(&mm->mmap_sem);--returnret;+returnget_user_pages_unlocked(current,mm,start,nr_pages,+write,0,pages);}EXPORT_SYMBOL_GPL(get_user_pages_fast);
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: 2014-10-03 18:02:51
We can leverage the VM_FAULT_RETRY functionality in the page fault
paths better by using either get_user_pages_locked or
get_user_pages_unlocked.
The former allow conversion of get_user_pages invocations that will
have to pass a "&locked" parameter to know if the mmap_sem was dropped
during the call. Example from:
down_read(&mm->mmap_sem);
do_something()
get_user_pages(tsk, mm, ..., pages, NULL);
up_read(&mm->mmap_sem);
to:
int locked = 1;
down_read(&mm->mmap_sem);
do_something()
get_user_pages_locked(tsk, mm, ..., pages, &locked);
if (locked)
up_read(&mm->mmap_sem);
The latter is suitable only as a drop in replacement of the form:
down_read(&mm->mmap_sem);
get_user_pages(tsk, mm, ..., pages, NULL);
up_read(&mm->mmap_sem);
into:
get_user_pages_unlocked(tsk, mm, ..., pages);
Where tsk, mm, the intermediate "..." paramters and "pages" can be any
value as before. Just the last parameter of get_user_pages (vmas) must
be NULL for get_user_pages_locked|unlocked to be usable (the latter
original form wouldn't have been safe anyway if vmas wasn't null, for
the former we just make it explicit by dropping the parameter).
If vmas is not NULL these two methods cannot be used.
This patch then applies the new forms in various places, in some case
also replacing it with get_user_pages_fast whenever tsk and mm are
current and current->mm. get_user_pages_unlocked varies from
get_user_pages_fast only if mm is not current->mm (like when
get_user_pages works on some other process mm). Whenever tsk and mm
matches current and current->mm get_user_pages_fast must always be
used to increase performance and get the page lockless (only with irq
disabled).
Signed-off-by: Andrea Arcangeli <redacted>
Reviewed-by: Andres Lagar-Cavilla <redacted>
Reviewed-by: Peter Feiner <redacted>
---
include/linux/mm.h | 7 +++
mm/gup.c | 178 +++++++++++++++++++++++++++++++++++++++++++++++++----
mm/nommu.c | 23 +++++++
3 files changed, 197 insertions(+), 11 deletions(-)
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: 2014-10-03 18:03:33
This new syscall will move anon pages across vmas, atomically and
without touching the vmas.
It only works on non shared anonymous pages because those can be
relocated without generating non linear anon_vmas in the rmap code.
It is the ideal mechanism to handle userspace page faults. Normally
the destination vma will have VM_USERFAULT set with
madvise(MADV_USERFAULT) while the source vma will normally have
VM_DONTCOPY set with madvise(MADV_DONTFORK).
MADV_DONTFORK set in the source vma avoids remap_anon_pages to fail if
the process forks during the userland page fault.
The thread triggering the sigbus signal handler by touching an
unmapped hole in the MADV_USERFAULT region, should take care to
receive the data belonging in the faulting virtual address in the
source vma. The data can come from the network, storage or any other
I/O device. After the data has been safely received in the private
area in the source vma, it will call remap_anon_pages to map the page
in the faulting address in the destination vma atomically. And finally
it will return from the signal handler.
It is an alternative to mremap.
It only works if the vma protection bits are identical from the source
and destination vma.
It can remap non shared anonymous pages within the same vma too.
If the source virtual memory range has any unmapped holes, or if the
destination virtual memory range is not a whole unmapped hole,
remap_anon_pages will fail respectively with -ENOENT or -EEXIST. This
provides a very strict behavior to avoid any chance of memory
corruption going unnoticed if there are userland race conditions. Only
one thread should resolve the userland page fault at any given time
for any given faulting address. This means that if two threads try to
both call remap_anon_pages on the same destination address at the same
time, the second thread will get an explicit error from this syscall.
The syscall retval will return "len" is succesful. The syscall however
can be interrupted by fatal signals or errors. If interrupted it will
return the number of bytes successfully remapped before the
interruption if any, or the negative error if none. It will never
return zero. Either it will return an error or an amount of bytes
successfully moved. If the retval reports a "short" remap, the
remap_anon_pages syscall should be repeated by userland with
src+retval, dst+reval, len-retval if it wants to know about the error
that interrupted it.
The RAP_ALLOW_SRC_HOLES flag can be specified to prevent -ENOENT
errors to materialize if there are holes in the source virtual range
that is being remapped. The holes will be accounted as successfully
remapped in the retval of the syscall. This is mostly useful to remap
hugepage naturally aligned virtual regions without knowing if there
are transparent hugepage in the regions or not, but preventing the
risk of having to split the hugepmd during the remap.
The main difference with mremap is that if used to fill holes in
unmapped anonymous memory vmas (if used in combination with
MADV_USERFAULT) remap_anon_pages won't create lots of unmergeable
vmas. mremap instead would create lots of vmas (because of non linear
vma->vm_pgoff) leading to -ENOMEM failures (the number of vmas is
limited).
MADV_USERFAULT and remap_anon_pages() can be tested with a program
like below:
===
#define _GNU_SOURCE
#include <sys/mman.h>
#include <pthread.h>
#include <strings.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <signal.h>
#include <sys/syscall.h>
#include <sys/types.h>
#define USE_USERFAULT
#define THP
#define MADV_USERFAULT 18
#define SIZE (1024*1024*1024)
#define SYS_remap_anon_pages 321
static volatile unsigned char *c, *tmp;
void userfault_sighandler(int signum, siginfo_t *info, void *ctx)
{
unsigned char *addr = info->si_addr;
int len = 4096;
int ret;
addr = (unsigned char *) ((unsigned long) addr & ~((getpagesize())-1));
#ifdef THP
addr = (unsigned char *) ((unsigned long) addr & ~((2*1024*1024)-1));
len = 2*1024*1024;
#endif
if (addr >= c && addr < c + SIZE) {
unsigned long offset = addr - c;
ret = syscall(SYS_remap_anon_pages, c+offset, tmp+offset, len, 0);
if (ret != len)
perror("sigbus remap_anon_pages"), exit(1);
//printf("sigbus offset %lu\n", offset);
return;
}
printf("sigbus error addr %p c %p tmp %p\n", addr, c, tmp), exit(1);
}
int main()
{
struct sigaction sa;
int ret;
unsigned long i;
#ifndef THP
/*
* Fails with THP due lack of alignment because of memset
* pre-filling the destination
*/
c = mmap(0, SIZE, PROT_READ|PROT_WRITE,
MAP_ANONYMOUS|MAP_PRIVATE, -1, 0);
if (c == MAP_FAILED)
perror("mmap"), exit(1);
tmp = mmap(0, SIZE, PROT_READ|PROT_WRITE,
MAP_ANONYMOUS|MAP_PRIVATE, -1, 0);
if (tmp == MAP_FAILED)
perror("mmap"), exit(1);
#else
ret = posix_memalign((void **)&c, 2*1024*1024, SIZE);
if (ret)
perror("posix_memalign"), exit(1);
ret = posix_memalign((void **)&tmp, 2*1024*1024, SIZE);
if (ret)
perror("posix_memalign"), exit(1);
#endif
/*
* MADV_USERFAULT must run before memset, to avoid THP 2m
* faults to map memory into "tmp", if "tmp" isn't allocated
* with hugepage alignment.
*/
if (madvise((void *)c, SIZE, MADV_USERFAULT))
perror("madvise"), exit(1);
memset((void *)tmp, 0xaa, SIZE);
sa.sa_sigaction = userfault_sighandler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_SIGINFO;
sigaction(SIGBUS, &sa, NULL);
#ifndef USE_USERFAULT
ret = syscall(SYS_remap_anon_pages, c, tmp, SIZE, 0);
if (ret != SIZE)
perror("remap_anon_pages"), exit(1);
#endif
for (i = 0; i < SIZE; i += 4096) {
if ((i/4096) % 2) {
/* exercise read and write MADV_USERFAULT */
c[i+1] = 0xbb;
}
if (c[i] != 0xaa)
printf("error %x offset %lu\n", c[i], i), exit(1);
}
printf("remap_anon_pages functions correctly\n");
return 0;
}
===
Signed-off-by: Andrea Arcangeli <redacted>
---
arch/x86/syscalls/syscall_32.tbl | 1 +
arch/x86/syscalls/syscall_64.tbl | 1 +
include/linux/huge_mm.h | 7 +
include/linux/syscalls.h | 4 +
kernel/sys_ni.c | 1 +
mm/fremap.c | 477 +++++++++++++++++++++++++++++++++++++++
mm/huge_memory.c | 110 +++++++++
7 files changed, 601 insertions(+)
@@ -327,6 +327,7 @@ 318 common getrandom sys_getrandom 319 common memfd_create sys_memfd_create 320 common kexec_file_load sys_kexec_file_load+321 common remap_anon_pages sys_remap_anon_pages # # x32-specific system call numbers start at 512 to avoid cache impact
@@ -451,6 +451,10 @@ asmlinkage long sys_mremap(unsigned long addr,asmlinkagelongsys_remap_file_pages(unsignedlongstart,unsignedlongsize,unsignedlongprot,unsignedlongpgoff,unsignedlongflags);+asmlinkagelongsys_remap_anon_pages(unsignedlongdst_start,+unsignedlongsrc_start,+unsignedlonglen,+unsignedlongflags);asmlinkagelongsys_msync(unsignedlongstart,size_tlen,intflags);asmlinkagelongsys_fadvise64(intfd,loff_toffset,size_tlen,intadvice);asmlinkagelongsys_fadvise64_64(intfd,loff_toffset,loff_tlen,intadvice);
@@ -310,3 +310,480 @@ void double_pt_unlock(spinlock_t *ptl1,if(ptl1!=ptl2)spin_unlock(ptl2);}++#define RAP_ALLOW_SRC_HOLES (1UL<<0)++/*+*Themmap_semforreadingisheldbythecaller.Justmovethepage+*fromsrc_pmdtodst_pmdifpossible,andreturntrueifsucceeded+*inmovingthepage.+*/+staticintremap_anon_pages_pte(structmm_struct*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,+unsignedlongflags)+{+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(!(flags&RAP_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(mm,dst_addr,dst_pte,orig_dst_pte);++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(mm,src_addr);+}else{+if(pte_file(orig_src_pte))+return-EFAULT;++entry=pte_to_swp_entry(orig_src_pte);+if(non_swap_entry(entry)){+if(is_migration_entry(entry)){+migration_entry_wait(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(mm,src_addr,src_pte))!=+pte_val(orig_src_pte))+BUG();+set_pte_at(mm,dst_addr,dst_pte,orig_src_pte);++double_pt_unlock(dst_ptl,src_ptl);+}++return0;+}++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;+}++/**+*sys_remap_anon_pages-remaparbitraryanonymouspagesofanexistingvma+*@dst_start:startofthedestinationvirtualmemoryrange+*@src_start:startofthesourcevirtualmemoryrange+*@len:lengthofthevirtualmemoryrange+*+*sys_remap_anon_pagesremapsarbitraryanonymouspagesatomicallyin+*zerocopy.Itonlyworksonnonsharedanonymouspagesbecause+*thosecanberelocatedwithoutgeneratingnonlinearanon_vmasin+*thermapcode.+*+*Itistheidealmechanismtohandleuserspacepagefaults.Normally+*thedestinationvmawillhaveVM_USERFAULTsetwith+*madvise(MADV_USERFAULT)whilethesourcevmawillhaveVM_DONTCOPY+*setwithmadvise(MADV_DONTFORK).+*+*Thethreadreceivingthepageduringtheuserlandpagefault+*(MADV_USERFAULT)willreceivethefaultingpageinthesourcevma+*throughthenetwork,storageoranyotherI/Odevice(MADV_DONTFORK+*inthesourcevmaavoidsremap_anon_pagestofailwith-EBUSYif+*theprocessforksbeforeremap_anon_pagesiscalled),thenitwill+*callremap_anon_pagestomapthepageinthefaultingaddressin+*thedestinationvma.+*+*Thissyscallworkspurelyviapagetables,soit'sthemost+*efficientwaytomovephysicalnonsharedanonymouspagesacross+*differentvirtualaddresses.Unlikemremap()/mmap()/munmap()it+*doesnotcreateanynewvmas.Themappinginthedestination+*addressisatomic.+*+*Itonlyworksifthevmaprotectionbitsareidenticalfromthe+*sourceanddestinationvma.+*+*Itcanremapnonsharedanonymouspageswithinthesamevmatoo.+*+*Ifthesourcevirtualmemoryrangehasanyunmappedholes,orif+*thedestinationvirtualmemoryrangeisnotawholeunmappedhole,+*remap_anon_pageswillfailrespectivelywith-ENOENTor+*-EEXIST.Thisprovidesaverystrictbehaviortoavoidanychance+*ofmemorycorruptiongoingunnoticedifthereareuserlandrace+*conditions.Onlyonethreadshouldresolvetheuserlandpagefault+*atanygiventimeforanygivenfaultingaddress.Thismeansthat+*iftwothreadstrytobothcallremap_anon_pagesonthesame+*destinationaddressatthesametime,thesecondthreadwillgetan+*expliciterrorfromthissyscall.+*+*Thesyscallretvalwillreturn"len"issuccesful.Thesyscall+*howevercanbeinterruptedbyfatalsignalsorerrors.If+*interrupteditwillreturnthenumberofbytessuccessfully+*remappedbeforetheinterruptionifany,orthenegativeerrorif+*none.Itwillneverreturnzero.Eitheritwillreturnanerroror+*anamountofbytessuccessfullymoved.Iftheretvalreportsa+*"short"remap,theremap_anon_pagessyscallshouldberepeatedby+*userlandwithsrc+retval,dst+reval,len-retvalifitwantstoknow+*abouttheerrorthatinterruptedit.+*+*TheRAP_ALLOW_SRC_HOLESflagcanbespecifiedtoprevent-ENOENT+*errorstomaterializeifthereareholesinthesourcevirtual+*rangethatisbeingremapped.Theholeswillbeaccountedas+*successfullyremappedintheretvalofthesyscall.Thisismostly+*usefultoremaphugepagenaturallyalignedvirtualregionswithout+*knowingiftherearetransparenthugepageintheregionsornot,+*butpreventingtheriskofhavingtosplitthehugepmdduringthe+*remap.+*+*Ifthere'sanyrmapwalkthatistakingtheanon_vmalockswithout+*firstobtainingthepagelock(forexamplesplit_huge_pageand+*page_referenced_anon),theywillhavetoverifyifthe+*page->mappinghaschangedaftertakingtheanon_vmalock.Ifit+*changedtheyshouldreleasethelockandretryobtaininganew+*anon_vma,becauseitmeanstheanon_vmawaschangedby+*remap_anon_pagesbeforethelockcouldbeobtained.Thisisthe+*onlyadditionalcomplexityaddedtothermapcodetoprovidethis+*anonymouspageremappingfunctionality.+*/+SYSCALL_DEFINE4(remap_anon_pages,+unsignedlong,dst_start,unsignedlong,src_start,+unsignedlong,len,unsignedlong,flags)+{+structmm_struct*mm=current->mm;+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;+longmoved=0;++/*+*Sanitizethesyscallparameters:+*/+if(src_start&~PAGE_MASK)+returnerr;+if(dst_start&~PAGE_MASK)+returnerr;+if(len&~PAGE_MASK)+returnerr;+if(flags&~RAP_ALLOW_SRC_HOLES)+returnerr;++/* Does the address range wrap, or is the span zero-sized? */+if(unlikely(src_start+len<=src_start))+returnerr;+if(unlikely(dst_start+len<=dst_start))+returnerr;++down_read(&mm->mmap_sem);++/*+*Makesurethevmaisnotshared,thatthesrcanddstremap+*rangesarebothvalidandfullywithinasingleexisting+*vma.+*/+src_vma=find_vma(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(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;++/*+*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(mm,src_addr);+if(unlikely(!src_pmd)){+if(!(flags&RAP_ALLOW_SRC_HOLES)){+err=-ENOENT;+break;+}else{+src_pmd=mm_alloc_pmd(mm,src_addr);+if(unlikely(!src_pmd)){+err=-ENOMEM;+break;+}+}+}+dst_pmd=mm_alloc_pmd(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_anon_pages_huge_pmd(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(!(flags&RAP_ALLOW_SRC_HOLES)){+err=-ENOENT;+break;+}else{+if(unlikely(__pte_alloc(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(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(mm,dst_pmd);+src_ptl=pte_lockptr(mm,src_pmd);++err=remap_anon_pages_pte(mm,+dst_pte,src_pte,src_pmd,+dst_vma,src_vma,+dst_addr,src_addr,+dst_ptl,src_ptl,flags);++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(&mm->mmap_sem);+BUG_ON(moved<0);+BUG_ON(err>0);+BUG_ON(!moved&&!err);+returnmoved?moved:err;+}
@@ -1555,6 +1555,116 @@ int change_huge_pmd(struct vm_area_struct *vma, pmd_t *pmd,}/*+*ThePTlockforsrc_pmdandthemmap_semforreadingareheldby+*thecaller,butitmustreturnafterreleasingthe+*page_table_lock.We'reguaranteedthesrc_pmdisapmd_trans_huge+*untilthePTlockofthesrc_pmdisreleased.Justmovethepage+*fromsrc_pmdtodst_pmdifpossible.Returnzeroifsucceededin+*movingthepage,-EAGAINifitneedstoberepeatedbythecaller,+*orothererrorsincaseoffailure.+*/+intremap_anon_pages_huge_pmd(structmm_struct*mm,+pmd_t*dst_pmd,pmd_t*src_pmd,+pmd_tdst_pmdval,+structvm_area_struct*dst_vma,+structvm_area_struct*src_vma,+unsignedlongdst_addr,+unsignedlongsrc_addr)+{+pmd_t_dst_pmd,src_pmdval;+structpage*src_page;+structanon_vma*src_anon_vma,*dst_anon_vma;+spinlock_t*src_ptl,*dst_ptl;+pgtable_tpgtable;++src_pmdval=*src_pmd;+src_ptl=pmd_lockptr(mm,src_pmd);++BUG_ON(!pmd_trans_huge(src_pmdval));+BUG_ON(pmd_trans_splitting(src_pmdval));+BUG_ON(!pmd_none(dst_pmdval));+BUG_ON(!spin_is_locked(src_ptl));+BUG_ON(!rwsem_is_locked(&mm->mmap_sem));++src_page=pmd_page(src_pmdval);+BUG_ON(!PageHead(src_page));+BUG_ON(!PageAnon(src_page));+if(unlikely(page_mapcount(src_page)!=1)){+spin_unlock(src_ptl);+return-EBUSY;+}++get_page(src_page);+spin_unlock(src_ptl);++mmu_notifier_invalidate_range_start(mm,src_addr,+src_addr+HPAGE_PMD_SIZE);++/* block all concurrent rmap walks */+lock_page(src_page);++/*+*split_huge_pagewalkstheanon_vmachainwithoutthepage+*lock.Serializeagainstitwiththeanon_vmalock,thepage+*lockisnotenough.+*/+src_anon_vma=page_get_anon_vma(src_page);+if(!src_anon_vma){+unlock_page(src_page);+put_page(src_page);+mmu_notifier_invalidate_range_end(mm,src_addr,+src_addr+HPAGE_PMD_SIZE);+return-EAGAIN;+}+anon_vma_lock_write(src_anon_vma);++dst_ptl=pmd_lockptr(mm,dst_pmd);+double_pt_lock(src_ptl,dst_ptl);+if(unlikely(!pmd_same(*src_pmd,src_pmdval)||+!pmd_same(*dst_pmd,dst_pmdval)||+page_mapcount(src_page)!=1)){+double_pt_unlock(src_ptl,dst_ptl);+anon_vma_unlock_write(src_anon_vma);+put_anon_vma(src_anon_vma);+unlock_page(src_page);+put_page(src_page);+mmu_notifier_invalidate_range_end(mm,src_addr,+src_addr+HPAGE_PMD_SIZE);+return-EAGAIN;+}++BUG_ON(!PageHead(src_page));+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(!pmd_same(pmdp_clear_flush(src_vma,src_addr,src_pmd),+src_pmdval))+BUG();+_dst_pmd=mk_huge_pmd(src_page,dst_vma->vm_page_prot);+_dst_pmd=maybe_pmd_mkwrite(pmd_mkdirty(_dst_pmd),dst_vma);+set_pmd_at(mm,dst_addr,dst_pmd,_dst_pmd);++pgtable=pgtable_trans_huge_withdraw(mm,src_pmd);+pgtable_trans_huge_deposit(mm,dst_pmd,pgtable);+double_pt_unlock(src_ptl,dst_ptl);++anon_vma_unlock_write(src_anon_vma);+put_anon_vma(src_anon_vma);++/* unblock rmap walks */+unlock_page(src_page);++mmu_notifier_invalidate_range_end(mm,src_addr,+src_addr+HPAGE_PMD_SIZE);+return0;+}++/**Returns1ifagivenpmdmapsastable(notundersplitting)thp.*Returns-1ifitmapsathpundersplitting.Returns0otherwise.*--
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: 2014-10-03 18:04:15
This adds two protocol commands to the userfaultfd protocol.
To register memory regions into userfaultfd you can write 16 bytes as:
[ start|0x1, end ]
to unregister write:
[ start|0x2, end ]
End is "start+len" (not start+len-1). Same as vma->vm_end.
This also enforces the constraint that start and end must both be page
aligned (so the last two bits become available to implement the
USERFAULTFD_RANGE_REGISTER|UNREGISTER commands).
This way there can be multiple userfaultfd for each process and each
one can register into its own virtual memory ranges.
If an userfaultfd tries to register into a virtual memory range
already registered into a different userfaultfd, -EBUSY will be
returned by the write() syscall.
userfaultfd can register into allocated ranges that don't have
MADV_USERFAULT set, but if MADV_USERFAULT is not set, no userfault
will fire on those.
Only if MADV_USERFAULT is set on the virtual memory range, and the
userfaultfd registered into the same range, the userfaultfd protocol
will engage.
If only MADV_USERFAULT is set and there's no userfaultfd registered on
a memory range, only a SIGBUS will be raised and the page fault will
not engage the userfaultfd protocol.
This also makes the handle_userfault() safe against race conditions
with regard to the mmap_sem by requiring FAULT_FLAG_ALLOW_RETRY to be
set the first time a fault is raised by any thread. In turn to work
reliably, the userfaultd depends on the gup_locked|unlocked patchset
to be applied.
If get_user_pages() is run on virtual memory ranges registered into
the userfaultfd, handle_userfault() will return VM_FAULT_SIGBUS and
gup() will return -EFAULT, because get_user_pages() doesn't allow
handle_userfault() to release the mmap_sem and in turn we cannot
safely engage the userfaultfd protocol. So the remaining
get_user_pages() calls must be restricted to memory ranges that we
know are not tracked through the userfaultfd protocol for the
userfaultfd to be reliable.
The only exception of a get_user_pages() that can safely run into an
userfaultfd triggering a -EFAULT is ptrace. ptrace would otherwise
hang so it's actually ok if it will get a -EFAULT instead of
hanging. But it would be ok also to phase out get_user_pages()
completely and have ptrace hang on the userfault (the hang can be
resolved sending SIGKILL to gdb or whatever process that is calling
ptrace). We could also decide to retain the current -EFAULT behavior
of ptrace using get_user_pages_locked with a NULL locked parameter so
the FAULT_FLAG_ALLOW_RETRY flag will not be set. Either ways would be
safe.
Signed-off-by: Andrea Arcangeli <redacted>
---
fs/userfaultfd.c | 411 +++++++++++++++++++++++++++-----------------
include/linux/mm.h | 2 +-
include/linux/mm_types.h | 11 ++
include/linux/userfaultfd.h | 19 +-
mm/madvise.c | 3 +-
mm/mempolicy.c | 4 +-
mm/mlock.c | 3 +-
mm/mmap.c | 39 +++--
mm/mprotect.c | 3 +-
9 files changed, 320 insertions(+), 175 deletions(-)
@@ -37,6 +38,8 @@ struct userfaultfd_ctx {unsignedintstate;/* released */boolreleased;+/* mm with one ore more vmas attached to this userfaultfd_ctx */+structmm_struct*mm;};structuserfaultfd_wait_queue{
@@ -181,25 +133,55 @@ int handle_userfault(struct vm_area_struct *vma, unsigned long address,unsignedintflags){structmm_struct*mm=vma->vm_mm;-structmm_slot*slot;structuserfaultfd_ctx*ctx;structuserfaultfd_wait_queueuwq;-intret;BUG_ON(!rwsem_is_locked(&mm->mmap_sem));-rcu_read_lock();-slot=get_mm_slot(mm);-if(!slot){-rcu_read_unlock();+ctx=vma->vm_userfaultfd_ctx.ctx;+if(!ctx)returnVM_FAULT_SIGBUS;-}-ctx=&slot->ctx;-if(!userfaultfd_ctx_get(ctx)){-rcu_read_unlock();++BUG_ON(ctx->mm!=mm);++/*+*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();+}+#endifreturnVM_FAULT_SIGBUS;}-rcu_read_unlock();++/*+*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;
@@ -214,60 +196,15 @@ int handle_userfault(struct vm_area_struct *vma, unsigned long address,*/__add_wait_queue(&ctx->fault_wqh,&uwq.wq);for(;;){-set_current_state(TASK_INTERRUPTIBLE);-if(fatal_signal_pending(current)){-/*-*Ifwehavetofailbecausethetaskis-*killedjustretrythefaulteitherby-*returningtouserlandorthrough-*VM_FAULT_RETRYifwecomefromapagefault-*andafatalsignalispending.-*/-ret=0;-if(flags&FAULT_FLAG_KILLABLE){-/*-*IfFAULT_FLAG_KILLABLEissetwe-*andthere'safatalsignalpending-*canreturnVM_FAULT_RETRY-*regardlessif-*FAULT_FLAG_ALLOW_RETRYissetor-*notaslongaswereleasethe-*mmap_sem.Thepagefaultwill-*returnstrighttouserlandthento-*handlethefatalsignal.-*/-up_read(&mm->mmap_sem);-ret=VM_FAULT_RETRY;-}-break;-}-if(!uwq.pending||ACCESS_ONCE(ctx->released)){-ret=0;-if(flags&FAULT_FLAG_ALLOW_RETRY){-ret=VM_FAULT_RETRY;-if(!(flags&FAULT_FLAG_RETRY_NOWAIT))-up_read(&mm->mmap_sem);-}-break;-}-if(((FAULT_FLAG_ALLOW_RETRY|FAULT_FLAG_RETRY_NOWAIT)&-flags)==-(FAULT_FLAG_ALLOW_RETRY|FAULT_FLAG_RETRY_NOWAIT)){-ret=VM_FAULT_RETRY;-/*-*Themmap_semmustnotbereleasedif-*FAULT_FLAG_RETRY_NOWAITissetdespitewe-*returnVM_FAULT_RETRY(FOLL_NOWAITcase).-*/+set_current_state(TASK_KILLABLE);+if(!uwq.pending||ACCESS_ONCE(ctx->released)||+fatal_signal_pending(current))break;-}spin_unlock(&ctx->fault_wqh.lock);-up_read(&mm->mmap_sem);wake_up_poll(&ctx->fd_wqh,POLLIN);schedule();-down_read(&mm->mmap_sem);spin_lock(&ctx->fault_wqh.lock);}__remove_wait_queue(&ctx->fault_wqh,&uwq.wq);
@@ -276,30 +213,53 @@ int handle_userfault(struct vm_area_struct *vma, unsigned long address,/**ctxmaygoawayafterthisiftheuserfaultpseudofdis-*releasedbyanotherCPU.+*alreadyreleased.*/userfaultfd_ctx_put(ctx);-returnret;+returnVM_FAULT_RETRY;}staticintuserfaultfd_release(structinode*inode,structfile*file){structuserfaultfd_ctx*ctx=file->private_data;-structmm_slot*mm_slot=container_of(ctx,structmm_slot,ctx);+structmm_struct*mm=ctx->mm;+structvm_area_struct*vma,*prev;__u64range[2]={0ULL,-1ULL};ACCESS_ONCE(ctx->released)=true;/*-*FlushpagefaultsoutofallCPUstoavoidraceconditions-*againstctx->released.Allpagefaultsmustberetried-*withoutreturningVM_FAULT_SIGBUSiftheget_mm_slotand-*userfaultfd_ctx_getbothsucceedsbutctx->releasedisset.+*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_slot->mm->mmap_sem);-up_write(&mm_slot->mm->mmap_sem);+down_write(&mm->mmap_sem);+prev=NULL;+for(vma=mm->mmap;vma;vma=vma->vm_next){+if(vma->vm_userfaultfd_ctx.ctx!=ctx)+continue;+prev=vma_merge(mm,prev,vma->vm_start,vma->vm_end,+vma->vm_flags,vma->anon_vma,+vma->vm_file,vma->vm_pgoff,+vma_policy(vma),+NULL_VM_USERFAULTFD_CTX);+if(prev)+vma=prev;+else+prev=vma;+vma->vm_userfaultfd_ctx=NULL_VM_USERFAULTFD_CTX;+}+up_write(&mm->mmap_sem);+/*+*Afternonewpagefaultscanwaitonthisfautl_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);
@@ -454,6 +414,140 @@ static int wake_userfault(struct userfaultfd_ctx *ctx, __u64 *range)returnret;}+staticssize_tuserfaultfd_range_register(structuserfaultfd_ctx*ctx,+unsignedlongstart,+unsignedlongend)+{+structmm_struct*mm=ctx->mm;+structvm_area_struct*vma,*prev;+intret;++down_write(&mm->mmap_sem);+vma=find_vma(mm,start);+if(!vma)+return-ENOMEM;+if(vma->vm_start>=end)+return-EINVAL;++prev=vma->vm_prev;+if(vma->vm_start<start)+prev=vma;++ret=0;+/* we got an overlap so start the splitting */+do{+if(vma->vm_userfaultfd_ctx.ctx==ctx)+gotonext;+if(vma->vm_userfaultfd_ctx.ctx){+ret=-EBUSY;+break;+}+prev=vma_merge(mm,prev,start,end,vma->vm_flags,+vma->anon_vma,vma->vm_file,vma->vm_pgoff,+vma_policy(vma),+((structvm_userfaultfd_ctx){ctx}));+if(prev){+vma=prev;+vma->vm_userfaultfd_ctx.ctx=ctx;+gotonext;+}+if(vma->vm_start<start){+ret=split_vma(mm,vma,start,1);+if(ret<0)+break;+}+if(vma->vm_end>end){+ret=split_vma(mm,vma,end,0);+if(ret<0)+break;+}+vma->vm_userfaultfd_ctx.ctx=ctx;+next:+start=vma->vm_end;+vma=vma->vm_next;+}while(vma&&vma->vm_start<end);+up_write(&mm->mmap_sem);++returnret;+}++staticssize_tuserfaultfd_range_unregister(structuserfaultfd_ctx*ctx,+unsignedlongstart,+unsignedlongend)+{+structmm_struct*mm=ctx->mm;+structvm_area_struct*vma,*prev;+intret;++down_write(&mm->mmap_sem);+vma=find_vma(mm,start);+if(!vma)+return-ENOMEM;+if(vma->vm_start>=end)+return-EINVAL;++prev=vma->vm_prev;+if(vma->vm_start<start)+prev=vma;++ret=0;+/* we got an overlap so start the splitting */+do{+if(!vma->vm_userfaultfd_ctx.ctx)+gotonext;+if(vma->vm_userfaultfd_ctx.ctx!=ctx){+ret=-EBUSY;+break;+}+prev=vma_merge(mm,prev,start,end,vma->vm_flags,+vma->anon_vma,vma->vm_file,vma->vm_pgoff,+vma_policy(vma),+NULL_VM_USERFAULTFD_CTX);+if(prev){+vma=prev;+vma->vm_userfaultfd_ctx=NULL_VM_USERFAULTFD_CTX;+gotonext;+}+if(vma->vm_start<start){+ret=split_vma(mm,vma,start,1);+if(ret<0)+break;+}+if(vma->vm_end>end){+ret=split_vma(mm,vma,end,0);+if(ret<0)+break;+}+vma->vm_userfaultfd_ctx.ctx=NULL;+next:+start=vma->vm_end;+vma=vma->vm_next;+}while(vma&&vma->vm_start<end);+up_write(&mm->mmap_sem);++returnret;+}++staticssize_tuserfaultfd_handle_range(structuserfaultfd_ctx*ctx,+__u64*range)+{+unsignedlongstart,end;++start=range[0]&USERFAULTFD_RANGE_MASK;+end=range[1];+BUG_ON(end<=start);+if(end>TASK_SIZE)+return-ENOMEM;++if(range[0]&USERFAULTFD_RANGE_REGISTER){+BUG_ON(range[0]&USERFAULTFD_RANGE_UNREGISTER);+returnuserfaultfd_range_register(ctx,start,end);+}else{+BUG_ON(!(range[0]&USERFAULTFD_RANGE_UNREGISTER));+returnuserfaultfd_range_unregister(ctx,start,end);+}+}+staticssize_tuserfaultfd_write(structfile*file,constchar__user*buf,size_tcount,loff_t*ppos){
@@ -483,9 +577,24 @@ static ssize_t userfaultfd_write(struct file *file, const char __user *buf,return-EINVAL;if(copy_from_user(&range,buf,sizeof(range)))return-EFAULT;-if(range[0]>=range[1])+/* the range mask requires 2 bits */+BUILD_BUG_ON(PAGE_SHIFT<2);+if(range[0]&~PAGE_MASK&USERFAULTFD_RANGE_MASK)+return-EINVAL;+if((range[0]&~USERFAULTFD_RANGE_MASK)==~USERFAULTFD_RANGE_MASK)+return-EINVAL;+if(range[1]&~PAGE_MASK)+return-EINVAL;+if((range[0]&PAGE_MASK)>=(range[1]&PAGE_MASK))return-ERANGE;+/* handle the register/unregister commands */+if(range[0]&~USERFAULTFD_RANGE_MASK){+ssize_tret=userfaultfd_handle_range(ctx,range);+BUG_ON(ret>0);+returnret<0?ret:sizeof(range);+}+/* always take the fd_wqh lock before the fault_wqh lock */if(find_userfault(ctx,NULL,POLLOUT))if(!wake_userfault(ctx,range))
@@ -552,7 +661,9 @@ static const struct file_operations userfaultfd_fops = {staticstructfile*userfaultfd_file_create(intflags){structfile*file;-structmm_slot*mm_slot;+structuserfaultfd_ctx*ctx;++BUG_ON(!current->mm);/* Check the UFFD_* constants for consistency. */BUILD_BUG_ON(UFFD_CLOEXEC!=O_CLOEXEC);
@@ -562,33 +673,25 @@ static struct file *userfaultfd_file_create(int flags)if(flags&~UFFD_SHARED_FCNTL_FLAGS)gotoout;-mm_slot=kmalloc(sizeof(*mm_slot),GFP_KERNEL);+ctx=kmalloc(sizeof(*ctx),GFP_KERNEL);file=ERR_PTR(-ENOMEM);-if(!mm_slot)+if(!ctx)gotoout;-mutex_lock(&mm_userlandfd_mutex);-file=ERR_PTR(-EBUSY);-if(get_mm_slot(current->mm))-gotoout_free_unlock;--atomic_set(&mm_slot->ctx.refcount,1);-init_waitqueue_head(&mm_slot->ctx.fault_wqh);-init_waitqueue_head(&mm_slot->ctx.fd_wqh);-mm_slot->ctx.flags=flags;-mm_slot->ctx.state=USERFAULTFD_STATE_ASK_PROTOCOL;-mm_slot->ctx.released=false;--file=anon_inode_getfile("[userfaultfd]",&userfaultfd_fops,-&mm_slot->ctx,+atomic_set(&ctx->refcount,1);+init_waitqueue_head(&ctx->fault_wqh);+init_waitqueue_head(&ctx->fd_wqh);+ctx->flags=flags;+ctx->state=USERFAULTFD_STATE_ASK_PROTOCOL;+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))-out_free_unlock:-kfree(mm_slot);-else-insert_to_mm_userlandfd_hash(current->mm,-mm_slot);-mutex_unlock(&mm_userlandfd_mutex);+kfree(ctx);out:returnfile;}
@@ -308,6 +318,7 @@ struct vm_area_struct {#ifdef CONFIG_NUMAstructmempolicy*vm_policy;/* NUMA policy for the VMA */#endif+structvm_userfaultfd_ctxvm_userfaultfd_ctx;};structcore_thread{
@@ -2670,7 +2684,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_USERFAULTFD_CTX);if(vma)gotoout;
@@ -2829,7 +2843,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>
This needs more explanation than that one-liner comment. Make the
commit message explain why the new FOLL_TRIED flag exists.
Linus
On Fri, Oct 3, 2014 at 10:07 AM, Andrea Arcangeli [off-list ref] wrote:
From: Andres Lagar-Cavilla <redacted>
Reviewed-by: Radim Krčmář <redacted>
Signed-off-by: Andres Lagar-Cavilla <redacted>
Signed-off-by: Andrea Arcangeli <redacted>
--
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: 2014-10-03 18:22:46
MADV_USERFAULT is a new madvise flag that will set VM_USERFAULT in the
vma flags. Whenever VM_USERFAULT is set in an anonymous vma, if
userland touches a still unmapped virtual address, a sigbus signal is
sent instead of allocating a new page. The sigbus signal handler will
then resolve the page fault in userland by calling the
remap_anon_pages syscall.
This functionality is needed to reliably implement postcopy live
migration in KVM (without having to use a special chardevice that
would disable all advanced Linux VM features, like swapping, KSM, THP,
automatic NUMA balancing, etc...).
MADV_USERFAULT could also be used to offload parts of anonymous memory
regions to remote nodes or to implement network distributed shared
memory.
Here I enlarged the vm_flags to 64bit as we run out of bits (noop on
64bit kernels). An alternative is to find some combination of flags
that are mutually exclusive if set.
Signed-off-by: Andrea Arcangeli <redacted>
---
arch/alpha/include/uapi/asm/mman.h | 3 ++
arch/mips/include/uapi/asm/mman.h | 3 ++
arch/parisc/include/uapi/asm/mman.h | 3 ++
arch/xtensa/include/uapi/asm/mman.h | 3 ++
fs/proc/task_mmu.c | 1 +
include/linux/mm.h | 1 +
include/uapi/asm-generic/mman-common.h | 3 ++
mm/huge_memory.c | 60 +++++++++++++++++++++-------------
mm/madvise.c | 17 ++++++++++
mm/memory.c | 13 ++++++++
10 files changed, 85 insertions(+), 22 deletions(-)
@@ -2645,6 +2645,11 @@ 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(vma->vm_flags&VM_USERFAULT){+pte_unmap_unlock(page_table,ptl);+returnVM_FAULT_SIGBUS;+}gotosetpte;}
@@ -2672,6 +2677,14 @@ 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(vma->vm_flags&VM_USERFAULT){+pte_unmap_unlock(page_table,ptl);+mem_cgroup_cancel_charge(page,memcg);+page_cache_release(page);+returnVM_FAULT_SIGBUS;+}+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>
On Fri, Oct 3, 2014 at 10:07 AM, Andrea Arcangeli [off-list ref] wrote:
This teaches gup_fast and __gup_fast to re-enable irqs and
cond_resched() if possible every BATCH_PAGES.
This is disgusting.
Many (most?) __gup_fast() users just want a single page, and the
stupid overhead of the multi-page version is already unnecessary.
This just makes things much worse.
Quite frankly, we should make a single-page version of __gup_fast(),
and convert existign users to use that. After that, the few multi-page
users could have this extra latency control stuff.
And yes, the single-page version of get_user_pages_fast() is actually
latency-critical. shared futexes hit it hard, and yes, I've seen this
in profiles.
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>
On Fri, Oct 3, 2014 at 10:08 AM, Andrea Arcangeli [off-list ref] wrote:
Overall this looks a fairly small change to the rmap code, notably
less intrusive than the nonlinear vmas created by remap_file_pages.
Considering that remap_file_pages() was an unmitigated disaster, and
-mm has a patch to remove it entirely, I'm not at all convinced this
is a good argument.
We thought remap_file_pages() was a good idea, and it really really
really wasn't. Almost nobody used it, why would the anonymous page
case be any different?
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>
From: Paolo Bonzini <pbonzini@redhat.com> Date: 2014-10-03 20:56:36
This needs more explanation than that one-liner comment. Make the
commit message explain why the new FOLL_TRIED flag exists.
This patch actually is extracted from a 3.18 commit in the KVM tree,
https://git.kernel.org/cgit/virt/kvm/kvm.git/commit/?h=next&id=234b239b.
Here is how that patch uses the flag:
/*
* The previous call has now waited on the IO. Now we can
* retry and complete. Pass TRIED to ensure we do not re
* schedule async IO (see e.g. filemap_fault).
*/
down_read(&mm->mmap_sem);
npages = __get_user_pages(tsk, mm, addr, 1, flags | FOLL_TRIED,
pagep, NULL, NULL);
--
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: Mike Hommey <hidden> Date: 2014-10-03 23:42:14
On Fri, Oct 03, 2014 at 07:07:58PM +0200, Andrea Arcangeli wrote:
MADV_USERFAULT is a new madvise flag that will set VM_USERFAULT in the
vma flags. Whenever VM_USERFAULT is set in an anonymous vma, if
userland touches a still unmapped virtual address, a sigbus signal is
sent instead of allocating a new page. The sigbus signal handler will
then resolve the page fault in userland by calling the
remap_anon_pages syscall.
What does "unmapped virtual address" mean in this context?
Mike
--
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 Fri, Oct 3, 2014 at 10:08 AM, Andrea Arcangeli [off-list ref] wrote:
quoted
Overall this looks a fairly small change to the rmap code, notably
less intrusive than the nonlinear vmas created by remap_file_pages.
Considering that remap_file_pages() was an unmitigated disaster, and
-mm has a patch to remove it entirely, I'm not at all convinced this
is a good argument.
We thought remap_file_pages() was a good idea, and it really really
really wasn't. Almost nobody used it, why would the anonymous page
case be any different?
I've posted code that uses this interface to qemu-devel and it works nicely;
so chalk up at least one user.
For the postcopy case I'm using it for, we need to place a page, atomically
some thread might try and access it, and must either
1) get caught by userfault etc or
2) must succeed in it's access
and we'll have that happening somewhere between thousands and millions of times
to pages in no particular order, so we need to avoid creating millions of mappings.
Dave
Linus
--
Dr. David Alan Gilbert / dgilbert@redhat.com / Manchester, UK
--
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: 2014-10-06 14:15:23
Hello,
On Fri, Oct 03, 2014 at 11:23:53AM -0700, Linus Torvalds wrote:
On Fri, Oct 3, 2014 at 10:07 AM, Andrea Arcangeli [off-list ref] wrote:
quoted
This teaches gup_fast and __gup_fast to re-enable irqs and
cond_resched() if possible every BATCH_PAGES.
This is disgusting.
Many (most?) __gup_fast() users just want a single page, and the
stupid overhead of the multi-page version is already unnecessary.
This just makes things much worse.
Quite frankly, we should make a single-page version of __gup_fast(),
and convert existign users to use that. After that, the few multi-page
users could have this extra latency control stuff.
Ok. I didn't think at a better way to add the latency control other
than to reduce nr_pages in a outer loop instead of altering the inner
calls, but this is what I got after implementing it... If somebody has
a cleaner way to implement the latency control stuff that's welcome
and I'd be glad to replace it.
And yes, the single-page version of get_user_pages_fast() is actually
latency-critical. shared futexes hit it hard, and yes, I've seen this
in profiles.
KVM would save a few cycles from a single-page version too. I just
thought further optimizations could be added later and this was better
than nothing.
Considering I've no better idea how to implement the latency control
stuff, for now I'll just drop this controversial patch, and I'll
convert those get_user_pages to gup_unlocked instead of converting
them to gup_fast, which is more than enough to obtain the mmap_sem
holding scalability improvement (that also solves the mmap_sem trouble
for the userfaultfd). gup_unlocked isn't as good as gup_fast but it's
at least better than the current get_user_pages().
I got into this gup_fast latency control stuff purely because there
were a few get_user_pages that could have been converted to
get_user_pages_fast as they were using "current" and "current->mm" as the
first two parameters, except for the risk of disabling irq for
long. So I tried to do the right thing and fix gup_fast but I'll leave
this further optimization queued for later.
About the missing commit header for the other patch Paolo already
replied to it, to clarify this a bit further in short I expect that
FOLL_TRIED flag to be merged through the KVM git tree which already
contains it. I'll add a comment to the commit header to specify
it. Sorry for the confusion about that patch.
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 Fri, Oct 3, 2014 at 10:08 AM, Andrea Arcangeli [off-list ref] wrote:
quoted
Overall this looks a fairly small change to the rmap code, notably
less intrusive than the nonlinear vmas created by remap_file_pages.
Considering that remap_file_pages() was an unmitigated disaster, and
-mm has a patch to remove it entirely, I'm not at all convinced this
is a good argument.
We thought remap_file_pages() was a good idea, and it really really
really wasn't. Almost nobody used it, why would the anonymous page
case be any different?
I've posted code that uses this interface to qemu-devel and it works nicely;
so chalk up at least one user.
For the postcopy case I'm using it for, we need to place a page, atomically
some thread might try and access it, and must either
1) get caught by userfault etc or
2) must succeed in it's access
and we'll have that happening somewhere between thousands and millions of times
to pages in no particular order, so we need to avoid creating millions of mappings.
Yes, that's our current use case.
Of course if somebody has better ideas on how to resolve an anonymous
userfault they're welcome.
How to resolve an userfault is orthogonal on how to detect it and to
notify userland about it and to be notified when the userfault has
been resolved. The latter is what the userfault and userfaultfd
do. The former is what remap_anon_pages is used for but we could use
something else too if there are better ways. mremap would clearly work
too, but it would be less strict (it could lead to silent data
corruption if there are bugs in the userland code), it would be slower
and it would eventually a hit a -ENOMEM failure because there would be
too many vmas.
I could in theory drop remap_anon_pages from this patchset, but
without an optimal way to resolve an userfault, the rest isn't so
useful.
We're currently discussing on what would be the best way to resolve a
MAP_SHARED userfault on tmpfs in fact (that's not sorted yet), but so
far, it seems remap_anon_pages fits the bill for anonymous memory.
remap_anon_pages is not as problematic to maintain as remap_file_pages
for the reason explained in the commit header, but there are other
reasons: it doesn't require special pte_file and it changes nothing of
how anonymous page faults works. All it requires is a loop to catch a
changed page->index (previously page->index couldn't change, not it
can, that's the only thing it changes).
remap_file_pages complexity derives from not being allowed to change
page->index during a move because the page_mapping may be bigger than
1, while that is precisely what remap_anon_pages does.
As long as this "rmap preparation" is the only constraints that
remap_anon_pages introduces in terms of rmap, it looks a nice
not-too-intrusive solution to resolve anonymous userfaults
efficiently.
Introducing remap_anon_pages in fact doesn't reduce the
simplification derived from the removal of remap_file_pages.
As opposed removing remap_anon_pages later would only have the benefit
of removing this very patch 10/17 and no other benefit.
In short remap_anon_pages does this (heavily simplified):
pte = *src_pte;
*src_pte = 0;
pte_page(pte)->index = adjusted according to src_vma/dst_vma->vm_pgoff
*dst_pte = pte;
It guarantees not to modify the vmas and in turn it doesn't require to
take the mmap_sem for writing.
To use remap_anon_pages, each thread has to create its own temporary
vma with MADV_DONTFORK set on it (not formally required by the syscall
strict checks, but then the application must never fork if
MADV_DONTFORK isn't set or remap_anon_pages could return -EBUSY:
there's no risk of silent data corruption even if the thread forks
without setting MADV_DONTFORK) as source region where receive data
through the network. Then after the data is fully received
rmap_anon_pages moves the page from the temporary vma to the address
where the userfault triggered atomically (while other threads may be
attempting to access the userfault address too, thanks to
remap_anon_pages atomic behavior they won't risk to ever see partial
data coming from the network).
remap_anon_pages as side effect creates an hole in the temporary
(source) vma, so the next recv() syscall receiving data from the
network will fault-in a new anonymous page without requiring any
further malloc/free or other kind of vma mangling.
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>
From: Andrea Arcangeli <hidden> Date: 2014-10-06 17:25:13
Hi,
On Sat, Oct 04, 2014 at 08:13:36AM +0900, Mike Hommey wrote:
On Fri, Oct 03, 2014 at 07:07:58PM +0200, Andrea Arcangeli wrote:
quoted
MADV_USERFAULT is a new madvise flag that will set VM_USERFAULT in the
vma flags. Whenever VM_USERFAULT is set in an anonymous vma, if
userland touches a still unmapped virtual address, a sigbus signal is
sent instead of allocating a new page. The sigbus signal handler will
then resolve the page fault in userland by calling the
remap_anon_pages syscall.
What does "unmapped virtual address" mean in this context?
To clarify this I added this in a second sentence in the commit
header:
"still unmapped virtual address" of the previous sentence in this
context means that the pte/trans_huge_pmd is null. It means it's an
hole inside the anonymous vma (the kind of hole that doesn't account
for RSS but only virtual size of the process). It is the same state
all anonymous virtual memory is, right after mmap. The same state that
if you read from it, will map a zeropage into the faulting virtual
address. If the page is swapped out, it will not trigger userfaults.
If something isn't clear let me know.
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>
I believe here and below should be BITS_PER_LONG_LONG instead: it will
catch unknown vmflags. And +1 is not needed un 64-bit systems.
/*
* In case if we meet a flag we don't know about.
*/
- [0 ... (BITS_PER_LONG-1)] = "??",
+ [0 ... (BITS_PER_LONG)] = "??",
[ilog2(VM_READ)] = "rd",
[ilog2(VM_WRITE)] = "wr",
--
Kirill A. Shutemov
--
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: Kirill A. Shutemov <hidden> Date: 2014-10-07 10:38:01
On Fri, Oct 03, 2014 at 07:07:58PM +0200, Andrea Arcangeli wrote:
MADV_USERFAULT is a new madvise flag that will set VM_USERFAULT in the
vma flags. Whenever VM_USERFAULT is set in an anonymous vma, if
userland touches a still unmapped virtual address, a sigbus signal is
sent instead of allocating a new page. The sigbus signal handler will
then resolve the page fault in userland by calling the
remap_anon_pages syscall.
Hm. I wounder if this functionality really fits madvise(2) interface: as
far as I understand it, it provides a way to give a *hint* to kernel which
may or may not trigger an action from kernel side. I don't think an
application will behaive reasonably if kernel ignore the *advise* and will
not send SIGBUS, but allocate memory.
I would suggest to consider to use some other interface for the
functionality: a new syscall or, perhaps, mprotect().
--
Kirill A. Shutemov
--
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: Dr. David Alan Gilbert <hidden> Date: 2014-10-07 10:47:21
* Kirill A. Shutemov (kirill@shutemov.name) wrote:
On Fri, Oct 03, 2014 at 07:07:58PM +0200, Andrea Arcangeli wrote:
quoted
MADV_USERFAULT is a new madvise flag that will set VM_USERFAULT in the
vma flags. Whenever VM_USERFAULT is set in an anonymous vma, if
userland touches a still unmapped virtual address, a sigbus signal is
sent instead of allocating a new page. The sigbus signal handler will
then resolve the page fault in userland by calling the
remap_anon_pages syscall.
Hm. I wounder if this functionality really fits madvise(2) interface: as
far as I understand it, it provides a way to give a *hint* to kernel which
may or may not trigger an action from kernel side. I don't think an
application will behaive reasonably if kernel ignore the *advise* and will
not send SIGBUS, but allocate memory.
Aren't DONTNEED and DONTDUMP similar cases of madvise operations that are
expected to do what they say ?
I would suggest to consider to use some other interface for the
functionality: a new syscall or, perhaps, mprotect().
Dave
--
Kirill A. Shutemov
--
Dr. David Alan Gilbert / dgilbert@redhat.com / Manchester, UK
--
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: Kirill A. Shutemov <hidden> Date: 2014-10-07 10:53:51
On Tue, Oct 07, 2014 at 11:46:04AM +0100, Dr. David Alan Gilbert wrote:
* Kirill A. Shutemov (kirill@shutemov.name) wrote:
quoted
On Fri, Oct 03, 2014 at 07:07:58PM +0200, Andrea Arcangeli wrote:
quoted
MADV_USERFAULT is a new madvise flag that will set VM_USERFAULT in the
vma flags. Whenever VM_USERFAULT is set in an anonymous vma, if
userland touches a still unmapped virtual address, a sigbus signal is
sent instead of allocating a new page. The sigbus signal handler will
then resolve the page fault in userland by calling the
remap_anon_pages syscall.
Hm. I wounder if this functionality really fits madvise(2) interface: as
far as I understand it, it provides a way to give a *hint* to kernel which
may or may not trigger an action from kernel side. I don't think an
application will behaive reasonably if kernel ignore the *advise* and will
not send SIGBUS, but allocate memory.
Aren't DONTNEED and DONTDUMP similar cases of madvise operations that are
expected to do what they say ?
No. If kernel would ignore MADV_DONTNEED or MADV_DONTDUMP it will not
affect correctness, just behaviour will be suboptimal: more than needed
memory used or wasted space in coredump.
--
Kirill A. Shutemov
--
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: Dr. David Alan Gilbert <hidden> Date: 2014-10-07 11:02:16
* Kirill A. Shutemov (kirill@shutemov.name) wrote:
On Tue, Oct 07, 2014 at 11:46:04AM +0100, Dr. David Alan Gilbert wrote:
quoted
* Kirill A. Shutemov (kirill@shutemov.name) wrote:
quoted
On Fri, Oct 03, 2014 at 07:07:58PM +0200, Andrea Arcangeli wrote:
quoted
MADV_USERFAULT is a new madvise flag that will set VM_USERFAULT in the
vma flags. Whenever VM_USERFAULT is set in an anonymous vma, if
userland touches a still unmapped virtual address, a sigbus signal is
sent instead of allocating a new page. The sigbus signal handler will
then resolve the page fault in userland by calling the
remap_anon_pages syscall.
Hm. I wounder if this functionality really fits madvise(2) interface: as
far as I understand it, it provides a way to give a *hint* to kernel which
may or may not trigger an action from kernel side. I don't think an
application will behaive reasonably if kernel ignore the *advise* and will
not send SIGBUS, but allocate memory.
Aren't DONTNEED and DONTDUMP similar cases of madvise operations that are
expected to do what they say ?
No. If kernel would ignore MADV_DONTNEED or MADV_DONTDUMP it will not
affect correctness, just behaviour will be suboptimal: more than needed
memory used or wasted space in coredump.
That's not how the manpage reads for DONTNEED; it calls it out as a special
case near the top, and explicitly says what will happen if you read the
area marked as DONTNEED.
It looks like there are openssl patches that use DONTDUMP to explicitly
make sure keys etc don't land in cores.
Dave
--
Kirill A. Shutemov
--
Dr. David Alan Gilbert / dgilbert@redhat.com / Manchester, UK
--
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: Kirill A. Shutemov <hidden> Date: 2014-10-07 11:11:17
On Fri, Oct 03, 2014 at 07:08:00PM +0200, Andrea Arcangeli wrote:
There's one constraint enforced to allow this simplification: the
source pages passed to remap_anon_pages must be mapped only in one
vma, but this is not a limitation when used to handle userland page
faults with MADV_USERFAULT. The source addresses passed to
remap_anon_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_anon_pages runs.
Have you considered triggering COW instead of adding limitation on
pages' mapcount? The limitation looks artificial from interface POV.
--
Kirill A. Shutemov
--
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: Kirill A. Shutemov <hidden> Date: 2014-10-07 11:30:48
On Tue, Oct 07, 2014 at 12:01:02PM +0100, Dr. David Alan Gilbert wrote:
* Kirill A. Shutemov (kirill@shutemov.name) wrote:
quoted
On Tue, Oct 07, 2014 at 11:46:04AM +0100, Dr. David Alan Gilbert wrote:
quoted
* Kirill A. Shutemov (kirill@shutemov.name) wrote:
quoted
On Fri, Oct 03, 2014 at 07:07:58PM +0200, Andrea Arcangeli wrote:
quoted
MADV_USERFAULT is a new madvise flag that will set VM_USERFAULT in the
vma flags. Whenever VM_USERFAULT is set in an anonymous vma, if
userland touches a still unmapped virtual address, a sigbus signal is
sent instead of allocating a new page. The sigbus signal handler will
then resolve the page fault in userland by calling the
remap_anon_pages syscall.
Hm. I wounder if this functionality really fits madvise(2) interface: as
far as I understand it, it provides a way to give a *hint* to kernel which
may or may not trigger an action from kernel side. I don't think an
application will behaive reasonably if kernel ignore the *advise* and will
not send SIGBUS, but allocate memory.
Aren't DONTNEED and DONTDUMP similar cases of madvise operations that are
expected to do what they say ?
No. If kernel would ignore MADV_DONTNEED or MADV_DONTDUMP it will not
affect correctness, just behaviour will be suboptimal: more than needed
memory used or wasted space in coredump.
That's not how the manpage reads for DONTNEED; it calls it out as a special
case near the top, and explicitly says what will happen if you read the
area marked as DONTNEED.
Your are right. MADV_DONTNEED doesn't fit the interface too. That's bad
and we can't fix it. But it's not a reason to make this mistake again.
Read the next sentence: "The kernel is free to ignore the advice."
Note, POSIX_MADV_DONTNEED has totally different semantics.
It looks like there are openssl patches that use DONTDUMP to explicitly
make sure keys etc don't land in cores.
That's nice to have. But openssl works on systems without the interface,
meaning it's not essential for functionality.
--
Kirill A. Shutemov
--
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 Mon, Oct 6, 2014 at 12:41 PM, Andrea Arcangeli [off-list ref] wrote:
Of course if somebody has better ideas on how to resolve an anonymous
userfault they're welcome.
So I'd *much* rather have a "write()" style interface (ie _copying_
bytes from user space into a newly allocated page that gets mapped)
than a "remap page" style interface
remapping anonymous pages involves page table games that really aren't
necessarily a good idea, and tlb invalidates for the old page etc.
Just don't do it.
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>
From: Andrea Arcangeli <hidden> Date: 2014-10-07 13:25:48
Hi Kirill,
On Tue, Oct 07, 2014 at 01:36:45PM +0300, Kirill A. Shutemov wrote:
On Fri, Oct 03, 2014 at 07:07:58PM +0200, Andrea Arcangeli wrote:
quoted
MADV_USERFAULT is a new madvise flag that will set VM_USERFAULT in the
vma flags. Whenever VM_USERFAULT is set in an anonymous vma, if
userland touches a still unmapped virtual address, a sigbus signal is
sent instead of allocating a new page. The sigbus signal handler will
then resolve the page fault in userland by calling the
remap_anon_pages syscall.
Hm. I wounder if this functionality really fits madvise(2) interface: as
far as I understand it, it provides a way to give a *hint* to kernel which
may or may not trigger an action from kernel side. I don't think an
application will behaive reasonably if kernel ignore the *advise* and will
not send SIGBUS, but allocate memory.
I would suggest to consider to use some other interface for the
functionality: a new syscall or, perhaps, mprotect().
I didn't feel like adding PROT_USERFAULT to mprotect, which looks
hardwired to just these flags:
PROT_NONE The memory cannot be accessed at all.
PROT_READ The memory can be read.
PROT_WRITE The memory can be modified.
PROT_EXEC The memory can be executed.
Normally mprotect doesn't just alter the vmas but it also alters
pte/hugepmds protection bits, that's something that is never needed
with VM_USERFAULT so I didn't feel like VM_USERFAULT is a protection
change to the VMA.
mprotect is also hardwired to mangle only the VM_READ|WRITE|EXEC
flags, while madvise is ideal to set arbitrary vma flags.
From an implementation standpoint the perfect place to set a flag in a
vma is madvise. This is what MADV_DONTFORK (it sets VM_DONTCOPY)
already does too in an identical way to MADV_USERFAULT/VM_USERFAULT.
MADV_DONTFORK is as critical as MADV_USERFAULT because people depends
on it for example to prevent the O_DIRECT vs fork race condition that
results in silent data corruption during I/O with threads that may
fork. The other reason why MADV_DONTFORK is critical is that fork()
would otherwise fail with OOM unless full overcommit is enabled
(i.e. pci hotplug crashes the guest if you forget to set
MADV_DONTFORK).
Another madvise that would generate a failure if not obeyed by the
kernel is MADV_DONTNEED that if it does nothing it could run lead to
OOM killing. We don't inflate virt balloons using munmap just to make
an example. Various other apps (maybe JVM garbage collection too)
makes extensive use of MADV_DONTNEED and depend on it.
Said that I can change it to mprotect, the only thing that I don't
like is that it'll result in a less clean patch and I can't possibly
see a practical risk in keeping it simpler with madvise, as long as we
always return -EINVAL whenever we encounter a vma type that cannot
raise userfaults yet (that is something I already enforced).
Yet another option would be to drop MADV_USERFAULT and
vm_flags&VM_USERFAULT entirely and in turn the ability to handle
userfaults with SIGBUS, and retain only the userfaultfd. The new
userfaultfd protocol requires registering each created userfaultfd
into its own private virtual memory ranges (that is to allow an
unlimited number of userfaultfd per process). Currently the
userfaultfd engages iff the fault address intersects both the
MADV_USERFAULT range and the userfaultfd registered ranges. So I could
drop MADV_USERFAULT and VM_USERFAULT and just check for
vma->vm_userfaultfd_ctx!=NULL to know if the userfaultfd protocol
needs to be engaged during the first page fault for a still unmapped
virtual address. I just thought it would be more flexibile to also
allow SIGBUS without forcing people to use userfaultfd (that's in fact
the only reason to still retain madvise(MADV_USERFAULT)!).
Volatile pages earlier patches only supported SIGBUS behavior for
example.. and I didn't intend to force them to use userfaultfd if
they're guaranteed to access the memory with the CPU and never through
a kernel syscall (that is something the app can enforce by
design). userfaultfd becomes necessary the moment you want to handle
userfaults through syscalls/gup etc... qemu obviously requires
userfaultfd and it never uses the userfaultfd-less SIGBUS behavior as
it touches the memory in all possible ways (first and foremost with
the KVM page fault that uses almost all variants of gup..).
So here somebody should comment and choose between:
1) set VM_USERFAULT with mprotect(PROT_USERFAULT) instead of
the current madvise(MADV_USERFAULT)
2) drop MADV_USERFAULT and VM_USERFAULT and force the usage of the
userfaultfd protocol as the only way for userland to catch
userfaults (each userfaultfd must already register itself into its
own virtual memory ranges so it's a trivial change for userfaultfd
users that deletes just 1 or 2 lines of userland code, but it would
prevent to use the SIGBUS behavior with info->si_addr=faultaddr for
other users)
3) keep things as they are now: use MADV_USERFAULT for SIGBUS
userfaults, with optional intersection between the
vm_flags&VM_USERFAULT ranges and the userfaultfd registered ranges
with vma->vm_userfaultfd_ctx!=NULL to know if to engage the
userfaultfd protocol instead of the plain SIGBUS
I will update the code accordingly to feedback, so please comment.
I implemented 3) because I thought it provided the most flexibility
for userland to choose if to engage in the userfaultfd protocol or to
stay simple with the SIGBUS if the app doesn't require to access the
userfault virtual memory from the kernel code. It also provides the
cleanest and simplest implementation to set the VM_USERFAULT flags
with madvise.
My second choice would be 2). We could always add MADV_USERFAULT later
except then we'd be forced to set and clear VM_USERFAULT within the
userfaultfd registration to remain backwards compatible. The main cons
and the reason I didn't pick 2) is that it wouldn't be a drop in
replacement for volatile pages that would then be force to use the
userfaultfd protocol too.
I don't like 3) very much mostly because the changes to mprotect would
just make things more complex on the implementation side with purely
conceptual benefits, but then it's possible too and it's feature
equivalent to 1) as far as volatile pages are concerned, so I'm
overall fine with this change if that's the preferred way.
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>
From: Andrea Arcangeli <hidden> Date: 2014-10-07 13:38:25
Hi Kirill,
On Tue, Oct 07, 2014 at 02:10:26PM +0300, Kirill A. Shutemov wrote:
On Fri, Oct 03, 2014 at 07:08:00PM +0200, Andrea Arcangeli wrote:
quoted
There's one constraint enforced to allow this simplification: the
source pages passed to remap_anon_pages must be mapped only in one
vma, but this is not a limitation when used to handle userland page
faults with MADV_USERFAULT. The source addresses passed to
remap_anon_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_anon_pages runs.
Have you considered triggering COW instead of adding limitation on
pages' mapcount? The limitation looks artificial from interface POV.
I haven't considered it, mostly because I see it as a feature that it
returns -EBUSY. I prefer to avoid the risk of userland getting a
successful retval but internally the kernel silently behaving
non-zerocopy by mistake because some userland bug forgot to set
MADV_DONTFORK on the src_vma.
COW would be not zerocopy so it's not ok. We get sub 1msec latency for
userfaults through 10gbit and we don't want to risk wasting CPU
caches.
I however considered allowing to extend the strict behavior (i.e. the
feature) later in a backwards compatible way. We could provide a
non-zerocopy beahvior with a RAP_ALLOW_COW flag that would then turn
the -EBUSY error into a copy.
It's also more complex to implement the cow now, so it would make the
code that really matters, harder to review. So it may be preferable to
extend this later in a backwards compatible way with a new
RAP_ALLOW_COW flag.
The current handling the flags is already written in a way that should
allow backwards compatible extension with RAP_ALLOW_*:
#define RAP_ALLOW_SRC_HOLES (1UL<<0)
SYSCALL_DEFINE4(remap_anon_pages,
unsigned long, dst_start, unsigned long, src_start,
unsigned long, len, unsigned long, flags)
[..]
long err = -EINVAL;
[..]
if (flags & ~RAP_ALLOW_SRC_HOLES)
return 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: 2014-10-07 15:10:45
Hello,
On Tue, Oct 07, 2014 at 08:47:59AM -0400, Linus Torvalds wrote:
On Mon, Oct 6, 2014 at 12:41 PM, Andrea Arcangeli [off-list ref] wrote:
quoted
Of course if somebody has better ideas on how to resolve an anonymous
userfault they're welcome.
So I'd *much* rather have a "write()" style interface (ie _copying_
bytes from user space into a newly allocated page that gets mapped)
than a "remap page" style interface
remapping anonymous pages involves page table games that really aren't
necessarily a good idea, and tlb invalidates for the old page etc.
Just don't do it.
I see what you mean. The only cons I see is that we couldn't use then
recv(tmp_addr, PAGE_SIZE), remap_anon_pages(faultaddr, tmp_addr,
PAGE_SIZE, ..) and retain the zerocopy behavior. Or how could we?
There's no recvfile(userfaultfd, socketfd, PAGE_SIZE).
Ideally if we could prevent the page data coming from the network to
ever become visible in the kernel we could avoid the TLB flush and
also be zerocopy but I can't see how we could achieve that.
The page data could come through a ssh pipe or anything (qemu supports
all kind of network transports for live migration), this is why
leaving the network protocol into userland is preferable.
As things stands now, I'm afraid with a write() syscall we couldn't do
it zerocopy. We'd still need to receive the memory in a temporary page
and then copy it to a kernel page (invisible to userland while we
write to it) to later map into the userfault address.
If it wasn't for the TLB flush of the old page, the remap_anon_pages
variant would be more optimal than doing a copy through a write
syscall. Is the copy cheaper than a TLB flush? I probably naively
assumed the TLB flush was always cheaper.
Now another idea that comes to mind to be able to add the ability to
switch between copy and TLB flush is using a RAP_FORCE_COPY flag, that
would then do a copy inside remap_anon_pages and leave the original
page mapped in place... (and such flag would also disable the -EBUSY
error if page_mapcount is > 1).
So then if the RAP_FORCE_COPY flag is set remap_anon_pages would
behave like you suggested (but with a mremap-like interface, instead
of a write syscall) and we could benchmark the difference between copy
and TLB flush too. We could even periodically benchmark it at runtime
and switch over the faster method (the more CPUs there are in the host
and the more threads the process has, the faster the copy will be
compared to the TLB flush).
Of course in terms of API I could implement the exact same mechanism
as described above for remap_anon_pages inside a write() to the
userfaultfd (it's a pseudo inode). It'd need two different commands to
prepare for the coming write (with a len multiple of PAGE_SIZE) to
know the address where the page should be mapped into and if to behave
zerocopy or if to skip the TLB flush and copy.
Because the copy vs TLB flush trade off is possible to achieve with
both interfaces, I think it really boils down to choosing between a
mremap like interface, or file+commands protocol interface. I tend to
like mremap more, that's why I opted for a remap_anon_pages syscall
kept orthogonal to the userfaultfd functionality (remap_anon_pages
could be also used standalone as an accelerated mremap in some
circumstances) but nothing prevents to just embed the same mechanism
inside userfaultfd if a file+commands API is preferable. Or we could
add a different syscall (separated from userfaultfd) that creates
another pseudofd to write a command plus the page data into it. Just I
wouldn't see the point of creating a pseudofd just to copy a page
atomically, the write() syscall would look more ideal if the
userfaultfd is already open for other reasons and the pseudofd
overhead is required anyway.
Last thing to keep in mind is that if using userfaults with SIGBUS and
without userfaultfd, remap_anon_pages would have been still useful, so
if we retain the SIGBUS behavior for volatile pages and we don't force
the usage for userfaultfd, it may be cleaner not to use userfaultfd
but a separate pseudofd to do the write() syscall though. Otherwise
the app would need to open the userfaultfd to resolve the fault even
though it's not using the userfaultfd protocol which doesn't look an
intuitive interface to me.
Comments welcome.
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>
From: Kirill A. Shutemov <hidden> Date: 2014-10-07 15:23:00
On Tue, Oct 07, 2014 at 03:24:58PM +0200, Andrea Arcangeli wrote:
Hi Kirill,
On Tue, Oct 07, 2014 at 01:36:45PM +0300, Kirill A. Shutemov wrote:
quoted
On Fri, Oct 03, 2014 at 07:07:58PM +0200, Andrea Arcangeli wrote:
quoted
MADV_USERFAULT is a new madvise flag that will set VM_USERFAULT in the
vma flags. Whenever VM_USERFAULT is set in an anonymous vma, if
userland touches a still unmapped virtual address, a sigbus signal is
sent instead of allocating a new page. The sigbus signal handler will
then resolve the page fault in userland by calling the
remap_anon_pages syscall.
Hm. I wounder if this functionality really fits madvise(2) interface: as
far as I understand it, it provides a way to give a *hint* to kernel which
may or may not trigger an action from kernel side. I don't think an
application will behaive reasonably if kernel ignore the *advise* and will
not send SIGBUS, but allocate memory.
I would suggest to consider to use some other interface for the
functionality: a new syscall or, perhaps, mprotect().
I didn't feel like adding PROT_USERFAULT to mprotect, which looks
hardwired to just these flags:
PROT_NOALLOC may be?
PROT_NONE The memory cannot be accessed at all.
PROT_READ The memory can be read.
PROT_WRITE The memory can be modified.
PROT_EXEC The memory can be executed.
To be complete: PROT_GROWSDOWN, PROT_GROWSUP and unused PROT_SEM.
So here somebody should comment and choose between:
1) set VM_USERFAULT with mprotect(PROT_USERFAULT) instead of
the current madvise(MADV_USERFAULT)
2) drop MADV_USERFAULT and VM_USERFAULT and force the usage of the
userfaultfd protocol as the only way for userland to catch
userfaults (each userfaultfd must already register itself into its
own virtual memory ranges so it's a trivial change for userfaultfd
users that deletes just 1 or 2 lines of userland code, but it would
prevent to use the SIGBUS behavior with info->si_addr=faultaddr for
other users)
3) keep things as they are now: use MADV_USERFAULT for SIGBUS
userfaults, with optional intersection between the
vm_flags&VM_USERFAULT ranges and the userfaultfd registered ranges
with vma->vm_userfaultfd_ctx!=NULL to know if to engage the
userfaultfd protocol instead of the plain SIGBUS
4) new syscall?
I will update the code accordingly to feedback, so please comment.
I don't have strong points on this. Just *feel* it doesn't fit advice
semantics.
The only userspace interface I've designed was not proven good by time.
I would listen what senior maintainers say. :)
--
Kirill A. Shutemov
--
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: 2014-10-07 15:54:10
On Tue, Oct 07, 2014 at 04:19:13PM +0200, Andrea Arcangeli wrote:
mremap like interface, or file+commands protocol interface. I tend to
like mremap more, that's why I opted for a remap_anon_pages syscall
kept orthogonal to the userfaultfd functionality (remap_anon_pages
could be also used standalone as an accelerated mremap in some
circumstances) but nothing prevents to just embed the same mechanism
Sorry for the self followup, but something else comes to mind to
elaborate this further.
In term of interfaces, the most efficient I could think of to minimize
the enter/exit kernel, would be to append the "source address" of the
data received from the network transport, to the userfaultfd_write()
command (by appending 8 bytes to the wakeup command). Said that,
mixing the mechanism to be notified about userfaults with the
mechanism to resolve an userfault to me looks a complication. I kind
of liked to keep the userfaultfd protocol is very simple and doing
just its thing. The userfaultfd doesn't need to know how the userfault
was resolved, even mremap would work theoretically (until we run out
of vmas). I thought it was simpler to keep it that way. However if we
want to resolve the fault with a "write()" syscall this may be the
most efficient way to do it, as we're already doing a write() into the
pseudofd to wakeup the page fault that contains the destination
address, I just need to append the source address to the wakeup command.
I probably grossly overestimated the benefits of resolving the
userfault with a zerocopy page move, sorry. So if we entirely drop the
zerocopy behavior and the TLB flush of the old page like you
suggested, the way to keep the userfaultfd mechanism decoupled from
the userfault resolution mechanism would be to implement an
atomic-copy syscall. That would work for SIGBUS userfaults too without
requiring a pseudofd then. It would be enough then to call
mcopy_atomic(userfault_addr,tmp_addr,len) with the only constraints
that len must be a multiple of PAGE_SIZE. Of course mcopy_atomic
wouldn't page fault or call GUP into the destination address (it can't
otherwise the in-flight partial copy would be visible to the process,
breaking the atomicity of the copy), but it would fill in the
pte/trans_huge_pmd with the same strict behavior that remap_anon_pages
currently has (in turn it would by design bypass the VM_USERFAULT
check and be ideal for resolving userfaults).
mcopy_atomic could then be also extended to tmpfs and it would work
without requiring the source page to be a tmpfs page too without
having to convert page types on the fly.
If I add mcopy_atomic, the patch in subject (10/17) can be dropped of
course so it'd be even less intrusive than the current
remap_anon_pages and it would require zero TLB flush during its
runtime (it would just require an atomic copy).
So should I try to embed a mcopy_atomic inside userfault_write or can
I expose it to userland as a standalone new syscall? Or should I do
something different? Comments?
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>
From: Andy Lutomirski <luto@amacapital.net> Date: 2014-10-07 15:55:24
On Tue, Oct 7, 2014 at 8:52 AM, Andrea Arcangeli [off-list ref] wrote:
On Tue, Oct 07, 2014 at 04:19:13PM +0200, Andrea Arcangeli wrote:
quoted
mremap like interface, or file+commands protocol interface. I tend to
like mremap more, that's why I opted for a remap_anon_pages syscall
kept orthogonal to the userfaultfd functionality (remap_anon_pages
could be also used standalone as an accelerated mremap in some
circumstances) but nothing prevents to just embed the same mechanism
Sorry for the self followup, but something else comes to mind to
elaborate this further.
In term of interfaces, the most efficient I could think of to minimize
the enter/exit kernel, would be to append the "source address" of the
data received from the network transport, to the userfaultfd_write()
command (by appending 8 bytes to the wakeup command). Said that,
mixing the mechanism to be notified about userfaults with the
mechanism to resolve an userfault to me looks a complication. I kind
of liked to keep the userfaultfd protocol is very simple and doing
just its thing. The userfaultfd doesn't need to know how the userfault
was resolved, even mremap would work theoretically (until we run out
of vmas). I thought it was simpler to keep it that way. However if we
want to resolve the fault with a "write()" syscall this may be the
most efficient way to do it, as we're already doing a write() into the
pseudofd to wakeup the page fault that contains the destination
address, I just need to append the source address to the wakeup command.
I probably grossly overestimated the benefits of resolving the
userfault with a zerocopy page move, sorry. So if we entirely drop the
zerocopy behavior and the TLB flush of the old page like you
suggested, the way to keep the userfaultfd mechanism decoupled from
the userfault resolution mechanism would be to implement an
atomic-copy syscall. That would work for SIGBUS userfaults too without
requiring a pseudofd then. It would be enough then to call
mcopy_atomic(userfault_addr,tmp_addr,len) with the only constraints
that len must be a multiple of PAGE_SIZE. Of course mcopy_atomic
wouldn't page fault or call GUP into the destination address (it can't
otherwise the in-flight partial copy would be visible to the process,
breaking the atomicity of the copy), but it would fill in the
pte/trans_huge_pmd with the same strict behavior that remap_anon_pages
currently has (in turn it would by design bypass the VM_USERFAULT
check and be ideal for resolving userfaults).
At the risk of asking a possibly useless question, would it make sense
to splice data into a userfaultfd?
--Andy
mcopy_atomic could then be also extended to tmpfs and it would work
without requiring the source page to be a tmpfs page too without
having to convert page types on the fly.
If I add mcopy_atomic, the patch in subject (10/17) can be dropped of
course so it'd be even less intrusive than the current
remap_anon_pages and it would require zero TLB flush during its
runtime (it would just require an atomic copy).
So should I try to embed a mcopy_atomic inside userfault_write or can
I expose it to userland as a standalone new syscall? Or should I do
something different? Comments?
Thanks,
Andrea
--
Andy Lutomirski
AMA Capital Management, LLC
--
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: Peter Feiner <hidden> Date: 2014-10-07 16:13:27
On Tue, Oct 07, 2014 at 05:52:47PM +0200, Andrea Arcangeli wrote:
I probably grossly overestimated the benefits of resolving the
userfault with a zerocopy page move, sorry. [...]
For posterity, I think it's worth noting that most expensive aspect of a TLB
shootdown is the interprocessor interrupt necessary to flush other CPUs' TLBs.
On a many-core machine, copying 4K of data looks pretty cheap compared to
taking an interrupt and invalidating TLBs on many cores :-)
[...] So if we entirely drop the
zerocopy behavior and the TLB flush of the old page like you
suggested, the way to keep the userfaultfd mechanism decoupled from
the userfault resolution mechanism would be to implement an
atomic-copy syscall. That would work for SIGBUS userfaults too without
requiring a pseudofd then. It would be enough then to call
mcopy_atomic(userfault_addr,tmp_addr,len) with the only constraints
that len must be a multiple of PAGE_SIZE. Of course mcopy_atomic
wouldn't page fault or call GUP into the destination address (it can't
otherwise the in-flight partial copy would be visible to the process,
breaking the atomicity of the copy), but it would fill in the
pte/trans_huge_pmd with the same strict behavior that remap_anon_pages
currently has (in turn it would by design bypass the VM_USERFAULT
check and be ideal for resolving userfaults).
mcopy_atomic could then be also extended to tmpfs and it would work
without requiring the source page to be a tmpfs page too without
having to convert page types on the fly.
If I add mcopy_atomic, the patch in subject (10/17) can be dropped of
course so it'd be even less intrusive than the current
remap_anon_pages and it would require zero TLB flush during its
runtime (it would just require an atomic copy).
I like this new approach. It will be good to have a single interface for
resolving anon and tmpfs userfaults.
So should I try to embed a mcopy_atomic inside userfault_write or can
I expose it to userland as a standalone new syscall? Or should I do
something different? Comments?
One interesting (ab)use of userfault_write would be that the faulting process
and the fault-handling process could be different, which would be necessary
for post-copy live migration in CRIU (http://criu.org).
Aside from the asthetic difference, I can't think of any advantage in favor of
a syscall.
Peter
On Tue, Oct 7, 2014 at 10:19 AM, Andrea Arcangeli [off-list ref] wrote:
I see what you mean. The only cons I see is that we couldn't use then
recv(tmp_addr, PAGE_SIZE), remap_anon_pages(faultaddr, tmp_addr,
PAGE_SIZE, ..) and retain the zerocopy behavior. Or how could we?
There's no recvfile(userfaultfd, socketfd, PAGE_SIZE).
You're doing completelt faulty math, and you haven't thought it through.
Your "zero-copy" case is no such thing. Who cares if some packet
receive is zero-copy, when you need to set up the anonymous page to
*receive* the zero copy into, which involves page allocation, page
zeroing, page table setup with VM and page table locking, etc etc.
The thing is, the whole concept of "zero-copy" is pure and utter
bullshit. Sun made a big deal about the whole concept back in the
nineties, and IT DOES NOT WORK. It's a scam. Don't buy into it. It's
crap. It's made-up and not real.
Then, once you've allocated and cleared the page, mapped it in, your
"zero-copy" model involves looking up the page in the page tables
again (locking etc), then doing that zero-copy to the page. Then, when
you remap it, you look it up in the page tables AGAIN, with locking,
move it around, have to clear the old page table entry (which involves
a locked cmpxchg64), a TLB flush with most likely a cross-CPU IPI -
since the people who do this are all threaded and want many CPU's, and
then you insert the page into the new place.
That's *insane*. It's crap. All just to try to avoid one page copy.
Don't do it. remapping games really are complete BS. They never beat
just copying the data. It's that simple.
As things stands now, I'm afraid with a write() syscall we couldn't do
it zerocopy.
Really, you need to rethink your whole "zerocopy" model. It's broken.
Nobody sane cares. You've bought into a model that Sun already showed
doesn't work.
The only time zero-copy works is in random benchmarks that are very
careful to not touch the data at all at any point, and also try to
make sure that the benchmark is very much single-threaded so that you
never have the whole cross-CPU IPI issue for the TLB invalidate. Then,
and only then, can zero-copy win. And it's just not a realistic
scenario.
If it wasn't for the TLB flush of the old page, the remap_anon_pages
variant would be more optimal than doing a copy through a write
syscall. Is the copy cheaper than a TLB flush? I probably naively
assumed the TLB flush was always cheaper.
A local TLB flush is cheap. That's not the problem. The problem is the
setup of the page, and the clearing of the page, and the cross-CPU TLB
flush. And the page table locking, etc etc.
So no, I'm not AT ALL worried about a single "invlpg" instruction.
That's nothing. Local CPU TLB flushes of single pages are basically
free. But that really isn't where the costs are.
Quite frankly, the *only* time page remapping has ever made sense is
when it is used for realloc() kind of purposes, where you need to
remap pages not because of zero-copy, but because you need to change
the virtual address space layout. And then you make sure it's not a
common operation, because you're not doing it as a performance win,
you're doing it because you're changing your virtual layout.
Really. Zerocopy is for benchmarks, and for things like "splice()"
when you can avoid the page tables *entirely*. But the notion that
page remapping of user pages is faster than a copy is pure and utter
garbage. It's simply not true.
So I really think you should aim for a "write()": kind of interface.
With write, you may not get the zero-copy, but on the other hand it
allows you to re-use the source buffer many times without having to
allocate new pages and map it in etc. So a "read()+write()" loop (or,
quite commonly a "generate data computationally from a compressed
source + write()" loop) is actually much more efficient than the
zero-copy remapping, because you don't have all the complexities and
overheads in creating the source page.
It is possible that that could involve "splice()" too, although I
don't really think the source data tends to be in page-aligned chunks.
But hey, splice() at least *can* be faster than copying (and then we
have vmsplice() not because it's magically faster, but because it can
under certain circumstances be worth it, and it kind of made sense to
allow the interface, but I really don't think it's used very much or
very useful).
Linus
From: Paolo Bonzini <pbonzini@redhat.com> Date: 2014-10-07 17:15:48
Il 07/10/2014 19:07, Dr. David Alan Gilbert ha scritto:
quoted
quoted
So I'd *much* rather have a "write()" style interface (ie _copying_
bytes from user space into a newly allocated page that gets mapped)
than a "remap page" style interface
Something like that might work for the postcopy case; it doesn't work
for some of the other uses that need to stop a page being changed by the
guest, but then need to somehow get a copy of that page internally to QEMU,
and perhaps provide it back later.
I cannot parse this. Which uses do you have in mind? Is it for
QEMU-specific or is it for other applications of userfaults?
As long as the page is atomically mapped, I'm not sure what the
difference from remap_anon_pages are (as far as the destination page is
concerned). Are you thinking of having userfaults enabled on the source
as well?
Paolo
remap_anon_pages worked for those cases
as well; I can't think of another current way of doing it in userspace.
--
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: Dr. David Alan Gilbert <hidden> Date: 2014-10-07 17:26:43
* Paolo Bonzini (pbonzini@redhat.com) wrote:
Il 07/10/2014 19:07, Dr. David Alan Gilbert ha scritto:
quoted
quoted
quoted
So I'd *much* rather have a "write()" style interface (ie _copying_
bytes from user space into a newly allocated page that gets mapped)
than a "remap page" style interface
Something like that might work for the postcopy case; it doesn't work
for some of the other uses that need to stop a page being changed by the
guest, but then need to somehow get a copy of that page internally to QEMU,
and perhaps provide it back later.
I cannot parse this. Which uses do you have in mind? Is it for
QEMU-specific or is it for other applications of userfaults?
As long as the page is atomically mapped, I'm not sure what the
difference from remap_anon_pages are (as far as the destination page is
concerned). Are you thinking of having userfaults enabled on the source
as well?
What I'm talking about here is when I want to stop a page being accessed by the
guest, do something with the data in qemu, and give it back to the guest sometime
later.
The main example is: Memory pools for guests where you swap RAM between a series of
VM hosts. You have to take the page out, send it over the wire, sometime later if
the guest tries to access it you userfault and pull it back.
(There's at least one or two companies selling something like this, and at least
one Linux based implementations with their own much more involved kernel hacks)
Dave
--
Dr. David Alan Gilbert / dgilbert@redhat.com / Manchester, UK
--
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 Mon, Oct 6, 2014 at 12:41 PM, Andrea Arcangeli [off-list ref] wrote:
quoted
Of course if somebody has better ideas on how to resolve an anonymous
userfault they're welcome.
So I'd *much* rather have a "write()" style interface (ie _copying_
bytes from user space into a newly allocated page that gets mapped)
than a "remap page" style interface
Something like that might work for the postcopy case; it doesn't work
for some of the other uses that need to stop a page being changed by the
guest, but then need to somehow get a copy of that page internally to QEMU,
and perhaps provide it back later. remap_anon_pages worked for those cases
as well; I can't think of another current way of doing it in userspace.
I'm thinking here of systems for making VMs with memory larger than a single
host; that's something that's not as well thought out. I've also seen people
writing emulation that want to trap and emulate some page accesses while
still having the original data available to the emulator itself.
So yes, OK for now, but the result is less general.
Dave
remapping anonymous pages involves page table games that really aren't
necessarily a good idea, and tlb invalidates for the old page etc.
Just don't do it.
Linus
--
Dr. David Alan Gilbert / dgilbert@redhat.com / Manchester, UK
--
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>
@@ -121,7 +121,7 @@ extern void split_huge_page_pmd_mm(struct mm_struct *mm, unsigned long address,#error "hugepages can't be allocated by the buddy allocator"#endifexterninthugepage_madvise(structvm_area_struct*vma,-unsignedlong*vm_flags,intadvice);+vm_flags_t*vm_flags,intadvice);externvoid__vma_adjust_trans_huge(structvm_area_struct*vma,unsignedlongstart,unsignedlongend,
@@ -239,7 +239,7 @@ static unsigned long move_vma(struct vm_area_struct *vma,{structmm_struct*mm=vma->vm_mm;structvm_area_struct*new_vma;-unsignedlongvm_flags=vma->vm_flags;+vm_flags_tvm_flags=vma->vm_flags;unsignedlongnew_pgoff;unsignedlongmoved_len;unsignedlongexcess=0;--
To unsubscribe, send a message with 'unsubscribe linux-mm' in
the body to majordomo-Bw31MaZKKs0EbZ0PF+XxCw@public.gmane.org For more info on Linux MM,
see: http://www.linux-mm.org/ .
Don't email: <a href=mailto:"dont-Bw31MaZKKs3YtjvyW6yDsg@public.gmane.org"> email-Bw31MaZKKs3YtjvyW6yDsg@public.gmane.org </a>