Memory · Linux v6.6 · source + diagram note

mm_core_init

page allocator, slab, vmalloc로 넘어가는 관문.

전체 흐름

Linux v6.6 arm64 부팅에서 setup_arch()는 bootmem_init()으로 sparsemem과 zone 구성을 먼저 준비한다. 이후 공통 start_kernel()의 mm_core_init()이 buddy에 페이지를 넘기고 slab·vmalloc을 초기화한다. 이 글의 범위는 부팅 중 메모리 할당기 전환이며, 일반적인 페이지 폴트나 reclaim 경로는 다루지 않는다.

그림 1. arm64 부팅에서 메모리 초기화의 호출 순서
start_kernel() → setup_arch()arm64 memblock·paging 준비 후 bootmem_init()에서 sparsemem·zone 설정
mm_core_init()아키텍처 초기화 뒤 공통 메모리 할당기 초기화 진입
mem_init()arm64 구현이 memblock_free_all()을 호출해 사용 가능한 페이지를 buddy로 전달
slab → vmallockmem_cache_init() 뒤 vmalloc_init()

상자는 호출 순서대로 배치했다. 예약 영역까지 모두 buddy에 넘긴다는 뜻은 아니다.

전환점을 확인할 때는 memblock의 free 범위, buddy의 managed-page 수, slab cache와 vmalloc의 준비 시점을 서로 구분한다.

소스 코드 위치

Linux v6.6 파일이 글에서 확인할 역할
init/main.csetup_arch() 다음 mm_core_init()이 호출되는 순서
arch/arm64/mm/init.cbootmem_init()의 zone 설정, arm64 mem_init()의 memblock_free_all() 호출
mm/mm_init.cmm_core_init()의 allocator 초기화 순서
mm/memblock.cmemblock_free_all()과 free memory range 순회

아래 첫 번째 코드 발췌는 공통 mm_core_init()의 앞부분이다. 두 번째 발췌는 이름이 비슷한 mem_init_print_info()로, arm64 mem_init()의 본문이 아니다.

대표 코드

void __init mm_core_init

원본 코드 발췌: mm/mm_init.c, 2757행 부근 (앞 함수의 닫는 중괄호 포함)
}

/*
 * Set up kernel memory allocators
 */
void __init mm_core_init(void)
{
	/* Initializations relying on SMP setup */
	build_all_zonelists(NULL);
	page_alloc_init_cpuhp();

	/*
	 * page_ext requires contiguous pages,
	 * bigger than MAX_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();
	mem_init();
	mem_init_print_info();
	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. */

읽기: 발췌의 mem_init(), kmem_cache_init(), vmalloc_init() 호출 순서를 확인한다. 이 발췌는 함수 끝까지 담지 않는다.

mem_init_print_info() 발췌 — arm64 mem_init()과 별개

원본 코드 발췌: mm/mm_init.c, 2703행 부근 (앞 함수의 끝과 mem_init_print_info의 앞부분)
		want_init_on_free() ? "on" : "off");
	if (want_init_on_free())
		pr_info("mem auto-init: clearing system memory may take some time...\n");
}

static void __init mem_init_print_info(void)
{
	unsigned long physpages, codesize, datasize, rosize, bss_size;
	unsigned long init_code_size, init_data_size;

	physpages = get_num_physpages();
	codesize = _etext - _stext;
	datasize = _edata - _sdata;
	rosize = __end_rodata - __start_rodata;
	bss_size = __bss_stop - __bss_start;
	init_data_size = __init_end - __init_begin;
	init_code_size = _einittext - _sinittext;

	/*
	 * Detect special cases and adjust section sizes accordingly:
	 * 1) .init.* may be embedded into .data sections
	 * 2) .init.text.* may be out of [__init_begin, __init_end],
	 *    please refer to arch/tile/kernel/vmlinux.lds.S.
	 * 3) .rodata.* may be embedded into .text or .data sections.
	 */
#define adj_init_size(start, end, size, pos, adj) \
	do { \
		if (&start[0] <= &pos[0] && &pos[0] < &end[0] && size > adj) \
			size -= adj; \
	} while (0)

	adj_init_size(__init_begin, __init_end, init_data_size,
		     _sinittext, init_code_size);
	adj_init_size(_stext, _etext, codesize, _sinittext, init_code_size);
	adj_init_size(_sdata, _edata, datasize, __init_begin, init_data_size);
	adj_init_size(_stext, _etext, codesize, __start_rodata, rosize);
	adj_init_size(_sdata, _edata, datasize, __start_rodata, rosize);

#undef	adj_init_size

읽기: 화면에 보이는 함수는 메모리 수치를 출력하기 위한 mem_init_print_info()다. buddy 공개를 설명하려면 위의 arm64 mem_init()과 공통 memblock_free_all()을 읽어야 한다.

함수별 분석

그림 2. mm_core_init() 안에서 달라지는 준비 상태
zone 목록build_all_zonelists() 및 page allocator CPU hotplug 준비
buddy 공개arm64 mem_init() → memblock_free_all()
slab 준비kmem_cache_init()은 mem_init() 뒤에 호출
가상 할당기뒤이어 vmalloc_init(); 구성별 후속 초기화는 코드에서 확인

그림은 주요 호출만 발췌했다. KFENCE, KMSAN, page_ext, debug object 등 구성별 호출도 실제 함수에 있다.

함수선행 조건·입력확인할 결과
bootmem_init() (arm64)memblock에 등록된 메모리·예약 정보sparsemem 및 zone 크기 준비. buddy에 free page를 내보내는 단계와 구분한다.
mm_core_init() (공통)아키텍처의 초기 메모리 구성mem_init(), kmem_cache_init(), vmalloc_init()의 소스 순서를 확인한다.
mem_init() (arm64)memblock의 사용 가능·예약 범위memblock_free_all()이 free 범위만 buddy로 넘긴다.

구조체와 필드

주소 공간 그림보다 초기화 중 메모리를 대표하는 자료구조가 중요하다. 같은 물리 메모리를 가리켜도 각 구조체가 답하는 질문은 다르다.

자료구조초기화 중 의미확인 지점
memblock.memory / reserved초기 물리 메모리와 예약 범위free range 순회가 reserved를 제외하는지
struct page memmap각 page frame의 메타데이터arm64 bootmem_init()의 sparse 초기화
struct zonebuddy가 관리할 zone과 managed-page 통계zone_sizes_init()와 memblock_free_all()의 순서
slab cache작은 객체 할당에 쓰는 cachekmem_cache_init() 호출 시점
vmalloc 주소 공간가상 연속 매핑의 관리 상태vmalloc_init() 호출 시점

실행 단계와 상태 변화

단계소스에서 확인되는 변화아직 성립하지 않는 가정
arm64 bootmem_init() 뒤sparse_init(), zone_sizes_init()이 단계만으로 모든 free page가 buddy에 들어간 것은 아니다.
mm_core_init() 진입zonelist와 일부 page allocator 보조 초기화slab·vmalloc 준비를 앞당겨 가정하지 않는다.
arm64 mem_init() 뒤memblock_free_all()을 통해 사용 가능 범위를 buddy에 반환예약 메모리까지 free되었다고 보지 않는다.
kmem_cache_init() 뒤slab 핵심 cache 초기화뒤의 vmalloc_init() 전 가상 할당기 상태와 구분한다.
vmalloc_init() 뒤vmalloc 초기화 완료이 표가 모든 MM 하위 기능의 완료를 뜻하지 않는다.

불변 조건과 실패 사례

예약 범위 보존

memblock_free_all()의 free-range 순회는 memblock.reserved와 구분된다. 장치 트리의 reserved-memory, 커널 이미지 등 실제 예약 원인을 먼저 확인한다.

초기화 순서

v6.6 mm_core_init()은 mem_init() 다음에 kmem_cache_init(), 뒤에 vmalloc_init()을 호출한다. slab·vmalloc 사용 가능 여부를 이전 단계에 소급하지 않는다.

아키텍처 경계

mem_init()의 구현은 아키텍처별이다. 여기서 기술한 memblock_free_all() 연결은 arm64 v6.6 기준이다.

계측의 한계

부팅 뒤의 page allocation trace만으로 mm_core_init()의 모든 내부 호출을 재구성할 수 없다. 부팅 로그와 고정 소스를 함께 대조한다.

계측과 검증

소스의 호출 순서를 기준으로 먼저 보드의 메모리 지도와 부팅 로그를 대조한다. 부팅 뒤 확인 가능한 숫자는 초기화가 진행된 결과의 단서이지, 각 함수가 실제로 실행된 순서의 직접 추적은 아니다.

관찰 자료검증할 질문한계
memblock=debug 부팅 로그초기 메모리·예약 범위가 예상과 일치하는가?옵션·로그 보존 상태에 따라 보이지 않을 수 있다.
/proc/zoneinfo부팅 후 zone의 managed/free 수치가 예상과 맞는가?실행 중 할당으로 값이 바뀐다.
/proc/slabinfoslab cache가 구성되었는가?kmem_cache_init()의 세부 호출 순서를 보여주지 않는다.
trace_mm_page_alloc런타임 buddy 할당을 관찰할 수 있는가?초기 부팅의 memblock→buddy 전환 전체를 재현하지 못한다.

추가 확인 사항

  • 현재 보드의 memblock.reserved 범위 중 어떤 것이 memblock_free_all() 단계에서도 유지되는가?
  • arm64 bootmem_init()의 zone 크기 구성과 mem_init()의 buddy 공개 사이에서 무엇이 달라지는가?
  • mm_core_init()에서 설정별 보조 초기화는 mem_init()의 앞뒤 어느 곳에 있는가?
  • 초기 부팅 로그가 없는 경우, 런타임 통계로 확인할 수 있는 사실과 확인할 수 없는 호출 순서는 무엇인가?