Ryotta's Linux 7.0 MM

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

# mremap 🔄

관련 소스: mm/mremap.c, mm/internal.h, include/uapi/linux/mman.h

개요 (Overview)

mremap()는 기존 가상 메모리 영역(VMA)의 크기를 변경하거나 다른 가상 주소로 이동하는 시스템 호출입니다. 이 시스템 호출은 메모리 매핑의 확장, 축소, 이동을 모두 처리하며, 리눅스 커널의 메모리 관리 서브시스템에서 중요한 역할을 합니다.

일상 비유: mremap()는 아파트를 리모델링하는 것과 같습니다. 방의 크기를 키우거나(확장), 줄이거나(축소), 아예 다른 층으로 옮기는(이동) 것과 비슷합니다. 커널은 이 작업을 수행할 때 페이지 테이블을 조정하여 프로세스가 계속해서 동일한 가상 주소를 사용할 수 있도록 합니다.

mremap()는 세 가지 기본 연산을 지원합니다: in-place 확장(in-place expansion), in-place 축소(in-place shrinking), 그리고 VMA 이동(VMA moving). 각 연산은 서로 다른 시나리오에서 사용되며, 커널은 최적의 방법을 자동으로 선택합니다.

소스 파일 경로:

mm/mremap.c          # 메인 구현 (1998줄)
mm/internal.h        # pagetable_move_control 구조체 정의
include/uapi/linux/mman.h  # MREMAP 플래그 정의

mremap 호출 흐름

mremap 호출 흐름

mremap 핵심 자료구조 관계도

mremap 핵심 자료구조 관계도

빠른 점검 명령

# 1. mremap 시스템 호출 호출 횟수 확인
cat /proc/kstat | grep mremap

# 2. 현재 프로세스의 VMA 맵 확인
cat /proc/self/maps | head -20

# 3. mremap 관련 커널 통계 확인
cat /proc/vmstat | grep -i mremap

# 4. mremap 실패 횟수 확인 (dmesg에서)
dmesg | grep -i "mremap" | tail -10

# 5. 특정 프로세스의 VMA 변경 추적
strace -e mremap <프로세스명>

# 6. mremap 플래그 확인
grep -r "MREMAP_" /usr/include/linux/mman.h

# 7. 커널 mremap 관련 모듈 확인
lsmod | grep -i mremap

# 8. mremap 관련 커널 파라미터 확인
sysctl -a | grep -i mremap

# 9. mremap 호출 시 VMA 변경 추적
perf trace -e mremap <프로세스명>

# 10. mremap 관련 커널 로그 확인
journalctl -k | grep -i mremap

# 11. VMA 맵 수 확인 (mremap 실패 원인 진단)
cat /proc/self/status | grep -i vmsize

# 12. 메모리 매핑 통계 확인
cat /proc/vmstat | grep -i mmap

# 13. 페이지 테이블 통계 확인
cat /proc/vmstat | grep -i pte

# 14. TLB 플러시 통계 확인
cat /proc/vmstat | grep -i tlb

핵심 자료구조

1. vma_remap_struct

mremap() 연산 전체를 설명하는 메인 구조체입니다.

/* mm/mremap.c:50-73 */
struct vma_remap_struct {
    /* 사용자 제공 상태 */
    unsigned long addr;      /* 리매핑할 원본 가상 주소 */
    unsigned long old_len;   /* 리매핑할 범위의 기존 길이 */
    unsigned long new_len;   /* 원하는 새 길이 */
    const unsigned long flags; /* MREMAP_* 플래그 */
    unsigned long new_addr;  /* 선택적 새 주소 */

    /* uffd 상태 */
    struct vm_userfaultfd_ctx *uf;
    struct list_head *uf_unmap_early;
    struct list_head *uf_unmap;

    /* VMA 상태 - do_mremap()에서 결정 */
    struct vm_area_struct *vma;

    /* 내부 상태 - do_mremap()에서 결정 */
    unsigned long delta;     /* old_len과 new_len의 절대 차이 */
    bool populate_expand;    /* mlock()된 확장 영역, populate 필요 */
    enum mremap_type remap_type; /* expand, shrink 등 */
    bool mmap_locked;        /* 현재 mm이 쓰기 잠금 상태인지 */
    unsigned long charged;   /* VM_ACCOUNT인 경우, 청구할 페이지 수 */
    bool vmi_needs_invalidate; /* VMA 반복자가 무효화되었는지 */
};

2. pagetable_move_control

페이지 테이블 이동 연산을 제어하는 구조체입니다.

/* mm/internal.h:46-56 */
struct pagetable_move_control {
    struct vm_area_struct *old; /* 소스 VMA */
    struct vm_area_struct *new; /* 대상 VMA */
    unsigned long old_addr;     /* 이동이 시작되는 주소 */
    unsigned long old_end;      /* 기존 범위가 끝나는 주소 (포함되지 않음) */
    unsigned long new_addr;     /* 페이지 테이블을 이동할 주소 */
    unsigned long len_in;       /* 사용자가 지정한 리매핑할 바이트 수 */
    
    bool need_rmap_locks;       /* rmap 잠금이 필요한지 */
    bool for_stack;             /* 초기 임시 스택을 이동 중인지 */
};

/* PAGETABLE_MOVE 매크로로 초기화 */
/* mm/internal.h:58-66 */
#define PAGETABLE_MOVE(name, old_, new_, old_addr_, new_addr_, len_)    \
    struct pagetable_move_control name = {                             \
        .old = old_,                                                   \
        .new = new_,                                                   \
        .old_addr = old_addr_,                                         \
        .old_end = (old_addr_) + (len_),                               \
        .new_addr = new_addr_,                                         \
        .len_in = len_,                                                \
    }

3. mremap_type 열거형

리매핑 연산 유형을 분류합니다.

/* mm/mremap.c:36-41 */
enum mremap_type {
    MREMAP_INVALID,    /* 초기 상태 */
    MREMAP_NO_RESIZE,  /* old_len == new_len, 이동하지 않으면 아무것도 하지 않음 */
    MREMAP_SHRINK,     /* old_len > new_len */
    MREMAP_EXPAND,     /* old_len < new_len */
};

4. MREMAP 플래그

/* include/uapi/linux/mman.h:9-11 */
#define MREMAP_MAYMOVE    1  /* 이동 허용 */
#define MREMAP_FIXED      2  /* 고정 주소로 이동 */
#define MREMAP_DONTUNMAP  4  /* 원본 매핑 유지 (복사 후 이동) */

MREMAP_MAYMOVE: mremap가 새 주소에서 매핑을 허용합니다. in-place 확장/축소가 불가능할 때 이동이 필요합니다.

MREMAP_FIXED: 지정된 주소(new_addr)로 이동합니다. 기존 매핑이 있으면 먼저 제거됩니다. MREMAP_MAYMOVE가 함께 설정되어야 합니다.

MREMAP_DONTUNMAP: 원본 매핑을 유지하면서 새 위치에 복사합니다. 프로세스 간 공유 메모리 복사에 사용됩니다.


핵심 함수

1. SYSCALL_DEFINE5(mremap)

mremap() 시스템 호출의 진입점입니다.

/* mm/mremap.c:1965-1998 */
SYSCALL_DEFINE5(mremap, unsigned long, addr, unsigned long, old_len,
        unsigned long, new_len, unsigned long, flags,
        unsigned long, new_addr)
{
    struct vm_userfaultfd_ctx uf = NULL_VM_UFFD_CTX;
    LIST_HEAD(uf_unmap_early);
    LIST_HEAD(uf_unmap);
    struct vma_remap_struct vrm = {
        .addr = untagged_addr(addr),  /* ARM64 태그 제거 */
        .old_len = old_len,
        .new_len = new_len,
        .flags = flags,
        .new_addr = new_addr,
        .uf = &uf,
        .uf_unmap_early = &uf_unmap_early,
        .uf_unmap = &uf_unmap,
        .remap_type = MREMAP_INVALID,
    };
    return do_mremap(&vrm);
}

역할: 사용자 공간에서 전달된 파라미터를 검증하고 vma_remap_struct를 초기화한 후 do_mremap()를 호출합니다. ARM64 태그 주소 처리를 위해 untagged_addr()을 사용합니다.

2. do_mremap

mremap의 메인 로직을 수행하는 함수입니다.

/* mm/mremap.c:1915-1956 */
static unsigned long do_mremap(struct vma_remap_struct *vrm)
{
    struct mm_struct *mm = current->mm;
    unsigned long res;
    bool failed;
    
    /* 길이를 페이지 크기에 정렬 */
    vrm->old_len = PAGE_ALIGN(vrm->old_len);
    vrm->new_len = PAGE_ALIGN(vrm->new_len);
    
    /* 파라미터 검증 */
    res = check_mremap_params(vrm);
    if (res)
        return res;
    
    /* mmap 쓰기 잠금 획득 */
    if (mmap_write_lock_killable(mm))
        return -EINTR;
    vrm->mmap_locked = true;
    
    /* 이동만 하는 경우 */
    if (vrm_move_only(vrm)) {
        res = remap_move(vrm);
    } else {
        /* VMA 조회 및 검증 */
        vrm->vma = vma_lookup(current->mm, vrm->addr);
        res = check_prep_vma(vrm);
        if (res)
            goto out;
        
        /* mremap 실행 */
        res = vrm_implies_new_addr(vrm) ? mremap_to(vrm) : mremap_at(vrm);
    }
    
out:
    failed = IS_ERR_VALUE(res);
    if (vrm->mmap_locked)
        mmap_write_unlock(mm);
    
    /* mlock된 확장 영역 populate */
    if (!failed && vrm->populate_expand)
        mm_populate(vrm->new_addr + vrm->old_len, vrm->delta);
    
    /* userfaultfd 알림 */
    notify_uffd(vrm, failed);
    return res;
}

역할: 페이지 정렬된 길이를 계산하고, 파라미터를 검증한 후, mmap 잠금을 획득합니다. 그 다음 이동 전용 경로 또는 일반 경로를 선택하여 실행합니다.

3. check_mremap_params

mremap 파라미터의 유효성을 검증하는 함수입니다.

/* mm/mremap.c:1756-1824 */
static unsigned long check_mremap_params(struct vma_remap_struct *vrm)
{
    unsigned long addr = vrm->addr;
    unsigned long flags = vrm->flags;
    
    /* 예상치 못한 플래그 값 방지 */
    if (flags & ~(MREMAP_FIXED | MREMAP_MAYMOVE | MREMAP_DONTUNMAP))
        return -EINVAL;
    
    /* 시작 주소는 페이지 정렬어야 함 */
    if (offset_in_page(addr))
        return -EINVAL;
    
    /* new_len이 0이면 안됨 */
    if (!vrm->new_len)
        return -EINVAL;
    
    /* new_len이 TASK_SIZE를 초과하면 안됨 */
    if (vrm->new_len > TASK_SIZE)
        return -EINVAL;
    
    /* 고정 주소 지정 시 추가 검증 */
    if (!vrm_implies_new_addr(vrm))
        return 0;
    
    /* new_addr이 TASK_SIZE - new_len을 초과하면 안됨 */
    if (vrm->new_addr > TASK_SIZE - vrm->new_len)
        return -EINVAL;
    
    /* new_addr은 페이지 정렬어야 함 */
    if (offset_in_page(vrm->new_addr))
        return -EINVAL;
    
    /* 고정 주소는 MREMAP_MAYMOVE 필요 */
    if (!(flags & MREMAP_MAYMOVE))
        return -EINVAL;
    
    /* MREMAP_DONTUNMAP은 크기 변경 불가 */
    if (flags & MREMAP_DONTUNMAP && vrm->old_len != vrm->new_len)
        return -EINVAL;
    
    /* 대상 VMA와 소스 VMA가 겹치면 안됨 */
    if (vrm_overlaps(vrm))
        return -EINVAL;
    
    /* VMA 맵 수 제한 확인 */
    if ((current->mm->map_count + 2) >= sysctl_max_map_count - 3)
        return -ENOMEM;
    
    return 0;
}

역할: 플래그 유효성, 주소 정렬, 크기 제한, VMA 겹침 등을 검증합니다.

4. check_prep_vma

VMA 이동 전 준비 검증을 수행하는 함수입니다.

/* mm/mremap.c:1655-1750 */
static int check_prep_vma(struct vma_remap_struct *vrm)
{
    struct vm_area_struct *vma = vrm->vma;
    struct mm_struct *mm = current->mm;
    unsigned long addr = vrm->addr;
    unsigned long old_len, new_len, pgoff;
    
    if (!vma)
        return -EFAULT;
    
    /* mseal()된 VMA는 mremap 불가 */
    if (vma_is_sealed(vma))
        return -EPERM;
    
    /* HugeTLB 페이지는 정렬 필요 */
    if (is_vm_hugetlb_page(vma) && !align_hugetlb(vrm))
        return -EINVAL;
    
    vrm_set_delta(vrm);
    vrm->remap_type = vrm_remap_type(vrm);
    
    /* 새 주소가 필요한 경우 */
    if (!vrm_implies_new_addr(vrm))
        vrm->new_addr = addr;
    
    if (!vrm_will_map_new(vrm))
        return 0;
    
    old_len = vrm->old_len;
    new_len = vrm->new_len;
    
    /* 비공유 매핑의 경우 old_len=0 허용 안됨 */
    if (!old_len && !(vma->vm_flags & (VM_SHARED | VM_MAYSHARE))) {
        pr_warn_once("%s (%d): attempted to duplicate a private mapping with mremap. This is not supported.\n",
                     current->comm, current->pid);
        return -EINVAL;
    }
    
    /* MREMAP_DONTUNMAP은 VM_DONTEXPAND/VM_PFNMAP 불가 */
    if ((vrm->flags & MREMAP_DONTUNMAP) &&
            (vma->vm_flags & (VM_DONTEXPAND | VM_PFNMAP)))
        return -EINVAL;
    
    /* 축소 시 old_len 조정 */
    if (vrm->remap_type == MREMAP_SHRINK)
        old_len = new_len;
    
    /* old_len이 VMA 범위를 초과하면 안됨 */
    if (old_len > vma->vm_end - addr)
        return -EFAULT;
    
    if (new_len == old_len)
        return 0;
    
    /* mlock된 VMA 확장 시 populate 필요 */
    if (vma->vm_flags & VM_LOCKED)
        vrm->populate_expand = true;
    
    /* 페이지 오버플로우 검사 */
    pgoff = (addr - vma->vm_start) >> PAGE_SHIFT;
    pgoff += vma->vm_pgoff;
    if (pgoff + (new_len >> PAGE_SHIFT) < pgoff)
        return -EINVAL;
    
    /* VM_DONTEXPAND/VM_PFNMAP는 확장 불가 */
    if (vma->vm_flags & (VM_DONTEXPAND | VM_PFNMAP))
        return -EFAULT;
    
    if (!mlock_future_ok(mm, vma->vm_flags & VM_LOCKED, vrm->delta))
        return -EAGAIN;
    
    if (!may_expand_vm(mm, vma->vm_flags, vrm->delta >> PAGE_SHIFT))
        return -ENOMEM;
    
    return 0;
}

역할: VMA 상태, mseal, HugeTLB 정렬, 확장 가능성을 검증합니다.

5. move_vma

VMA를 새 위치로 이동하는 핵심 함수입니다.

/* mm/mremap.c:1270-1321 */
static unsigned long move_vma(struct vma_remap_struct *vrm)
{
    struct mm_struct *mm = current->mm;
    struct vm_area_struct *new_vma;
    unsigned long hiwater_vm;
    int err;
    
    /* 이동 전 준비 */
    err = prep_move_vma(vrm);
    if (err)
        return err;
    
    /* 계정 처리 */
    if (!vrm_calc_charge(vrm))
        return -ENOMEM;
    
    /* VMA 쓰기 잠금 획득 */
    vma_start_write(vrm->vma);
    
    /* 복사 단계 수행 */
    err = copy_vma_and_data(vrm, &new_vma);
    if (err && !new_vma)
        return err;
    
    /* 통계 계정 */
    hiwater_vm = mm->hiwater_vm;
    vrm_stat_account(vrm, vrm->new_len);
    
    /* 원본 VMA 처리 */
    if (unlikely(!err && (vrm->flags & MREMAP_DONTUNMAP)))
        dontunmap_complete(vrm, new_vma);
    else
        unmap_source_vma(vrm);
    
    mm->hiwater_vm = hiwater_vm;
    return err ? (unsigned long)err : vrm->new_addr;
}

역할: VMA를 새 위치로 복사하고, 페이지 테이블을 이동한 후, 원본 VMA를 처리합니다. MREMAP_DONTUNMAP 플래그가 설정된 경우 원본을 유지합니다.

6. move_page_tables

페이지 테이블을 실제 이동하는 함수입니다.

/* mm/mremap.c:795-879 */
unsigned long move_page_tables(struct pagetable_move_control *pmc)
{
    unsigned long extent;
    struct mmu_notifier_range range;
    pmd_t *old_pmd, *new_pmd;
    pud_t *old_pud, *new_pud;
    struct mm_struct *mm = pmc->old->vm_mm;
    
    if (!pmc->len_in)
        return 0;
    
    /* HugeTLB 페이지의 경우 별도 처리 */
    if (is_vm_hugetlb_page(pmc->old))
        return move_hugetlb_page_tables(pmc->old, pmc->new, pmc->old_addr,
                        pmc->new_addr, pmc->len_in);
    
    /* PMD 경계에 정렬하여 성능 향상 */
    try_realign_addr(pmc, PMD_MASK);
    
    /* 캐시 플러시 및 MMU 알림 시작 */
    flush_cache_range(pmc->old, pmc->old_addr, pmc->old_end);
    mmu_notifier_range_init(&range, MMU_NOTIFY_UNMAP, 0, mm,
                pmc->old_addr, pmc->old_end);
    mmu_notifier_invalidate_range_start(&range);
    
    /* 페이지 테이블 순회 및 이동 */
    for (; !pmc_done(pmc); pmc_next(pmc, extent)) {
        cond_resched();
        
        /* PUD 레벨 이동 시도 */
        extent = get_extent(NORMAL_PUD, pmc);
        old_pud = get_old_pud(mm, pmc->old_addr);
        if (!old_pud)
            continue;
        new_pud = alloc_new_pud(mm, pmc->new_addr);
        if (!new_pud)
            break;
        
        /* Huge PUD 이동 */
        if (pud_trans_huge(*old_pud)) {
            if (extent == HPAGE_PUD_SIZE) {
                move_pgt_entry(pmc, HPAGE_PUD, old_pud, new_pud);
                continue;
            }
        } else if (IS_ENABLED(CONFIG_HAVE_MOVE_PUD) && extent == PUD_SIZE) {
            if (move_pgt_entry(pmc, NORMAL_PUD, old_pud, new_pud))
                continue;
        }
        
        /* PMD 레벨 이동 시도 */
        extent = get_extent(NORMAL_PMD, pmc);
        old_pmd = get_old_pmd(mm, pmc->old_addr);
        if (!old_pmd)
            continue;
        new_pmd = alloc_new_pmd(mm, pmc->new_addr);
        if (!new_pmd)
            break;
        
        /* Huge PMD 이동 */
        if (pmd_is_huge(*old_pmd)) {
            if (extent == HPAGE_PMD_SIZE &&
                move_pgt_entry(pmc, HPAGE_PMD, old_pmd, new_pmd))
                continue;
            split_huge_pmd(pmc->old, old_pmd, pmc->old_addr);
        } else if (IS_ENABLED(CONFIG_HAVE_MOVE_PMD) && extent == PMD_SIZE) {
            if (move_pgt_entry(pmc, NORMAL_PMD, old_pmd, new_pmd))
                continue;
        }
        
        /* PTE 레벨 이동 */
        if (pmd_none(*old_pmd))
            continue;
        if (pte_alloc(pmc->new->vm_mm, new_pmd))
            break;
        if (move_ptes(pmc, extent, old_pmd, new_pmd) < 0)
            goto again;
    }
    
    mmu_notifier_invalidate_range_end(&range);
    return pmc_progress(pmc);
}

역할: 페이지 테이블 계층(PUD→PMD→PTE)을 순회하면서 각 엔트리를 새 위치로 이동합니다. Huge 페이지와 일반 페이지를 구분하여 처리하며, rmap 잠금을 관리합니다.

7. move_ptes

개별 PTE를 이동하는 함수입니다.

/* mm/mremap.c:197-319 */
static int move_ptes(struct pagetable_move_control *pmc,
        unsigned long extent, pmd_t *old_pmd, pmd_t *new_pmd)
{
    struct vm_area_struct *vma = pmc->old;
    bool need_clear_uffd_wp = vma_has_uffd_without_event_remap(vma);
    struct mm_struct *mm = vma->vm_mm;
    pte_t *old_ptep, *new_ptep;
    pte_t old_pte, pte;
    pmd_t dummy_pmdval;
    spinlock_t *old_ptl, *new_ptl;
    bool force_flush = false;
    unsigned long old_addr = pmc->old_addr;
    unsigned long new_addr = pmc->new_addr;
    unsigned long old_end = old_addr + extent;
    unsigned long len = old_end - old_addr;
    int max_nr_ptes;
    int nr_ptes;
    int err = 0;
    
    /* rmap 잠금 필요시 획득 */
    if (pmc->need_rmap_locks)
        take_rmap_locks(vma);
    
    /* 소스 PTE 잠금 */
    old_ptep = pte_offset_map_lock(mm, old_pmd, old_addr, &old_ptl);
    if (!old_ptep) {
        err = -EAGAIN;
        goto out;
    }
    
    /* 대상 PTE 매핑 */
    new_ptep = pte_offset_map_rw_nolock(mm, new_pmd, new_addr, &dummy_pmdval,
                       &new_ptl);
    if (!new_ptep) {
        pte_unmap_unlock(old_ptep, old_ptl);
        err = -EAGAIN;
        goto out;
    }
    
    /* 중첩 잠금 처리 */
    if (new_ptl != old_ptl)
        spin_lock_nested(new_ptl, SINGLE_DEPTH_NESTING);
    flush_tlb_batched_pending(vma->vm_mm);
    lazy_mmu_mode_enable();
    
    /* PTE 순회 및 이동 */
    for (; old_addr < old_end; old_ptep += nr_ptes, old_addr += nr_ptes * PAGE_SIZE,
        new_ptep += nr_ptes, new_addr += nr_ptes * PAGE_SIZE) {
        VM_WARN_ON_ONCE(!pte_none(*new_ptep));
        
        nr_ptes = 1;
        max_nr_ptes = (old_end - old_addr) >> PAGE_SHIFT;
        old_pte = ptep_get(old_ptep);
        if (pte_none(old_pte))
            continue;
        
        /* 유효한 PTE인 경우 TLB 플러시 필요 */
        if (pte_present(old_pte)) {
            nr_ptes = mremap_folio_pte_batch(vma, old_addr, old_ptep,
                             old_pte, max_nr_ptes);
            force_flush = true;
        }
        
        /* PTE 이동 */
        pte = get_and_clear_ptes(mm, old_addr, old_ptep, nr_ptes);
        pte = move_pte(pte, old_addr, new_addr);
        pte = move_soft_dirty_pte(pte);
        
        /* uffd-wp 처리 */
        if (need_clear_uffd_wp && pte_is_uffd_wp_marker(pte))
            pte_clear(mm, new_addr, new_ptep);
        else {
            if (need_clear_uffd_wp) {
                if (pte_present(pte))
                    pte = pte_clear_uffd_wp(pte);
                else
                    pte = pte_swp_clear_uffd_wp(pte);
            }
            set_ptes(mm, new_addr, new_ptep, pte, nr_ptes);
        }
    }
    
    lazy_mmu_mode_disable();
    if (force_flush)
        flush_tlb_range(vma, old_end - len, old_end);
    if (new_ptl != old_ptl)
        spin_unlock(new_ptl);
    pte_unmap(new_ptep - 1);
    pte_unmap_unlock(old_ptep - 1, old_ptl);
out:
    if (pmc->need_rmap_locks)
        drop_rmap_locks(vma);
    return err;
}

역할: 개별 PTE를 소스에서 대상으로 이동합니다. folio 배치 처리, soft-dirty 비트 처리, userfaultfd-wp 처리를 수행합니다.

8. mremap_folio_pte_batch

large folio의 경우 여러 PTE를 한 번에 배치 처리하는 함수입니다.

/* mm/mremap.c:178-195 */
static int mremap_folio_pte_batch(struct vm_area_struct *vma, unsigned long addr,
        pte_t *ptep, pte_t pte, int max_nr)
{
    struct folio *folio;
    
    if (max_nr == 1)
        return 1;
    
    /* large folio가 아니면 배치 불가 */
    if (pte_batch_hint(ptep, pte) == 1)
        return 1;
    
    folio = vm_normal_folio(vma, addr, pte);
    if (!folio || !folio_test_large(folio))
        return 1;
    
    return folio_pte_batch_flags(folio, NULL, ptep, &pte, max_nr, FPB_RESPECT_WRITE);
}

역할: large folio의 경우 여러 PTE를 한 번에 처리하여 성능을 향상시킵니다.

9. move_soft_dirty_pte

soft-dirty 비트를 설정하는 함수입니다.

/* mm/mremap.c:159-176 */
static pte_t move_soft_dirty_pte(pte_t pte)
{
    if (pte_none(pte))
        return pte;
    
    /* soft dirty 비트 설정하여 사용자 공간에서 PTE 이동 감지 */
    if (pgtable_supports_soft_dirty()) {
        if (pte_present(pte))
            pte = pte_mksoft_dirty(pte);
        else
            pte = pte_swp_mksoft_dirty(pte);
    }
    
    return pte;
}

역할: PTE가 이동되었음을 사용자 공간에서 감지할 수 있도록 soft-dirty 비트를 설정합니다.

10. mremap_to

지정된 주소로 VMA를 리매핑하는 함수입니다.

/* mm/mremap.c:1367-1417 */
static unsigned long mremap_to(struct vma_remap_struct *vrm)
{
    struct mm_struct *mm = current->mm;
    unsigned long err;
    
    /* MREMAP_FIXED인 경우 대상 영역 먼저 제거 */
    if (vrm->flags & MREMAP_FIXED) {
        err = do_munmap(mm, vrm->new_addr, vrm->new_len,
                vrm->uf_unmap_early);
        vrm->vma = NULL;
        vrm->vmi_needs_invalidate = true;
        if (err)
            return err;
        
        /* VMA 재조회 */
        vrm->vma = vma_lookup(mm, vrm->addr);
        if (!vrm->vma)
            return -EFAULT;
    }
    
    /* 축소 처리 */
    if (vrm->remap_type == MREMAP_SHRINK) {
        err = shrink_vma(vrm, false);
        if (err)
            return err;
        
        vrm->old_len = vrm->new_len;
    }
    
    /* MREMAP_DONTUNMAP은 확장 검증 */
    if (vrm->flags & MREMAP_DONTUNMAP) {
        vm_flags_t vm_flags = vrm->vma->vm_flags;
        unsigned long pages = vrm->old_len >> PAGE_SHIFT;
        
        if (!may_expand_vm(mm, vm_flags, pages))
            return -ENOMEM;
    }
    
    /* 새 주소 결정 */
    err = vrm_set_new_addr(vrm);
    if (err)
        return err;
    
    return move_vma(vrm);
}

역할: MREMAP_FIXED 플래그로 지정된 주소로 VMA를 이동합니다.

11. mremap_at

주소 지정 없이 VMA 크기를 변경하는 함수입니다.

/* mm/mremap.c:1554-1584 */
static unsigned long mremap_at(struct vma_remap_struct *vrm)
{
    unsigned long res;
    
    switch (vrm->remap_type) {
    case MREMAP_INVALID:
        break;
    case MREMAP_NO_RESIZE:
        /* 크기 변경 없음 - no-op */
        return vrm->addr;
    case MREMAP_SHRINK:
        /* 축소 - 항상 in-place 가능 */
        res = shrink_vma(vrm, true);
        if (res)
            return res;
        
        return vrm->addr;
    case MREMAP_EXPAND:
        return expand_vma(vrm);
    }
    
    WARN_ON_ONCE(1);
    return -EINVAL;
}

역할: MREMAP_NO_RESIZE, MREMAP_SHRINK, MREMAP_EXPAND 유형에 따라 적절한 처리를 수행합니다.

12. expand_vma

VMA를 확장하는 함수입니다.

/* mm/mremap.c:1516-1548 */
static unsigned long expand_vma(struct vma_remap_struct *vrm)
{
    unsigned long err;
    
    /* in-place 확장 가능 여부 확인 */
    if (vrm_can_expand_in_place(vrm)) {
        err = expand_vma_in_place(vrm);
        if (err)
            return err;
        
        return vrm->addr;
    }
    
    /* in-place 확장 불가능하면 이동 필요 */
    if (!(vrm->flags & MREMAP_MAYMOVE))
        return -ENOMEM;
    
    /* 새 주소 결정 */
    err = vrm_set_new_addr(vrm);
    if (err)
        return err;
    
    return move_vma(vrm);
}

역할: in-place 확장을 시도하고, 불가능하면 MREMAP_MAYMOVE 플래그가 설정된 경우 이동을 수행합니다.

13. expand_vma_in_place

VMA를 in-place로 확장하는 함수입니다.

/* mm/mremap.c:1456-1484 */
static unsigned long expand_vma_in_place(struct vma_remap_struct *vrm)
{
    struct mm_struct *mm = current->mm;
    struct vm_area_struct *vma = vrm->vma;
    VMA_ITERATOR(vmi, mm, vma->vm_end);
    
    /* 계정 처리 */
    if (!vrm_calc_charge(vrm))
        return -ENOMEM;
    
    /* VMA 확장 및 병합 */
    vma = vma_merge_extend(&vmi, vma, vrm->delta);
    if (!vma) {
        vrm_uncharge(vrm);
        return -ENOMEM;
    }
    vrm->vma = vma;
    
    /* 통계 계정 */
    vrm_stat_account(vrm, vrm->delta);
    
    return 0;
}

역할: VMA를 in-place로 확장하고, 인접 VMA와 병합을 시도합니다.

14. shrink_vma

VMA를 축소하는 함수입니다.

/* mm/mremap.c:1330-1361 */
static unsigned long shrink_vma(struct vma_remap_struct *vrm,
                bool drop_lock)
{
    struct mm_struct *mm = current->mm;
    unsigned long unmap_start = vrm->addr + vrm->new_len;
    unsigned long unmap_bytes = vrm->delta;
    unsigned long res;
    VMA_ITERATOR(vmi, mm, unmap_start);
    
    VM_BUG_ON(vrm->remap_type != MREMAP_SHRINK);
    
    /* 축소 영역 제거 */
    res = do_vmi_munmap(&vmi, mm, unmap_start, unmap_bytes,
                vrm->uf_unmap, drop_lock);
    vrm->vma = NULL;
    if (res)
        return res;
    
    /* 잠금 해제 시 VMA 재조회 */
    if (drop_lock) {
        vrm->mmap_locked = false;
    } else {
        vrm->vma = vma_lookup(mm, vrm->addr);
        if (!vrm->vma)
            return -EFAULT;
    }
    
    return 0;
}

역할: VMA의 끝부분을 제거하여 크기를 축소합니다.

15. remap_move

여러 VMA를 한 번에 이동하는 함수입니다.

/* mm/mremap.c:1826-1913 */
static unsigned long remap_move(struct vma_remap_struct *vrm)
{
    struct vm_area_struct *vma;
    unsigned long start = vrm->addr;
    unsigned long end = vrm->addr + vrm->old_len;
    unsigned long new_addr = vrm->new_addr;
    unsigned long target_addr = new_addr;
    unsigned long res = -EFAULT;
    unsigned long last_end;
    bool seen_vma = false;
    
    VMA_ITERATOR(vmi, current->mm, start);
    
    /* 범위 내 모든 VMA 순회 */
    for_each_vma_range(vmi, vma, end) {
        unsigned long addr = max(vma->vm_start, start);
        unsigned long len = min(end, vma->vm_end) - addr;
        unsigned long offset, res_vma;
        bool multi_allowed;
        
        /* 시작 부분의 간격 허용 안됨 */
        if (!seen_vma && start < vma->vm_start)
            return -EFAULT;
        
        /* 간격 유지하여 이동 */
        offset = seen_vma ? vma->vm_start - last_end : 0;
        last_end = vma->vm_end;
        
        vrm->vma = vma;
        vrm->addr = addr;
        vrm->new_addr = target_addr + offset;
        vrm->old_len = vrm->new_len = len;
        
        multi_allowed = vma_multi_allowed(vma);
        if (!multi_allowed) {
            if (seen_vma)
                return -EFAULT;
            if (vma->vm_end < end)
                return -EFAULT;
        }
        
        res_vma = check_prep_vma(vrm);
        if (!res_vma)
            res_vma = mremap_to(vrm);
        if (IS_ERR_VALUE(res_vma))
            return res_vma;
        
        if (!seen_vma) {
            VM_WARN_ON_ONCE(multi_allowed && res_vma != new_addr);
            res = res_vma;
        }
        
        if (vrm->vmi_needs_invalidate) {
            vma_iter_invalidate(&vmi);
            vrm->vmi_needs_invalidate = false;
        }
        seen_vma = true;
        target_addr = res_vma + vrm->new_len;
    }
    
    return res;
}

역할: 여러 VMA를 한 번에 이동할 때 간격을 유지하면서 병렬로 처리합니다.


호출 흐름

SYSCALL_DEFINE5(mremap)  ← 사용자 시스템 호출 진입점
    ↓
do_mremap()  ← 메인 로직 처리
    ├─ check_mremap_params()  # 파라미터 검증 (플래그, 주소, 크기)
    ├─ mmap_write_lock_killable()  # mmap 쓰기 잠금 획득
    ├─ vrm_move_only()?
    │   ├─ YES → remap_move()  # 다중 VMA 이동 (간격 유지)
    │   │         └─ for_each_vma_range()
    │   │             ├─ check_prep_vma()  # VMA 준비 검증
    │   │             └─ mremap_to()  # 지정 주소로 이동
    │   │                 ├─ do_munmap()  # 대상 영역 제거
    │   │                 ├─ vrm_set_new_addr()  # 새 주소 결정
    │   │                 └─ move_vma()
    │   │                     ├─ prep_move_vma()  # VMA 분할 준비
    │   │                     ├─ vrm_calc_charge()  # 계정 처리
    │   │                     ├─ copy_vma_and_data()
    │   │                     │   ├─ copy_vma()  # VMA 복사
    │   │                     │   └─ move_page_tables()  # 페이지 테이블 이동
    │   │                     │       ├─ try_realign_addr()  # PMD 경계 정렬
    │   │                     │       ├─ move_pgt_entry()  # PUD/PMD 레벨 이동
    │   │                     │       └─ move_ptes()  # PTE 레벨 이동
    │   │                     │           └─ mremap_folio_pte_batch()  # large folio 배치
    │   │                     ├─ vrm_stat_account()  # 통계 계정
    │   │                     └─ unmap_source_vma()  # 원본 제거
    │   └─ NO → vma_lookup()  # VMA 조회
    │           ├─ check_prep_vma()  # VMA 준비 검증
    │           └─ mremap_to() 또는 mremap_at()
    │               ├─ mremap_to()  # 고정 주소로 이동
    │               │   ├─ do_munmap()  # 대상 영역 제거
    │               │   ├─ shrink_vma()  # 축소 처리
    │               │   └─ move_vma()
    │               └─ mremap_at()  # 주소 지정 없이 이동
    │                   ├─ MREMAP_NO_RESIZE → 즉시 반환
    │                   ├─ MREMAP_SHRINK → shrink_vma()
    │                   └─ MREMAP_EXPAND → expand_vma()
    │                       ├─ vrm_can_expand_in_place()?
    │                       │   ├─ YES → expand_vma_in_place()
    │                       │   │         └─ vma_merge_extend()
    │                       │   └─ NO → move_vma()
    └─ mmap_write_unlock()  # mmap 잠금 해제

흐름 설명

1. 진입점: SYSCALL_DEFINE5(mremap)이 사용자로부터 파라미터를 받아 vma_remap_struct를 초기화합니다.

2. 파라미터 검증: check_mremap_params()가 플래그, 주소 정렬, 크기 제한 등을 검증합니다.

3. 잠금 획득: mmap_write_lock_killable()로 mmap 쓰기 잠금을 획득합니다.

4. 경로 선택:

- vrm_move_only()가 true이면 remap_move()로 여러 VMA를 한 번에 이동합니다.

- 아니면 단일 VMA 처리 경로를 따릅니다.

5. VMA 처리:

- mremap_to(): MREMAP_FIXED 플래그로 지정된 주소로 이동

- mremap_at(): 주소 지정 없이 크기 변경

6. 페이지 테이블 이동: move_page_tables()가 PUD→PMD→PTE 순서로 페이지 테이블을 이동합니다.

7. 성능 최적화:

- try_realign_addr(): PMD 경계에 정렬하여 처리 효율 향상

- mremap_folio_pte_batch(): large folio의 경우 여러 PTE를 한 번에 처리

8. 마무리: mmap 잠금 해제, userfaultfd 알림, mlock된 영역 populate 처리


조건별 비교

1. MREMAP 플래그별 동작

플래그동작사용 시나리오
MREMAP_MAYMOVE1이동 허용기본 mremap 사용 시
MREMAP_FIXED2고정 주소로 이동특정 주소에 매핑 강제
MREMAP_DONTUNMAP4원본 유지프로세스 간 공유 메모리 복사

2. 리매핑 타입별 처리

타입조건처리 방법결과
MREMAP_NO_RESIZEold_len == new_len즉시 반환no-op
MREMAP_SHRINKold_len > new_lenin-place 축소새 주소 반환
MREMAP_EXPANDold_len < new_lenin-place 확장 또는 이동새 주소 반환

3. 페이지 테이블 이동 우선순위

레벨조건이동 방법성능
HPAGE_PUDHuge PUD + 정렬move_huge_pud()가장 빠름
NORMAL_PUD일반 PUD + 정렬move_normal_pud()빠름
HPAGE_PMDHuge PMD + 정렬move_huge_pmd()빠름
NORMAL_PMD일반 PMD + 정렬move_normal_pmd()보통
PTE일반 페이지move_ptes()가장 느림

4. 특수 처리 조건

조건처리설명
HugeTLB 페이지move_hugetlb_page_tables()별도의 huge page 테이블 이동
uffd-wp 등록pte_clear_uffd_wp()userfaultfd-wp 마커 제거
VM_ACCOUNTvrm_calc_charge()메모리 계정 청구
VM_LOCKEDpopulate_expand확장 영역 자동 populate
KSM 페이지ksm_madvise(MADV_UNMERGEABLE)KSM 병합 해제
mseal()된 VMAcheck_prep_vma()에서 거부봉인된 VMA 리매핑 불가
large foliomremap_folio_pte_batch()여러 PTE 배치 처리

5. 확장/축소 동작 비교

연산조건in-place 가능이동 필요
확장 (EXPAND)VMA 끝부분 + 빈 공간가능불가능 시 이동
축소 (SHRINK)VMA 끝부분 제거항상 가능불필요
이동 (MOVE)새 주소 지정불가능항상 이동

6. rmap 잠금 처리 비교

레벨rmap 잠금설명
NORMAL_PMD항상 필요일반 PMD 레벨 이동
NORMAL_PUD항상 필요일반 PUD 레벨 이동
HPAGE_PMD선택적Huge PMD 이동 시
HPAGE_PUD선택적Huge PUD 이동 시
PTE선택적PTE 레벨 이동 시

관련 문서

  • 메모리 관리 개요
  • Buddy Allocator
  • VMA / mmap
  • Compaction
  • Migration
  • mprotect
  • Page Table Walk