MMU 없는 시스템 (NOMMU)

개요 (Overview)

Linux 커널은 MMU(Memory Management Unit)가 없는 환경에서도 동작할 수 있도록 NOMMU 서브시스템을 제공합니다. 이는 주로 임베디드 시스템(uClinux), 특정 아키텍처(SuperH, RISC-V NOMMU, ARM uClinux, Xtensa 등), 그리고 일부 특수 하드웨어에서 사용됩니다. NOMMU 환경에서는 가상 메모리 주소 변환 없이 물리 주소를 직접 사용하므로, 멀티프로세싱(fork), COW(Copy-on-Write), 메모리 보호 같은 기능이 제한되거나 지원되지 않습니다.

mm/nommu.c는 MMU가 있을 때 mm/mmap.c, mm/vmalloc.c, mm/mlock.c 등에서 수행하는 역할을 대체하는 래퍼 함수들을 포함합니다. 핵심 메커니즘은 vm_region 구조체 기반의 전역 레드-블랙 트리(nommu_region_tree)로, 매핑된 메모리 영역을 추적하고 공유를 관리합니다. fork()가 없으므로 VMA 복제가 필요 없고, 대신 vm_region.vm_usage 참조 카운트로 영역 공유를 관리합니다.

소스 파일 경로:
├── mm/nommu.c                              ← NOMMU 메모리 관리 핵심 (1903줄)
├── include/linux/mm_types.h                ← struct vm_region 정의 (L700-716)
├── include/linux/mm.h                      ← nommu_region_tree/sem 선언, is_nommu_shared_mapping()
├── mm/Makefile                             ← mmu-y := nommu.o (기본 빌드 대상)
├── mm/Kconfig                              ← NOMMU_INITIAL_TRIM_EXCESS, MMAP_ALLOW_UNINITIALIZED
├── Documentation/admin-guide/mm/nommu-mmap.rst ← 공식 문서
└── arch/arm/Kconfig-nommu                  ← ARM uClinux 설정
NOMMU 구조와 흐름

빠른 점검 명령

# NOMMU 커널 여부 확인
cat /proc/version | grep -i nommu || zcat /proc/config.gz 2>/dev/null | grep CONFIG_MMU || grep CONFIG_MMU /boot/config-$(uname -r) 2>/dev/null

# 시스템에 MMU 지원 여부 확인
getconf PAGESIZE
ls /proc/self/pagemap 2>/dev/null && echo "MMU 지원" || echo "MMU 미지원 또는 접근 불가"

# 현재 시스템의 메모리 매핑 구조 확인
cat /proc/self/maps | head -10

# vm_area_struct 관련 정보 확인
cat /proc/self/smaps | head -20

# 커널 설정에서 NOMMU 옵션 확인
zcat /proc/config.gz 2>/dev/null | grep -E "NOMMU|!MMU" || grep -E "NOMMU|!MMU" /boot/config-$(uname -r) 2>/dev/null

# 커널 빌드 시 nommu.o 포함 여부 확인
cat /lib/modules/$(uname -r)/build/.config 2>/dev/null | grep CONFIG_MMU

# 메모리 매핑 관련 커널 로그 확인
dmesg | grep -iE "nommu|mmap|region" | tail -5

# RISC-V NOMMU defconfig 존재 여부 확인 (크로스 컴파일 환경)
ls arch/riscv/configs/nommu* 2>/dev/null

# ARM NOMMU 관련 설정 확인
ls arch/arm/configs/*nommu* 2>/dev/null || ls arch/arm/Kconfig*nommu* 2>/dev/null

핵심 자료구조

1. `struct vm_region` (include/linux/mm_types.h:704)

NOMMU 환경에서 매핑된 메모리 영역을 추적하는 전역 구조체입니다. 전역 레드-블랙 트리에 저장되며, 여러 VMA가 하나의 region을 공유할 수 있습니다.

// include/linux/mm_types.h:704-716
struct vm_region {
struct rb_node  vm_rb;          // 전역 region 트리의 연결 노드
vm_flags_t      vm_flags;       // VMA vm_flags
unsigned long   vm_start;       // region 시작 주소
unsigned long   vm_end;         // region 초기화된 끝 주소
unsigned long   vm_top;         // region 할당된 끝 주소 (vm_end >= vm_top)
unsigned long   vm_pgoff;       // vm_file에서 vm_start에 대응하는 오프셋
struct file     *vm_file;       // 백업 파일 또는 NULL

int             vm_usage;       // region 참조 카운트 (nommu_region_sem 하에서 접근)
bool            vm_icache_flushed : 1; // icache 플러시 여부
};

필드 설명:

  • vm_start / vm_end: 매핑된 주소 범위. vm_end는 실제 사용 가능한 끝 주소.
  • vm_top: 실제 할당된 물리 페이지의 끝. vm_end <= vm_top이며, 예약된 공간을 포함.
  • vm_usage: 이 region을 가리키는 VMA의 수. 0이 되면 해제됨.
  • VM_MAPPED_COPY: 익명 매핑 또는 사본 매핑에 설정. 직접 매핑이 아님을 의미.
  • 2. `struct vm_area_struct`의 NOMMU 확장 (include/linux/mm.h:270-275)

    NOMMU에서는 vm_area_struct에 전역 region 트리와 세마포어가 추가됩니다.

    // include/linux/mm.h:270-275
    #ifndef CONFIG_MMU
    extern struct rb_root nommu_region_tree;    // 전역 region 레드-블랙 트리
    extern struct rw_semaphore nommu_region_sem; // region 트리 접근 세마포어
    
    extern unsigned int kobjsize(const void *objp); // 할당 크기 반환
    #endif

    3. NOMMU 매핑 능력 플래그 (mm/nommu.c:730-826)

    파일 매핑 시 지원 가능한 매핑 방식을 결정하는 플래그입니다.

    // mm/nommu.c:738-746 (파일 타입별 기본 능력)
    switch (file_inode(file)->i_mode & S_IFMT) {
    case S_IFREG:
    case S_IFBLK:
    capabilities = NOMMU_MAP_COPY;        // 사본 허용
    break;
    case S_IFCHR:
    capabilities = NOMMU_MAP_DIRECT |     // 직접 매핑
    NOMMU_MAP_READ |       // 읽기 가능
    NOMMU_MAP_WRITE;       // 쓰기 가능
    break;
    }

    능력 플래그 역할:

  • NOMMU_MAP_DIRECT: 파일 시스템/드라이버가 직접 매핑을 지원하는 경우
  • NOMMU_MAP_COPY: 파일 내용을 메모리로 복사하여 매핑하는 경우
  • NOMMU_MAP_READ / NOMMU_MAP_WRITE / NOMMU_MAP_EXEC: 보호 수준별 직접 매핑 지원
  • 4. `is_nommu_shared_mapping()` (include/linux/mm.h:1944-1956)

    공유 매핑 여부를 판단하는 인라인 함수입니다.

    // include/linux/mm.h:1944-1956
    #ifndef CONFIG_MMU
    static inline bool is_nommu_shared_mapping(vm_flags_t flags)
    {
    // 공유 매핑: MAP_SHARED이거나 R/O MAP_PRIVATE 오버레이
    return flags & (VM_MAYSHARE | VM_MAYOVERLAY);
    }
    #endif

    5. `generic_file_vm_ops` (mm/nommu.c:56-57)

    NOMMU에서 파일 기반 VM 연산의 빈 래퍼입니다.

    // mm/nommu.c:56-57
    const struct vm_operations_struct generic_file_vm_ops = {
    };

    핵심 함수

    1. `do_mmap()` — 메모리 매핑 생성 (mm/nommu.c:1012-1247)

    NOMMU 환경에서 mmap() 시스템 콜의 핵심 구현입니다. 매핑 요청을 검증하고, region을 할당하며, VMA를 생성합니다.

    시그니처:

    // mm/nommu.c:1012-1020
    unsigned long do_mmap(struct file *file,
    unsigned long addr,
    unsigned long len,
    unsigned long prot,
    unsigned long flags,
    vm_flags_t vm_flags,
    unsigned long pgoff,
    unsigned long *populate,
    struct list_head *uf)

    주요 분기 로직:

    1. validate_mmap_request() → 매핑 가능 여부 판단

    2. is_nommu_shared_mapping(vm_flags) → 공유 매핑이면 기존 region 탐색 (rb-tree 순회)

    3. 기존 region 발견 시 vm_usage++로 공유, do_mmap_shared_file() 호출

    4. 새 매핑 시 do_mmap_private()로 사본 할당 또는 do_mmap_shared_file()로 직접 매핑

    5. add_nommu_region()으로 전역 트리에 추가

    6. 익명 매핑且 MAP_UNINITIALIZED 미설정 시 memset(0)으로 초기화

    2. `validate_mmap_request()` — 매핑 요청 검증 (mm/nommu.c:690-842)

    매핑 요청의 유효성을 검사하고, 파일 타입과 권한에 따라 매핑 능력을 결정합니다.

    시그니처:

    // mm/nommu.c:690-696
    static int validate_mmap_request(struct file *file,
    unsigned long addr,
    unsigned long len,
    unsigned long prot,
    unsigned long flags,
    unsigned long pgoff,
    unsigned long *_capabilities)

    핵심 검증:

  • MAP_FIXED 사용 불가 (NOMMU에서는 주소 힌트 무시)
  • MAP_PRIVATE 또는 MAP_SHARED만 허용
  • 파일 매핑: can_mmap_file(), mmap_capabilities() 확인
  • MAP_SHARED + 쓰기: 파일 쓰기 권한 필요
  • path_noexec()PROT_EXEC 거부
  • READ_IMPLIES_EXEC personality 처리
  • 3. `do_mmap_private()` — 사본 매핑 (mm/nommu.c:914-1007)

    MAP_PRIVATE 파일 매핑 또는 익명 매핑을 위해 물리 페이지를 할당하고 파일 내용을 복사합니다.

    시그니처:

    // mm/nommu.c:914-917
    static int do_mmap_private(struct vm_area_struct *vma,
    struct vm_region *region,
    unsigned long len,
    unsigned long capabilities)

    흐름:

    1. NOMMU_MAP_DIRECT이면 mmap_file() 시도 → 직접 매핑 성공 시 복사 불필요

    2. 직접 매핑 실패 시 alloc_pages_exact()로 연속 물리 페이지 할당

    3. VM_MAPPED_COPY 플래그 설정

    4. 파일 매핑이면 kernel_read()로 내용 복사

    5. 익명 매핑이면 vma_set_anonymous() 설정

    4. `do_munmap()` — 매핑 해제 (mm/nommu.c:1426-1486)

    매핑된 메모리 영역을 해제합니다. NOMMU에서는 단일 VMA로 덮여 있어야 합니다.

    시그니처:

    // mm/nommu.c:1426-1426
    int do_munmap(struct mm_struct *mm, unsigned long start,
    size_t len, struct list_head *uf)

    분기:

  • 파일 매핑: 부분 해제 불가, 전체 VMA만 해제 가능
  • 익명 매핑: split_vma()로 분할 후 vmi_shrink_vma()로 축소 가능
  • delete_vma_from_mm()delete_vma()put_nommu_region()free_page_series()
  • 5. `__put_nommu_region()` — region 참조 해제 (mm/nommu.c:530-551)

    region의 참조 카운트를 감소시키고, 0이 되면 해제합니다.

    시그니처:

    // mm/nommu.c:530-531
    static void __put_nommu_region(struct vm_region *region)
    __releases(nommu_region_sem)

    흐름:

    1. vm_usage-- → 0이면 region 해제

    2. delete_nommu_region()으로 전역 트리에서 제거

    3. vm_file이 있으면 fput()으로 파일 참조 해제

    4. VM_MAPPED_COPY이면 free_page_series()로 물리 페이지 해제

    5. kmem_cache_free(vm_region_jar)로 slab 반환


    호출 흐름

    mmap() 시스템 콜 흐름

    mmap() 시스템 콜
    └─ ksys_mmap_pgoff()                     [mm/nommu.c:1249]
    └─ vm_mmap_pgoff()                   [공통]
    └─ do_mmap()                     [mm/nommu.c:1012]
    ├─ validate_mmap_request()  [mm/nommu.c:690]  ← 매핑 검증
    ├─ determine_vm_flags()     [mm/nommu.c:848]  ← VM 플래그 결정
    ├─ vm_area_alloc()          [VMA 할당]
    ├─ [공유 매핑 검색]          [rb-tree 순회]
    │    ├─ 기존 region 발견 → vm_usage++
    │    └─ do_mmap_shared_file() [mm/nommu.c:893]
    ├─ [새 매핑]
    │    ├─ NOMMU_MAP_DIRECT → mmap_file()
    │    └─ do_mmap_private()   [mm/nommu.c:914]
    │         └─ alloc_pages_exact()  [물리 페이지 할당]
    ├─ add_nommu_region()       [mm/nommu.c:471]  ← 전역 트리 추가
    ├─ setup_vma_to_mm()        [mm/nommu.c:562]
    └─ vma_iter_store_new()     [mm 트리에 VMA 추가]

    munmap() 시스템 콜 흐름

    munmap() 시스템 콜
    └─ vm_munmap()                            [mm/nommu.c:1488]
    └─ do_munmap()                       [mm/nommu.c:1426]
    ├─ [파일 매핑] → 전체 VMA만 해제
    ├─ [익명 매핑]
    │    ├─ split_vma()             [mm/nommu.c:1306]  ← 필요 시 분할
    │    └─ vmi_shrink_vma()        [mm/nommu.c:1384]  ← 축소
    └─ delete_vma_from_mm() → delete_vma()
    └─ put_nommu_region()      [mm/nommu.c:556]
    └─ __put_nommu_region() [mm/nommu.c:530]
    ├─ delete_nommu_region()  [전역 트리 제거]
    ├─ free_page_series()     [물리 페이지 해제]
    └─ kmem_cache_free()      [slab 반환]

    vfree() / vmalloc() 흐름

    vfree(addr)
    └─ kfree(addr)                            [mm/nommu.c:108]
    
    vmalloc(size)
    └─ __vmalloc_noprof(size, GFP_KERNEL)     [mm/nommu.c:112]
    └─ kmalloc_noprof(size, ...)         [NOMMU에서는 kmalloc으로 단순화]
    
    vmalloc_to_page(addr)
    └─ virt_to_page(addr)                     [mm/nommu.c:168]

    조건별 비교

    MMU vs NOMMU 비교

    항목MMU 시스템NOMMU 시스템
    가상 메모리페이지 테이블 기반 주소 변환물리 주소 직접 사용
    fork()COW로 복제미지원 (fork 불가, clone须 CLONE_VM)
    mprotect()페이지 접근 권한 변경미지원
    mmap() 주소커널이 최적 주소 선택주소 힌트 무시, 연속 할당
    공유 메모리페이지 공유 + COWregion 참조 카운트로 공유
    메모리 보호PTE 기반 읽기/쓰기/실행 보호없음 (프로세스 간 보호 없음)
    스왑지원미지원
    vmalloc별도 가상 영역 할당kmalloc 래퍼로 단순화
    VMA 관리파일 기반 매핑 분리region 기반 전역 관리

    매핑 타입별 동작 비교

    매핑 타입MMUNOMMU
    Anonymous MAP_PRIVATE임의 페이지, COW연속 물리 페이지 할당
    Anonymous MAP_SHARED공유 + COWMAP_PRIVATE와 동일 (fork 미지원)
    파일 MAP_PRIVATE (읽기만)페이지 캐시 공유직접 매핑 또는 사본 복사
    파일 MAP_PRIVATE (쓰기)COW 사본항상 사본 복사
    파일 MAP_SHARED공유 페이지직접 매핑만 가능 (드라이버 지원 필요)
    파일 MAP_SHARED (쓰기)백그라운드 쓰기미지원

    매핑 능력(NOMMU_MAP_*) 결정 흐름

    조건직접 매핑 (DIRECT)사본 (COPY)결과
    파일 미지정 (익명)가능사본 할당
    Regular 파일불가가능파일 내용 복사
    Char 디바이스가능 (드라이버 지원 시)드라이버 제공 주소에 직접 매핑
    Block 디바이스불가가능사본 복사
    공유 매핑 (MAP_SHARED)필수불가직접 매핑 실패 시 -ENODEV
    프라이빗 쓰기불가가능항상 사본

    아키텍처별 NOMMU 지원

    아키텍처defconfig특징
    ARM uClinuxarch/arm/Kconfig-nommuDRAM/FLASH 베이스, 벡터 리매핑, ARM MPU 지원
    RISC-V NOMMUarch/riscv/configs/nommu_*virt, K210 보드 지원
    SuperHarch/sh/mm/Kconfig`config NOMMU` 명시적 정의
    Xtensaarch/xtensa/configs/nommu_*KC705 보드 지원
    m68kvmlinux-nommu.ldr 링커 스크립트 존재

    Kconfig 옵션 비교

    옵션기본값설명
    `CONFIG_MMAP_ALLOW_UNINITIALIZED`n`MAP_UNINITIALIZED`로 미초기화 메모리 허용 (EXPERT + !MMU)
    `CONFIG_NOMMU_INITIAL_TRIM_EXCESS`1부팅 시 mmap 잔여 공간 트리밍 (`/proc/sys/vm/nr_trim_pages`)
    `CONFIG_NEED_PER_CPU_KM`!SMP \\!MMUkm 기반 per-cpu 할당자 사용

    vmalloc / vfree NOMMU 구현

    NOMMU에서는 vmalloc()이 별도 가상 영역을 할당하지 않고 kmalloc()으로 단순화됩니다. 이는 가상-물리 주소 변환이 없으므로 굳이 별도 매핑이 필요 없기 때문입니다.

    // mm/nommu.c:112-119
    void *__vmalloc_noprof(unsigned long size, gfp_t gfp_mask)
    {
    // __GFP_HIGHMEM는 kmalloc에서 사용 불가 (논리 주소만 반환)
    return kmalloc_noprof(size, (gfp_mask | __GFP_COMP) & ~__GFP_HIGHMEM);
    }
    
    // mm/nommu.c:106-109
    void vfree(const void *addr)
    {
    kfree(addr);  // NOMMU에서는 vfree가 kfree의 래퍼
    }

    지원 함수들:

  • vmalloc()__vmalloc_noprof()kmalloc_noprof()
  • vzalloc()__vmalloc_noprof(GFP_KERNEL | __GFP_ZERO)
  • vmalloc_node()vmalloc_noprof() (노드 무시)
  • vmalloc_32()__vmalloc_noprof()
  • vmalloc_to_page()virt_to_page() (직접 변환)
  • vmalloc_to_pfn()page_to_pfn(virt_to_page())
  • 사용 불가 함수들 (BUG() 호출):

  • vmap()BUG() (별도 가상 매핑 불가)
  • vunmap()BUG()
  • vm_map_ram()BUG()
  • vm_unmap_ram()BUG()
  • free_vm_area()BUG()

  • brk() / mremap() 제약

    brk() 동작 (mm/nommu.c:380-403)

    NOMMU brk()는 매우 단순합니다. mm->start_brk ~ mm->context.end_brk 범위 내에서만 크기 변경 가능합니다.

    // mm/nommu.c:380-403
    SYSCALL_DEFINE1(brk, unsigned long, brk)
    {
    struct mm_struct *mm = current->mm;
    if (brk < mm->start_brk || brk > mm->context.end_brk)
    return mm->brk;           // 범위 밖 → 현재 brk 반환
    if (mm->brk == brk)
    return mm->brk;           // 변경 없음
    if (brk <= mm->brk) {
    mm->brk = brk;            // 축소 허용
    return brk;
    }
    flush_icache_user_range(mm->brk, brk); // 확장 시 icache 플러시
    return mm->brk = brk;
    }

    mremap() 제약 (mm/nommu.c:1542-1576)

    NOMMU에서는 MREMAP_FIXED가 지원되지 않으며, 같은 region 내에서만 크기 변경 가능합니다.

    // mm/nommu.c:1567-1568
    if (is_nommu_shared_mapping(vma->vm_flags))
    return (unsigned long) -EPERM;  // 공유 매핑은 재할당 불가

    디버그 지원

    region 트리 검증 (mm/nommu.c:437-466)

    CONFIG_DEBUG_NOMMU_REGIONS 활성화 시 region 트리의 무결성을 검증합니다.

    // mm/nommu.c:438-461
    #ifdef CONFIG_DEBUG_NOMMU_REGIONS
    static noinline void validate_nommu_regions(void)
    {
    // region 시작/끝 주소 유효성 확인
    BUG_ON(region->vm_end <= region->vm_start);
    BUG_ON(region->vm_top < region->vm_end);
    // region 간 겹침 없음 확인
    BUG_ON(region->vm_start < last->vm_top);
    }
    #endif

    주요 디버그/sysctl

    항목경로설명
    `nr_trim_pages``/proc/sys/vm/nr_trim_pages`mmap 잔여 페이지 트리밍 기준
    `mmap_pages_allocated`커널 내부할당된 mmap 페이지 수
    `sysctl_max_map_count``/proc/sys/vm/max_map_count`프로세스당 최대 매핑 수

    관련 문서

  • 00 — 메모리 관리 개요
  • 03 — VMA / mmap
  • 06 — vmalloc
  • 37 — mremap
  • 51 — ARM64 vs x86_64
  • 52 — Linux 6.x → 7.0 변경점
  • Documentation/admin-guide/mm/nommu-mmap.rst