Ryotta's Linux 7.0 MM

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

Memblock 할당자

개요 (Overview)

Memblock은 Linux 커널 부팅 초기에 일반 메모리 할당자가 아직 사용 불가능한 상태에서 물리 메모리 영역을 관리하기 위한 메커니즘이다. 시스템 메모리를 "사용 가능한 메모리(memory)"와 "예약된 메모리(reserved)" 두 가지 컬렉션으로 나누어 관리하며, 펌웨어(E820/UEFI/Device Tree)가 전달한 물리 메모리 레이아웃을 파싱하여 초기 할당을 수행한다. 부팅이 완료되면 memblock_free_all()을 통해 Buddy Allocator에 남은 페이지를 모두 반환하고, CONFIG_ARCH_KEEP_MEMBLOCK이 설정되지 않으면 memblock 데이터 구조 자체를 해제한다.

memblock은 크게 세 가지 메모리 타입을 관리한다: memory(커널이 사용할 수 있는 물리 메모리), reserved(이미 할당/예약된 영역), physmem(실제 물리 메모리, 일부 아키텍처에서만 지원). 각 타입은 struct memblock_type으로 표현되며, 정렬된 memblock_region 배열을 포함한다. 할당은 bottom-up 또는 top-down 방식으로 수행되며, NUMA 노드 할당, 미러링, nomap 플래그 등을 지원한다.

일상 비유: memblock은 정식 창고 관리 시스템(Buddy Allocator)이 켜지기 전, 이사 현장에서 임시로 쓰는 큰 종이 배치도와 같다. 펌웨어가 넘겨준 건물 도면에서 사용할 수 있는 방은 memory에 표시하고, 커널 이미지, initrd, ACPI/EFI 테이블, crashkernel처럼 이미 점유한 방은 reserved에 표시한 뒤, 공사가 끝나면 남은 방 목록을 Buddy Allocator에 넘긴다.

소스 파일 경로:
  mm/memblock.c              ← 핵심 구현 (2768줄)
  include/linux/memblock.h   ← 공개 API 및 자료구조 정의 (624줄)

빠른 점검 명령

# memblock 디버그 활성화 (커널 파라미터)
# 부팅 시: memblock=debug

# 현재 memblock 상태 확인 (CONFIG_MEMBLOCK_DEBUG 활성화 시)
dmesg | grep -i memblock

# 물리 메모리 전체 크기 확인
cat /proc/meminfo | head -5

# Buddy Allocator에 반환된 후 메모리 현황
cat /proc/buddyinfo

# memblock이 관리하는 영역 확인 (debugfs)
sudo cat /sys/kernel/debug/memblock/memory 2>/dev/null
sudo cat /sys/kernel/debug/memblock/reserved 2>/dev/null

# 커널 부팅 시 memblock 메시지 확인
dmesg | grep -E 'BIOS-e820|Memory:|MEMBLOCK'

# NUMA 노드별 메모리 배치
numactl -H 2>/dev/null

# 펌웨어/커널이 본 물리 주소 공간
cat /proc/iomem | head -80

# memblock에 영향을 준 부팅 파라미터 확인
cat /proc/cmdline

# initrd, crashkernel, reserved-memory까지 함께 확인
dmesg | grep -Ei 'memblock|BIOS-e820|efi: mem|OF: fdt|reserved-memory|initrd|crashkernel'

# CONFIG_HAVE_MEMBLOCK_PHYS_MAP + debugfs 유지 시 physmem 확인
sudo cat /sys/kernel/debug/memblock/physmem 2>/dev/null

# 직접 매핑, 페이지 테이블, vmalloc 사용량 확인
grep -E 'Vmalloc|DirectMap|PageTables' /proc/meminfo

# x86 CONFIG_X86_PTDUMP + debugfs 활성화 시 커널 페이지 테이블 확인
sudo cat /sys/kernel/debug/kernel_page_tables 2>/dev/null | head -40

핵심 자료구조

struct memblock_region — 메모리 영역 하나

struct memblock_region {
    phys_addr_t base;
    phys_addr_t size;
    enum memblock_flags flags;
#ifdef CONFIG_NUMA
    int nid;
#endif
};

위치: include/linux/memblock.h:73-80. basesize는 반열림 구간 [base, base + size)를 만들고, flags는 HOTPLUG/MIRROR/NOMAP 같은 속성을 담는다. CONFIG_NUMA에서는 nid가 함께 저장되어 초기 물리 주소 범위를 NUMA 노드로 묶을 수 있다.

struct memblock_type — 메모리 영역 컬렉션

struct memblock_type {
    unsigned long cnt;
    unsigned long max;
    phys_addr_t total_size;
    struct memblock_region *regions;
    char *name;
};

위치: include/linux/memblock.h:90-96. cnt는 현재 유효한 region 개수이고 max는 배열 용량이다. total_size는 이 타입에 들어 있는 영역 크기의 합이며, regions는 base 기준으로 정렬된 memblock_region 배열을 가리킨다.

struct memblock — 전역 할당자 메타데이터

struct memblock {
    bool bottom_up;  /* is bottom up direction? */
    phys_addr_t current_limit;
    struct memblock_type memory;
    struct memblock_type reserved;
};

위치: include/linux/memblock.h:105-110. bottom_up은 낮은 주소부터 찾을지 높은 주소부터 찾을지를 정하고, current_limitMEMBLOCK_ALLOC_ACCESSIBLE 요청의 상한으로 쓰인다. memory는 사용 가능한 RAM 목록, reserved는 이미 점유된 물리 주소 목록이다.

enum memblock_flags — 영역 속성 플래그

enum memblock_flags {
    MEMBLOCK_NONE       = 0x0,  /* 특별한 요청 없음 */
    MEMBLOCK_HOTPLUG    = 0x1,  /* 핫플러그 가능한 영역 */
    MEMBLOCK_MIRROR     = 0x2,  /* 미러링된 영역 */
    MEMBLOCK_NOMAP      = 0x4,  /* don't add to kernel direct mapping */
    MEMBLOCK_DRIVER_MANAGED = 0x8,  /* always detected via a driver */
    MEMBLOCK_RSRV_NOINIT    = 0x10, /* don't initialize struct pages */
    MEMBLOCK_RSRV_KERN  = 0x20, /* memory reserved for kernel use */
    MEMBLOCK_KHO_SCRATCH    = 0x40, /* scratch memory for kexec handover */
};

위치: include/linux/memblock.h:55-64. MEMBLOCK_NOMAP은 직접 매핑에서 제외되는 RAM을 표시하고, MEMBLOCK_RSRV_KERN은 memblock 할당 API가 예약한 커널용 영역에 붙는다. MEMBLOCK_DRIVER_MANAGEDMEMBLOCK_KHO_SCRATCH는 일반 System RAM과 다르게 취급해야 하는 영역을 구분한다.

전역 초기화 데이터

static struct memblock_region memblock_memory_init_regions[INIT_MEMBLOCK_MEMORY_REGIONS] __initdata_memblock;
static struct memblock_region memblock_reserved_init_regions[INIT_MEMBLOCK_RESERVED_REGIONS] __initdata_memblock;

struct memblock memblock __initdata_memblock = {
    .memory.regions     = memblock_memory_init_regions,
    .memory.max     = INIT_MEMBLOCK_MEMORY_REGIONS,
    .memory.name        = "memory",

    .reserved.regions   = memblock_reserved_init_regions,
    .reserved.max       = INIT_MEMBLOCK_RESERVED_REGIONS,
    .reserved.name      = "reserved",

    .bottom_up      = false,
    .current_limit      = MEMBLOCK_ALLOC_ANYWHERE,
};

위치: mm/memblock.c:123-140. 초기 배열 용량은 INIT_MEMBLOCK_MEMORY_REGIONSINIT_MEMBLOCK_RESERVED_REGIONS로 시작한다. 영역이 더 많이 필요할 때는 memblock_allow_resize() 이후 memblock_double_array()가 새 배열을 잡아 확장한다.

struct memblock_type physmem = {
    .regions        = memblock_physmem_init_regions,
    .max            = INIT_PHYSMEM_REGIONS,
    .name           = "physmem",
};

위치: mm/memblock.c:142-148. CONFIG_HAVE_MEMBLOCK_PHYS_MAP이 있는 아키텍처에서는 physmem이 실제 물리 메모리 목록을 별도로 보존한다. mem= 같은 제한으로 커널 사용 가능 메모리가 줄어도, 펌웨어가 보고한 실제 물리 메모리 관찰점은 physmem 쪽에 남을 수 있다.

핵심 함수

1. memblock_add_range() — 영역 추가의 핵심

// mm/memblock.c:609-713
static int __init_memblock memblock_add_range(struct memblock_type *type,
                phys_addr_t base, phys_addr_t size,
                int nid, enum memblock_flags flags)

역할: 새로운 메모리 영역을 memblock 타입에 추가한다. 기존 영역과 중복될 수 있으며, 중복 시 겹치지 않는 부분만 삽입한다. 추가 후 인접 호환 영역을 자동 병합한다.

분기 로직:

  • 배열이 비어 있으면 첫 번째 영역으로 바로 설정
  • cnt * 2 + 1 <= max이면 바로 삽입 가능하며, 그렇지 않으면 크기 확장 후 반복
  • 기존 영역과 겹치는 부분은 잘라내고 나머지 삽입
  • memblock_double_array()로 배열 크기 2배 확장 가능
  • if (!size)
        return 0;
    
    /* special case for empty array */
    if (type->regions[0].size == 0) {
        WARN_ON(type->cnt != 0 || type->total_size);
        type->regions[0].base = base;
        type->regions[0].size = size;
        type->regions[0].flags = flags;
        memblock_set_region_node(&type->regions[0], nid);
        type->total_size = size;
        type->cnt = 1;
        return 0;
    }
    
    /*
     * The worst case is when new range overlaps all existing regions,
     * then we'll need type->cnt + 1 empty regions in @type. So if
     * type->cnt * 2 + 1 is less than or equal to type->max, we know
     * that there is enough empty regions in @type, and we can insert
     * regions directly.
     */
    if (type->cnt * 2 + 1 <= type->max)
        insert = true;

    위치: mm/memblock.c:619-642. 첫 region을 넣는 경우와 배열 여유가 충분한 경우를 먼저 빠르게 처리한다.

    /* insert the remaining portion */
    if (base < end) {
        nr_new++;
        if (insert) {
            if (start_rgn == -1)
                start_rgn = idx;
            end_rgn = idx + 1;
            memblock_insert_region(type, idx, base, end - base,
                           nid, flags);
        }
    }
    
    if (!nr_new)
        return 0;
    
    /*
     * If this was the first round, resize array and repeat for actual
     * insertions; otherwise, merge and return.
     */
    if (!insert) {
        while (type->cnt + nr_new > type->max)
            if (memblock_double_array(type, obase, size) < 0)
                return -ENOMEM;
        insert = true;
        goto repeat;
    } else {
        memblock_merge_regions(type, start_rgn, end_rgn);
        return 0;
    }

    위치: mm/memblock.c:684-712. 첫 pass는 필요한 region 수를 계산하고, 두 번째 pass는 삽입 후 인접한 같은 속성의 region을 병합한다.

    2. memblock_find_in_range_node() — 영역 검색

    // mm/memblock.c:308-328
    static phys_addr_t __init_memblock memblock_find_in_range_node(
        phys_addr_t size, phys_addr_t align,
        phys_addr_t start, phys_addr_t end, int nid,
        enum memblock_flags flags)

    역할: 지정된 범위와 NUMA 노드에서 요청 크기의 빈 영역을 찾는다.

    분기 로직:

  • end == MEMBLOCK_ALLOC_ACCESSIBLEmemblock.current_limit로 대체
  • start는 최소 PAGE_SIZE (첫 페이지 할당 회피)
  • memblock_bottom_up() → bottom-up / top-down 분기
  • static phys_addr_t __init_memblock memblock_find_in_range_node(phys_addr_t size,
                        phys_addr_t align, phys_addr_t start,
                        phys_addr_t end, int nid,
                        enum memblock_flags flags)
    {
        /* pump up @end */
        if (end == MEMBLOCK_ALLOC_ACCESSIBLE ||
            end == MEMBLOCK_ALLOC_NOLEAKTRACE)
            end = memblock.current_limit;
    
        /* avoid allocating the first page */
        start = max_t(phys_addr_t, start, PAGE_SIZE);
        end = max(start, end);
    
        if (memblock_bottom_up())
            return __memblock_find_range_bottom_up(start, end, size, align,
                                   nid, flags);
        else
            return __memblock_find_range_top_down(start, end, size, align,
                                  nid, flags);
    }

    위치: mm/memblock.c:308-328. MEMBLOCK_ALLOC_NOLEAKTRACE도 접근 가능 상한과 같은 방식으로 current_limit을 사용하지만, 나중에 kmemleak 등록을 건너뛰는 의미가 추가된다.

    3. memblock_alloc_range_nid() — 부팅 메모리 할당

    // mm/memblock.c:1474-1546
    phys_addr_t __init memblock_alloc_range_nid(phys_addr_t size,
                        phys_addr_t align, phys_addr_t start,
                        phys_addr_t end, int nid, bool exact_nid)

    역할: 부팅 중 메모리를 할당하고 물리 주소를 반환한다.

    분기 로직:

  • slab_is_available() → slab 할당자로 fallback
  • 지정 노드에서 할당 실패 시 exact_nid == false → 모든 노드로 확장
  • 미러 메모리 할당 실패 → 미러 플래그 제거 후 재시도
  • 할당 성공 시 kmemleak_alloc_phys()로 누수 추적 제외
  • if (WARN_ON_ONCE(slab_is_available())) {
        void *vaddr = kzalloc_node(size, GFP_NOWAIT, nid);
    
        return vaddr ? virt_to_phys(vaddr) : 0;
    }
    
    if (!align) {
        /* powerpc에서 부팅 초기에는 WARN을 사용할 수 없음 */
        dump_stack();
        align = SMP_CACHE_BYTES;
    }
    
    again:
    found = memblock_find_in_range_node(size, align, start, end, nid,
                        flags);
    if (found && !__memblock_reserve(found, size, nid, MEMBLOCK_RSRV_KERN))
        goto done;
    
    if (numa_valid_node(nid) && !exact_nid) {
        found = memblock_find_in_range_node(size, align, start,
                            end, NUMA_NO_NODE,
                            flags);
        if (found && !memblock_reserve_kern(found, size))
            goto done;
    }
    
    if (flags & MEMBLOCK_MIRROR) {
        flags &= ~MEMBLOCK_MIRROR;
        pr_warn_ratelimited("Could not allocate %pap bytes of mirrored memory\n",
            &size);
        goto again;
    }

    위치: mm/memblock.c:1487-1518. slab 준비 이후의 우발적 호출은 kzalloc_node()로 우회하고, NUMA fallback과 mirror fallback은 같은 검색 함수를 다른 조건으로 다시 호출한다.

    done:
        /*
         * Skip kmemleak for those places like kasan_init() and
         * early_pgtable_alloc() due to high volume.
         */
        if (end != MEMBLOCK_ALLOC_NOLEAKTRACE)
            /*
             * Memblock allocated blocks are never reported as
             * leaks. This is because many of these blocks are
             * only referred via the physical address which is
             * not looked up by kmemleak.
             */
            kmemleak_alloc_phys(found, size, 0);
    
        /*
         * Some Virtual Machine platforms, such as Intel TDX or AMD SEV-SNP,
         * require memory to be accepted before it can be used by the
         * guest.
         *
         * Accept the memory of the allocated buffer.
         */
        accept_memory(found, size);
    
        return found;

    위치: mm/memblock.c:1522-1545. 물리 주소만으로 참조되는 초기 할당은 kmemleak 오탐을 피하고, TDX/SEV-SNP 같은 게스트 환경에서는 반환 전에 메모리 accept를 수행한다.

    4. memblock_alloc_try_nid() — 가상 주소 반환 할당

    // mm/memblock.c:1716-1732
    void * __init memblock_alloc_try_nid(phys_addr_t size, phys_addr_t align,
                         phys_addr_t min_addr, phys_addr_t max_addr, int nid)

    역할: memblock_alloc_range_nid()의 결과를 phys_to_virt()로 변환하여 가상 주소를 반환하고, memset(ptr, 0, size)로 제로잉한다.

    void * __init memblock_alloc_try_nid(
                phys_addr_t size, phys_addr_t align,
                phys_addr_t min_addr, phys_addr_t max_addr,
                int nid)
    {
        void *ptr;
    
        memblock_dbg("%s: %llu bytes align=0x%llx nid=%d from=%pa max_addr=%pa %pS\n",
                 __func__, (u64)size, (u64)align, nid, &min_addr,
                 &max_addr, (void *)_RET_IP_);
        ptr = memblock_alloc_internal(size, align,
                           min_addr, max_addr, nid, false);
        if (ptr)
            memset(ptr, 0, size);
    
        return ptr;
    }

    위치: mm/memblock.c:1716-1732. memblock_alloc_try_nid_raw()와 달리 반환 직전 버퍼를 0으로 채운다.

    5. memblock_free_all() — Buddy Allocator에 반환

    void __init memblock_free_all(void)
    {
        unsigned long pages;
    
        free_unused_memmap();
        reset_all_zones_managed_pages();
    
        memblock_clear_kho_scratch_only();
        pages = free_low_memory_core_early();
        totalram_pages_add(pages);
    }

    역할: 부팅이 완료되면 남은 모든 free 메모리를 Buddy Allocator에 반환한다. free_low_memory_core_early()for_each_free_mem_range()로 순회하며 __free_pages_memory()로 각 영역을 해제한다.

    호출 흐름

    펌웨어 메모리맵 (E820/UEFI/DT)
        │
        ▼
    arch_mem_init() / setup_arch()
        │
        ├── memblock_add(base, size)          ← 물리 메모리 등록
        │       └── memblock_add_range()
        │               ├── memblock_insert_region()
        │               ├── memblock_double_array()   ← 배열 확장
        │               └── memblock_merge_regions()  ← 인접 영역 병합
        │
        ├── memblock_reserve(base, size)      ← 예약 (커널 이미지, initrd 등)
        │       └── __memblock_reserve()
        │               └── memblock_add_range(&memblock.reserved, ...)
        │
        ├── memblock_alloc(size, align)       ← 부팅 중 할당
        │       └── memblock_alloc_try_nid()
        │               └── memblock_alloc_internal()
        │                       └── memblock_alloc_range_nid()
        │                               ├── memblock_find_in_range_node()
        │                               │       ├── __memblock_find_range_top_down()
        │                               │       └── __memblock_find_range_bottom_up()
        │                               └── __memblock_reserve()
        │
        └── memblock_free_all()               ← 부팅 완료 시 Buddy에 반환
                ├── free_unused_memmap()
                ├── reset_all_zones_managed_pages()
                └── free_low_memory_core_early()
                        └── __free_pages_memory()

    펌웨어 입력과 예약 영역

    memblock_add()의 원천은 아키텍처별 펌웨어 메모리맵이다. x86은 BIOS E820 또는 UEFI Memory Map을, ARM64/임베디드 시스템은 Device Tree의 /memory/reserved-memory를, NUMA 서버는 ACPI SRAT/HMAT 같은 노드 정보를 함께 사용한다. 이 단계에서 memory에 들어가는 것은 커널이 일반 RAM으로 사용할 수 있는 범위이고, 장치 MMIO, 펌웨어 런타임 서비스, ACPI 테이블, initrd, crashkernel, CMA 예약 영역은 일반 할당 대상으로 남겨 두면 안 된다.

    reserved는 단순히 "사용 불가" 목록이 아니라 초기 부팅 중 이미 목적이 정해진 물리 주소 목록이다. 커널 이미지와 초기 페이지 테이블은 부팅 자체에 필요하고, initrd는 루트 파일시스템 전개 전까지 보존되어야 하며, crashkernel은 패닉 이후 두 번째 커널이 사용할 수 있어야 한다. Device Tree의 /reserved-memory는 펌웨어, 보안 영역, 디스플레이 버퍼, 원격 프로세서 공유 메모리, CMA처럼 운영 중에도 일반 Buddy 할당과 섞이면 안 되는 영역을 표현한다.

    /proc/iomem은 부팅 이후 물리 주소 공간을 보는 가장 직접적인 관찰점이다. System RAM으로 표시된 범위가 대체로 memblock memory의 출발점이고, 그 하위의 Kernel code, Kernel data, reserved, Crash kernel 같은 항목은 memblock reserved 또는 이후 resource tree에서 예약된 영역과 연결된다. 커널 가상 주소 배치가 궁금할 때는 /proc/meminfoDirectMap, Vmalloc, PageTableskernel_page_tables debugfs를 함께 봐야 한다.

    조건별 비교

    조건할당 방향메모리 타입사용 API비고
    UMA 시스템top-down (기본)memory + reserved`memblock_add()` + `memblock_reserve()`NUMA 노드 미사용
    NUMA 시스템top-down (기본)memory + reserved`memblock_add_node()` + `memblock_set_node()`노드별 영역 관리
    bottom-up 모드bottom-upmemory + reserved`memblock_set_bottom_up(true)` 이후 할당특정 초기화 시 사용
    미러 메모리top-downmemory (MIRROR 플래그)`memblock_mark_mirror()` + 일반 할당미러 할당 실패 시 일반 영역으로 fallback
    KHO scratchbottom-upmemory (KHO_SCRATCH 플래그)`memblock_set_kho_scratch_only()`kexec handover 시 임시 메모리
    nomap 영역top-downmemory (NOMAP 플래그)`memblock_mark_nomap()`직접 매핑에서 제외, struct page는 PageReserved()
    memblock=debug---부팅 시 상세 로그 출력
    slab 사용 가능 시--`kzalloc_node()` fallbackmemblock 초기화 후 slab이 준비되면 자동 전환

    메모리맵 입력별 처리

    입력memblock 처리주의할 점확인 방법
    BIOS E820RAM 범위는 `memblock_add()`, reserved/ACPI/NVS/MMIO는 예약 또는 제외E820의 전체 물리 주소 공간과 커널 직접 매핑 범위는 같은 표가 아님`dmesg \grep BIOS-e820`, `cat /proc/iomem`
    UEFI Memory MapConventional Memory는 사용 후보, Runtime/ACPI/Reserved 타입은 보존EFI runtime 서비스 영역은 나중에도 접근 경로가 필요할 수 있음`dmesg \grep 'efi: mem'`
    Device Tree `/memory`RAM bank를 `memory` region으로 등록bank 사이 hole과 SoC MMIO를 RAM으로 취급하면 안 됨`dmesg \grep -Ei 'OF: fdt\Memory:'`
    Device Tree `/reserved-memory`CMA, framebuffer, secure memory 등을 `reserved` 또는 NOMAP으로 분리드라이버 전용 공유 메모리는 Buddy에 섞이면 데이터 손상 가능`dmesg \grep -Ei 'reserved-memory\cma'`
    ACPI SRAT/HMAT`memblock_add_node()` 또는 `memblock_set_node()`로 NUMA 노드 연결주소가 연속이어도 latency/locality가 다를 수 있음`numactl -H`, `/sys/devices/system/node/`
    `mem=`, `crashkernel=`, `movable_node`사용 가능 상한, crash kernel 예약, hotplug 선호 플래그에 영향`physmem`과 `memory`가 서로 다른 의미를 가질 수 있음`cat /proc/cmdline`, `cat /proc/iomem`

    초기 할당자 비교

    구분memblockBuddy AllocatorSLUB
    사용 시점부팅 초기, 일반 할당자 준비 전Zone과 `struct page` 초기화 이후Buddy 위에서 커널 객체 할당 시
    관리 단위물리 주소 구간 `[base, size)``struct page`와 order별 free list캐시별 object/slab
    대표 API`memblock_add()`, `memblock_reserve()`, `memblock_alloc()``alloc_pages()`, `__free_pages()``kmalloc()`, `kmem_cache_alloc()`
    강점펌웨어 메모리맵과 예약 영역을 단순한 구간 배열로 정리런타임 페이지 할당/해제와 병합 처리작은 객체 반복 할당 최적화
    전환점`memblock_free_all()`에서 남은 free range 반환이후 일반 페이지 할당을 담당slab 준비 후 memblock 우발 호출 fallback 대상

    메모리 할당 API 계층

    함수반환 타입특징
    `memblock_phys_alloc()``phys_addr_t`물리 주소 반환, 제로잉 없음
    `memblock_alloc()``void *`가상 주소 반환, 제로잉 포함
    `memblock_alloc_raw()``void *`가상 주소 반환, 제로잉 없음
    `memblock_alloc_low()``void *`낮은 주소 영역에서 할당
    `memblock_alloc_node()``void *`특정 NUMA 노드에서 할당
    `memblock_alloc_or_panic()``void *`할당 실패 시 panic 발생

    memblock → Buddy 전환

    부팅이 완료되면 memblock_free_all()이 호출되어 모든 남은 페이지를 Buddy Allocator로 넘긴다. 이 과정에서:

    1. free_unused_memmap() — 사용하지 않는 struct page 배열 메모리를 해제

    2. reset_all_zones_managed_pages() — Zone의 managed_pages 카운터 리셋

    3. free_low_memory_core_early()for_each_free_mem_range()로 순회하며 __free_pages_memory()로 각 페이지를 Buddy에 반환

    4. totalram_pages_add() — 전체 RAM 페이지 수 업데이트

    이후 CONFIG_ARCH_KEEP_MEMBLOCK이 없으면 memblock_discard()가 호출되어 memblock 내부 배열을 해제하고, memblock 포인터는 NULL로 설정된다.

    관련 문서

  • 00-overview.html — 메모리 관리 개요
  • 01-page_alloc.html — Buddy Allocator (memblock 이후 단계)
  • 03-vma_mmap.html — VMA / mmap (가상 메모리 관리)
  • 13-numa.html — NUMA (memblock의 NUMA 노드 할당)
  • 22-memory_hotplug.html — Hotplug (MEMBLOCK_HOTPLUG 플래그)
  • SVG 다이어그램

    Memblock 할당자 구조
    Memblock 호출 흐름