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 ํ˜ธ์ถœ ํ๋ฆ„

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 ๋ชจ๋“œ ๋น„๊ต

๋ชจ๋“œioctlsrc_addr ์‚ฌ์šฉ๋Œ€์ƒ VMA์„ค๋ช…
`COPY``UFFDIO_COPY`O (์‚ฌ์šฉ์ž ์†Œ์Šค)anonymous, shmem์†Œ์Šค์—์„œ ๋Œ€์ƒ์œผ๋กœ ํŽ˜์ด์ง€ ๋ณต์‚ฌ
`ZEROPAGE``UFFDIO_ZEROPAGE`Xanonymous์˜(0) ํŽ˜์ด์ง€ ๋งคํ•‘
`CONTINUE``UFFDIO_CONTINUE`Xshmem only๊ธฐ์กด shmem ํŽ˜์ด์ง€๋ฅผ PTE์— ์—ฐ๊ฒฐ
`POISON``UFFDIO_POISON`Xanonymous, shmemPTE ๋งˆ์ปค๋ฅผ POISONED๋กœ ์„ค์ •

lock ์ „๋žต ๋น„๊ต (PER_VMA_LOCK)

์ƒํ™ฉmmap_lockvma 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 ํ•€ ๋ฐœ์ƒ

๊ด€๋ จ ๋ฌธ์„œ

  • ๋ฉ”๋ชจ๋ฆฌ ๊ด€๋ฆฌ ๊ฐœ์š”
  • VMA / mmap
  • vmalloc
  • Swap / zswap
  • Huge Pages / THP
  • Folio / Page Cache
  • Memory Cgroup
  • Reverse Mapping
  • GUP (Get User Pages)
  • Migration