Ryotta's Linux 7.0 MM

메모리 관리 서브시스템 완전 분석

23. HMM — Heterogeneous Memory Management

HMM 호출 흐름
HMM 자료구조 관계도

개요 (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_flagspfn_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_BRIDGEDMA_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 페이지stickyPCI P2P 매핑
`HMM_PFN_P2PDMA_BUS`버스 매핑 P2Psticky버스 주소 기반 P2P

PTE 처리 경로 비교

PTE 상태처리 함수동작
`pte_none()``hmm_pte_need_fault()`폴트 필요시 fault-in, 아니면 스킵
Device Private (자기 소유)직접 기록`HMM_PFN_VALID` + PFN 기록
Device Private (타인)fault 트리거`hmm_vma_fault()` 호출
Swapfault 트리거스왑에서 인
Migration`migration_entry_wait()`대기 후 `-EBUSY` 반환
`pte_present()``pte_to_hmm_pfn_flags()`플래그 변환 후 PFN 기록

DMA 매핑 경로 비교

조건매핑 방식함수
IOVA 지원 + !dma_need_unmapIOVA 기반`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()`
이미 매핑됨재사용기존 주소 반환

관련 문서

  • 00-overview.html — 메모리 관리 개요
  • 20-migrate.html — 페이지 마이그레이션 (HMM 내 migration 처리와 연관)
  • 03-vma_mmap.html — VMA / mmap (HMM이 순회하는 대상)
  • 10-hugepage.html — Huge Pages / THP (HMM의 THP 처리)
  • 24-memory_tiers.html — 메모리 티어링 (HMM과 함께 디바이스 메모리 관리)
  • 38-pagewalk.html — Page Table Walk (HMM이 활용하는 walk_page_range)