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 ์๋์์ ๋ ์๋ฆผ ํ์
์ด ์ด๋ป๊ฒ ๊ฐ๋ผ์ง๋์ง ๋ณด์ฌ์ค๋๋ค.
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 + spinlock | SRCU + 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 |