Userfaultfd
๊ฐ์ (Overview)
Userfaultfd๋ Linux ์ปค๋์์ ์ ๊ณตํ๋ ์ฌ์ฉ์ ์์ค ํ์ด์ง ํดํธ ์ฒ๋ฆฌ ๋ฉ์ปค๋์ฆ์ ๋๋ค. ์ผ๋ฐ์ ์ธ ํ์ด์ง ํดํธ๋ ์ปค๋์ด ์๋์ผ๋ก ํด๊ฒฐํ์ง๋ง, userfaultfd๋ฅผ ์ฌ์ฉํ๋ฉด ์ปค๋์ด ํดํธ๋ฅผ ํด๊ฒฐํ๋ ๋์ ์ฌ์ฉ์ ๊ณต๊ฐ ํธ๋ค๋ฌ์๊ฒ ์์ํฉ๋๋ค. ์ด๋ ๊ฐ์ ๋จธ์ (VMM), ์ค์๊ฐ ๋ฐ์ดํฐ ์ด๋, ๋ฉ๋ชจ๋ฆฌ ์ ์ฑ ์ ์ฉ ๋ฑ์์ ์ ์ฉํ๊ฒ ์ฌ์ฉ๋ฉ๋๋ค.
์ฌ์ฉ์ ๊ณต๊ฐ์ userfaultfd() ์์คํ
์ฝ๋ก ํ์ผ ๋์คํฌ๋ฆฝํฐ๋ฅผ ์์ฑํ ํ, UFFDIO_REGISTER๋ก ๋ชจ๋ํฐ๋งํ ๋ฉ๋ชจ๋ฆฌ ์์ญ์ ๋ฑ๋กํฉ๋๋ค. ์ดํ ํด๋น ์์ญ์์ ํ์ด์ง ํดํธ๊ฐ ๋ฐ์ํ๋ฉด ์ปค๋์ handle_userfault()๋ฅผ ํตํด ์ฌ์ฉ์ ํธ๋ค๋ฌ์๊ฒ uffd_msg๋ฅผ ์ ๋ฌํ๊ณ , ์ฌ์ฉ์๋ UFFDIO_COPY, UFFDIO_WRITEPROTECT, UFFDIO_CONTINUE ๋ฑ์ ioctl๋ก ํดํธ๋ฅผ ํด๊ฒฐํฉ๋๋ค. Linux 7.0์์๋ UFFDIO_MOVE(zero-copy ํ์ด์ง ์ด๋)์ UFFDIO_POISON(์ค์ผ ๋ง์ปค) ๊ธฐ๋ฅ์ด ์ถ๊ฐ๋์ด ๋ ์ ๊ตํ ๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ๊ฐ ๊ฐ๋ฅํฉ๋๋ค.
์ผ์์ ์ธ ๋น์ ๋ก ๋ณด๋ฉด userfaultfd๋ ๊ฑด๋ฌผ ๊ด๋ฆฌ์ธ์ด ๋ชจ๋ ๋ฏผ์์ ์ง์ ์ฒ๋ฆฌํ์ง ์๊ณ , ํน์ ๋ฐฉ์์ ๋ฌธ์ ๊ฐ ์๊ธฐ๋ฉด ์ ๋ด ๋ด๋น์์๊ฒ ํธ์ถ ๋ฒจ์ ์ธ๋ฆฌ๋ ๊ตฌ์กฐ์ ๋น์ทํฉ๋๋ค. ํดํธ๋ฅผ ๋ธ ์ค๋ ๋๋ ์ ์ ๋ฉ์ถ๊ณ , ์ฌ์ฉ์ ๊ณต๊ฐ ํธ๋ค๋ฌ๊ฐ ํ์ํ ํ์ด์ง๋ฅผ ๋ณต์ฌํ๊ฑฐ๋ ๊ธฐ์กด ํ์ด์ง๋ฅผ ์ฐ๊ฒฐํ๊ฑฐ๋ ์ฐ๊ธฐ ๋ณดํธ๋ฅผ ํด์ ํ๋ฉด ์ปค๋์ด ๋ค์ ์ค๋ ๋๋ฅผ ๊นจ์ ๊ฐ์ ์ฃผ์ ์ ๊ทผ์ ์ฌ์๋ํ๊ฒ ํฉ๋๋ค.
์์ค ํ์ผ:
mm/userfaultfd.c โ ์ปค๋ ์ธก mfill_atomic/move/writeprotect ๋ก์ง (2116์ค)
fs/userfaultfd.c โ ์์คํ
์ฝ, ioctl, handle_userfault, read/poll (2233์ค)
include/linux/userfaultfd_k.h โ userfaultfd_ctx, mfill_atomic_mode, API ์ ์ธ (468์ค)
include/uapi/linux/userfaultfd.h โ UAPI ioctl ์ ์, uffd_msg, uffdio_* ๊ตฌ์กฐ์ฒด (386์ค)
include/linux/mm_types.h โ vm_userfaultfd_ctx (VMA์ ๋ฑ๋ก)
Userfaultfd ํธ์ถ ํ๋ฆ
ํต์ฌ ์๋ฃ๊ตฌ์กฐ ๊ด๊ณ๋
๋น ๋ฅธ ์ ๊ฒ ๋ช ๋ น
# 1. userfaultfd ์ปค๋ ์ง์ ํ์ธ
grep CONFIG_USERFAULTFD /boot/config-$(uname -r) 2>/dev/null || zgrep CONFIG_USERFAULTFD /proc/config.gz 2>/dev/null
# 2. ํ์ฌ ์์คํ
์ userfaultfd ๊ด๋ จ ํ๋ก์ธ์ค ํ์ธ
grep -l userfaultfd /proc/*/fdinfo/* 2>/dev/null | head -5
# 3. userfaultfd ์์คํ
์ฝ ์กด์ฌ ํ์ธ
ausyscall userfaultfd 2>/dev/null || grep -r userfaultfd /usr/include/asm/unistd*.h 2>/dev/null
# 4. /dev/userfaultfd ๋๋ฐ์ด์ค ํ์ธ
ls -la /dev/userfaultfd 2>/dev/null
# 5. sysctl ์ค์ ํ์ธ (๋นํน๊ถ userfaultfd ํ์ฉ ์ฌ๋ถ)
sysctl vm.unprivileged_userfaultfd 2>/dev/null
# 6. perf๋ก userfaultfd ๊ด๋ จ ์ด๋ฒคํธ ๊ด์ฐฐ
perf stat -e userfaultfd:uffd_msg -a sleep 1 2>/dev/null || echo "tracepoint not available"
# 7. ์ปค๋์์ userfaultfd ๊ด๋ จ ์ฌ๋ณผ ํ์ธ
cat /proc/kallsyms | grep -E "handle_userfault|mfill_atomic|move_pages" | head -10
# 8. CONFIG_USERFAULTFD ์ปดํ์ผ ์ต์
ํ์ธ
grep -r "CONFIG_USERFAULTFD" /lib/modules/$(uname -r)/build/.config 2>/dev/null || \
zcat /proc/config.gz 2>/dev/null | grep CONFIG_USERFAULTFD
# 9. userfaultfd ๊ด๋ จ ๋ชจ๋ ๋ก๋ ์ํ ํ์ธ
lsmod | grep -i userfaultfd 2>/dev/null; echo " Built-in: $(grep CONFIG_USERFAULTFD=y /boot/config-$(uname -r) 2>/dev/null | wc -l)"
# 10. strace๋ก userfaultfd ์์คํ
์ฝ ์ฌ์ฉ ์ถ์
strace -e userfaultfd,ioctl -f <ํ๋ก์ธ์คPID> 2>&1 | grep -E "UFFDIO|userfaultfd" | head -5
# 11. userfaultfd ๊ด๋ จ /proc ์ธํฐํ์ด์ค ํ์ธ
cat /proc/vmstat | grep -E "uffd|userfault" 2>/dev/null || echo "no uffd vmstat counters"
# 12. UFFD ๊ธฐ๋ฅ ํ๋๊ทธ ๋น๋ ์ ์ง์ ์ฌ๋ถ ํ์ธ
grep -E "UFFD_FEATURE_" include/uapi/linux/userfaultfd.h | head -20
ํต์ฌ ์๋ฃ๊ตฌ์กฐ
struct userfaultfd_ctx
userfaultfd์ ํต์ฌ ์ปจํ ์คํธ ๊ตฌ์กฐ์ฒด์ ๋๋ค. ๋ค ๊ฐ์ wait queue head๋ฅผ ํตํด ์ปค๋-์ฌ์ฉ์ ๊ฐ ์ด๋ฒคํธ ํต์ ์ ๊ด๋ฆฌํฉ๋๋ค.
// include/linux/userfaultfd_k.h:49-79
struct userfaultfd_ctx {
/* ๋๊ธฐ ์ค์ธ (์ฝํ์ง ์์) ํดํธ๋ฅผ ์ํ waitqueue */
wait_queue_head_t fault_pending_wqh;
/* ์ฒ๋ฆฌ ์ค์ธ ํดํธ๋ฅผ ์ํ waitqueue */
wait_queue_head_t fault_wqh;
/* poll/read๋ฅผ ๊นจ์ฐ๊ธฐ ์ํ pseudo fd waitqueue */
wait_queue_head_t fd_wqh;
/* ์ด๋ฒคํธ(fork, remap, remove)๋ฅผ ์ํ waitqueue */
wait_queue_head_t event_wqh;
/* refile ์ํ์ค ์นด์ดํธ (fault_pending_wqh lock ๋ณดํธ) */
seqcount_spinlock_t refile_seq;
/* pseudo fd ์ฐธ์กฐ ์นด์ดํธ */
refcount_t refcount;
/* userfaultfd ์์คํ
์ฝ ํ๋๊ทธ (UFFD_USER_MODE_ONLY ๋ฑ) */
unsigned int flags;
/* ์ฌ์ฉ์ ๊ณต๊ฐ์ด ์์ฒญํ ๊ธฐ๋ฅ (UFFD_FEATURE_*) */
unsigned int features;
/* ํด์ ์ฌ๋ถ */
bool released;
/* ๋นํ๋ ฅ์ ์ด๋ฒคํธ(mremap ๋ฑ) ์ค mfill/move/wp ์ฐจ๋จ์ฉ */
struct rw_semaphore map_changing_lock;
/* ๋ฉ๋ชจ๋ฆฌ ๋งคํ ๋ณ๊ฒฝ ์ค ํ๋๊ทธ */
atomic_t mmap_changing;
/* ์ด userfaultfd์ ์ฐ๊ฒฐ๋ mm */
struct mm_struct *mm;
};
struct vm_userfaultfd_ctx
VMA(vm_area_struct)์ userfaultfd๋ฅผ ๋ฑ๋กํ ๋ ์ฌ์ฉ๋๋ ์ฐ๊ฒฐ ๊ตฌ์กฐ์ฒด์
๋๋ค.
// include/linux/mm_types.h:718-722
#ifdef CONFIG_USERFAULTFD
#define NULL_VM_UFFD_CTX ((struct vm_userfaultfd_ctx) { NULL, })
struct vm_userfaultfd_ctx {
struct userfaultfd_ctx *ctx; // ์ค์ userfaultfd_ctx ํฌ์ธํฐ
};
#endif
struct uffd_msg (read()๋ก ์ ๋ฌ๋๋ ๋ฉ์์ง)
// include/uapi/linux/userfaultfd.h:108-146
struct uffd_msg {
__u8 event;
__u8 reserved1;
__u16 reserved2;
__u32 reserved3;
union {
struct {
__u64 flags;
__u64 address;
union {
__u32 ptid;
} feat;
} pagefault;
struct {
__u32 ufd;
} fork;
struct {
__u64 from;
__u64 to;
__u64 len;
} remap;
struct {
__u64 start;
__u64 end;
} remove;
struct {
/* ์ฌ์ฉํ์ง ์๋ ์์ฝ ํ๋ */
__u64 reserved1;
__u64 reserved2;
__u64 reserved3;
} reserved;
} arg;
} __packed;
enum mfill_atomic_mode
mfill_atomic ํจ์์์ ์ฌ์ฉํ๋ ์์ ๋ชจ๋์ ๋๋ค.
// include/linux/userfaultfd_k.h:87-93
enum mfill_atomic_mode {
MFILL_ATOMIC_COPY, // ์์ค์์ ๋์์ผ๋ก ํ์ด์ง ๋ณต์ฌ
MFILL_ATOMIC_ZEROPAGE, // ์(0) ํ์ด์ง ๋งคํ
MFILL_ATOMIC_CONTINUE, // shmem ๊ธฐ์กด ํ์ด์ง ์ฐ๊ฒฐ (UFFDIO_CONTINUE)
MFILL_ATOMIC_POISON, // ์ค์ผ๋ ํ์ด์ง ๋ง์ปค ์ค์น (UFFDIO_POISON)
NR_MFILL_ATOMIC_MODES,
};
struct uffdio_copy (UFFDIO_COPY ioctl ์ธ์)
// include/uapi/linux/userfaultfd.h:275-294
struct uffdio_copy {
__u64 dst;
__u64 src;
__u64 len;
#define UFFDIO_COPY_MODE_DONTWAKE ((__u64)1<<0)
/*
* UFFDIO_COPY_MODE_WP๋ ํ์ด์ง๋ฅผ ์ฆ์ write-protected ์ํ๋ก ๋งคํํ๋ค.
* ์ด ๋ชจ๋๋ uffdio_register.ioctls์ ๋ฐ๋ผ ํด๋น ๋ฒ์์ write protect
* ioctl์ด ๊ตฌํ๋์ด ์์ ๋๋ง ์ฌ์ฉํ ์ ์๋ค.
*/
#define UFFDIO_COPY_MODE_WP ((__u64)1<<1)
__u64 mode;
/* "copy"๋ ioctl์ด ์ฐ๋ฉฐ ๋ง์ง๋ง 8๋ฐ์ดํธ๋ copy_from_user๊ฐ ์ฝ์ง ์๋๋ค. */
__s64 copy;
};
struct uffdio_move (UFFDIO_MOVE ioctl ์ธ์)
// include/uapi/linux/userfaultfd.h:359-375
struct uffdio_move {
__u64 dst;
__u64 src;
__u64 len;
/*
* ์ฃผ์ ๊ณต๊ฐ์์ ๋ฉ๋ชจ๋ฆฌ๋ฅผ ์์์ ์ผ๋ก ์ ๊ฑฐํ๋ ์ฉ๋๋ก ์ฐ๋ฉด
* dst ๋ฒ์์ wake๋ ํ์ํ์ง ์๋ค.
*/
#define UFFDIO_MOVE_MODE_DONTWAKE ((__u64)1<<0)
#define UFFDIO_MOVE_MODE_ALLOW_SRC_HOLES ((__u64)1<<1)
__u64 mode;
/* "move"๋ ioctl์ด ์ฐ๋ฉฐ ๋ง์ง๋ง 8๋ฐ์ดํธ๋ copy_from_user๊ฐ ์ฝ์ง ์๋๋ค. */
__s64 move;
};
double_pt_lock โ ์ด์ค PTL ์ ๊ธ
๋ ๊ฐ์ ํ์ด์ง ํ ์ด๋ธ ์ ๊ธ(PTL)์ ์์ ํ๊ฒ ํ๋ํ๋ ํฌํผ ํจ์์ ๋๋ค. ๊ฐ์ ์ฃผ์ ์์๋ก ์ ๊ธ์ ํ๋ํ์ฌ lock inversion์ ๋ฐฉ์งํฉ๋๋ค.
// mm/userfaultfd.c:1000-1013
void double_pt_lock(spinlock_t *ptl1, spinlock_t *ptl2)
__acquires(ptl1)
__acquires(ptl2)
{
if (ptl1 > ptl2)
swap(ptl1, ptl2);
/* ๊ฐ์ ์ฃผ์ ์์๋ก ์ ๊ธ์ ํ๋ํ์ฌ lock inversion ๋ฐฉ์ง */
spin_lock(ptl1);
if (ptl1 != ptl2)
spin_lock_nested(ptl2, SINGLE_DEPTH_NESTING);
else
__acquire(ptl2);
}
ํต์ฌ ํจ์
handle_userfault() โ ํ์ด์ง ํดํธ ์ง์ ์
ํ์ด์ง ํดํธ๊ฐ ๋ฐ์ํ์ ๋ ์ปค๋์ด ํธ์ถํ๋ ํจ์์ ๋๋ค. VMA์ ๋ฑ๋ก๋ userfaultfd_ctx๋ฅผ ์ฐพ์ waitqueue์ ๋๊ธฐ ํญ๋ชฉ์ ์ถ๊ฐํ๊ณ , ์ฌ์ฉ์ ํธ๋ค๋ฌ๊ฐ ๊นจ์ฐ๊ธฐ๋ฅผ ๊ธฐ๋ค๋ฆฝ๋๋ค.
// fs/userfaultfd.c:381-558
vm_fault_t handle_userfault(struct vm_fault *vmf, unsigned long reason)
{
struct vm_area_struct *vma = vmf->vma;
struct mm_struct *mm = vma->vm_mm;
struct userfaultfd_ctx *ctx;
struct userfaultfd_wait_queue uwq;
vm_fault_t ret = VM_FAULT_SIGBUS;
bool must_wait;
unsigned int blocking_state;
/* ๋ง์ง๋ง child pid ๊ฐฑ์ ๊ณผ ์ฝ์ด๋คํ ์ค์๋ userfault ์ฒ๋ฆฌ๋ฅผ ํ์ง ์๋๋ค. */
if (current->flags & (PF_EXITING|PF_DUMPCORE))
goto out;
assert_fault_locked(vmf);
ctx = vma->vm_userfaultfd_ctx.ctx;
if (!ctx)
goto out;
VM_WARN_ON_ONCE(ctx->mm != mm);
/* ์ ์ ์๋ ํ๋๊ทธ๋ ๋ฒ๊ทธ๋ค. */
VM_WARN_ON_ONCE(reason & ~__VM_UFFD_FLAGS);
/* ํ๋๊ทธ 0๊ฐ ๋๋ 2๊ฐ ์ด์์ ๋ฒ๊ทธ๋ค. ์ ํํ 1๊ฐ๋ฅผ ๊ธฐ๋ํ๋ค. */
VM_WARN_ON_ONCE(!reason || (reason & (reason - 1)));
if (ctx->features & UFFD_FEATURE_SIGBUS)
goto out;
if (!(vmf->flags & FAULT_FLAG_USER) && (ctx->flags & UFFD_USER_MODE_ONLY))
goto out;
/*
* VM_FAULT_RETRY๋ฅผ ๋ฐํํ ์ ์๋์ง ํ์ธํ๋ค.
*
* VM_UFFD_MISSING ํดํธ์์๋ FAULT_FLAG_ALLOW_RETRY๊ฐ ํ์ํ๋ค.
*/
if (unlikely(!(vmf->flags & FAULT_FLAG_ALLOW_RETRY))) {
/*
* nowait ํธ์ถ์์ ์๋ชป SIGBUS๋ฅผ ๋ฐํํ์ง ์๋๋ก
* nowait์ด๋ฉด retry๊ฐ ํ์ฉ๋์ด์ผ ํ๋ค๋ ๋ถ๋ณ์์ ํ์ธํ๋ค.
*/
VM_WARN_ON_ONCE(vmf->flags & FAULT_FLAG_RETRY_NOWAIT);
#ifdef CONFIG_DEBUG_VM
if (printk_ratelimit()) {
pr_warn("FAULT_FLAG_ALLOW_RETRY missing %x\n",
vmf->flags);
dump_stack();
}
#endif
goto out;
}
/* nowait์ด๋ฉด ๊ธฐ๋ค๋ฆฌ์ง ๋ง๊ณ ์ฌ์๋๋ฅผ ์์ฒญํ๋ค. */
ret = VM_FAULT_RETRY;
if (vmf->flags & FAULT_FLAG_RETRY_NOWAIT)
goto out;
if (unlikely(READ_ONCE(ctx->released))) {
/*
* ๋์ release๊ฐ ๋ณด์ด๋ฉด SIGBUS/NOPAGE ๋์ VM_FAULT_RETRY๋ฅผ
* ๋ฐํํ๊ณ fault lock์ ์ ์ ์ ์ผ๋ก ํด์ ํ๋ค.
*/
release_fault_lock(vmf);
goto out;
}
/* mmap_lock์ ๋๊ธฐ ์ ์ ์ฐธ์กฐ๋ฅผ ์ก๋๋ค. */
userfaultfd_ctx_get(ctx);
init_waitqueue_func_entry(&uwq.wq, userfaultfd_wake_function);
uwq.wq.private = current;
uwq.msg = userfault_msg(vmf->address, vmf->real_address, vmf->flags,
reason, ctx->features);
uwq.ctx = ctx;
uwq.waken = false;
blocking_state = userfaultfd_get_blocking_state(vmf->flags);
/*
* userfaultfd_huge_must_wait()๋ฅผ ์์ ํ๊ฒ ํธ์ถํ๊ธฐ ์ํด
* ์ฌ๊ธฐ์ vma lock์ ์ก๋๋ค. sleep ๊ฐ๋ฅํ vma lock์ ํ์ฌ task
* ์ํ๋ฅผ ๋ฐ๊ฟ ์ ์์ผ๋ฏ๋ก set_current_state()๋ณด๋ค ๋จผ์ ์ํํ๋ค.
*/
if (is_vm_hugetlb_page(vma))
hugetlb_vma_lock_read(vma);
spin_lock_irq(&ctx->fault_pending_wqh.lock);
/* __add_wait_queue ๋ค์๋ poll/read()๋ฅผ ํตํด ์ฌ์ฉ์ ๊ณต๊ฐ์ ๋ณด์ธ๋ค. */
__add_wait_queue(&ctx->fault_pending_wqh, &uwq.wq);
/*
* __set_current_state ๋ค์ smp_mb()๋ spin_unlock ๋ค์ ์ฝ๊ธฐ๊ฐ
* __add_wait_queue์ list_add๋ณด๋ค ๋จผ์ ์คํ๋์ง ์๊ฒ ํ๋ค.
*/
set_current_state(blocking_state);
spin_unlock_irq(&ctx->fault_pending_wqh.lock);
if (is_vm_hugetlb_page(vma)) {
must_wait = userfaultfd_huge_must_wait(ctx, vmf, reason);
hugetlb_vma_unlock_read(vma);
} else {
must_wait = userfaultfd_must_wait(ctx, vmf, reason);
}
release_fault_lock(vmf);
if (likely(must_wait && !READ_ONCE(ctx->released))) {
wake_up_poll(&ctx->fd_wqh, EPOLLIN);
schedule();
}
__set_current_state(TASK_RUNNING);
/*
* userfaultfd_ctx_read()๊ฐ ๋ ๋ฆฌ์คํธ ์ฌ์ด์์ refileํ๋ ๋์์๋
* list_empty_careful()์ด stack์ uwq๋ฅผ ์์ ํ๊ฒ ํ์ธํ ์ ์๋ค.
*/
if (!list_empty_careful(&uwq.wq.entry)) {
spin_lock_irq(&ctx->fault_pending_wqh.lock);
/* stack์ uwq๋ ๊ณง ์ฌ๋ผ์ง๋ฏ๋ก list_del_init()์ ํ์ ์๋ค. */
list_del(&uwq.wq.entry);
spin_unlock_irq(&ctx->fault_pending_wqh.lock);
}
/* pseudo fd๊ฐ ์ด๋ฏธ ํด์ ๋์๋ค๋ฉด ์ด ๋ค๋ก ctx๊ฐ ์ฌ๋ผ์ง ์ ์๋ค. */
userfaultfd_ctx_put(ctx);
out:
return ret;
}
์ญํ : ํ์ด์ง ํดํธ๋ฅผ ์ฌ์ฉ์ ๊ณต๊ฐ์ผ๋ก ์์
๋ถ๊ธฐ ๋ก์ง:
1. PF_EXITING | PF_DUMPCORE โ ์ข
๋ฃ/์ฝ์ด๋คํ ์ค์ด๋ฉด ์ฆ์ VM_FAULT_SIGBUS ๋ฐํ
2. ctx == NULL โ userfaultfd ๋ฏธ๋ฑ๋ก VMA โ VM_FAULT_SIGBUS
3. UFFD_FEATURE_SIGBUS ์ค์ ๋จ โ ์ผ๋ฐ SIGBUS ๊ฒฝ๋ก๋ก ๋ณต๊ท
4. !FAULT_FLAG_USER && UFFD_USER_MODE_ONLY โ ์ปค๋ ๋ชจ๋ ํดํธ ๋ฌด์
5. !FAULT_FLAG_ALLOW_RETRY โ ์ฌ์๋ ๋ถ๊ฐ โ VM_FAULT_SIGBUS
6. FAULT_FLAG_RETRY_NOWAIT โ ์ฆ์ VM_FAULT_RETRY ๋ฐํ
7. ctx->released โ userfaultfd ํด์ ์ค โ VM_FAULT_RETRY
8. ์ ์ โ fault_pending_wqh์ ์ถ๊ฐ โ schedule() โ ๊นจ์ด๋จ
mfill_atomic() โ ๋ฉ๋ชจ๋ฆฌ ์ฑ์ ํต์ฌ ๋ฃจํ
UFFDIO_COPY, UFFDIO_ZEROPAGE, UFFDIO_CONTINUE, UFFDIO_POISON์ ๊ณตํต ์ฒ๋ฆฌ ํจ์์
๋๋ค.
// mm/userfaultfd.c:704-866
static __always_inline ssize_t mfill_atomic(struct userfaultfd_ctx *ctx,
unsigned long dst_start,
unsigned long src_start,
unsigned long len,
uffd_flags_t flags)
{
struct mm_struct *dst_mm = ctx->mm;
struct vm_area_struct *dst_vma;
ssize_t err;
pmd_t *dst_pmd;
unsigned long src_addr, dst_addr;
long copied;
struct folio *folio;
/* ๋ช
๋ น ์ธ์๋ฅผ ์ ๊ทํํ๋ค. */
VM_WARN_ON_ONCE(dst_start & ~PAGE_MASK);
VM_WARN_ON_ONCE(len & ~PAGE_MASK);
/* ์ฃผ์ ๋ฒ์๊ฐ wrap๋๊ฑฐ๋ ๊ธธ์ด๊ฐ 0์ธ์ง ํ์ธํ๋ค. */
VM_WARN_ON_ONCE(src_start + len <= src_start);
VM_WARN_ON_ONCE(dst_start + len <= dst_start);
src_addr = src_start;
dst_addr = dst_start;
copied = 0;
folio = NULL;
retry:
/*
* dst ๋ฒ์๊ฐ ํ๋์ ๊ธฐ์กด VMA ์์ ์์ ํ ๋ค์ด๊ฐ๊ณ ,
* ํด๋น VMA๊ฐ ์ ํจํ์ง ํ์ธํ๋ค.
*/
dst_vma = uffd_mfill_lock(dst_mm, dst_start, len);
if (IS_ERR(dst_vma)) {
err = PTR_ERR(dst_vma);
goto out;
}
/*
* mremap ๊ฐ์ ๋นํ๋ ฅ ๋งคํ ๋ณ๊ฒฝ์ด ๋ณ๋ ฌ๋ก ์งํ ์ค์ด๋ฉด
* ์ฌ์ฉ์๊ฐ ๋์ค์ ๋ค์ ์๋ํ๋๋ก -EAGAIN์ ๋ฐํํ๋ค.
*/
down_read(&ctx->map_changing_lock);
err = -EAGAIN;
if (atomic_read(&ctx->mmap_changing))
goto out_unlock;
err = -EINVAL;
/*
* MAP_ANONYMOUS|MAP_SHARED mmap์์๋ shmem_zero_setup์ด vm_ops๋ฅผ
* ๋ฎ์ด์ฐ๋ฏ๋ก vma_is_anonymous๋ false๋ฅผ ๋ฐํํด์ผ ํ๋ค.
*/
if (WARN_ON_ONCE(vma_is_anonymous(dst_vma) &&
dst_vma->vm_flags & VM_SHARED))
goto out_unlock;
/*
* dst_vma๋ฅผ ํ์ธํ ๋ค mode๋ฅผ ๊ฒ์ฆํ๋ค. WP๋ก ๋ฑ๋ก๋์ง ์์
* userfaultfd์๋ wrprotect copy๋ฅผ ํ์ฉํ์ง ์๋๋ค.
*/
if ((flags & MFILL_ATOMIC_WP) && !(dst_vma->vm_flags & VM_UFFD_WP))
goto out_unlock;
/* HUGETLB VMA์ด๋ฉด ์ ์ฉ ๋ฃจํด์ผ๋ก ๋๊ธด๋ค. */
if (is_vm_hugetlb_page(dst_vma))
return mfill_atomic_hugetlb(ctx, dst_vma, dst_start,
src_start, len, flags);
if (!vma_is_anonymous(dst_vma) && !vma_is_shmem(dst_vma))
goto out_unlock;
if (!vma_is_shmem(dst_vma) &&
uffd_flags_mode_is(flags, MFILL_ATOMIC_CONTINUE))
goto out_unlock;
while (src_addr < src_start + len) {
pmd_t dst_pmdval;
VM_WARN_ON_ONCE(dst_addr >= dst_start + len);
dst_pmd = mm_alloc_pmd(dst_mm, dst_addr);
if (unlikely(!dst_pmd)) {
err = -ENOMEM;
break;
}
dst_pmdval = pmdp_get_lockless(dst_pmd);
if (unlikely(pmd_none(dst_pmdval)) &&
unlikely(__pte_alloc(dst_mm, dst_pmd))) {
err = -ENOMEM;
break;
}
dst_pmdval = pmdp_get_lockless(dst_pmd);
/*
* dst_pmd๊ฐ THP์ด๋ฉด ๋ฎ์ด์ฐ์ง ์๊ณ ์๊ฒฉํ๊ฒ ์คํจํ๋ค.
* __pte_alloc() ๋ค์ PMD๊ฐ THP์์ none์ผ๋ก ๋ฐ๋ ๊ฒฝ์ฐ๋ ํฌํจํ๋ค.
*/
if (unlikely(!pmd_present(dst_pmdval) ||
pmd_trans_huge(dst_pmdval))) {
err = -EEXIST;
break;
}
if (unlikely(pmd_bad(dst_pmdval))) {
err = -EFAULT;
break;
}
/*
* shmem ๋งคํ์์๋ khugepaged๊ฐ ์๋์์ page table์ ์ ๊ฑฐํ ์ ์๊ณ ,
* pte_offset_map_lock()์ด ๊ทธ ์ํฉ์ ์ฒ๋ฆฌํ๋ค.
*/
err = mfill_atomic_pte(dst_pmd, dst_vma, dst_addr,
src_addr, flags, &folio);
cond_resched();
if (unlikely(err == -ENOENT)) {
void *kaddr;
up_read(&ctx->map_changing_lock);
uffd_mfill_unlock(dst_vma);
VM_WARN_ON_ONCE(!folio);
kaddr = kmap_local_folio(folio, 0);
err = copy_from_user(kaddr,
(const void __user *) src_addr,
PAGE_SIZE);
kunmap_local(kaddr);
if (unlikely(err)) {
err = -EFAULT;
goto out;
}
flush_dcache_folio(folio);
goto retry;
} else
VM_WARN_ON_ONCE(folio);
if (!err) {
dst_addr += PAGE_SIZE;
src_addr += PAGE_SIZE;
copied += PAGE_SIZE;
if (fatal_signal_pending(current))
err = -EINTR;
}
if (err)
break;
}
out_unlock:
up_read(&ctx->map_changing_lock);
uffd_mfill_unlock(dst_vma);
out:
if (folio)
folio_put(folio);
VM_WARN_ON_ONCE(copied < 0);
VM_WARN_ON_ONCE(err > 0);
VM_WARN_ON_ONCE(!copied && !err);
return copied ? copied : err;
}
์ญํ : ์ง์ ๋ ์ฃผ์ ๋ฒ์์ ํ์ด์ง ํ ์ด๋ธ์ ์ PTE๋ฅผ ์ค์น
๋ถ๊ธฐ ๋ก์ง:
1. mmap_changing ํ์ธ โ mremap ๋ฑ ๋ณ๋ ฌ ๋ณ๊ฒฝ ์ -EAGAIN
2. is_vm_hugetlb_page(dst_vma) โ mfill_atomic_hugetlb() ๊ฒฝ๋ก๋ก ๋ถ๊ธฐ
3. !vma_is_anonymous && !vma_is_shmem โ ์ง์ ๋ถ๊ฐ -EINVAL
4. MFILL_ATOMIC_CONTINUE && !vma_is_shmem โ shmem๋ง ๊ฐ๋ฅ -EINVAL
5. ํ์ด์ง๋ณ PMD ํ ๋น โ mfill_atomic_pte() ํธ์ถ
6. -ENOENT ๋ฐํ โ mmap_lock ํด์ ํ ์ฌ์๋ (folio๋ฅผ ์ฌ์ฉ์ ๊ณต๊ฐ์์ ๋ณต์ฌ)
mfill_atomic_install_pte() โ PTE ์ค์น
์ค์ ํ์ด์ง ํ ์ด๋ธ ํญ๋ชฉ์ ์ค์นํ๋ ์ต์ข ๋จ๊ณ ํจ์์ ๋๋ค.
// mm/userfaultfd.c:168-239
int mfill_atomic_install_pte(pmd_t *dst_pmd,
struct vm_area_struct *dst_vma,
unsigned long dst_addr, struct page *page,
bool newly_allocated, uffd_flags_t flags)
{
int ret;
struct mm_struct *dst_mm = dst_vma->vm_mm;
pte_t _dst_pte, *dst_pte;
bool writable = dst_vma->vm_flags & VM_WRITE;
bool vm_shared = dst_vma->vm_flags & VM_SHARED;
spinlock_t *ptl;
struct folio *folio = page_folio(page);
bool page_in_cache = folio_mapping(folio);
pte_t dst_ptep;
_dst_pte = mk_pte(page, dst_vma->vm_page_prot);
_dst_pte = pte_mkdirty(_dst_pte);
if (page_in_cache && !vm_shared)
writable = false;
if (writable)
_dst_pte = pte_mkwrite(_dst_pte, dst_vma);
if (flags & MFILL_ATOMIC_WP)
_dst_pte = pte_mkuffd_wp(_dst_pte);
ret = -EAGAIN;
dst_pte = pte_offset_map_lock(dst_mm, dst_pmd, dst_addr, &ptl);
if (!dst_pte)
goto out;
if (mfill_file_over_size(dst_vma, dst_addr)) {
ret = -EFAULT;
goto out_unlock;
}
ret = -EEXIST;
dst_ptep = ptep_get(dst_pte);
/*
* UFFD PTE marker๋ ๋ฎ์ด์ธ ์ ์๋ค. MISSING|WP๊ฐ ํจ๊ป ๋ฑ๋ก๋ ๋ค
* ํ์ด์ง ์บ์ page๊ฐ ์๋ none PTE๋ฅผ ๋จผ์ wr-protectํ๊ณ ์ ๊ทผํ๋
* ๊ฒฝ์ฐ๋ฅผ ๊ณ ๋ คํ๋ค.
*/
if (!pte_none(dst_ptep) && !pte_is_uffd_marker(dst_ptep))
goto out_unlock;
if (page_in_cache) {
/* ๋ณดํต cache page๋ ์ด๋ฏธ LRU์ ์ถ๊ฐ๋์ด ์๋ค. */
if (newly_allocated)
folio_add_lru(folio);
folio_add_file_rmap_pte(folio, page, dst_vma);
} else {
folio_add_new_anon_rmap(folio, dst_vma, dst_addr, RMAP_EXCLUSIVE);
folio_add_lru_vma(folio, dst_vma);
}
/*
* mm_counter()๊ฐ mapping์ ํ์ธํ๋ฏ๋ก rmap ๋ค์ ์คํ๋์ด์ผ ํ๋ค.
* mapping์ __page_set_anon_rmap()์์ ์ค์ ๋๋ค.
*/
inc_mm_counter(dst_mm, mm_counter(folio));
set_pte_at(dst_mm, dst_addr, dst_pte, _dst_pte);
/* ์ด์ ์๋ non-present์์ผ๋ฏ๋ก invalidate๊ฐ ํ์ ์๋ค. */
update_mmu_cache(dst_vma, dst_addr, dst_pte);
ret = 0;
out_unlock:
pte_unmap_unlock(dst_pte, ptl);
out:
return ret;
}
์ญํ : PTE๋ฅผ ๊ตฌ์ฑํ๊ณ ํ์ด์ง ํ ์ด๋ธ์ ๊ธฐ๋ก
๋ถ๊ธฐ ๋ก์ง:
1. mfill_file_over_size() โ ํ์ผ ๋ฒ์ ์ด๊ณผ -EFAULT
2. !pte_none(dst_ptep) && !pte_is_uffd_marker() โ ์ด๋ฏธ ๋งคํ๋จ -EEXIST
3. page_in_cache โ ํ์ผ ๊ธฐ๋ฐ ํ์ด์ง โ folio_add_file_rmap_pte()
4. !page_in_cache โ ์ต๋ช
ํ์ด์ง โ folio_add_new_anon_rmap()
5. MFILL_ATOMIC_WP ํ๋๊ทธ โ pte_mkuffd_wp()๋ก ์ฐ๊ธฐ ๋ณดํธ PTE ์ค์น
mwriteprotect_range() โ ์ฐ๊ธฐ ๋ณดํธ ๋ฒ์ ์ค์
UFFDIO_WRITEPROTECT ioctl๋ก ์ง์ ๋ ์ฃผ์ ๋ฒ์์ PTE๋ฅผ ์ฐ๊ธฐ ๋ณดํธํ๊ฑฐ๋ ํด์ ํฉ๋๋ค.
// mm/userfaultfd.c:936-997
int mwriteprotect_range(struct userfaultfd_ctx *ctx, unsigned long start,
unsigned long len, bool enable_wp)
{
struct mm_struct *dst_mm = ctx->mm;
unsigned long end = start + len;
unsigned long _start, _end;
struct vm_area_struct *dst_vma;
unsigned long page_mask;
long err;
VMA_ITERATOR(vmi, dst_mm, start);
/* ๋ช
๋ น ์ธ์๋ฅผ ์ ๊ทํํ๋ค. */
VM_WARN_ON_ONCE(start & ~PAGE_MASK);
VM_WARN_ON_ONCE(len & ~PAGE_MASK);
/* ์ฃผ์ ๋ฒ์๊ฐ wrap๋๊ฑฐ๋ ๊ธธ์ด๊ฐ 0์ธ์ง ํ์ธํ๋ค. */
VM_WARN_ON_ONCE(start + len <= start);
mmap_read_lock(dst_mm);
/*
* mremap ๊ฐ์ ๋นํ๋ ฅ ๋งคํ ๋ณ๊ฒฝ์ด ๋ณ๋ ฌ๋ก ์งํ ์ค์ด๋ฉด
* ์ฌ์ฉ์๊ฐ ๋์ค์ ๋ค์ ์๋ํ๋๋ก -EAGAIN์ ๋ฐํํ๋ค.
*/
down_read(&ctx->map_changing_lock);
err = -EAGAIN;
if (atomic_read(&ctx->mmap_changing))
goto out_unlock;
err = -ENOENT;
for_each_vma_range(vmi, dst_vma, end) {
if (!userfaultfd_wp(dst_vma)) {
err = -ENOENT;
break;
}
if (is_vm_hugetlb_page(dst_vma)) {
err = -EINVAL;
page_mask = vma_kernel_pagesize(dst_vma) - 1;
if ((start & page_mask) || (len & page_mask))
break;
}
_start = max(dst_vma->vm_start, start);
_end = min(dst_vma->vm_end, end);
err = uffd_wp_range(dst_vma, _start, _end - _start, enable_wp);
/* ์ฑ๊ณต์ด๋ฉด 0, ์คํจํ๋ฉด ์์ ๊ฐ์ ๋ฐํํ๋ค. */
if (err < 0)
break;
err = 0;
}
out_unlock:
up_read(&ctx->map_changing_lock);
mmap_read_unlock(dst_mm);
return err;
}
์ญํ : ํ์ด์ง ํ ์ด๋ธ์ ์ฐ๊ธฐ ๊ถํ ์ผ๊ด ๋ณ๊ฒฝ
๋ถ๊ธฐ ๋ก์ง:
1. mmap_changing ํ์ธ โ -EAGAIN
2. for_each_vma_range()๋ก VMA ์ํ
3. !userfaultfd_wp(dst_vma) โ WP ๋ฏธ๋ฑ๋ก VMA -ENOENT
4. is_vm_hugetlb_page() โ huge page ์ ๋ ฌ ๊ฒ์ฆ
5. change_protection() ํธ์ถ โ ์ค์ PTE ๋ณ๊ฒฝ
move_pages() โ ํ์ด์ง ์ด๋ ํต์ฌ ํจ์
UFFDIO_MOVE ioctl๋ก ์ต๋ช
ํ์ด์ง๋ฅผ ํ ์ฃผ์์์ ๋ค๋ฅธ ์ฃผ์๋ก ์ ๋ก์นดํผ ์ด๋ํฉ๋๋ค.
// mm/userfaultfd.c:1766-1939
ssize_t move_pages(struct userfaultfd_ctx *ctx, unsigned long dst_start,
unsigned long src_start, unsigned long len, __u64 mode)
{
struct mm_struct *mm = ctx->mm;
struct vm_area_struct *src_vma, *dst_vma;
unsigned long src_addr, dst_addr, src_end;
pmd_t *src_pmd, *dst_pmd;
long err = -EINVAL;
ssize_t moved = 0;
/* ๋ช
๋ น ์ธ์๋ฅผ ์ ๊ทํํ๋ค. */
VM_WARN_ON_ONCE(src_start & ~PAGE_MASK);
VM_WARN_ON_ONCE(dst_start & ~PAGE_MASK);
VM_WARN_ON_ONCE(len & ~PAGE_MASK);
/* ์ฃผ์ ๋ฒ์๊ฐ wrap๋๊ฑฐ๋ ๊ธธ์ด๊ฐ 0์ธ์ง ํ์ธํ๋ค. */
VM_WARN_ON_ONCE(src_start + len < src_start);
VM_WARN_ON_ONCE(dst_start + len < dst_start);
err = uffd_move_lock(mm, dst_start, src_start, &dst_vma, &src_vma);
if (err)
goto out;
/* map_changing_lock์ ์ก์ ๋ค ๋ค์ ํ์ธํ๋ค. */
err = -EAGAIN;
down_read(&ctx->map_changing_lock);
if (likely(atomic_read(&ctx->mmap_changing)))
goto out_unlock;
/*
* src/dst remap ๋ฒ์๊ฐ ๊ณต์ VMA๊ฐ ์๋๋ฉฐ ๊ฐ๊ฐ ํ๋์ ๊ธฐ์กด VMA ์์
* ์์ ํ ๋ค์ด๊ฐ๋์ง ํ์ธํ๋ค.
*/
err = -EINVAL;
if (src_vma->vm_flags & VM_SHARED)
goto out_unlock;
if (src_start + len > src_vma->vm_end)
goto out_unlock;
if (dst_vma->vm_flags & VM_SHARED)
goto out_unlock;
if (dst_start + len > dst_vma->vm_end)
goto out_unlock;
err = validate_move_areas(ctx, src_vma, dst_vma);
if (err)
goto out_unlock;
for (src_addr = src_start, dst_addr = dst_start, src_end = src_start + len;
src_addr < src_end;) {
spinlock_t *ptl;
pmd_t dst_pmdval;
unsigned long step_size;
/*
* ์ต๋ช
์์ญ์๋ transparent huge PUD๊ฐ ์๊ธฐ ๋๋ฌธ์ ์๋ ๋ก์ง์ด
* ๋์ํ๋ค. file-backed ์ง์์ด ์ถ๊ฐ๋๋ฉด ๊ทธ ๊ฒฝ์ฐ๋ ์ฒ๋ฆฌํด์ผ ํ๋ค.
*/
src_pmd = mm_find_pmd(mm, src_addr);
if (unlikely(!src_pmd)) {
if (!(mode & UFFDIO_MOVE_MODE_ALLOW_SRC_HOLES)) {
err = -ENOENT;
break;
}
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 = pmdp_get_lockless(dst_pmd);
/*
* dst_pmd๊ฐ THP๋ก ๋งคํ๋์ด ์์ผ๋ฉด ๋ฎ์ด์ฐ์ง ์๊ณ ์๊ฒฉํ๊ฒ ์คํจํ๋ค.
* ์ด ํ์ธ ๋ค dst_pmd๊ฐ THP๋ก ๋ฐ๋๋ฉด huge/pte ๊ฒฝ๋ก๊ฐ ๊ฐ๊ฐ ์ฌ์๋๋
* ์คํจ๋ก ๊ฐ์งํ๋ค.
*/
if (unlikely(pmd_trans_huge(dst_pmdval))) {
err = -EEXIST;
break;
}
ptl = pmd_trans_huge_lock(src_pmd, src_vma);
if (ptl) {
/* PMD๋ฅผ splitํ์ง ์๊ณ ์ฎ๊ธธ ์ ์๋์ง ํ์ธํ๋ค. */
if (move_splits_huge_pmd(dst_addr, src_addr, src_start + len) ||
!pmd_none(dst_pmdval)) {
/* migration entry์ผ ์ ์๋ค. */
if (pmd_present(*src_pmd)) {
struct folio *folio = pmd_folio(*src_pmd);
if (!is_huge_zero_folio(folio) &&
!PageAnonExclusive(&folio->page)) {
spin_unlock(ptl);
err = -EBUSY;
break;
}
}
spin_unlock(ptl);
split_huge_pmd(src_vma, src_pmd, src_addr);
/* folio๋ move_pages_pte()์์ split๋๋ค. */
continue;
}
err = move_pages_huge_pmd(mm, dst_pmd, src_pmd,
dst_pmdval, dst_vma, src_vma,
dst_addr, src_addr);
step_size = HPAGE_PMD_SIZE;
} else {
long ret;
if (pmd_none(*src_pmd)) {
if (!(mode & UFFDIO_MOVE_MODE_ALLOW_SRC_HOLES)) {
err = -ENOENT;
break;
}
if (unlikely(__pte_alloc(mm, src_pmd))) {
err = -ENOMEM;
break;
}
}
if (unlikely(pte_alloc(mm, dst_pmd))) {
err = -ENOMEM;
break;
}
ret = move_pages_ptes(mm, dst_pmd, src_pmd,
dst_vma, src_vma, dst_addr,
src_addr, src_end - src_addr, mode);
if (ret < 0)
err = ret;
else
step_size = ret;
}
cond_resched();
if (fatal_signal_pending(current)) {
/* ๊ธฐ์กด ์๋ฌ๋ฅผ ๋ฎ์ด์ฐ์ง ์๋๋ค. */
if (!err || err == -EAGAIN)
err = -EINTR;
break;
}
if (err) {
if (err == -EAGAIN)
continue;
break;
}
/* ๋ค์ ํ์ด์ง๋ก ์งํํ๋ค. */
dst_addr += step_size;
src_addr += step_size;
moved += step_size;
}
out_unlock:
up_read(&ctx->map_changing_lock);
uffd_move_unlock(dst_vma, src_vma);
out:
VM_WARN_ON_ONCE(moved < 0);
VM_WARN_ON_ONCE(err > 0);
VM_WARN_ON_ONCE(!moved && !err);
return moved ? moved : err;
}
์ญํ : ์ต๋ช ํ์ด์ง๋ฅผ ์ ๋ก์นดํผ๋ก ์ด๋ (VMA ์์ฑ ์์ด)
์ ์ ์กฐ๊ฑด: src_vma์ dst_vma ๋ชจ๋ ์ต๋ช , ์ฐ๊ธฐ ๊ฐ๋ฅ, ๋์ผ ์ ๊ทผ ๊ถํ
์ด ๊ฒฝ๋ก๋ reverse mapping๊ณผ DMA pin ์ํ์ ๋ฏผ๊ฐํ๋ค.
move_pages_ptes() โ PTE ์์ค ํ์ด์ง ์ด๋
์ผ๋ฐ PTE ๋จ์๋ก ํ์ด์ง๋ฅผ ์ด๋ํ๋ ํต์ฌ ํจ์์ ๋๋ค.
// mm/userfaultfd.c:1247-1327
static long move_pages_ptes(struct mm_struct *mm, pmd_t *dst_pmd, pmd_t *src_pmd,
struct vm_area_struct *dst_vma,
struct vm_area_struct *src_vma,
unsigned long dst_addr, unsigned long src_addr,
unsigned long len, __u64 mode)
{
struct swap_info_struct *si = NULL;
pte_t orig_src_pte, orig_dst_pte;
pte_t src_folio_pte;
spinlock_t *src_ptl, *dst_ptl;
pte_t *src_pte = NULL;
pte_t *dst_pte = NULL;
pmd_t dummy_pmdval;
pmd_t dst_pmdval;
struct folio *src_folio = NULL;
struct mmu_notifier_range range;
long ret = 0;
mmu_notifier_range_init(&range, MMU_NOTIFY_CLEAR, 0, mm,
src_addr, src_addr + len);
mmu_notifier_invalidate_range_start(&range);
retry:
/*
* dst_pte๊ฐ ์์ ๋ ๊ฒ์ด๋ฏ๋ก maywrite ๋ฒ์ ์ ์ฌ์ฉํ๋ค. dst_pte๋ none์ด์ด์ผ
* ํ๊ณ ๋ค์ pte_same()๋ง์ผ๋ก๋ dst_pte page๊ฐ ๋ณ๋ ฌ๋ก ํด์ ๋๋ ๊ฒ์ ๋ง์ ์
* ์์ผ๋ฏ๋ก dst_pmdval๋ ์ป์ด ๋์ค์ pmd_same()์ผ๋ก ๋ค์ ํ์ธํ๋ค.
*/
dst_pte = pte_offset_map_rw_nolock(mm, dst_pmd, dst_addr, &dst_pmdval,
&dst_ptl);
/* ์๋์์ huge pmd๊ฐ ์๊ธฐ๋ฉด ์ฌ์๋ํ๋ค. */
if (unlikely(!dst_pte)) {
ret = -EAGAIN;
goto out;
}
/*
* dst_pte์ ๋ฌ๋ฆฌ ๋ค์ pte_same()์ผ๋ก src_pte page ์์ ์ฑ์ ํ์ธํ ์
* ์์ผ๋ฏ๋ก pmdval์ ์ป์ ํ์๊ฐ ์๊ณ dummy ๋ณ์๋ง ๋๊ธด๋ค.
*/
src_pte = pte_offset_map_rw_nolock(mm, src_pmd, src_addr, &dummy_pmdval,
&src_ptl);
/*
* read mmap_lock์ ์ก๊ณ ์์ผ๋ฏ๋ก MADV_DONTNEED๊ฐ transparent huge page๋ฅผ
* zapํ๊ฑฐ๋ transparent huge page fault๊ฐ ์ THP๋ฅผ ๋ง๋ค ์ ์๋ค.
*/
if (unlikely(!src_pte)) {
ret = -EAGAIN;
goto out;
}
/* ์์
์ ๊ธฐ๋ณธ ๊ฒ์ฌ๋ฅผ ์ํํ๋ค. */
if (pmd_none(*dst_pmd) || pmd_none(*src_pmd) ||
pmd_trans_huge(*dst_pmd) || pmd_trans_huge(*src_pmd)) {
ret = -EINVAL;
goto out;
}
spin_lock(dst_ptl);
orig_dst_pte = ptep_get(dst_pte);
spin_unlock(dst_ptl);
if (!pte_none(orig_dst_pte)) {
ret = -EEXIST;
goto out;
}
spin_lock(src_ptl);
orig_src_pte = ptep_get(src_pte);
spin_unlock(src_ptl);
if (pte_none(orig_src_pte)) {
if (!(mode & UFFDIO_MOVE_MODE_ALLOW_SRC_HOLES))
ret = -ENOENT;
else /* hole์ ์ฎ๊ธธ ๋ด์ฉ์ด ์๋ค. */
ret = PAGE_SIZE;
goto out;
}
// mm/userfaultfd.c:1335-1489
if (pte_present(orig_src_pte)) {
if (is_zero_pfn(pte_pfn(orig_src_pte))) {
ret = move_zeropage_pte(mm, dst_vma, src_vma,
dst_addr, src_addr, dst_pte, src_pte,
orig_dst_pte, orig_src_pte,
dst_pmd, dst_pmdval, dst_ptl, src_ptl);
goto out;
}
/*
* source folio๋ฅผ pinํ๊ณ lockํ๋ค. RCU read section ์์์๋ blockํ ์
* ์์ผ๋ฏ๋ก ๊ฒฝํฉ์ด ์์ผ๋ฉด pte mapping์ ํด์ ํ๊ณ lock์ ์ป์ ๋ค ์ฌ์๋ํ๋ค.
*/
if (!src_folio) {
struct folio *folio;
bool locked;
/* lock์ ์ก์ ์ํ์์ page๋ฅผ pinํ์ฌ ์๋์์ ํด์ ๋์ง ์๊ฒ ํ๋ค. */
spin_lock(src_ptl);
if (!pte_same(orig_src_pte, ptep_get(src_pte))) {
spin_unlock(src_ptl);
ret = -EAGAIN;
goto out;
}
folio = vm_normal_folio(src_vma, src_addr, orig_src_pte);
if (!folio || !PageAnonExclusive(&folio->page)) {
spin_unlock(src_ptl);
ret = -EBUSY;
goto out;
}
locked = folio_trylock(folio);
/*
* large folio๋ refcount๊ฐ ์ฆ๊ฐํ๋ฉด split_folio()๊ฐ ๋์ค์ ์คํจํ๊ณ
* ์ฌ์๋ํ ์ ์์ผ๋ฏ๋ก folio lock ๋๊ธฐ๋ฅผ ํผํ๋ค.
*/
if (!locked && folio_test_large(folio)) {
spin_unlock(src_ptl);
ret = -EAGAIN;
goto out;
}
folio_get(folio);
src_folio = folio;
src_folio_pte = orig_src_pte;
spin_unlock(src_ptl);
if (!locked) {
pte_unmap(src_pte);
pte_unmap(dst_pte);
src_pte = dst_pte = NULL;
/* ์ด์ blockํ๊ณ ๊ธฐ๋ค๋ฆด ์ ์๋ค. */
folio_lock(src_folio);
goto retry;
}
if (WARN_ON_ONCE(!folio_test_anon(src_folio))) {
ret = -EBUSY;
goto out;
}
}
/* ์ฌ๊ธฐ๊น์ง ์ค๋ฉด src_folio lock์ ์ก์ ์ํ๋ค. */
if (folio_test_large(src_folio)) {
/* split_folio()๋ blockํ ์ ์๋ค. */
pte_unmap(src_pte);
pte_unmap(dst_pte);
src_pte = dst_pte = NULL;
ret = split_folio(src_folio);
if (ret)
goto out;
/* split ๋ค์๋ folio๋ฅผ ๋ค์ ์ป์ด์ผ ํ๋ค. */
folio_unlock(src_folio);
folio_put(src_folio);
src_folio = NULL;
goto retry;
}
ret = move_present_ptes(mm, dst_vma, src_vma,
dst_addr, src_addr, dst_pte, src_pte,
orig_dst_pte, orig_src_pte, dst_pmd,
dst_pmdval, dst_ptl, src_ptl, &src_folio,
len);
} else { /* !pte_present() ๊ฒฝ๋ก */
struct folio *folio = NULL;
const softleaf_t entry = softleaf_from_pte(orig_src_pte);
if (softleaf_is_migration(entry)) {
pte_unmap(src_pte);
pte_unmap(dst_pte);
src_pte = dst_pte = NULL;
migration_entry_wait(mm, src_pmd, src_addr);
ret = -EAGAIN;
goto out;
} else if (!softleaf_is_swap(entry)) {
ret = -EFAULT;
goto out;
}
if (!pte_swp_exclusive(orig_src_pte)) {
ret = -EBUSY;
goto out;
}
si = get_swap_device(entry);
if (unlikely(!si)) {
ret = -EAGAIN;
goto out;
}
/*
* swapcache ์กด์ฌ ์ฌ๋ถ๋ฅผ ํ์ธํ๋ค. ์์ผ๋ฉด PTE๊ฐ swap entry์ฌ๋ folio์
* index์ mapping์ ๊ฐฑ์ ํด์ผ ํ๋ค. swap entry๊ฐ exclusive์ด๋ฏ๋ก ์ด ๊ณผ์ ์์
* anon_vma lock์ ์ก์ง ์๋๋ค.
*/
if (!src_folio)
folio = swap_cache_get_folio(entry);
if (folio) {
if (folio_test_large(folio)) {
ret = -EBUSY;
folio_put(folio);
goto out;
}
src_folio = folio;
src_folio_pte = orig_src_pte;
if (!folio_trylock(src_folio)) {
pte_unmap(src_pte);
pte_unmap(dst_pte);
src_pte = dst_pte = NULL;
put_swap_device(si);
si = NULL;
/* ์ด์ blockํ๊ณ ๊ธฐ๋ค๋ฆด ์ ์๋ค. */
folio_lock(src_folio);
goto retry;
}
}
ret = move_swap_pte(mm, dst_vma, dst_addr, src_addr, dst_pte, src_pte,
orig_dst_pte, orig_src_pte, dst_pmd, dst_pmdval,
dst_ptl, src_ptl, src_folio, si, entry);
}
์ญํ : ์์ค PTE๋ฅผ ๋์ PTE๋ก ์ด๋ (์ ๋ก์นดํผ)
๋ถ๊ธฐ ๋ก์ง:
1. pte_none(orig_src_pte) โ UFFDIO_MOVE_MODE_ALLOW_SRC_HOLES์ ๋ฐ๋ผ -ENOENT ๋๋ PAGE_SIZE ๋ฐํ
2. is_zero_pfn(pte_pfn(orig_src_pte)) โ move_zeropage_pte() ํธ์ถ
3. pte_present(orig_src_pte) โ folio ์ ๊ธ โ move_present_ptes() ํธ์ถ
4. softleaf_is_migration(entry) โ migration_entry_wait() ํ -EAGAIN
5. softleaf_is_swap(entry) โ move_swap_pte() ํธ์ถ
check_ptes_for_batched_move() โ ๋ฐฐ์น ์ด๋ ๊ฒ์ฆ
์ฐ์๋ PTE๊ฐ ๋ฐฐ์น ์ด๋ ๊ฐ๋ฅํ์ง ๊ฒ์ฆํ๋ ํจ์์ ๋๋ค.
// mm/userfaultfd.c:1043-1066
static struct folio *check_ptes_for_batched_move(struct vm_area_struct *src_vma,
unsigned long src_addr,
pte_t *src_pte, pte_t *dst_pte)
{
pte_t orig_dst_pte, orig_src_pte;
struct folio *folio;
orig_dst_pte = ptep_get(dst_pte);
if (!pte_none(orig_dst_pte))
return NULL;
orig_src_pte = ptep_get(src_pte);
if (!pte_present(orig_src_pte) || is_zero_pfn(pte_pfn(orig_src_pte)))
return NULL;
folio = vm_normal_folio(src_vma, src_addr, orig_src_pte);
if (!folio || !folio_trylock(folio))
return NULL;
if (!PageAnonExclusive(&folio->page) || folio_test_large(folio)) {
folio_unlock(folio);
return NULL;
}
return folio;
}
์ญํ : ๋ฐฐ์น ์ด๋ ๊ฐ๋ฅ ์ฌ๋ถ ํ๋จ (์ฐ์ PTE๋ฅผ ํ๋ฒ์ ์ด๋)
๋ฐํ: ์ด๋ ๊ฐ๋ฅํ folio ํฌ์ธํฐ ๋๋ NULL
validate_move_areas() โ ์ด๋ ์์ญ ๊ฒ์ฆ
move_pages() ํธ์ถ ์ ์์ค/๋์ VMA์ ์ ํจ์ฑ์ ๊ฒ์ฆํฉ๋๋ค.
// mm/userfaultfd.c:1536-1570
static int validate_move_areas(struct userfaultfd_ctx *ctx,
struct vm_area_struct *src_vma,
struct vm_area_struct *dst_vma)
{
/* ๋ VMA์ ์ ๊ทผ ๊ถํ๊ณผ protection์ด ๊ฐ์ ๋๋ง ์ด๋์ ํ์ฉํ๋ค. */
if ((src_vma->vm_flags & VM_ACCESS_FLAGS) != (dst_vma->vm_flags & VM_ACCESS_FLAGS) ||
pgprot_val(src_vma->vm_page_prot) != pgprot_val(dst_vma->vm_page_prot))
return -EINVAL;
/* ๋ VMA๊ฐ ๋ชจ๋ mlocked์ด๊ฑฐ๋ ๋ชจ๋ mlocked๊ฐ ์๋ ๋๋ง ํ์ฉํ๋ค. */
if ((src_vma->vm_flags & VM_LOCKED) != (dst_vma->vm_flags & VM_LOCKED))
return -EINVAL;
/*
* ์ง๊ธ์ ๋จ์ํ๊ฒ writable VMA ์ฌ์ด์ ์ด๋๋ง ํ์ฉํ๋ค.
* ์ ๊ทผ ํ๋๊ทธ๊ฐ ๊ฐ์ผ๋ฏ๋ก source๋ง ํ์ธํด๋ ์ถฉ๋ถํ๋ค.
*/
if (!(src_vma->vm_flags & VM_WRITE))
return -EINVAL;
/* VMA ํ๋๊ทธ๊ฐ ์ด๋ ๊ฐ๋ฅํ ๋ด์ฉ์ ๋ํ๋ด๋์ง ํ์ธํ๋ค. */
if (!vma_move_compatible(src_vma) || !vma_move_compatible(dst_vma))
return -EINVAL;
/* dst_vma๊ฐ ํ์ฌ ์กฐ์ ์ค์ธ uffd์ ๋ฑ๋ก๋์ด ์๋์ง ๋ณด์ฅํ๋ค. */
if (!dst_vma->vm_userfaultfd_ctx.ctx ||
dst_vma->vm_userfaultfd_ctx.ctx != ctx)
return -EINVAL;
/* ์ต๋ช
VMA ์ฌ์ด์ ์ด๋๋ง ํ์ฉํ๋ค. */
if (!vma_is_anonymous(src_vma) || !vma_is_anonymous(dst_vma))
return -EINVAL;
return 0;
}
์ญํ : ์ด๋ ์ VMA ํธํ์ฑ ๊ฒ์ฆ
๋ฐํ: ์ฑ๊ณต 0, ์คํจ -EINVAL
ํธ์ถ ํ๋ฆ
ํ์ด์ง ํดํธ ์ฒ๋ฆฌ ํ๋ฆ
ํ๋ก์ธ์ค ์ค๋ ๋
โ ๊ฐ์ ๋ฉ๋ชจ๋ฆฌ ์ ๊ทผ
โผ
handle_userfault(vmf, reason) โ fs/userfaultfd.c:381
โ ctx = vma->vm_userfaultfd_ctx.ctx
โ userfaultfd_ctx_get(ctx)
โ __add_wait_queue(fault_pending_wqh)
โ set_current_state(TASK_INTERRUPTIBLE)
โผ
userfaultfd_must_wait() / userfaultfd_huge_must_wait()
โ ์กฐ๊ฑด ์ฌํ์ธ (PTE๊ฐ ์ฌ์ ํ none์ธ์ง ๋ฑ)
โผ
release_fault_lock(vmf)
โ
โโ must_wait && !released โ wake_up_poll(fd_wqh) + schedule()
โ โ
โ โผ
โ (์ฌ์ฉ์ ํธ๋ค๋ฌ๊ฐ read()๋ก ์์ )
โ โ
โ โผ
โ userfaultfd_ctx_read() โ fs/userfaultfd.c:992
โ โ fault_pending_wqh์์ fault_wqh๋ก refile
โ โ uffd_msg ๋ฐํ
โ โ
โ โผ
โ ์ฌ์ฉ์ ioctl(UFFDIO_COPY ๋ฑ)
โ โ
โ โผ
โ mfill_atomic_copy() โ mm/userfaultfd.c:868
โ โ mfill_atomic()
โ โ โโ uffd_mfill_lock()
โ โ โโ mm_alloc_pmd() + __pte_alloc()
โ โ โโ mfill_atomic_pte()
โ โ โ โโ mfill_atomic_pte_copy()
โ โ โ โโ mfill_atomic_pte_zeropage()
โ โ โ โโ mfill_atomic_pte_continue()
โ โ โ โโ mfill_atomic_pte_poison()
โ โ โโ mfill_atomic_install_pte()
โ โ โโ set_pte_at()
โ
โโ !must_wait โ VM_FAULT_RETRY
move_pages() โ ํ์ด์ง ์ด๋ ํต์ฌ ํจ์ ํ๋ฆ
userfaultfd_move() โ fs/userfaultfd.c
โผ
move_pages(ctx, dst_start, src_start, len, flags) โ mm/userfaultfd.c:1766
โ uffd_move_lock() โ VMA 2๊ฐ ์ ๊ธ
โ validate_move_areas() โ ์ ๊ทผ ๊ถํ/๋ณดํธ ๋นํธ ๊ฒ์ฆ
โผ
move_pages_ptes() โ mm/userfaultfd.c:1247
โ pte_offset_map_rw_nolock(dst/src)
โ
โโ pte_present(orig_src_pte)
โ โโ is_zero_pfn โ move_zeropage_pte()
โ โโ folio lock โ move_present_ptes()
โ โ ๋ฐฐ์น ์ด๋ (์ฐ์ PTE๋ฅผ ํ๋ฒ์ ์ด๋)
โ โ check_ptes_for_batched_move() โ ๋ฐฐ์น ๊ฐ๋ฅ ์ฌ๋ถ ํ์ธ
โ โ double_pt_lock() โ ์ด์ค PTL ์ ๊ธ
โ โ ptep_get_and_clear โ folio_move_anon_rmap โ set_pte_at
โ โ double_pt_unlock()
โ
โโ !pte_present (swap entry)
โโ move_swap_pte()
โ swap cache ํ์ธ โ swap entry ์ด๋
โ double_pt_lock โ ptep_get_and_clear โ set_pte_at โ double_pt_unlock
userfaultfd_register_range() โ ๋ฒ์ ๋ฑ๋ก ํ๋ฆ
userfaultfd_register() โ fs/userfaultfd.c
โผ
userfaultfd_register_range() โ mm/userfaultfd.c:2007
โ for_each_vma_range(vmi, vma, end)
โ โโ ์ด๋ฏธ ๋ฑ๋ก๋จ (ctx == ctx && flags == flags) โ skip
โ โโ vma_modify_flags_uffd() โ VMA ๋ถํ /๋ณํฉ
โ โโ userfaultfd_set_ctx() โ vm_userfaultfd_ctx ์ค์
โ โโ userfaultfd_set_vm_flags() โ VM_UFFD_* ํ๋๊ทธ ์
๋ฐ์ดํธ
โ โโ (๊ณต์ ๋งคํ ์) vma_set_page_prot() โ writenotify ํ์ฑํ
์กฐ๊ฑด๋ณ ๋น๊ต
Userfaultfd ๋ฑ๋ก ๋ชจ๋ ๋น๊ต
| ๋ชจ๋ | VM ํ๋๊ทธ | ํดํธ ์ ๋์ | ์ฃผ์ ์ฌ์ฉ์ฒ |
|---|---|---|---|
| `MISSING` | `VM_UFFD_MISSING` | PTE๊ฐ none์ธ ์์ญ์์ ํดํธ ๋ฐ์ ์ ์ฌ์ฉ์์๊ฒ ์๋ฆผ | VMM (KVM/QEMU), CR/CRIU |
| `WP` | `VM_UFFD_WP` | ์ฐ๊ธฐ ์ PTE WP ๋นํธ๋ก ์ฐ๊ธฐ ๋ณดํธ ํดํธ ๋ฐ์ | KSM, ๋ฉ๋ชจ๋ฆฌ ์ ์ฑ , live migration |
| `MINOR` | `VM_UFFD_MINOR` | shmem/hugetlb์ ๊ธฐ์กด ํ์ด์ง ๋งคํ ์ ๋ฏธ๋์ด ํดํธ | ํ์ผ ๋งคํ ๊ธฐ๋ฐ VMM |
mfill_atomic ๋ชจ๋ ๋น๊ต
| ๋ชจ๋ | ioctl | src_addr ์ฌ์ฉ | ๋์ VMA | ์ค๋ช |
|---|---|---|---|---|
| `COPY` | `UFFDIO_COPY` | O (์ฌ์ฉ์ ์์ค) | anonymous, shmem | ์์ค์์ ๋์์ผ๋ก ํ์ด์ง ๋ณต์ฌ |
| `ZEROPAGE` | `UFFDIO_ZEROPAGE` | X | anonymous | ์(0) ํ์ด์ง ๋งคํ |
| `CONTINUE` | `UFFDIO_CONTINUE` | X | shmem only | ๊ธฐ์กด shmem ํ์ด์ง๋ฅผ PTE์ ์ฐ๊ฒฐ |
| `POISON` | `UFFDIO_POISON` | X | anonymous, shmem | PTE ๋ง์ปค๋ฅผ POISONED๋ก ์ค์ |
lock ์ ๋ต ๋น๊ต (PER_VMA_LOCK)
| ์ํฉ | mmap_lock | vma lock | ์ค๋ช |
|---|---|---|---|
| `CONFIG_PER_VMA_LOCK` ON | `mmap_read_lock` ์ต์ํ | `lock_vma_under_rcu()` ์๋ | RCU ๊ธฐ๋ฐ ๋ฝ์ผ๋ก ์ฑ๋ฅ ํฅ์ |
| PER_VMA_LOCK OFF ๋๋ ์คํจ | `mmap_read_lock` ์ฌ์ฉ | VMA ์์ ์ ์ฝ๊ธฐ ์ ๊ธ | ๊ธฐ์กด ๋ฐฉ์ |
| `uffd_move_lock()` | dst/src VMA ์ ๊ธ | `vma_start_read_locked()` | ์ด์ค ์ ๊ธ์ผ๋ก ๊ต์ฐจ ์ ๊ทผ ๋ฐฉ์ง |
UFFDIO_MOVE ์ฒ๋ฆฌ ๊ฒฝ๋ก ๋น๊ต
| ์์ค PTE ์ํ | ํ๋๊ทธ | ์ฒ๋ฆฌ ํจ์ | ๋ฐํ๊ฐ |
|---|---|---|---|
| `pte_none` | `ALLOW_SRC_HOLES` ์์ | `-ENOENT` | ์๋ฌ |
| `pte_none` | `ALLOW_SRC_HOLES` ์์ | `PAGE_SIZE` (๊ฑด๋๋) | ์ฑ๊ณต |
| `is_zero_pfn` | - | `move_zeropage_pte()` | `PAGE_SIZE` |
| `pte_present` (์ผ๋ฐ) | - | `move_present_ptes()` | ์ด๋๋ ๋ฐ์ดํธ |
| `pte_present` (large folio) | - | `split_folio()` โ ์ฌ์๋ | `-EAGAIN` |
| swap + migration | - | `migration_entry_wait()` โ ์ฌ์๋ | `-EAGAIN` |
| swap + exclusive | - | `move_swap_pte()` | `PAGE_SIZE` |
| swap + non-exclusive | - | `-EBUSY` | ์๋ฌ |
move_present_ptes() ๋ฐฐ์น ์ด๋ ์กฐ๊ฑด
| ์กฐ๊ฑด | ๊ฒฐ๊ณผ | ์ค๋ช |
|---|---|---|
| `folio_test_large(src_folio)` | `-EBUSY` | large folio๋ ๋ฐฐ์น ์ด๋ ๋ถ๊ฐ |
| `folio_maybe_dma_pinned(src_folio)` | `-EBUSY` | DMA ํ๋ folio๋ ์ด๋ ๋ถ๊ฐ |
| `!PageAnonExclusive(&src_folio->page)` | `-EBUSY` | ๊ณต์ ์ต๋ช ํ์ด์ง ์ด๋ ๋ถ๊ฐ |
| `check_ptes_for_batched_move()` ์คํจ | ๋ฐฐ์น ์ค๋จ | ๋ค์ PTE์์ ๊ฐ๋ณ ์ด๋ |
| `folio_maybe_dma_pinned()` (์ด๋ ์ค) | `-EBUSY` + ๋ณต์ | ์ด๋ ์ค DMA ํ ๋ฐ์ |