58. MMU ์•Œ๋ฆผ (MMU Notifier)

๊ฐœ์š”

MMU ์•Œ๋ฆผ(MMU Notifier)์€ ๋ฆฌ๋ˆ…์Šค ์ปค๋„์˜ ๊ฐ€์ƒ ๋จธ์‹ (Virtual Machine)๊ณผ ๋””๋ฐ”์ด์Šค ๋“œ๋ผ์ด๋ฒ„์—์„œ ์‚ฌ์šฉ๋˜๋Š” ๋ฉ”๋ชจ๋ฆฌ ๋™๊ธฐํ™” ๋ฉ”์ปค๋‹ˆ์ฆ˜์ž…๋‹ˆ๋‹ค. ๊ฐ€์ƒ ๋จธ์‹ ์€ ์ž์ฒด์ ์ธ 2์ฐจ MMU(Second-stage MMU)๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๊ฒŒ์ŠคํŠธ ๊ฐ€์ƒ ์ฃผ์†Œ๋ฅผ ํ˜ธ์ŠคํŠธ ๋ฌผ๋ฆฌ ์ฃผ์†Œ๋กœ ๋ณ€ํ™˜ํ•˜๋Š”๋ฐ, ํ˜ธ์ŠคํŠธ ์ธก์—์„œ ํŽ˜์ด์ง€ ํ…Œ์ด๋ธ”์ด ๋ณ€๊ฒฝ๋  ๋•Œ 2์ฐจ MMU๋„ ํ•จ๊ป˜ ๋™๊ธฐํ™”ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. MMU ์•Œ๋ฆผ์€ ์ด๋Ÿฌํ•œ ๋™๊ธฐํ™”๋ฅผ ์œ„ํ•ด ์ปค๋„์ด ๋“ฑ๋ก๋œ ๋“œ๋ผ์ด๋ฒ„์—๊ฒŒ ํŽ˜์ด์ง€ ํ…Œ์ด๋ธ” ๋ณ€๊ฒฝ ์ด๋ฒคํŠธ๋ฅผ ํ†ต๋ณดํ•˜๋Š” ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

MMU ์•Œ๋ฆผ์€ ํฌ๊ฒŒ ๋‘ ๊ฐ€์ง€ ์œ ํ˜•์œผ๋กœ ๋‚˜๋‰ฉ๋‹ˆ๋‹ค: ์ „ํ†ต์ ์ธ mmu_notifier(hlist ๊ธฐ๋ฐ˜)์™€ ์„ฑ๋Šฅ์ด ํ–ฅ์ƒ๋œ mmu_interval_notifier(interval tree ๊ธฐ๋ฐ˜)์ž…๋‹ˆ๋‹ค. interval notifier๋Š” ํŠน์ • ๊ฐ€์ƒ ์ฃผ์†Œ ๋ฒ”์œ„์— ๋Œ€ํ•ด ํšจ์œจ์ ์ธ ์œ ํšจํ™”(invalidation)๋ฅผ ๊ฐ€๋Šฅํ•˜๊ฒŒ ํ•˜๋ฉฐ, KVM์˜ ๊ฒฝ์šฐ TDP(Two-Dimensional Paging) ์„ธ์ปจ๋“œ ์Šคํ…Œ์ด์ง€ ๋ณ€ํ™˜ ๊ด€๋ฆฌ์— ์ฃผ๋กœ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.

ํ•œ ์ค„๋กœ ๋ณด๋ฉด, ํŽ˜์ด์ง€ ํ…Œ์ด๋ธ”์ด "์„œ๊ฐ€ ๋ฐฐ์น˜ํ‘œ"๋ผ๋ฉด MMU notifier๋Š” ์ฑ…์ด ์˜ฎ๊ฒจ์ง€๊ธฐ ์ „ํ›„๋กœ ๊ด€๋ จ ๋“œ๋ผ์ด๋ฒ„์—๊ฒŒ ์ƒˆ ์œ„์น˜๋ฅผ ์•Œ๋ ค ์ฃผ๋Š” ์•ˆ๋‚ด ๋ฐฉ์†ก์ž…๋‹ˆ๋‹ค. ๊ทธ๋ž˜์„œ munmap(), mprotect(), mremap(), migrate_vma_collect() ๊ฐ™์€ ๊ฒฝ๋กœ์™€ TLB ๋ฌดํšจํ™”๋ฅผ ํ•จ๊ป˜ ๋ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

์†Œ์Šค ํŒŒ์ผ ๊ฒฝ๋กœ:

mm/mmu_notifier.c                    // MMU ์•Œ๋ฆผ ํ•ต์‹ฌ ๊ตฌํ˜„
include/linux/mmu_notifier.h         // ์ธํ„ฐํŽ˜์ด์Šค ์ •์˜
drivers/gpu/drm/i915/gem/i915_gem_userptr.c  // GPU ๋“œ๋ผ์ด๋ฒ„ ์‚ฌ์šฉ ์˜ˆ์‹œ
drivers/gpu/drm/amd/amdgpu/amdgpu_hmm.c      // HMM/interval notifier ์‚ฌ์šฉ ์˜ˆ์‹œ
drivers/iommu/intel/svm.c                    // IOMMU SVA ์—ฐ๋™ ์˜ˆ์‹œ

๋น ๋ฅธ ์ ๊ฒ€ ๋ช…๋ น

# MMU ์•Œ๋ฆผ ํ™œ์„ฑํ™” ํ™•์ธ
cat /boot/config-$(uname -r) | grep CONFIG_MMU_NOTIFIER

# KVM/MMU ์•Œ๋ฆผ ๋™์ž‘ ํ™•์ธ
dmesg | grep -i "mmu.*notif" | tail -20
sudo trace-cmd record -p function_graph -l __mmu_notifier_invalidate_range_start -l __mmu_notifier_invalidate_range_end -l mmu_interval_notifier_remove sleep 5
sudo trace-cmd report | head -20

# MMU ์•Œ๋ฆผ ๋””๋ฒ„๊ทธ ์ •๋ณด
cat /proc/mmu_notifiers 2>/dev/null || echo "์ง€์›ํ•˜์ง€ ์•Š๋Š” ์ปค๋„"

# ํ˜„์žฌ mm ๊ตฌ์กฐ์ฒด์˜ notifier ๊ตฌ์กฐ ํ™•์ธ
sudo cat /proc/1/maps | head -5
# (์‹ค์ œ MMU ์•Œ๋ฆผ ์ •๋ณด๋Š” ftrace๋กœ ํ™•์ธ)
grep -E 'mmu_notifier|mmu_interval' /sys/kernel/debug/tracing/available_filter_functions 2>/dev/null | head -20
sudo trace-cmd report | head -20

# interval notifier ํŠธ๋ฆฌ ํ™•์ธ (KVM ์‚ฌ์šฉ ์‹œ)
sudo cat /sys/kernel/debug/kvm/mmu_notifier_count 2>/dev/null || echo "๋””๋ฒ„๊ทธ ์ง€์› ์•ˆ ํ•จ"

# ๊ด€๋ จ ์ปค๋„ ๋ชจ๋“ˆ ํ™•์ธ
lsmod | grep -E "(kvm|vfio|i915)"

# MMU ์•Œ๋ฆผ ์ด๋ฒคํŠธ ๋ชจ๋‹ˆํ„ฐ๋ง
sudo perf probe -a 'mmu_notifier_invalidate_range_start'
sudo perf record -e probe:mmu_notifier_invalidate_range_start -aR sleep 5
sudo perf script

# ํŽ˜์ด์ง€ ํดํŠธ์™€ ์ฃผ์†Œ ๊ณต๊ฐ„ ์ ๊ฒ€
cat /proc/vmstat | grep -E 'pgfault|pgmajfault'
cat /proc/pressure/memory
grep -E 'PageTables|Vmalloc|KReclaimable' /proc/meminfo
sudo cat /sys/kernel/debug/kernel_page_tables 2>/dev/null | head -40

ํ•ต์‹ฌ ์ž๋ฃŒ๊ตฌ์กฐ

struct mmu_notifier_subscriptions

mm/mmu_notifier.c:39-50 โ€” MM์˜ ๋ชจ๋“  MMU ์•Œ๋ฆผ์„ ๊ด€๋ฆฌํ•˜๋Š” ๊ตฌ์กฐ์ฒด์ž…๋‹ˆ๋‹ค.

struct mmu_notifier_subscriptions {
/* ์ด MM์— ๋“ฑ๋ก๋œ ๋ชจ๋“  MMU ์•Œ๋ฆผ์ด ์ด ๋ฆฌ์ŠคํŠธ์— ํ์ž‰๋จ */
struct hlist_head list;           // hlist ๊ธฐ๋ฐ˜ ์•Œ๋ฆผ ๋ชฉ๋ก
bool has_itree;                   // interval tree ์‚ฌ์šฉ ์—ฌ๋ถ€
spinlock_t lock;                  // ๋ฆฌ์ŠคํŠธ ์ˆ˜์ • ์‹œ๋ฆฌ์–ผ๋ผ์ด์ œ์ด์…˜
unsigned long invalidate_seq;     // ์œ ํšจํ™” ์‹œํ€€์Šค ๋ฒˆํ˜ธ
unsigned long active_invalidate_ranges; // ํ™œ์„ฑ ์œ ํšจํ™” ๋ฒ”์œ„ ์ˆ˜
struct rb_root_cached itree;     // interval tree ๋ฃจํŠธ
wait_queue_head_t wq;            // ๋Œ€๊ธฐ ํ
struct hlist_head deferred_list;  // ์ง€์—ฐ๋œ ์ถ”๊ฐ€/์ œ๊ฑฐ ๋ชฉ๋ก
};

struct mmu_notifier

include/linux/mmu_notifier.h:228-234 โ€” ๊ฐœ๋ณ„ MMU ์•Œ๋ฆผ ๊ตฌ๋… ๊ตฌ์กฐ์ฒด์ž…๋‹ˆ๋‹ค.

struct mmu_notifier {
struct hlist_node hlist;                  // ํ•ด์‹œ ๋ฆฌ์ŠคํŠธ ๋…ธ๋“œ
const struct mmu_notifier_ops *ops;      // ์ฝœ๋ฐฑ ํ•จ์ˆ˜ ํ…Œ์ด๋ธ”
struct mm_struct *mm;                     // ์—ฐ๊ฒฐ๋œ mm ๊ตฌ์กฐ์ฒด
struct rcu_head rcu;                      // RCU ์ •๋ฆฌ์šฉ
unsigned int users;                       // ์ฐธ์กฐ ์นด์šดํŠธ
};

struct mmu_interval_notifier

include/linux/mmu_notifier.h:248-254 โ€” interval tree ๊ธฐ๋ฐ˜ ์•Œ๋ฆผ ๊ตฌ์กฐ์ฒด์ž…๋‹ˆ๋‹ค.

struct mmu_interval_notifier {
struct interval_tree_node interval_tree;  // interval tree ๋…ธ๋“œ
const struct mmu_interval_notifier_ops *ops; // ์ฝœ๋ฐฑ ํ•จ์ˆ˜
struct mm_struct *mm;                     // ์—ฐ๊ฒฐ๋œ mm ๊ตฌ์กฐ์ฒด
struct hlist_node deferred_item;          // ์ง€์—ฐ ๋ชฉ๋ก ๋…ธ๋“œ
unsigned long invalidate_seq;            // ์œ ํšจํ™” ์‹œํ€€์Šค ๋ฒˆํ˜ธ
};

struct mmu_notifier_ops

include/linux/mmu_notifier.h:64-215 โ€” MMU ์•Œ๋ฆผ ์ฝœ๋ฐฑ ํ•จ์ˆ˜ ํ…Œ์ด๋ธ”์ž…๋‹ˆ๋‹ค.

struct mmu_notifier_ops {
/* ๋ชจ๋“  ํŽ˜์ด์ง€ ํ•ด์ œ ์ „ MM ์ข…๋ฃŒ ์‹œ ํ˜ธ์ถœ๋จ */
void (*release)(struct mmu_notifier *subscription, struct mm_struct *mm);

/* PTE young ๋น„ํŠธ ํ…Œ์ŠคํŠธ ๋ฐ ํด๋ฆฌ์–ด ํ›„ ์ Š์€ ํŽ˜์ด์ง€ ํ”Œ๋Ÿฌ์‹œ */
int (*clear_flush_young)(struct mmu_notifier *subscription,
struct mm_struct *mm,
unsigned long start, unsigned long end);

/* PTE young ๋น„ํŠธ ์ Š์€ ํŽ˜์ด์ง€ ํ”Œ๋Ÿฌ์‹œ ์—†์ด ํด๋ฆฌ์–ด */
int (*clear_young)(struct mmu_notifier *subscription,
struct mm_struct *mm,
unsigned long start, unsigned long end);

/* PTE young ๋น„ํŠธ ํ…Œ์ŠคํŠธ๋งŒ ์ˆ˜ํ–‰ */
int (*test_young)(struct mmu_notifier *subscription,
struct mm_struct *mm, unsigned long address);

/* ์œ ํšจํ™” ์‹œ์ž‘ ์•Œ๋ฆผ (SPTES ์„ค์ • ๊ธˆ์ง€) */
int (*invalidate_range_start)(struct mmu_notifier *subscription,
const struct mmu_notifier_range *range);

/* ์œ ํšจํ™” ์ข…๋ฃŒ ์•Œ๋ฆผ (ํŽ˜์ด์ง€ ํ•ด์ œ ์™„๋ฃŒ) */
void (*invalidate_range_end)(struct mmu_notifier *subscription,
const struct mmu_notifier_range *range);

/* ์„ธ์ปจ๋“œ TLB ์œ ํšจํ™” (้ž-sleeping) */
void (*arch_invalidate_secondary_tlbs)(struct mmu_notifier *subscription,
struct mm_struct *mm,
unsigned long start, unsigned long end);

/* ์•Œ๋ฆผ ๋ฉ”๋ชจ๋ฆฌ ํ• ๋‹น ์ฝœ๋ฐฑ */
struct mmu_notifier *(*alloc_notifier)(struct mm_struct *mm);

/* ์•Œ๋ฆผ ๋ฉ”๋ชจ๋ฆฌ ํ•ด์ œ ์ฝœ๋ฐฑ */
void (*free_notifier)(struct mmu_notifier *subscription);
};

struct mmu_notifier_range

include/linux/mmu_notifier.h:262-269 โ€” ์œ ํšจํ™” ๋ฒ”์œ„๋ฅผ ๋‚˜ํƒ€๋‚ด๋Š” ๊ตฌ์กฐ์ฒด์ž…๋‹ˆ๋‹ค.

struct mmu_notifier_range {
struct mm_struct *mm;           // ๋Œ€์ƒ mm
unsigned long start;           // ์‹œ์ž‘ ๊ฐ€์ƒ ์ฃผ์†Œ
unsigned long end;             // ์ข…๋ฃŒ ๊ฐ€์ƒ ์ฃผ์†Œ
unsigned flags;                // ํ”Œ๋ž˜๊ทธ (MMU_NOTIFIER_RANGE_BLOCKABLE ๋“ฑ)
enum mmu_notifier_event event; // ์ด๋ฒคํŠธ ํƒ€์ž…
void *owner;                   // ์†Œ์œ ์ž (MIGRATE/EXCLUSIVE์šฉ)
};

์•„๋ž˜ ๊ทธ๋ฆผ์€ ๊ณตํ†ต mmu_notifier_subscriptions ์•„๋ž˜์—์„œ ๋‘ ์•Œ๋ฆผ ํƒ€์ž…์ด ์–ด๋–ป๊ฒŒ ๊ฐˆ๋ผ์ง€๋Š”์ง€ ๋ณด์—ฌ์ค๋‹ˆ๋‹ค.

MMU notifier ๊ตฌ์กฐ ๊ด€๊ณ„๋„

enum mmu_notifier_event

include/linux/mmu_notifier.h:51-60 โ€” MMU ์•Œ๋ฆผ ์ด๋ฒคํŠธ ํƒ€์ž…์ž…๋‹ˆ๋‹ค.

enum mmu_notifier_event {
MMU_NOTIFY_UNMAP = 0,           // munmap/mremap๋กœ ๋งคํ•‘ ํ•ด์ œ
MMU_NOTIFY_CLEAR,               // PTE ํด๋ฆฌ์–ด (madvise ๋“ฑ)
MMU_NOTIFY_PROTECTION_VMA,      // VMA ๋ณดํ˜ธ ๋ณ€๊ฒฝ (mprotect)
MMU_NOTIFY_PROTECTION_PAGE,     // ํŽ˜์ด์ง€ ๋ณดํ˜ธ ๋ณ€๊ฒฝ (PTE ์ง์ ‘ ์ˆ˜์ •)
MMU_NOTIFY_SOFT_DIRTY,          // ์†Œํ”„ํŠธ ๋”ํ‹ฐ ๊ณ„์ •
MMU_NOTIFY_RELEASE,             // MM ํ•ด์ œ (interval notifier)
MMU_NOTIFY_MIGRATE,             // ํŽ˜์ด์ง€ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜
MMU_NOTIFY_EXCLUSIVE,           // ๋””๋ฐ”์ด์Šค ๋…์  ๋งคํ•‘
};

ํ•ต์‹ฌ ํ•จ์ˆ˜

mmu_notifier_register()

mm/mmu_notifier.c:698-708 โ€” MM์— MMU ์•Œ๋ฆผ์„ ๋“ฑ๋กํ•ฉ๋‹ˆ๋‹ค.

int mmu_notifier_register(struct mmu_notifier *subscription,
struct mm_struct *mm)
{
int ret;

mmap_write_lock(mm);
ret = __mmu_notifier_register(subscription, mm);
mmap_write_unlock(mm);
return ret;
}

๋™์ž‘ ์›๋ฆฌ: mmap_lock ์“ฐ๊ธฐ ์ž ๊ธˆ์„ ํš๋“ํ•œ ํ›„ __mmu_notifier_register()๋ฅผ ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค. ์ด ํ•จ์ˆ˜๋Š” notifier_subscriptions๊ฐ€ ์—†์œผ๋ฉด ํ• ๋‹นํ•˜๊ณ , ๋ชจ๋“  ์ž ๊ธˆ(mm_take_all_locks)์„ ํš๋“ํ•œ ํ›„ ์•Œ๋ฆผ์„ ๋ฆฌ์ŠคํŠธ์— ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.

mmu_notifier_unregister()

mm/mmu_notifier.c:796-836 โ€” MMU ์•Œ๋ฆผ ๋“ฑ๋ก์„ ํ•ด์ œํ•ฉ๋‹ˆ๋‹ค.

void mmu_notifier_unregister(struct mmu_notifier *subscription,
struct mm_struct *mm)
{
BUG_ON(atomic_read(&mm->mm_count) <= 0);

if (!hlist_unhashed(&subscription->hlist)) {
/* SRCU๊ฐ€ ->release๊ฐ€ ๋๋‚  ๋•Œ๊นŒ์ง€ exit_mmap์„ ๊ธฐ๋‹ค๋ฆฌ๊ฒŒ ํ•ฉ๋‹ˆ๋‹ค. */
int id;

id = srcu_read_lock(&srcu);
/* exit_mmap์€ ํŽ˜์ด์ง€๋ฅผ ํ•ด์ œํ•˜๊ธฐ ์ „์— ->release๊ฐ€ ํ˜ธ์ถœ๋˜๋„๋ก ๊ธฐ๋‹ค๋ฆฝ๋‹ˆ๋‹ค. */
if (subscription->ops->release)
subscription->ops->release(subscription, mm);
srcu_read_unlock(&srcu, id);

spin_lock(&mm->notifier_subscriptions->lock);
/* __mmu_notifier_release๊ฐ€ ๋จผ์ € ์ง€์šธ ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ list_del_rcu()๋Š” ์“ฐ์ง€ ์•Š์Šต๋‹ˆ๋‹ค. */
hlist_del_init_rcu(&subscription->hlist);
spin_unlock(&mm->notifier_subscriptions->lock);
}

/* ์‹คํ–‰ ์ค‘์ธ ๋ชจ๋“  ๋ฉ”์„œ๋“œ๊ฐ€ ๋๋‚  ๋•Œ๊นŒ์ง€ ๊ธฐ๋‹ค๋ฆฝ๋‹ˆ๋‹ค. */
synchronize_srcu(&srcu);
mmdrop(mm);
}

๋™์ž‘ ์›๋ฆฌ: ๋จผ์ € ->release ์ฝœ๋ฐฑ์„ ํ˜ธ์ถœํ•˜์—ฌ ๋ชจ๋“  SPTES(์„ธ์ปจ๋“œ PTE)๋ฅผ ํ•ด์ œํ•˜๊ณ , ๋ฆฌ์ŠคํŠธ์—์„œ ์ œ๊ฑฐํ•œ ํ›„ SRCU ๋™๊ธฐํ™”๋ฅผ ํ†ตํ•ด ์ง„ํ–‰ ์ค‘์ธ ์ฝœ๋ฐฑ์ด ์™„๋ฃŒ๋  ๋•Œ๊นŒ์ง€ ๋Œ€๊ธฐํ•ฉ๋‹ˆ๋‹ค.

mmu_notifier_get_locked()

mm/mmu_notifier.c:750-776 โ€” ๊ธฐ์กด ์•Œ๋ฆผ์„ ์žฌ์‚ฌ์šฉํ•˜๊ฑฐ๋‚˜ ์ƒˆ๋กœ ํ• ๋‹นํ•˜์—ฌ ๋“ฑ๋กํ•ฉ๋‹ˆ๋‹ค.

struct mmu_notifier *mmu_notifier_get_locked(const struct mmu_notifier_ops *ops,
struct mm_struct *mm)
{
struct mmu_notifier *subscription;
int ret;

mmap_assert_write_locked(mm);

if (mm->notifier_subscriptions) {
subscription = find_get_mmu_notifier(mm, ops);
if (subscription)
return subscription;
}

subscription = ops->alloc_notifier(mm);
if (IS_ERR(subscription))
return subscription;
subscription->ops = ops;
ret = __mmu_notifier_register(subscription, mm);
if (ret)
goto out_free;
return subscription;
out_free:
subscription->ops->free_notifier(subscription);
return ERR_PTR(ret);
}

๋™์ž‘ ์›๋ฆฌ: ๋จผ์ € ๋™์ผํ•œ ops ํฌ์ธํ„ฐ๋ฅผ ๊ฐ€์ง„ ๊ธฐ์กด ์•Œ๋ฆผ์ด ์žˆ๋Š”์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค. ์—†์œผ๋ฉด ops->alloc_notifier()๋กœ ์ƒˆ ์•Œ๋ฆผ์„ ํ• ๋‹นํ•˜๊ณ  ๋“ฑ๋กํ•ฉ๋‹ˆ๋‹ค. ์ด๋Š” KVM๊ณผ ๊ฐ™์€ ๋“œ๋ผ์ด๋ฒ„์—์„œ ์—ฌ๋Ÿฌ vCPU๊ฐ€ ๋™์ผํ•œ MM์— ๋Œ€ํ•ด ์ค‘๋ณต ๋“ฑ๋กํ•˜๋Š” ๊ฒƒ์„ ๋ฐฉ์ง€ํ•ฉ๋‹ˆ๋‹ค.

mmu_notifier_put()

mm/mmu_notifier.c:871-887 โ€” MMU ์•Œ๋ฆผ์— ๋Œ€ํ•œ ์ฐธ์กฐ๋ฅผ ํ•ด์ œํ•ฉ๋‹ˆ๋‹ค.

void mmu_notifier_put(struct mmu_notifier *subscription)
{
struct mm_struct *mm = subscription->mm;

spin_lock(&mm->notifier_subscriptions->lock);
if (WARN_ON(!subscription->users) || --subscription->users)
goto out_unlock;
hlist_del_init_rcu(&subscription->hlist);  // ๋งˆ์ง€๋ง‰ ์ฐธ์กฐ์‹œ ๋ฆฌ์ŠคํŠธ ์ œ๊ฑฐ
spin_unlock(&mm->notifier_subscriptions->lock);

call_srcu(&srcu, &subscription->rcu, mmu_notifier_free_rcu);  // RCU ์ฝœ๋ฐฑ
return;

out_unlock:
spin_unlock(&mm->notifier_subscriptions->lock);
}

mmu_interval_notifier_insert()

mm/mmu_notifier.c:971-991 โ€” interval tree ๊ธฐ๋ฐ˜ ์•Œ๋ฆผ์„ ์‚ฝ์ž…ํ•ฉ๋‹ˆ๋‹ค.

int mmu_interval_notifier_insert(struct mmu_interval_notifier *interval_sub,
struct mm_struct *mm, unsigned long start,
unsigned long length,
const struct mmu_interval_notifier_ops *ops)
{
struct mmu_notifier_subscriptions *subscriptions;
int ret;

might_lock(&mm->mmap_lock);

subscriptions = smp_load_acquire(&mm->notifier_subscriptions);
if (!subscriptions || !subscriptions->has_itree) {
ret = mmu_notifier_register(NULL, mm);
if (ret)
return ret;
subscriptions = mm->notifier_subscriptions;
}
return __mmu_interval_notifier_insert(interval_sub, mm, subscriptions,
start, length, ops);
}

๋™์ž‘ ์›๋ฆฌ: interval tree ๊ธฐ๋ฐ˜ ์•Œ๋ฆผ์„ ํŠน์ • VA ๋ฒ”์œ„์— ๋“ฑ๋กํ•ฉ๋‹ˆ๋‹ค. notifier_subscriptions๊ฐ€ ์—†๊ฑฐ๋‚˜ itree๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š์œผ๋ฉด mmu_notifier_register(NULL, mm)๋กœ ์ดˆ๊ธฐํ™”ํ•œ ํ›„ ์‚ฝ์ž…ํ•ฉ๋‹ˆ๋‹ค.

__mmu_interval_notifier_insert()

mm/mmu_notifier.c:915-952 โ€” ํ™œ์„ฑ ์œ ํšจํ™” ์ค‘์ธ์ง€์— ๋”ฐ๋ผ ์ฆ‰์‹œ ์‚ฝ์ž…ํ•˜๊ฑฐ๋‚˜ ์ง€์—ฐ ๋ชฉ๋ก์œผ๋กœ ๋„˜๊น๋‹ˆ๋‹ค.

/*
* If some invalidate_range_start/end region is going on in parallel
* we don't know what VA ranges are affected, so we must assume this
* new range is included.
*
* If the itree is invalidating then we are not allowed to change
* it. Retrying until invalidation is done is tricky due to the
* possibility for live lock, instead defer the add to
* mn_itree_inv_end() so this algorithm is deterministic.
*
* In all cases the value for the interval_sub->invalidate_seq should be
* odd, see mmu_interval_read_begin()
*/
spin_lock(&subscriptions->lock);
if (subscriptions->active_invalidate_ranges) {
if (mn_itree_is_invalidating(subscriptions))
hlist_add_head(&interval_sub->deferred_item,
&subscriptions->deferred_list);
else {
subscriptions->invalidate_seq |= 1;
interval_tree_insert(&interval_sub->interval_tree,
&subscriptions->itree);
}
interval_sub->invalidate_seq = subscriptions->invalidate_seq;
} else {
WARN_ON(mn_itree_is_invalidating(subscriptions));
/* ์œ ํšจํ™” ์ค‘์ด ์•„๋‹Œ ๊ตฌ๋…์˜ ์‹œ์ž‘ ์‹œํ€€์Šค๋Š” ํ™€์ˆ˜์—ฌ์•ผ ํ•ฉ๋‹ˆ๋‹ค. */
interval_sub->invalidate_seq =
subscriptions->invalidate_seq - 1;
interval_tree_insert(&interval_sub->interval_tree,
&subscriptions->itree);
}
spin_unlock(&subscriptions->lock);
return 0;

mmu_interval_read_begin()

mm/mmu_notifier.c:187-261 โ€” ์„ธ์ปจ๋“œ ์Šคํ…Œ์ด์ง€ ์œ ํšจํ™”์™€ ์ถฉ๋Œ ํšŒํ”ผ๋ฅผ ์œ„ํ•œ ์ฝ๊ธฐ ์ž„๊ณ„ ๊ตฌ๊ฐ„์„ ์‹œ์ž‘ํ•ฉ๋‹ˆ๋‹ค.

unsigned long
mmu_interval_read_begin(struct mmu_interval_notifier *interval_sub)
{
struct mmu_notifier_subscriptions *subscriptions =
interval_sub->mm->notifier_subscriptions;
unsigned long seq;
bool is_invalidating;

spin_lock(&subscriptions->lock);
seq = READ_ONCE(interval_sub->invalidate_seq);  // ํ˜„์žฌ ์‹œํ€€์Šค ์ฝ๊ธฐ
is_invalidating = seq == subscriptions->invalidate_seq;  // ์œ ํšจํ™” ์ค‘์ธ์ง€ ํ™•์ธ
spin_unlock(&subscriptions->lock);

/* ์œ ํšจํ™” ์ค‘์ด๋ฉด ์™„๋ฃŒ๋  ๋•Œ๊นŒ์ง€ ๋Œ€๊ธฐ */
if (is_invalidating)
wait_event(subscriptions->wq,
READ_ONCE(subscriptions->invalidate_seq) != seq);

return seq;
}

mmu_interval_notifier_remove()

mm/mmu_notifier.c:1037-1078 โ€” interval ์•Œ๋ฆผ์„ ์ œ๊ฑฐํ•˜๊ณ , ์ง„ํ–‰ ์ค‘์ธ ์œ ํšจํ™”๊ฐ€ ์žˆ์œผ๋ฉด ๋๋‚  ๋•Œ๊นŒ์ง€ ๊ธฐ๋‹ค๋ฆฝ๋‹ˆ๋‹ค.

void mmu_interval_notifier_remove(struct mmu_interval_notifier *interval_sub)
{
struct mm_struct *mm = interval_sub->mm;
struct mmu_notifier_subscriptions *subscriptions =
mm->notifier_subscriptions;
unsigned long seq = 0;

might_sleep();

spin_lock(&subscriptions->lock);
if (mn_itree_is_invalidating(subscriptions)) {
/* insert ์ดํ›„ deferred list์— ๋“ค์–ด๊ฐ”์ง€๋งŒ ์•„์ง ์ฒ˜๋ฆฌ๋˜์ง€ ์•Š์€ ๊ฒฝ์šฐ์ž…๋‹ˆ๋‹ค. */
if (RB_EMPTY_NODE(&interval_sub->interval_tree.rb)) {
hlist_del(&interval_sub->deferred_item);
} else {
hlist_add_head(&interval_sub->deferred_item,
&subscriptions->deferred_list);
seq = subscriptions->invalidate_seq;
}
} else {
WARN_ON(RB_EMPTY_NODE(&interval_sub->interval_tree.rb));
interval_tree_remove(&interval_sub->interval_tree,
&subscriptions->itree);
}
spin_unlock(&subscriptions->lock);

/* ์œ ํšจํ™” ์ฝœ๋ฐฑ์ด ์žก์€ ๋ฝ๊ณผ ๊ฒน์น  ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ ์ž ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. */
lock_map_acquire(&__mmu_notifier_invalidate_range_start_map);
lock_map_release(&__mmu_notifier_invalidate_range_start_map);
if (seq)
wait_event(subscriptions->wq,
mmu_interval_seq_released(subscriptions, seq));

/* mmu_interval_notifier_insert()์˜ mmgrab๊ณผ ์ง์„ ์ด๋ฃน๋‹ˆ๋‹ค. */
mmdrop(mm);
}

ํ˜ธ์ถœ ํ๋ฆ„

MMU ์•Œ๋ฆผ ๋“ฑ๋ก ํ๋ฆ„

KVM ๋“œ๋ผ์ด๋ฒ„ ์ดˆ๊ธฐํ™”
โ””โ”€โ”€ kvm_mmu_notifier_init()
โ””โ”€โ”€ mmu_notifier_get_locked()
โ”œโ”€โ”€ find_get_mmu_notifier()  // ๊ธฐ์กด ์•Œ๋ฆผ ๊ฒ€์ƒ‰
โ”‚   โ””โ”€โ”€ [์žˆ์œผ๋ฉด] ์ฐธ์กฐ ์นด์šดํŠธ ์ฆ๊ฐ€ ํ›„ ๋ฐ˜ํ™˜
โ””โ”€โ”€ [์—†์œผ๋ฉด]
โ”œโ”€โ”€ ops->alloc_notifier()  // ์ƒˆ ์•Œ๋ฆผ ํ• ๋‹น
โ””โ”€โ”€ __mmu_notifier_register()
โ”œโ”€โ”€ notifier_subscriptions ํ• ๋‹น (์ตœ์ดˆ 1ํšŒ)
โ”œโ”€โ”€ mm_take_all_locks()
โ””โ”€โ”€ hlist_add_head_rcu()  // ๋ฆฌ์ŠคํŠธ์— ์ถ”๊ฐ€

์„ธ์ปจ๋“œ ์Šคํ…Œ์ด์ง€ ์œ ํšจํ™” ํ๋ฆ„

์ด ํ๋ฆ„์€ munmap(), mprotect(), mremap(), migrate_vma_collect() ๊ฐ™์€ ๊ฒฝ๋กœ์—์„œ ์‹œ์ž‘๋ฉ๋‹ˆ๋‹ค.

ํ˜ธ์ŠคํŠธ ์ปค๋„ (PTE ๋ณ€๊ฒฝ)
โ”œโ”€โ”€ mmu_notifier_invalidate_range_start()
โ”‚   โ”œโ”€โ”€ [itree ์‚ฌ์šฉ ์‹œ] mn_itree_invalidate()
โ”‚   โ”‚   โ””โ”€โ”€ interval_sub->ops->invalidate()  // ์„ธ์ปจ๋“œ ์Šคํ…Œ์ด์ง€ SPTES ์ œ๊ฑฐ
โ”‚   โ””โ”€โ”€ [hlist ์‚ฌ์šฉ ์‹œ] mn_hlist_invalidate_range_start()
โ”‚       โ””โ”€โ”€ subscription->ops->invalidate_range_start()
โ”œโ”€โ”€ [PTE ์‹ค์ œ ๋ณ€๊ฒฝ]
โ””โ”€โ”€ mmu_notifier_invalidate_range_end()
โ”œโ”€โ”€ [itree ์‚ฌ์šฉ ์‹œ] mn_itree_inv_end()
โ””โ”€โ”€ [hlist ์‚ฌ์šฉ ์‹œ] mn_hlist_invalidate_end()

Interval Notifier ์ฝ๊ธฐ-์“ฐ๊ธฐ ์ถฉ๋Œ ํšŒํ”ผ

mmu_interval_read_begin()          mn_itree_inv_start_range()
โ”œโ”€โ”€ seq = interval_sub->invalidate_seq  โ”œโ”€โ”€ seq = ++subscriptions->invalidate_seq
โ”œโ”€โ”€ seq == subs->invalidate_seq?        โ””โ”€โ”€ interval_sub->ops->invalidate()
โ”‚   โ””โ”€โ”€ [๊ฐ™์œผ๋ฉด] wait_event()              โ””โ”€โ”€ user_lock()
โ”‚                                             โ”œโ”€โ”€ mmu_interval_set_seq()
โ”‚                                             โ”‚   โ””โ”€โ”€ interval_sub->invalidate_seq = seq
โ”‚                                             โ””โ”€โ”€ user_unlock
โ”‚
โ””โ”€โ”€ mmu_interval_read_retry(seq)
โ””โ”€โ”€ interval_sub->invalidate_seq != seq?  โ†’ true๋ฉด ์žฌ์‹œ๋„ ํ•„์š”

์กฐ๊ฑด๋ณ„ ๋น„๊ต

๋น„๊ต ํ•ญ๋ชฉmmu_notifier (hlist)mmu_interval_notifier (itree)
**๋ฐ์ดํ„ฐ ๊ตฌ์กฐ**ํ•ด์‹œ ๋ฆฌ์ŠคํŠธ์ธํ„ฐ๋ฒŒ ํŠธ๋ฆฌ (rb-tree)
**๊ฒ€์ƒ‰ ๋ณต์žก๋„**O(n) ์ „์ฒด ์ˆœํšŒO(log n) + ๊ฒน์น˜๋Š” ๋ฒ”์œ„๋งŒ
**์‚ฌ์šฉ์ฒ˜**์ „ํ†ต์  MMU ์•Œ๋ฆผKVM ์„ธ์ปจ๋“œ ์Šคํ…Œ์ด์ง€, VFIO
**์ฝœ๋ฐฑ ์ˆ˜**7๊ฐœ (release, clear_flush_young, clear_young, test_young, invalidate_range_start/end, arch_invalidate_secondary_tlbs)1๊ฐœ (invalidate)
**์‹œํ€€์Šค ๋ฒˆํ˜ธ**์‚ฌ์šฉ ์•ˆ ํ•จ์ถฉ๋Œ ํšŒํ”ผ๋ฅผ ์œ„ํ•œ ์‹œํ€€์Šค ๊ธฐ๋ฐ˜
**๋™๊ธฐํ™”**SRCU + spinlockSRCU + spinlock + wait_queue
**์ง€์—ฐ ์ฒ˜๋ฆฌ**์—†์Œdeferred_list๋กœ tree ์—…๋ฐ์ดํŠธ ์ง€์—ฐ
**๋ธ”๋กœํ‚น ์ง€์›**๋ธ”๋กœํ‚น/๋…ผ๋ธ”๋กœํ‚น ๋ถ„๋ฆฌinvalidate ์ฝœ๋ฐฑ์—์„œ ๋ธ”๋กœํ‚น ๊ฐ€๋Šฅ
**๋“ฑ๋ก ํ•ด์ œ**unregister (์ฆ‰์‹œ) / put (์ง€์—ฐ)remove (์ง€์—ฐ ๋Œ€๊ธฐ)
์ด๋ฒคํŠธ ํƒ€์ž…์„ค๋ช…์‚ฌ์šฉ ์‹œ๋‚˜๋ฆฌ์˜ค
`MMU_NOTIFY_UNMAP`munmap/mremap๋กœ ๋งคํ•‘ ํ•ด์ œKVM VM ์ข…๋ฃŒ ์‹œ
`MMU_NOTIFY_CLEAR`PTE ํด๋ฆฌ์–ด (๊ต์ฒด ๋“ฑ)ํŽ˜์ด์ง€ ๊ต์ฒด, madvise
`MMU_NOTIFY_PROTECTION_VMA`VMA ๋ณดํ˜ธ ๋ณ€๊ฒฝmprotect ์‹œ์Šคํ…œ ์ฝœ
`MMU_NOTIFY_PROTECTION_PAGE`ํŽ˜์ด์ง€ ๋ณดํ˜ธ ๋ณ€๊ฒฝPTE ์ง์ ‘ ์ˆ˜์ •
`MMU_NOTIFY_SOFT_DIRTY`์†Œํ”„ํŠธ ๋”ํ‹ฐ ๊ณ„์ •ํŽ˜์ด์ง€ ์ถ”์ , COW
`MMU_NOTIFY_RELEASE`MM ํ•ด์ œVM ํŒŒ๊ดด ์‹œ
`MMU_NOTIFY_MIGRATE`ํŽ˜์ด์ง€ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜NUMA ๋ฐธ๋Ÿฐ์‹ฑ
`MMU_NOTIFY_EXCLUSIVE`๋””๋ฐ”์ด์Šค ๋…์  ๋งคํ•‘GPU/DPU ๋ฉ”๋ชจ๋ฆฌ ํ• ๋‹น
ํ•จ์ˆ˜์—ญํ• ์ž ๊ธˆ
`mmu_notifier_register()`MM์— ์•Œ๋ฆผ ๋“ฑ๋กmmap_write_lock
`mmu_notifier_unregister()`์•Œ๋ฆผ ๋“ฑ๋ก ํ•ด์ œ (์ฆ‰์‹œ)SRCU
`mmu_notifier_get()`์•Œ๋ฆผ ์žฌ์‚ฌ์šฉ/ํ• ๋‹นmmap_write_lock
`mmu_notifier_put()`์ฐธ์กฐ ํ•ด์ œ (์ง€์—ฐ ํ•ด์ œ)spinlock
`mmu_interval_notifier_insert()`interval ์•Œ๋ฆผ ์‚ฝ์ž…might_lock(mmap_lock)
`mmu_interval_notifier_remove()`interval ์•Œ๋ฆผ ์ œ๊ฑฐ (๋Œ€๊ธฐ)spinlock + wait_event

๊ด€๋ จ ๋ฌธ์„œ

  • 00-overview.html โ€” ๋ฉ”๋ชจ๋ฆฌ ๊ด€๋ฆฌ ๊ฐœ์š”
  • 03-vma_mmap.html โ€” VMA / mmap
  • 08-oom.html โ€” OOM Killer
  • 28-rmap.html โ€” Reverse Mapping
  • 38-pagewalk.html โ€” Page Table Walk
  • 13-numa.html โ€” NUMA ๋ฉ”๋ชจ๋ฆฌ ๊ด€๋ฆฌ
  • 51-arm64_vs_x86.html โ€” ์•„ํ‚คํ…์ฒ˜๋ณ„ ์ฐจ์ด์ 
  • 52-linux_6x_to_7x.html โ€” Linux 6.x โ†’ 7.0 ๋ณ€๊ฒฝ์ 