Memory · Linux 6.18.37 LTS · source + diagram note

mm_core_init

최신 LTS 기준: kernel.org longterm 6.18.37 기준으로 파일 위치와 링크를 맞춘 원본 코드 분석 노트입니다. page allocator, slab, vmalloc로 넘어가는 관문.

전체 흐름

arm64에서는 bootmem_init()의 zone_sizes_init()가 free_area_init()을 호출해 zone 경계와 node 구조를 먼저 준비한다. 이것만으로 자유 페이지가 buddy에 넘어간 것은 아니다. 이후 공통 mm_core_init() 안에서 memblock_free_all()이 예약되지 않은 메모리 범위를 buddy에 넘긴다.

그림 1. zone 구성과 자유 페이지 이관은 다른 시점이다
arm64 bootmem_init에서 zone을 먼저 구성하고, mm_core_init의 memblock_free_all에서 미예약 범위만 buddy로 이관한 뒤 arm64 mem_init, slab, vmalloc 순서로 준비되는 초기화 도해

화살표는 호출·준비 순서다. free_area_init()은 zone 경계와 메타데이터를 마련하고, memblock_free_all()은 for_each_free_mem_range로 미예약 범위만 골라 buddy 이관을 시도한다. 개별 페이지는 deferred struct-page 초기화나 KMSAN 조건에서 즉시 넣지 않을 수 있다. 그림 원본 크게 열기.

근거: init/main.c:931–970, arch/arm64/kernel/setup.c:334–344, arch/arm64/mm/init.c:292–322, mm/mm_init.c:1824–1910, mm/mm_init.c:2483–2501, mm/mm_init.c:2679–2729, mm/memblock.c:2291–2349.

소스 코드 위치

아래 순서는 arm64의 zone 준비와 공통 MM 초기화 경계를 함께 읽기 위한 것이다. mm/mm_init.c의 빈 weak mem_init()과 arm64의 실제 구현을 혼동하면 buddy 이관 시점을 잘못 읽게 된다.

대표 코드

arm64의 이른 zone 준비

고정 원문: arch/arm64/mm/init.c:292–322 · v6.18.37
void __init bootmem_init(void)
{
	unsigned long min, max;

	min = PFN_UP(memblock_start_of_DRAM());
	max = PFN_DOWN(memblock_end_of_DRAM());

	early_memtest(min << PAGE_SHIFT, max << PAGE_SHIFT);

	max_pfn = max_low_pfn = max;
	min_low_pfn = min;

	arch_numa_init();

	/*
	 * must be done after arch_numa_init() which calls numa_init() to
	 * initialize node_online_map that gets used in hugetlb_cma_reserve()
	 * while allocating required CMA size across online nodes.
	 */
#if defined(CONFIG_HUGETLB_PAGE) && defined(CONFIG_CMA)
	arm64_hugetlb_cma_reserve();
#endif

	kvm_hyp_reserve();

	/*
	 * sparse_init() tries to allocate memory from memblock, so must be
	 * done after the fixed reservations
	 */
	sparse_init();
	zone_sizes_init();

bootmem_init()은 sparse_init() 다음에 zone_sizes_init()을 호출한다. 이 발췌는 함수 앞부분이며 뒤의 예약 처리까지 포함한 전체 함수가 아니다.

mm_core_init의 실제 호출 순서

고정 원문: mm/mm_init.c:2679–2729 · v6.18.37 · 사이트 원문
void __init mm_core_init(void)
{
	arch_mm_preinit();
	hugetlb_bootmem_alloc();

	/* Initializations relying on SMP setup */
	BUILD_BUG_ON(MAX_ZONELISTS > 2);
	build_all_zonelists(NULL);
	page_alloc_init_cpuhp();
	alloc_tag_sec_init();
	/*
	 * page_ext requires contiguous pages,
	 * bigger than MAX_PAGE_ORDER unless SPARSEMEM.
	 */
	page_ext_init_flatmem();
	mem_debugging_and_hardening_init();
	kfence_alloc_pool_and_metadata();
	report_meminit();
	kmsan_init_shadow();
	stack_depot_early_init();

	/*
	 * KHO memory setup must happen while memblock is still active, but
	 * as close as possible to buddy initialization
	 */
	kho_memory_init();

	memblock_free_all();
	mem_init();
	kmem_cache_init();
	/*
	 * page_owner must be initialized after buddy is ready, and also after
	 * slab is ready so that stack_depot_init() works properly
	 */
	page_ext_init_flatmem_late();
	kmemleak_init();
	ptlock_cache_init();
	pgtable_cache_init();
	debug_objects_mem_init();
	vmalloc_init();
	/* If no deferred init page_ext now, as vmap is fully initialized */
	if (!deferred_struct_pages)
		page_ext_init();
	/* Should be run before the first non-init thread is created */
	init_espfix_bsp();
	/* Should be run after espfix64 is set up. */
	pti_init();
	kmsan_init_runtime();
	mm_cache_init();
	execmem_init();
}

kho_memory_init()은 memblock이 살아 있을 때 실행되고, memblock_free_all() 다음에 architecture mem_init(), slab, vmalloc이 순서대로 온다.

buddy로 넘기는 위치

고정 원문: mm/memblock.c:2339–2349 · v6.18.37 · 사이트 원문
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_low_memory_core_early()는 예약되지 않은 memblock 범위를 순회하며 __free_memory_core()를 통해 buddy에 넣는다. 모든 물리 메모리를 무조건 free하지 않는다.

arm64 mem_init의 실제 내용

고정 원문: arch/arm64/mm/init.c:388–392 · v6.18.37
void __init mem_init(void)
{
	page_alloc_available = true;
	swiotlb_update_mem_attributes();
}

arm64 구현은 page_alloc_available를 켜고 SWIOTLB 메모리 속성을 갱신한다. zone 구성이나 자유 페이지의 buddy 이관 자체를 이 함수에서 처음 수행하지 않는다.

함수별 분석

1. 먼저 zone을 기술한다. arm64 bootmem_init()은 zone_sizes_init()으로 DMA·DMA32·Normal 경계를 정하고 공통 free_area_init()에 전달한다. 이때 zone 구조가 준비되지만 미예약 페이지의 buddy 이관은 뒤에 온다. 근거: arch/arm64/mm/init.c:121–153, arch/arm64/mm/init.c:292–322.

2. memblock이 가진 미예약 범위를 넘긴다. mm_core_init()은 kho_memory_init() 뒤 memblock_free_all()을 호출한다. 여기서 free_unused_memmap()과 managed-page 계수 초기화 뒤, for_each_free_mem_range가 고른 범위를 memblock_free_pages()에 전달한다. 예약 범위까지 전부 free하는 호출이 아니다. 근거: mm/mm_init.c:2700–2708, mm/memblock.c:2291–2349, mm/memblock.c:2199–2239.

3. architecture와 상위 allocator를 준비한다. 바로 다음 arm64 mem_init()은 page_alloc_available을 설정하고 swiotlb_update_mem_attributes()를 실행한다. 이어 kmem_cache_init()가 slab을 준비하고, 여러 객체 초기화 뒤 vmalloc_init()이 호출된다. 근거: arch/arm64/mm/init.c:386–392, mm/mm_init.c:2706–2718.

따라서 이 글에서 mem_init()은 페이지를 buddy에 넣는 함수가 아니다. 공통 파일에는 빈 weak 구현도 있지만, arm64 빌드에서는 architecture 구현을 확인해야 한다.

구조체와 상태의 소유권

각 이름이 가리키는 자료와 책임
이름이 단계에서 하는 일혼동하기 쉬운 점
memblock.memory / reserved초기 물리 메모리 범위와 예약 범위를 기록한다.memblock_free_all()은 미예약 free 범위만 순회한다.
pgdat / zonePFN 경계와 node·zone의 초기 구조를 담는다.구조가 존재한다고 buddy free list에 페이지가 찬 것은 아니다.
buddy free areamemblock_free_pages() 경로로 넘겨받은 페이지를 할당 후보로 관리한다.예약된 영역은 이 단계의 일반 할당 후보가 아니다.
page_alloc_availablearm64 mem_init()에서 true로 설정하는 architecture 상태다.이 flag 설정이 자유 페이지를 이관하는 호출은 아니다.

근거: arch/arm64/mm/init.c:121–153, mm/mm_init.c:1884–1910, mm/memblock.c:2291–2349, arch/arm64/mm/init.c:386–392.

실행 단계와 확인 가능한 상태

아래는 단일 객체의 상태 기계가 아니라, 부트 중 각 하위 시스템이 준비되는 순서다. memblock 장부가 memblock_free_all() 직후 완전히 폐기된다고 해석해서는 안 된다.

arm64에서 구분할 초기화 경계
순서호출확인되는 변화아직 뜻하지 않는 것
① 이른 준비bootmem_init() → zone_sizes_init() → free_area_init()node·zone 경계 및 초기 메타데이터.모든 미예약 페이지가 buddy에 들어간 상태.
② 이관 직전mm_core_init() → kho_memory_init()KHO 작업은 memblock이 활성인 동안 수행.memblock의 미예약 범위를 이미 모두 free한 상태.
③ 자유 페이지 이관memblock_free_all()미예약 범위를 buddy 경로로 넘기고 totalram_pages를 갱신. 개별 페이지의 지연 초기화·KMSAN 예외는 별도.예약 범위까지 모두 free하거나 memblock 장부를 즉시 폐기.
④ architecture 후처리arm64 mem_init()page_alloc_available = true, SWIOTLB 속성 갱신.이 시점에야 처음 zone을 만들거나 free list를 채움.
⑤ 상위 allocatorkmem_cache_init(), 뒤이어 vmalloc_init()slab 이후 vmalloc 계층까지 차례로 준비.두 함수가 연달아 즉시 호출됨. 사이에 다른 초기화가 있다.

소스 좌표: arch/arm64/mm/init.c:292–322, mm/mm_init.c:2483–2501, mm/mm_init.c:2679–2729, mm/memblock.c:2339–2349, arch/arm64/mm/init.c:388–392.

읽을 때 지킬 경계

zone 구조와 자유 페이지

free_area_init()의 zone 준비와 memblock_free_all()의 buddy 이관은 별도 시점이다. 구조가 마련된 것만으로 할당 가능한 free page 수를 추론하지 않는다.

예약 범위

for_each_free_mem_range가 순회한 미예약 범위만 이관한다. 커널 이미지, 펌웨어·장치용 예약 등을 모두 일반 buddy 페이지로 간주하지 않는다.

architecture override

공통 mm/mm_init.c의 mem_init()은 빈 weak 함수다. arm64의 구현을 열어 실제 작업을 확인한다.

상위 allocator

kmem_cache_init()와 vmalloc_init()은 buddy 이관 이후에 호출된다. 모든 초기화 단계에서 동일한 kmalloc·vmalloc 사용을 가정하지 않는다.

계측과 검증

부트 초기는 일반 추적 도구 자체가 아직 준비되지 않았을 수 있다. 단계 중단을 재현할 때는 해당 소스의 호출 전후 로그와 설정을 먼저 대조하고, 부트 이후 도구는 결과 상태를 확인하는 용도로 쓴다.

관찰 수단확인할 내용한계
memblock=debug이른 예약·할당 기록과 미예약 범위의 해석.buddy 이관 뒤 slab·vmalloc 준비 완료를 단독으로 증명하지 않는다.
부트 로그의 Memory: 정보자유 페이지와 전체 RAM 보고를 이관 이후의 관찰값으로 확인.개별 예약 범위의 소유권을 전부 설명하지 않는다.
/proc/zoneinfo, /proc/buddyinfo부트 완료 뒤 zone별 managed/free 수와 buddy order 분포.부트 중 각 함수가 정확히 실행된 시각의 trace가 아니다.
page_owner, slabinfo설정된 경우, 이후 할당 출처와 slab cache 상태.그 자체로 mm_core_init()의 중간 단계 정지를 판별하지 않는다.

추가 확인 사항

  • free_area_init()이 만든 zone과 memblock_free_all()이 넘긴 자유 페이지를 어떤 값으로 구분할 수 있는가?
  • for_each_free_mem_range에 포함되지 않는 예약 범위는 무엇이며, 그 예약을 누가 해제하는가?
  • arm64 mem_init()의 page_alloc_available 설정과 swiotlb_update_mem_attributes()는 buddy 이관 이후 어떤 의미가 있는가?
  • kmem_cache_init()와 vmalloc_init() 사이의 초기화 호출을 원문에서 확인하고, 어느 지점부터 각 allocator의 사용을 가정할 수 있는가?