# mremap 🔄
관련 소스:mm/mremap.c,mm/internal.h,include/uapi/linux/mman.h
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 플래그 정의
# 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
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 반복자가 무효화되었는지 */
};
페이지 테이블 이동 연산을 제어하는 구조체입니다.
/* 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_, \
}
리매핑 연산 유형을 분류합니다.
/* 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 */
};
/* 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: 원본 매핑을 유지하면서 새 위치에 복사합니다. 프로세스 간 공유 메모리 복사에 사용됩니다.
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()을 사용합니다.
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 잠금을 획득합니다. 그 다음 이동 전용 경로 또는 일반 경로를 선택하여 실행합니다.
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 겹침 등을 검증합니다.
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 정렬, 확장 가능성을 검증합니다.
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 플래그가 설정된 경우 원본을 유지합니다.
페이지 테이블을 실제 이동하는 함수입니다.
/* 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 잠금을 관리합니다.
개별 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 처리를 수행합니다.
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를 한 번에 처리하여 성능을 향상시킵니다.
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 비트를 설정합니다.
지정된 주소로 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를 이동합니다.
주소 지정 없이 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 유형에 따라 적절한 처리를 수행합니다.
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 플래그가 설정된 경우 이동을 수행합니다.
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와 병합을 시도합니다.
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의 끝부분을 제거하여 크기를 축소합니다.
여러 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 처리
| 플래그 | 값 | 동작 | 사용 시나리오 |
|---|---|---|---|
| MREMAP_MAYMOVE | 1 | 이동 허용 | 기본 mremap 사용 시 |
| MREMAP_FIXED | 2 | 고정 주소로 이동 | 특정 주소에 매핑 강제 |
| MREMAP_DONTUNMAP | 4 | 원본 유지 | 프로세스 간 공유 메모리 복사 |
| 타입 | 조건 | 처리 방법 | 결과 |
|---|---|---|---|
| MREMAP_NO_RESIZE | old_len == new_len | 즉시 반환 | no-op |
| MREMAP_SHRINK | old_len > new_len | in-place 축소 | 새 주소 반환 |
| MREMAP_EXPAND | old_len < new_len | in-place 확장 또는 이동 | 새 주소 반환 |
| 레벨 | 조건 | 이동 방법 | 성능 |
|---|---|---|---|
| HPAGE_PUD | Huge PUD + 정렬 | move_huge_pud() | 가장 빠름 |
| NORMAL_PUD | 일반 PUD + 정렬 | move_normal_pud() | 빠름 |
| HPAGE_PMD | Huge PMD + 정렬 | move_huge_pmd() | 빠름 |
| NORMAL_PMD | 일반 PMD + 정렬 | move_normal_pmd() | 보통 |
| PTE | 일반 페이지 | move_ptes() | 가장 느림 |
| 조건 | 처리 | 설명 |
|---|---|---|
| HugeTLB 페이지 | move_hugetlb_page_tables() | 별도의 huge page 테이블 이동 |
| uffd-wp 등록 | pte_clear_uffd_wp() | userfaultfd-wp 마커 제거 |
| VM_ACCOUNT | vrm_calc_charge() | 메모리 계정 청구 |
| VM_LOCKED | populate_expand | 확장 영역 자동 populate |
| KSM 페이지 | ksm_madvise(MADV_UNMERGEABLE) | KSM 병합 해제 |
| mseal()된 VMA | check_prep_vma()에서 거부 | 봉인된 VMA 리매핑 불가 |
| large folio | mremap_folio_pte_batch() | 여러 PTE 배치 처리 |
| 연산 | 조건 | in-place 가능 | 이동 필요 |
|---|---|---|---|
| 확장 (EXPAND) | VMA 끝부분 + 빈 공간 | 가능 | 불가능 시 이동 |
| 축소 (SHRINK) | VMA 끝부분 제거 | 항상 가능 | 불필요 |
| 이동 (MOVE) | 새 주소 지정 | 불가능 | 항상 이동 |
| 레벨 | rmap 잠금 | 설명 |
|---|---|---|
| NORMAL_PMD | 항상 필요 | 일반 PMD 레벨 이동 |
| NORMAL_PUD | 항상 필요 | 일반 PUD 레벨 이동 |
| HPAGE_PMD | 선택적 | Huge PMD 이동 시 |
| HPAGE_PUD | 선택적 | Huge PUD 이동 시 |
| PTE | 선택적 | PTE 레벨 이동 시 |