Ryotta's Linux 7.0 MM

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

# GUP (Get User Pages) — Linux 7.0

1. 개요

Get User Pages(GUP)는 커널이 사용자 공간의 물리 페이지를 직접 참조하거나 고정(pin)하는 메커니즘이다. 사용자 프로세스의 가상 주소에 매핑된 물리 페이지를 커널이 찾아서 참조 카운트를 올려, 해당 페이지가 메모리에서 해제되지 않도록 보장한다. 대표적인 사용 사례로 DMA 버퍼 할당, RDMA, virtio, 디버그 도구, 코어 덤프 등이 있다.

GUP은 크게 두 가지 경로로 동작한다: slow pathmmap_lock을 획득한 후 VMA 검증, 페이지 테이블 순회, 페이지 폴트 처리를 수행하고, fast path는 IRQ를 비활성화하여 페이지 테이블을 직접 순회하여 잠금 없이 빠르게 페이지를 핀한다. fast path에서 실패하면 slow path로 폴백한다.

GUP 호출 흐름
GUP 자료구조 및 Pin 카운팅
소스 파일: mm/gup.c, include/linux/mm_types.h, include/linux/mm.h

2. 빠른 점검 명령

# GUP 관련 카운터 확인
grep -E "nr_foll_pin_(acquired|released)" /proc/vmstat

# 현재 시스템에서 pin_user_pages 사용 현황 (kprobe가 필요한 경우)
sudo perf probe -l | grep -E "gup|pin_user_pages|unpin_user_page"

# 메모리 맵에서 GUP이 사용할 수 있는 영역 확인
sed -n '1,20p' /proc/self/maps

# 페이지 테이블 구조 확인
od -An -tx8 -N 40 /proc/self/pagemap 2>/dev/null || echo "pagemap 접근 권한 필요"

# GUP 관련 커널 로그 확인
dmesg | grep -i "pin\|gup" | tail -10

# 페이지 참조 카운트 확인 (해당 페이지가 핀되어 있는지)
od -An -tu8 -N 40 /proc/kpagecount 2>/dev/null || echo "kpagecount 접근 권한 필요"

# GUP 관련 심볼 확인
grep -E "get_user_pages|pin_user_pages|unpin_user_page" /proc/kallsyms | head -20

# GUP fast path 관련 커널 설정 확인
zcat /proc/config.gz 2>/dev/null | grep -E "HAVE_GUP_FAST|CONFIG_MMU|CONFIG_DEBUG_VM" || grep -E "HAVE_GUP_FAST|CONFIG_MMU|CONFIG_DEBUG_VM" /boot/config-$(uname -r)

3. 핵심 자료구조

3.1 FOLL_* 플래그 (`include/linux/mm_types.h:1809-1852`)

GUP 함수들의 동작을 제어하는 플래그 모음이다. 각 플래그는 페이지 핀의 용도와 조건을 정의한다.

// include/linux/mm_types.h:1809-1852
enum {
    /* pte가 writable인지 확인 */
    FOLL_WRITE   = 1 << 0,
    /* page에서 get_page 수행 */
    FOLL_GET     = 1 << 1,
    /* 구멍이 0이 될 경우 오류 반환 */
    FOLL_DUMP    = 1 << 2,
    /* 권한 없이 get_user_pages 읽기/쓰기 */
    FOLL_FORCE   = 1 << 3,
    /* 디스크 전송이 필요하면 IO를 시작하고 기다리지 않고 반환 */
    FOLL_NOWAIT  = 1 << 4,
    /* 페이지를 fault-in 하지 않음 */
    FOLL_NOFAULT = 1 << 5,
    /* hwpoison 여부를 확인 */
    FOLL_HWPOISON = 1 << 6,
    /* 파일 매핑을 하지 않음 */
    FOLL_ANON    = 1 << 7,
    /* FOLL_LONGTERM은 페이지가 무기한에 가까운 기간 동안 잡혀 있을 수 있음을 뜻함 */
    FOLL_LONGTERM = 1 << 8,
    /* 반환 전에 huge pmd를 분할 */
    FOLL_SPLIT_PMD = 1 << 9,
    /* PCI P2PDMA 페이지 반환 허용 */
    FOLL_PCI_P2PDMA = 1 << 10,
    /* 일반 signal에서 인터럽트 허용 */
    FOLL_INTERRUPTIBLE = 1 << 11,
    /* 항상 NUMA 힌트 fault를 반영 */
    FOLL_HONOR_NUMA_FAULT = 1 << 12,
};

3.2 GUP Pin 카운팅 메커니즘

FOLL_GET과 FOLL_PIN은 서로 다른 참조 카운팅 방식을 사용한다. FOLL_GET은 page->_refcount만 증가시키지만, FOLL_PIN은 대형 폴리오의 경우 별도의 _pincount 필드를 사용하여 정확한 핀 수를 추적한다.

// mm/gup.c:140-174 — try_grab_folio()
int __must_check try_grab_folio(struct folio *folio, int refs,
                unsigned int flags)
{
    if (WARN_ON_ONCE(folio_ref_count(folio) <= 0))
        return -ENOMEM;

    if (unlikely(!(flags & FOLL_PCI_P2PDMA) && folio_is_pci_p2pdma(folio)))
        return -EREMOTEIO;

    if (flags & FOLL_GET)
        folio_ref_add(folio, refs);
    else if (flags & FOLL_PIN) {
        /* zero page에는 pin을 걸지 않는다. 움직이지도 않고, 많은 곳에서 사용된다. */
        if (is_zero_folio(folio))
            return 0;

        /* 페이지가 정말 pin되도록 normal page refcount도 최소 한 번은 증가시킨다. */
        if (folio_has_pincount(folio)) {
            folio_ref_add(folio, refs);
            atomic_add(refs, &folio->_pincount);
        } else {
            folio_ref_add(folio, refs * GUP_PIN_COUNTING_BIAS);
        }
        node_stat_mod_folio(folio, NR_FOLL_PIN_ACQUIRED, refs);
    }
    return 0;
}

3.3 pages_or_folios 구조체 (`mm/gup.c:2200-2215`)

페이지 배열과 폴리오 배열을 모두 지원하는 래퍼 구조체이다. FOLL_LONGTERM 마이그레이션 처리에 사용된다.

// mm/gup.c:2200-2215
/*
 * pages 또는 folio 배열 중 하나를 담는다.
 * struct page와 struct folio의 배치가 앞으로 완전히 달라질 수 있으므로,
 * 단순히 folio 배열을 page 배열처럼 해석하지 않는다.
 * 또한 page_folio() 호출을 과도하게 반복하지 않게 해 준다.
 */
struct pages_or_folios {
    union {
        struct page **pages;
        struct folio **folios;
        void **entries;
    };
    bool has_folios;        // folios 사용 여부
    long nr_entries;        // 엔트리 수
};

3.4 FOLL 플래그 분류 표

구분플래그설명
접근 제어FOLL_WRITE쓰기 접근 허용
FOLL_FORCECOW 매핑에서도 쓰기 허용
FOLL_ANON익명 페이지만 대상
할당 방식FOLL_GETput_page()로 해제
FOLL_PINunpin_user_page()로 해제
FOLL_LONGTERM장시간 핀 (마이그레이션 동반)
동작 제어FOLL_NOWAIT폴트 시 즉시 반환
FOLL_NOFAULT폴트 미발생
FOLL_INTERRUPTIBLE신호로 중단 가능
특수 용도FOLL_DUMP코어 덤프
FOLL_HWPOISON하드웨어 오류 페이지
FOLL_PCI_P2PDMAPCI P2P DMA
FOLL_SPLIT_PMDTHP 분할
FOLL_HONOR_NUMA_FAULTNUMA 폴트 처리

4. 핵심 함수

4.1 get_user_pages() (`mm/gup.c:2644-2655`)

현재 프로세스의 사용자 페이지를 핀하는 가장 일반적인 API이다. 내부적으로 __get_user_pages_locked()를 호출한다.

// mm/gup.c:2644-2655
long get_user_pages(unsigned long start, unsigned long nr_pages,
            unsigned int gup_flags, struct page **pages)
{
    int locked = 1;

    if (!is_valid_gup_args(pages, NULL, &gup_flags, FOLL_TOUCH))
        return -EINVAL;

    return __get_user_pages_locked(current->mm, start, nr_pages, pages,
                       &locked, gup_flags);
}
EXPORT_SYMBOL(get_user_pages);

4.2 pin_user_pages() (`mm/gup.c:3376-3386`)

DMA 버퍼 할당 등을 위해 페이지를 장시간 고정할 때 사용한다. FOLL_PIN 플래그가 설정되어 별도의 핀 카운팅을 사용한다.

// mm/gup.c:3376-3386
long pin_user_pages(unsigned long start, unsigned long nr_pages,
            unsigned int gup_flags, struct page **pages)
{
    int locked = 1;

    if (!is_valid_gup_args(pages, NULL, &gup_flags, FOLL_PIN))
        return 0;
    return __gup_longterm_locked(current->mm, start, nr_pages,
                     pages, &locked, gup_flags);
}
EXPORT_SYMBOL(pin_user_pages);

4.3 get_user_pages_fast() (`mm/gup.c:3276-3289`)

mmap_lock 없이 빠르게 페이지를 핀하는 함수이다. fast path에서 실패하면 slow path로 폴백한다.

// mm/gup.c:3276-3289
int get_user_pages_fast(unsigned long start, int nr_pages,
            unsigned int gup_flags, struct page **pages)
{
    /* 호출자가 FOLL_GET을 명시했을 수도 있고 아닐 수도 있다. 둘 다 가능하다.
     * 하지만 mm/gup.c 내부의 빠른 경로는 반드시 FOLL_GET을 설정해야 한다.
     * gup fast는 항상 "page refcount +1" 성격의 요청이기 때문이다.
     */
    if (!is_valid_gup_args(pages, NULL, &gup_flags, FOLL_GET))
        return -EINVAL;
    return gup_fast_fallback(start, nr_pages, gup_flags, pages);
}
EXPORT_SYMBOL_GPL(get_user_pages_fast);

4.4 faultin_page() (`mm/gup.c:1087-1160`)

페이지 폴트를 수동으로 발생시켜 페이지를 메모리에 로드한다. VMA 권한 검증 후 handle_mm_fault()를 호출한다.

// mm/gup.c:1087-1160
static int faultin_page(struct vm_area_struct *vma,
        unsigned long address, unsigned int flags, bool unshare,
        int *locked)
{
    unsigned int fault_flags = 0;
    vm_fault_t ret;

    if (flags & FOLL_NOFAULT)
        return -EFAULT;
    if (flags & FOLL_WRITE)
        fault_flags |= FAULT_FLAG_WRITE;
    if (flags & FOLL_REMOTE)
        fault_flags |= FAULT_FLAG_REMOTE;

    if (flags & FOLL_UNLOCKABLE) {
        fault_flags |= FAULT_FLAG_ALLOW_RETRY | FAULT_FLAG_KILLABLE;
        /* FAULT_FLAG_INTERRUPTIBLE은 선택 사항이다. GUP 호출자가 FOLL_INTERRUPTIBLE을 줘야 켠다. */
        if (flags & FOLL_INTERRUPTIBLE)
            fault_flags |= FAULT_FLAG_INTERRUPTIBLE;
    }
    if (flags & FOLL_NOWAIT)
        fault_flags |= FAULT_FLAG_ALLOW_RETRY | FAULT_FLAG_RETRY_NOWAIT;
    if (flags & FOLL_TRIED) {
        /* FAULT_FLAG_ALLOW_RETRY와 FAULT_FLAG_TRIED는 함께 존재할 수 있다. */
        fault_flags |= FAULT_FLAG_TRIED;
    }
    if (unshare) {
        fault_flags |= FAULT_FLAG_UNSHARE;
        /* FAULT_FLAG_WRITE와 FAULT_FLAG_UNSHARE는 함께 쓸 수 없다. */
        VM_WARN_ON_ONCE(fault_flags & FAULT_FLAG_WRITE);
    }

    ret = handle_mm_fault(vma, address, fault_flags, NULL);

    if (ret & VM_FAULT_COMPLETED) {
        /* FAULT_FLAG_RETRY_NOWAIT를 썼다면 page fault handler가 mmap lock을 놓지 않는다. */
        WARN_ON_ONCE(fault_flags & FAULT_FLAG_RETRY_NOWAIT);
        *locked = 0;

        /* VM_FAULT_RETRY와 같은 의미를 두고 싶지만 -EBUSY는 실제 상황을 잘 설명하지 못한다. */
        return -EAGAIN;
    }

    if (ret & VM_FAULT_ERROR) {
        int err = vm_fault_to_errno(ret, flags);

        if (err)
            return err;
        BUG();
    }

    if (ret & VM_FAULT_RETRY) {
        if (!(fault_flags & FAULT_FLAG_RETRY_NOWAIT))
            *locked = 0;
        return -EBUSY;
    }

    return 0;
}

4.5 follow_page_mask() (`mm/gup.c:1007-1028`)

페이지 테이블을 순회하여 사용자 가상 주소에 해당하는 페이지를 찾는다.

// mm/gup.c:1007-1028
static struct page *follow_page_mask(struct vm_area_struct *vma,
                  unsigned long address, unsigned int flags,
                  unsigned long *page_mask)
{
    pgd_t *pgd;
    struct mm_struct *mm = vma->vm_mm;
    struct page *page;

    vma_pgtable_walk_begin(vma);
    *page_mask = 0;
    pgd = pgd_offset(mm, address);

    if (pgd_none(*pgd) || unlikely(pgd_bad(*pgd)))
        page = no_page_table(vma, flags, address);
    else
        page = follow_p4d_mask(vma, address, pgd, flags, page_mask);

    vma_pgtable_walk_end(vma);
    return page;
}

5. 호출 흐름

5.1 Slow Path (get_user_pages)

get_user_pages()
  └→ __get_user_pages_locked()
       ├→ mmap_read_lock()          // mmap_lock 획득
       └→ __get_user_pages()
            ├→ gup_vma_lookup()     // VMA 검색
            ├→ check_vma_flags()    // 권한 검증
            ├→ follow_page_mask()   // 페이지 테이블 순회
            │    ├→ pgd_offset()
            │    ├→ follow_p4d_mask()
            │    ├→ follow_pud_mask()
            │    ├→ follow_pmd_mask()
            │    │    ├→ follow_page_pte()   // PTE 레벨
            │    │    └→ follow_huge_pmd()   // THP/HugeTLB
            │    └→ try_grab_folio()         // 참조 카운트 증가
            ├→ faultin_page()        // 폴트 발생 시
            │    └→ handle_mm_fault()
            └→ mmap_read_unlock()   // mmap_lock 해제

5.2 Fast Path (get_user_pages_fast)

get_user_pages_fast()
  └→ gup_fast_fallback()
       ├→ gup_fast()
       │    ├→ raw_seqcount_try_begin()  // write_protect_seq 확인
       │    ├→ local_irq_save()          // IRQ 비활성화
       │    ├→ gup_fast_pgd_range()      // 페이지 테이블 순회
       │    │    └→ gup_fast_pte_range() // 직접 PTE 순회
       │    │         ├→ ptep_get_lockless()
       │    │         ├→ try_grab_folio_fast()
       │    │         └→ gup_fast_folio_allowed()
       │    └→ local_irq_restore()
       └→ __gup_longterm_locked()  // 남은 페이지를 slow path로 처리

6. 조건별 비교

6.1 get_user_pages vs pin_user_pages

항목get_user_pagespin_user_pages
플래그FOLL_GETFOLL_PIN
해제 APIput_page()unpin_user_page()
카운팅 방식refcount만 증가refcount + _pincount
장시간 사용불가 (FOLL_LONGTERM 불가)가능
마이그레이션미지원longterm 시 자동 마이그레이션
주요 사용 사례임시 접근, 디버그DMA, RDMA, virtio

6.2 Slow Path vs Fast Path

항목Slow PathFast Path
mmap_lock필요불필요
IRQ 상태활성화비활성화
VMA 검증OX
페이지 폴트 처리OX (폴트 시 실패)
THP 처리PMD 분할 가능PMD leaf 직접 핀
대상모든 시나리오핫 패스, IRQ 컨텍스트
성능느림빠름

6.3 FOLL_WRITE 처리 경로

조건동작
VMA에 VM_WRITE 없음-EFAULT 반환 (FOLL_FORCE 시 COW 시도)
PTE 읽기 전용can_follow_write_pte()로 COW 가능 여부 확인
공유 매핑FOLL_FORCE로도 쓰기 불가
PageAnonExclusive()COW 해제 가능 시 쓰기 허용

6.4 longterm 핀 마이그레이션 흐름

단계동작
1. FOLL_LONGTERM 핀__gup_longterm_locked() 진입
2. collect_longterm_unpinnable_folios()장시간 핀 불가 페이지 수집
3. folio_isolate_lru()LRU에서 격리
4. migrate_longterm_unpinnable_folios()마이그레이션 수행
5. -EAGAIN 반환호출자가 다시 핀 시도
6. 모든 페이지 핀 성공성공 반환

7. 주의사항 및 운영 고려사항

7.1 GUP Fast Path 제약 조건

  • IRQ가 비활성화된 상태에서 동작하므로 긴 작업 금지
  • write_protect_seq로 fork()의 COW와 경합 방지
  • secretmem, shmem가 아닌 파일 기반 매핑은 longterm+write 시 거부
  • PCI P2P DMA 페이지는 별도 플래그(FOLL_PCI_P2PDMA) 필요
  • 7.2 pin 카운팅 정확성

  • 대형 폴리오(large folio): _pincount 필드로 정확한 핀 수 추적
  • 단일 페이지: refcountGUP_PIN_COUNTING_BIAS(256)를 곱하여 핀 표시
  • folio_add_pin(): 기존 핀에 추가 핀 가능 (memfd_pin_folios에서 활용)
  • 7.3 성능 튜닝

  • get_user_pages_fast_only(): fast path만 사용, slow path 폴백 없음
  • FOLL_NOWAIT: 폴트 대기 없이 즉시 반환
  • FOLL_INTERRUPTIBLE: 신호로 중단 가능 (긴 GUP 방지)
  • FOLL_NOFAULT: 폴트 미발생 (기존 매핑 확인용)

  • 8. 관련 문서

  • 메모리 관리 개요
  • VMA / mmap
  • Huge Pages / THP
  • Reverse Mapping
  • mlock / munlock
  • Migration
  • Secret Memory
  • memfd