전체 흐름
Linux v6.6 arm64 부팅에서 setup_arch()는 bootmem_init()으로 sparsemem과 zone 구성을 먼저 준비한다. 이후 공통 start_kernel()의 mm_core_init()이 buddy에 페이지를 넘기고 slab·vmalloc을 초기화한다. 이 글의 범위는 부팅 중 메모리 할당기 전환이며, 일반적인 페이지 폴트나 reclaim 경로는 다루지 않는다.
bootmem_init()에서 sparsemem·zone 설정memblock_free_all()을 호출해 사용 가능한 페이지를 buddy로 전달kmem_cache_init() 뒤 vmalloc_init()상자는 호출 순서대로 배치했다. 예약 영역까지 모두 buddy에 넘긴다는 뜻은 아니다.
전환점을 확인할 때는 memblock의 free 범위, buddy의 managed-page 수, slab cache와 vmalloc의 준비 시점을 서로 구분한다.
소스 코드 위치
| Linux v6.6 파일 | 이 글에서 확인할 역할 |
|---|---|
| init/main.c | setup_arch() 다음 mm_core_init()이 호출되는 순서 |
| arch/arm64/mm/init.c | bootmem_init()의 zone 설정, arm64 mem_init()의 memblock_free_all() 호출 |
| mm/mm_init.c | mm_core_init()의 allocator 초기화 순서 |
| mm/memblock.c | memblock_free_all()과 free memory range 순회 |
아래 첫 번째 코드 발췌는 공통 mm_core_init()의 앞부분이다. 두 번째 발췌는 이름이 비슷한 mem_init_print_info()로, arm64 mem_init()의 본문이 아니다.
대표 코드
void __init mm_core_init
}
/*
* 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()과 별개
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()을 읽어야 한다.
함수별 분석
mm_core_init() 안에서 달라지는 준비 상태build_all_zonelists() 및 page allocator CPU hotplug 준비mem_init() → memblock_free_all()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 zone | buddy가 관리할 zone과 managed-page 통계 | zone_sizes_init()와 memblock_free_all()의 순서 |
| slab cache | 작은 객체 할당에 쓰는 cache | kmem_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/slabinfo | slab 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()의 앞뒤 어느 곳에 있는가?- 초기 부팅 로그가 없는 경우, 런타임 통계로 확인할 수 있는 사실과 확인할 수 없는 호출 순서는 무엇인가?