전체 흐름
arm64에서는 bootmem_init()의 zone_sizes_init()가 free_area_init()을 호출해 zone 경계와 node 구조를 먼저 준비한다. 이것만으로 자유 페이지가 buddy에 넘어간 것은 아니다. 이후 공통 mm_core_init() 안에서 memblock_free_all()이 예약되지 않은 메모리 범위를 buddy에 넘긴다.
화살표는 호출·준비 순서다. 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 이관 시점을 잘못 읽게 된다.
init/main.c:931–970—setup_arch()가mm_core_init()보다 먼저 호출됨. 사이트 내 고정 원문.arch/arm64/mm/init.c:292–322—bootmem_init()에서sparse_init()뒤zone_sizes_init().mm/mm_init.c:1824–1910—free_area_init()이 memblock 범위로 zone 경계를 계산하고 node별 초기화를 수행.mm/mm_init.c:2679–2729—mm_core_init()의 실제 호출 순서. 사이트 내 고정 원문.mm/memblock.c:2291–2349— 미예약 범위의 buddy 이관. 사이트 내 고정 원문.arch/arm64/mm/init.c:388–392— arm64mem_init()의 두 작업.
대표 코드
arm64의 이른 zone 준비
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의 실제 호출 순서
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로 넘기는 위치
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의 실제 내용
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 / zone | PFN 경계와 node·zone의 초기 구조를 담는다. | 구조가 존재한다고 buddy free list에 페이지가 찬 것은 아니다. |
| buddy free area | memblock_free_pages() 경로로 넘겨받은 페이지를 할당 후보로 관리한다. | 예약된 영역은 이 단계의 일반 할당 후보가 아니다. |
page_alloc_available | arm64 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() 직후 완전히 폐기된다고 해석해서는 안 된다.
| 순서 | 호출 | 확인되는 변화 | 아직 뜻하지 않는 것 |
|---|---|---|---|
| ① 이른 준비 | 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를 채움. |
| ⑤ 상위 allocator | kmem_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.
읽을 때 지킬 경계
free_area_init()의 zone 준비와 memblock_free_all()의 buddy 이관은 별도 시점이다. 구조가 마련된 것만으로 할당 가능한 free page 수를 추론하지 않는다.
for_each_free_mem_range가 순회한 미예약 범위만 이관한다. 커널 이미지, 펌웨어·장치용 예약 등을 모두 일반 buddy 페이지로 간주하지 않는다.
공통 mm/mm_init.c의 mem_init()은 빈 weak 함수다. arm64의 구현을 열어 실제 작업을 확인한다.
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의 사용을 가정할 수 있는가?