23. HMM โ Heterogeneous Memory Management
๊ฐ์ (Overview)
HMM(Heterogeneous Memory Management)๋ ๋ฆฌ๋
์ค ์ปค๋์์ GPU, FPGA ๊ฐ์ ๋น-CPU ๋๋ฐ์ด์ค๊ฐ CPU ๊ฐ์ ์ฃผ์ ๊ณต๊ฐ์ ์ง์ ๋งคํํ ์ ์๋๋ก ์ง์ํ๋ ์๋ธ์์คํ
์ด๋ค. ๋๋ฐ์ด์ค๊ฐ CPU์ ํ์ด์ง ํ
์ด๋ธ์ ์ฝ๊ณ , ํ์์ ํ์ด์ง ํดํธ๋ฅผ ํธ๋ฆฌ๊ฑฐํ๋ฉฐ, DMA ๋งคํ๊น์ง ์ฒ๋ฆฌํ ์ ์๋ ํ๋ ์์ํฌ๋ฅผ ์ ๊ณตํ๋ค. ํต์ฌ ์์ด๋์ด๋ ๋๋ฐ์ด์ค ๋๋ผ์ด๋ฒ๊ฐ hmm_range_fault()๋ฅผ ํธ์ถํ์ฌ ํน์ ๊ฐ์ ์ฃผ์ ๋ฒ์์ ํ์ด์ง ์ํ๋ฅผ ํ๊บผ๋ฒ์ ํ์ธํ๊ณ , ํ์์ ์๋์ผ๋ก fault-inํ๋ ๊ฒ์ด๋ค.
์์ค ํ์ผ ์์น:
mm/hmm.c โ HMM ํต์ฌ ๊ตฌํ (895์ค)
include/linux/hmm.h โ hmm_range, hmm_pfn_flags ์ ์
include/linux/hmm-dma.h โ hmm_dma_map ๊ตฌ์กฐ์ฒด ์ ์
์ด ๊ตฌํ์ MMU interval notifier๋ก ๋ฒ์๋ฅผ ๋ณดํธํ๋ฉด์ CPU ํ์ด์ง ํ
์ด๋ธ ์ํ๋ฅผ ์ฝ์ด ๋๋ฐ์ด์ค๊ฐ ๋ฐ๋ก ์ฌ์ฉํ PFN ๋ฐฐ์ด์ ์ฑ์ฐ๋ ๋ฐ ์ด์ ์ ๋๋ค. default_flags์ pfn_flags_mask๋ฅผ ์กฐํฉํ๋ฉด ์ ์ฒด ๋ฒ์๋ฅผ ๋ฏธ๋ฆฌ fault-inํ๊ฑฐ๋, ๊ฐ๋ณ PFN์๋ง fault/์ฐ๊ธฐ ์๊ตฌ๋ฅผ ์์ด ๋ฃ๋ ๋ฐฉ์๋ ๊ฐ๋ฅํ๋ค. ์ดํ์๋ ๊ฐ์ PFN ๊ฒฐ๊ณผ๋ฅผ hmm_dma_map_pfn()์ผ๋ก ๋๊ฒจ IOVA ๋๋ PCI P2P DMA ์ฃผ์๋ก ๋ณํํ๋ค.
๋น ๋ฅธ ์ ๊ฒ ๋ช ๋ น
# HMM ๊ด๋ จ ์ปค๋ ์ค์ ํ์ธ
grep -E "CONFIG_HMM|CONFIG_DEVICE_PRIVATE" /boot/config-$(uname -r)
# HMM ๋ชจ๋ ๋ก๋ ์ํ ํ์ธ
lsmod | grep -i hmm
# /sys์์ HMM ๊ด๋ จ ๋๋ฒ๊ทธ ์ ๋ณด ํ์ธ (ํด๋น ์ปค๋ ๋น๋ ์)
cat /sys/kernel/debug/hmm/* 2>/dev/null || echo "HMM debug not available"
# ๋๋ฐ์ด์ค์ DMA ์ฃผ์ ๋ฒ์ ํ์ธ
cat /sys/class/dma/*/dmaaddress 2>/dev/null || echo "N/A"
# ์ปค๋ ๋ก๊ทธ์์ HMM ๊ด๋ จ ๋ฉ์์ง ํ์ธ
dmesg | grep -i "hmm\|device.private\|dma.map"
# ํ์ฌ ์์คํ
์ NUMA ๋
ธ๋ ๋ฐ ๋ฉ๋ชจ๋ฆฌ ํฐ์ด ํ์ธ
numactl --hardware
ls /sys/devices/system/memory/ | head -20
# ํ์ด์ง ํดํธ ํต๊ณ ํ์ธ (HMM์ด fault๋ฅผ ํธ๋ฆฌ๊ฑฐํ ๋ ํ์ฉ)
vmstat 1 3
ํต์ฌ ์๋ฃ๊ตฌ์กฐ
1. `struct hmm_range` โ ๊ฐ์ ์ฃผ์ ๋ฒ์ ์ถ์
๋๋ฐ์ด์ค๊ฐ ์์ฒญํ ๊ฐ์ ์ฃผ์ ๋ฒ์์ ํ์ด์ง ์ํ๋ฅผ ์ถ์ ํ๋ค. MMU interval notifier๋ฅผ ํตํด ๋ฌดํจํ(invalidation)๋ก๋ถํฐ ๋ณดํธ๋ฐ๋๋ค.
/* include/linux/hmm.h:111-120 */
struct hmm_range {
struct mmu_interval_notifier *notifier; /* MMU ๋ฌดํจํ ์๋ฆผ */
unsigned long notifier_seq; /* ๋ฌดํจํ ์ํ์ค ๋ฒํธ */
unsigned long start; /* ๋ฒ์ ์์ ๊ฐ์ ์ฃผ์ (inclusive) */
unsigned long end; /* ๋ฒ์ ๋ ๊ฐ์ ์ฃผ์ (exclusive) */
unsigned long *hmm_pfns; /* PFN ๋ฐฐ์ด (๋ฒ์ ํฌ๊ธฐ๋งํผ) */
unsigned long default_flags; /* ๋ฒ์ ๊ธฐ๋ณธ ํ๋๊ทธ (์ฝ๊ธฐ/์ฐ๊ธฐ ๋ฑ) */
unsigned long pfn_flags_mask; /* ๊ฐ๋ณ PFN ๋ง์คํฌ */
void *dev_private_owner; /* ๋๋ฐ์ด์ค ํ๋ผ์ด๋น ํ์ด์ง ์์ ์ */
};
2. `enum hmm_pfn_flags` โ PFN ์ ์ถ๋ ฅ ํ๋๊ทธ
ํ์ด์ง PFN์ ์ํ๋ฅผ ๋ํ๋ด๋ ๋นํธ ํ๋๊ทธ. ์ ๋ ฅ ํ๋๊ทธ์ ์ถ๋ ฅ ํ๋๊ทธ๊ฐ ๊ตฌ๋ถ๋๋ค.
/* include/linux/hmm.h:38-58 */
enum hmm_pfn_flags {
/* ์ถ๋ ฅ ํ๋ ๋ฐ ํ๋๊ทธ */
HMM_PFN_VALID = 1UL << (BITS_PER_LONG - 1), /* ์ ํจํ PFN */
HMM_PFN_WRITE = 1UL << (BITS_PER_LONG - 2), /* ์ฐ๊ธฐ ๊ฐ๋ฅ */
HMM_PFN_ERROR = 1UL << (BITS_PER_LONG - 3), /* ์ ๊ทผ ๋ถ๊ฐ (์ค๋ฅ) */
HMM_PFN_DMA_MAPPED = 1UL << (BITS_PER_LONG - 4), /* DMA ๋งคํ๋จ */
HMM_PFN_P2PDMA = 1UL << (BITS_PER_LONG - 5), /* P2P ํ์ด์ง */
HMM_PFN_P2PDMA_BUS = 1UL << (BITS_PER_LONG - 6), /* ๋ฒ์ค ๋งคํ P2P */
HMM_PFN_ORDER_SHIFT = (BITS_PER_LONG - 11),
/* ์
๋ ฅ ํ๋๊ทธ */
HMM_PFN_REQ_FAULT = HMM_PFN_VALID, /* ํดํธ ์์ฒญ */
HMM_PFN_REQ_WRITE = HMM_PFN_WRITE, /* ์ฐ๊ธฐ ํดํธ ์์ฒญ */
HMM_PFN_FLAGS = ~((1UL << HMM_PFN_ORDER_SHIFT) - 1), /* PFN ๋นํธ ๋ง์คํฌ */
};
3. `struct hmm_vma_walk` โ ๋ด๋ถ VMA ์ํฌ ์ํ
hmm_range_fault() ๋ด๋ถ์์ ํ์ด์ง ํ
์ด๋ธ์ ์ํํ ๋ ์ฌ์ฉ๋๋ ์์ ์ํ ๊ตฌ์กฐ์ฒด.
/* mm/hmm.c:33-36 */
struct hmm_vma_walk {
struct hmm_range *range; /* ํ์ฌ ์ฒ๋ฆฌ ์ค์ธ hmm_range */
unsigned long last; /* ๋ง์ง๋ง์ผ๋ก ์ฒ๋ฆฌ๋ ์ฃผ์ (์ฌ์์ ์ง์ ) */
};
4. `struct hmm_dma_map` โ DMA ๋งคํ ๋ฐฐ์ด
PFN ๋ชฉ๋ก๊ณผ DMA ์ฃผ์ ๋ฐฐ์ด์ ๊ด๋ฆฌํ๋ ๊ตฌ์กฐ์ฒด. IOVA ๊ธฐ๋ฐ ๋งคํ๊ณผ ์ผ๋ฐ ๋งคํ์ ์ง์ํ๋ค.
/* include/linux/hmm-dma.h:19-24 */
struct hmm_dma_map {
struct dma_iova_state state; /* DMA IOVA ์ํ */
unsigned long *pfn_list; /* PFN ๋ฐฐ์ด */
dma_addr_t *dma_list; /* DMA ์ฃผ์ ๋ฐฐ์ด */
size_t dma_entry_size; /* ๊ฐ DMA ์ํธ๋ฆฌ ํฌ๊ธฐ */
};
5. `struct mm_walk_ops` โ ํ์ด์ง ํ ์ด๋ธ ์ํฌ ์ฐ์ฐ์
HMM์ด ํ์ด์ง ํ ์ด๋ธ์ ์ํํ ๋ ํธ์ถ๋๋ ์ฝ๋ฐฑ ํจ์ ํ ์ด๋ธ.
/* mm/hmm.c:631-638 */
static const struct mm_walk_ops hmm_walk_ops = {
.pud_entry = hmm_vma_walk_pud, /* PUD ์ํธ๋ฆฌ ์ฒ๋ฆฌ */
.pmd_entry = hmm_vma_walk_pmd, /* PMD ์ํธ๋ฆฌ ์ฒ๋ฆฌ */
.pte_hole = hmm_vma_walk_hole, /* PTE ๊ณต๋ฐฑ ์ฒ๋ฆฌ */
.hugetlb_entry = hmm_vma_walk_hugetlb_entry, /* HugeTLB ์ฒ๋ฆฌ */
.test_walk = hmm_vma_walk_test, /* VMA ์ ํจ์ฑ ๊ฒ์ฌ */
.walk_lock = PGWALK_RDLOCK, /* ์ฝ๊ธฐ ์ ๊ธ */
};
ํต์ฌ ํจ์
1. `hmm_range_fault()` โ ๋ฉ์ธ ์ง์ ์
/* mm/hmm.c:659-686 */
int hmm_range_fault(struct hmm_range *range)
์ญํ : ๋๋ฐ์ด์ค๊ฐ ์์ฒญํ ๊ฐ์ ์ฃผ์ ๋ฒ์์ ํ์ด์ง๋ฅผ fault-inํ๊ณ , ํด๋น PFN์ range->hmm_pfns[] ๋ฐฐ์ด์ ๊ธฐ๋กํ๋ค.
๋ถ๊ธฐ ๋ก์ง:
1. mmu_interval_check_retry() โ ๋ฒ์๊ฐ ๋ฌดํจํ๋์์ผ๋ฉด -EBUSY ๋ฐํ (์ฌ์์)
2. walk_page_range() ํธ์ถ โ ํ์ด์ง ํ
์ด๋ธ ์ํ
3. -EBUSY ๋ฐํ ์ hmm_vma_walk.last์์ ์ฌ์์ (do-while ๋ฃจํ)
2. `hmm_vma_walk_pmd()` โ PMD ์ํธ๋ฆฌ ์ฒ๋ฆฌ
/* mm/hmm.c:396-471 */
static int hmm_vma_walk_pmd(pmd_t *pmdp, unsigned long start,
unsigned long end, struct mm_walk *walk)
์ญํ : PMD ์์ค์ ํ์ด์ง ํ ์ด๋ธ ์ํธ๋ฆฌ๋ฅผ ์ฒ๋ฆฌํ๋ค. Huge Page, migration, ์ผ๋ฐ PTE ๋๋ ํ ๋ฆฌ๋ก ๋ถ๊ธฐํ๋ค.
๋ถ๊ธฐ ๋ก์ง:
1. pmd_none() โ hmm_vma_walk_hole() (๋น ์์ญ ์ฒ๋ฆฌ)
2. pmd_is_migration_entry() โ ๋ง์ด๊ทธ๋ ์ด์
๋๊ธฐ ํ ์ฌ์์
3. !pmd_present() โ hmm_vma_handle_absent_pmd() (๋๋ฐ์ด์ค ํ๋ผ์ด๋น ๋ฑ)
4. pmd_trans_huge() โ hmm_vma_handle_pmd() (THP ์ฒ๋ฆฌ)
5. pmd_bad() โ HMM_PFN_ERROR ๋ฐํ
6. ์ผ๋ฐ PTE โ PTE ๋ฃจํ๋ก ์ง์
3. `hmm_vma_handle_pte()` โ ๊ฐ๋ณ PTE ์ฒ๋ฆฌ
/* mm/hmm.c:235-332 */
static int hmm_vma_handle_pte(struct mm_walk *walk, unsigned long addr,
unsigned long end, pmd_t *pmdp, pte_t *ptep,
unsigned long *hmm_pfn)
์ญํ : ๋จ์ผ PTE์ ์ํ๋ฅผ ํ์ธํ๊ณ , ํ์์ ํดํธ๋ฅผ ํธ๋ฆฌ๊ฑฐํ๊ฑฐ๋PFN ๊ฒฐ๊ณผ๋ฅผ ๊ธฐ๋กํ๋ค.
๋ถ๊ธฐ ๋ก์ง:
1. pte_none() ๋๋ UFFD WP ๋ง์ปค โ ํดํธ ํ์์ fault, ์๋๋ฉด ์คํต
2. !pte_present() โ softleaf ํ์
๋ณ ๋ถ๊ธฐ:
- Device Private (์๊ธฐ ์์ ) โ HMM_PFN_VALID ์ง์ ๊ธฐ๋ก
- Swap โ fault ํธ๋ฆฌ๊ฑฐ
- Device Private (ํ์ธ) โ fault ํธ๋ฆฌ๊ฑฐ
- Device Exclusive โ fault ํธ๋ฆฌ๊ฑฐ
- Migration โ migration_entry_wait() ํ -EBUSY
- ๊ธฐํ โ -EFAULT
3. pte_present() โ pte_to_hmm_pfn_flags()๋ก ํ๋๊ทธ ๋ณํ ํ ๊ธฐ๋ก
4. `hmm_vma_fault()` โ ํ์ด์ง ํดํธ ํธ๋ฆฌ๊ฑฐ
/* mm/hmm.c:73-94 */
static int hmm_vma_fault(unsigned long addr, unsigned long end,
unsigned int required_fault, struct mm_walk *walk)
์ญํ : ์ง์ ๋ ๋ฒ์์ ํ์ด์ง๋ฅผ fault-inํ๋ค. handle_mm_fault()๋ฅผ ํธ์ถํ์ฌ ์ปค๋์ ํ์ค ํ์ด์ง ํดํธ ์ฒ๋ฆฌ๋ฅผ ํ์ฉํ๋ค.
๋ถ๊ธฐ ๋ก์ง:
1. HMM_NEED_WRITE_FAULT && !(vma->vm_flags & VM_WRITE) โ -EPERM
2. handle_mm_fault()์์ VM_FAULT_ERROR โ -EFAULT
3. ์ฑ๊ณต ์ -EBUSY ๋ฐํ (์ฌ์์ ์ ๋)
5. `hmm_dma_map_pfn()` โ PFN์ DMA ์ฃผ์๋ก ๋งคํ
/* mm/hmm.c:771-857 */
dma_addr_t hmm_dma_map_pfn(struct device *dev, struct hmm_dma_map *map,
size_t idx,
struct pci_p2pdma_map_state *p2pdma_state)
์ญํ : HMM PFN์ DMA ๊ฐ๋ฅํ ์ฃผ์๋ก ๋ณํํ๋ค. IOVA ๊ธฐ๋ฐ ๋งคํ๊ณผ ์ผ๋ฐ ๋งคํ์ ๋ชจ๋ ์ง์ํ๋ฉฐ, PCI P2P DMA๋ ์ฒ๋ฆฌํ๋ค.
๋ถ๊ธฐ ๋ก์ง:
1. HMM_PFN_DMA_MAPPED && !HMM_PFN_P2PDMA_BUS โ ์ด๋ฏธ ๋งคํ๋จ, IOVA/dma_addrs์์ ๋ฐํ
2. pci_p2pdma_state() ๋ถ๊ธฐ:
- MAP_THRU_HOST_BRIDGE โ DMA_ATTR_MMIO ์ค์
- MAP_BUS_ADDR โ ๋ฒ์ค ์ฃผ์ ์ง์ ๋ฐํ
3. dma_use_iova() โ IOVA ๊ธฐ๋ฐ ๋งคํ (dma_iova_link + dma_iova_sync)
4. ์ผ๋ฐ โ dma_map_phys()๋ก ์ง์ ๋งคํ
ํธ์ถ ํ๋ฆ
hmm_range_fault() โ ๋๋ฐ์ด์ค ๋๋ผ์ด๋ฒ๊ฐ ํธ์ถ
โ
โโ do-while (-EBUSY ์ฌ์์ ๋ฃจํ)
โ โ
โ โโ mmu_interval_check_retry() โ ๋ฌดํจํ ํ์ธ
โ โ
โ โโ walk_page_range() โ ํ์ด์ง ํ
์ด๋ธ ์ํ ์์
โ โ
โ โโ hmm_vma_walk_test() โ VMA ์ ํจ์ฑ ๊ฒ์ฌ (VM_IO/VM_PFNMAP ์คํต)
โ โ
โ โโ hmm_vma_walk_pud() โ PUD ์์ค ์ฒ๋ฆฌ (CONFIG_HAVE_ARCH_TRANSPARENT_HUGEPAGE_PUD)
โ โ โโ pud_leaf() โ pfn ๊ธฐ๋ก
โ โ โโ ACTION_SUBTREE โ PMD ์์ค์ผ๋ก ๋ด๋ ค๊ฐ
โ โ
โ โโ hmm_vma_walk_pmd() โ PMD ์์ค ์ฒ๋ฆฌ
โ โ โโ pmd_none() โ hmm_vma_walk_hole()
โ โ โโ pmd_is_migration_entry() โ migration_entry_wait()
โ โ โโ !pmd_present() โ hmm_vma_handle_absent_pmd()
โ โ โโ pmd_trans_huge() โ hmm_vma_handle_pmd()
โ โ โโ ์ผ๋ฐ PTE โ hmm_vma_handle_pte() [for ๋ฃจํ]
โ โ
โ โโ hmm_vma_walk_hole() โ ๋น ์์ญ ์ฒ๋ฆฌ
โ โ โโ hmm_vma_fault() ํ์์ ํธ์ถ
โ โ
โ โโ hmm_vma_walk_hugetlb_entry() โ HugeTLB ์ํธ๋ฆฌ ์ฒ๋ฆฌ
โ
โโ ๋ฐํ (0: ์ฑ๊ณต, -EBUSY: ์ฌ์์ ํ์, ๊ธฐํ: ์ค๋ฅ)
hmm_dma_map_pfn() โ DMA ๋งคํ ์ ํธ์ถ
โ
โโ ์ด๋ฏธ ๋งคํ๋จ? โ ๊ธฐ์กด ์ฃผ์ ๋ฐํ
โ
โโ pci_p2pdma_state() โ P2P ๋ถ๊ธฐ
โ โโ MAP_THRU_HOST_BRIDGE โ MMIO ์์ฑ
โ โโ MAP_BUS_ADDR โ ๋ฒ์ค ์ฃผ์ ๋ฐํ
โ โโ MAP_NONE โ ๊ณ์
โ
โโ dma_use_iova() โ IOVA ๋งคํ
โ โโ dma_iova_link()
โ โโ dma_iova_sync()
โ
โโ ์ผ๋ฐ โ dma_map_phys()
์กฐ๊ฑด๋ณ ๋น๊ต
PFN ํ๋๊ทธ ์ํ ๋น๊ต
| ํ๋๊ทธ | ์๋ฏธ | ์ ๋ ฅ ์ | ์ถ๋ ฅ ์ |
|---|---|---|---|
| `HMM_PFN_VALID` | ์ ํจํ ํ์ด์ง | `HMM_PFN_REQ_FAULT`๋ก ์ฌ์ฌ์ฉ | ํ์ด์ง๊ฐ ์กด์ฌํจ |
| `HMM_PFN_WRITE` | ์ฐ๊ธฐ ๊ฐ๋ฅ | `HMM_PFN_REQ_WRITE`๋ก ์ฌ์ฌ์ฉ | PTE์ ์ฐ๊ธฐ ๊ถํ ์์ |
| `HMM_PFN_ERROR` | ์ ๊ทผ ๋ถ๊ฐ | - | ์ค๋ฅ ํ์ด์ง (poisoned, no vma ๋ฑ) |
| `HMM_PFN_DMA_MAPPED` | DMA ๋งคํ๋จ | sticky (์ ๋ ฅโ์ถ๋ ฅ ์ ์ง) | DMA ๋งคํ ์๋ฃ |
| `HMM_PFN_P2PDMA` | P2P ํ์ด์ง | sticky | PCI P2P ๋งคํ |
| `HMM_PFN_P2PDMA_BUS` | ๋ฒ์ค ๋งคํ P2P | sticky | ๋ฒ์ค ์ฃผ์ ๊ธฐ๋ฐ P2P |
PTE ์ฒ๋ฆฌ ๊ฒฝ๋ก ๋น๊ต
| PTE ์ํ | ์ฒ๋ฆฌ ํจ์ | ๋์ |
|---|---|---|
| `pte_none()` | `hmm_pte_need_fault()` | ํดํธ ํ์์ fault-in, ์๋๋ฉด ์คํต |
| Device Private (์๊ธฐ ์์ ) | ์ง์ ๊ธฐ๋ก | `HMM_PFN_VALID` + PFN ๊ธฐ๋ก |
| Device Private (ํ์ธ) | fault ํธ๋ฆฌ๊ฑฐ | `hmm_vma_fault()` ํธ์ถ |
| Swap | fault ํธ๋ฆฌ๊ฑฐ | ์ค์์์ ์ธ |
| Migration | `migration_entry_wait()` | ๋๊ธฐ ํ `-EBUSY` ๋ฐํ |
| `pte_present()` | `pte_to_hmm_pfn_flags()` | ํ๋๊ทธ ๋ณํ ํ PFN ๊ธฐ๋ก |
DMA ๋งคํ ๊ฒฝ๋ก ๋น๊ต
| ์กฐ๊ฑด | ๋งคํ ๋ฐฉ์ | ํจ์ |
|---|---|---|
| IOVA ์ง์ + !dma_need_unmap | IOVA ๊ธฐ๋ฐ | `dma_iova_link()` + `dma_iova_sync()` |
| IOVA ๋ฏธ์ง์ + dma_need_unmap | ์ผ๋ฐ DMA | `dma_map_phys()` |
| P2P (Host Bridge ๊ฒฝ์ ) | MMIO ๋งคํ | `DMA_ATTR_MMIO` ์ค์ |
| P2P (Bus Address) | ๋ฒ์ค ์ฃผ์ | `pci_p2pdma_bus_addr_map()` |
| ์ด๋ฏธ ๋งคํ๋จ | ์ฌ์ฌ์ฉ | ๊ธฐ์กด ์ฃผ์ ๋ฐํ |