전체 흐름
kernel_init_freeable()은 PID 1의 kernel_init()에서 kthreadd_done 완료를 기다린 다음 호출된다. Linux v6.6의 이 함수는 CPU·workqueue·메모리·초기화 호출을 단계별로 진행하고, initramfs 대기와 rootfs 콘솔 열기 시도를 거친 뒤 초기 사용자 공간 경로의 접근 가능 여부에 따라 네임스페이스 준비를 분기한다. 함수 이름의 freeable은 함수 자체가 메모리를 해제한다는 뜻이 아니다. 초기화 메모리 해제는 함수가 돌아온 다음 kernel_init()에 있다.
kernel_init()wait_for_completion(&kthreadd_done)kernel_init_freeable()초기 CPU·서브시스템·rootfs 경로 준비async_synchronize_full()free_initmem()은 kernel_init() 안에서 실행소스의 호출 순서를 요약했다. 3·4단계는 이 함수의 내부가 아니라 호출자인 kernel_init()의 후속 작업이며, 그 사이에 상태 변경과 관련 정리가 있다.
이 함수의 마지막 호출은 integrity_load_keys()다. 이 반환만으로 사용자 공간 init 프로세스가 이미 실행됐거나 initmem이 이미 해제됐다고 볼 수 없다.
소스 코드 위치
기준 파일은 Linux v6.6 init/main.c:1518–1573이다. 이 함수가 호출하는 do_basic_setup()의 본문은 1323–1330, 함수 바깥 수명 경계는 kernel_init():1428–1447에서 확인한다.
아래 그림의 순서는 한 CPU에서 진행되는 소스의 호출 순서다. 각 초기화 함수의 내부 완료 조건이나 다른 CPU의 관찰 시점까지 보장하는 타임라인은 아니다.
init_eaccess(ramdisk_execute_command)의 결과에 따라 prepare_namespace() 호출 여부가 갈린다. 접근 성공 경로에도 뒤의 integrity_load_keys()는 실행된다.
대표 코드
두 발췌는 Linux v6.6 init/main.c의 실제 함수 경계를 그대로 사용했다. 행 번호는 파일의 실제 소스 행을 가리킨다.
kernel_init_freeable() 전체 함수
원본 코드: init/main.c:1518–1573
static noinline void __init kernel_init_freeable(void)
{
/* Now the scheduler is fully set up and can do blocking allocations */
gfp_allowed_mask = __GFP_BITS_MASK;
/*
* init can allocate pages on any node
*/
set_mems_allowed(node_states[N_MEMORY]);
cad_pid = get_pid(task_pid(current));
smp_prepare_cpus(setup_max_cpus);
workqueue_init();
init_mm_internals();
rcu_init_tasks_generic();
do_pre_smp_initcalls();
lockup_detector_init();
smp_init();
sched_init_smp();
workqueue_init_topology();
padata_init();
page_alloc_init_late();
do_basic_setup();
kunit_run_all_tests();
wait_for_initramfs();
console_on_rootfs();
/*
* check if there is an early userspace init. If yes, let it do all
* the work
*/
if (init_eaccess(ramdisk_execute_command) != 0) {
ramdisk_execute_command = NULL;
prepare_namespace();
}
/*
* Ok, we have completed the initial bootup, and
* we're essentially up and running. Get rid of the
* initmem segments and start the user-mode stuff..
*
* rootfs is available now, try loading the public keys
* and default modules
*/
integrity_load_keys();
}
do_basic_setup() 전체 함수
원본 코드: init/main.c:1323–1330
static void __init do_basic_setup(void)
{
cpuset_init_smp();
driver_init();
init_irq_proc();
do_ctors();
do_initcalls();
}
kernel_init_freeable() 안에 free_initmem()은 없다. 호출자 kernel_init()이 먼저 async_synchronize_full()로 비동기 __init 작업의 종료를 기다리고, 상태 변경·관련 정리를 거쳐 free_initmem()을 호출한다.
함수별 분석
첫 묶음에서 gfp_allowed_mask를 넓히고 PID 1의 노드 메모리 허용 집합과 cad_pid를 정한다. 이어 CPU와 workqueue 기반을 준비하고, do_pre_smp_initcalls()을 실행한 다음 smp_init()과 sched_init_smp()을 거쳐 do_basic_setup()에 도달한다.
smp_prepare_cpus(), workqueue_init()do_pre_smp_initcalls() · 별도 호출smp_init() → sched_init_smp()do_basic_setup() 안의 do_initcalls()do_pre_smp_initcalls()과 do_initcalls()은 서로 다른 위치에서 호출된다. 후자는 do_basic_setup()의 마지막 호출이다.
| 소스 순서 | 호출 | 이 글에서 확인할 결과 |
|---|---|---|
| 1530–1538 | smp_prepare_cpus()부터 do_pre_smp_initcalls()까지 | CPU 준비와 초기 호출이 smp_init()보다 앞에 있다. |
| 1540–1541 | smp_init(), sched_init_smp() | SMP 설정과 스케줄러 SMP 설정이 차례로 호출된다. |
| 1543–1547 | workqueue topology, padata, page allocator late 단계, do_basic_setup() | do_basic_setup() 전에 놓인 후속 준비 호출을 빠뜨리지 않는다. |
| 1323–1330 | do_basic_setup() 내부 | cpuset, driver, IRQ proc, constructors 이후 do_initcalls() 호출. |
상태와 경계
이 함수는 단일 구조체의 수명 관리보다 여러 전역 상태와 부팅 단계의 경계를 다룬다. cad_pid는 현재 PID 1의 PID 참조를 얻지만, 이것을 initmem 해제나 사용자 공간 실행과 혼동하면 안 된다.
init_eaccess(ramdisk_execute_command) == 0초기 사용자 공간 init 경로에 접근할 수 있다. 이 조건문에서는 prepare_namespace()를 호출하지 않고 뒤의 integrity_load_keys()로 간다.
init_eaccess(ramdisk_execute_command) != 0ramdisk_execute_command = NULL로 바꾼 뒤 prepare_namespace()를 호출한다. 이후 두 경로가 합류한다.
분기는 접근 검사 결과에 대한 init/main.c의 조건이다. prepare_namespace() 내부의 마운트 세부 동작은 이 파일만으로 단정하지 않는다.
| 대상 | 함수 안의 사용 지점 | 읽을 때 구분할 점 |
|---|---|---|
gfp_allowed_mask | 1521행에서 __GFP_BITS_MASK로 설정 | 이후 초기화 단계의 할당 허용 범위를 바꾸는 코드. |
cad_pid | 1528행에서 현재 태스크의 PID 참조를 획득 | PID 1의 부팅 단계에서 이뤄지는 참조 설정. |
ramdisk_execute_command | 1558–1560행에서 접근 검사와 조건부 NULL 설정 | 초기 사용자 공간 init 경로 접근 성공 여부로 분기. |
| initmem | 이 함수 내부에는 해제 호출이 없음 | kernel_init_freeable() 복귀 후 호출자 kernel_init()의 동기화와 해제를 별도로 본다. |
실행 단계와 상태 변화
do_basic_setup() 이후에도 이 함수는 끝나지 않는다. kunit_run_all_tests() 호출, initramfs 대기, rootfs 콘솔 열기 시도, 초기 init 경로 접근 검사, 무결성 키 로드를 차례로 지난다. 사용자 공간 init 실행은 이 함수가 반환한 뒤의 kernel_init() 경로에 남아 있다.
do_basic_setup()wait_for_initramfs()console_on_rootfs()init_eaccess() 분기integrity_load_keys()async_synchronize_full()free_initmem()init 실행 시도는 그 이후두 번째 행은 kernel_init()의 순서만 요약한다. 첫 행의 마지막 칸이 두 번째 행의 첫 칸보다 앞선다.
| 경계 | 소스 위치 | 보장하거나 아직 보장하지 않는 것 |
|---|---|---|
do_basic_setup() 반환 | 1547행 호출 복귀 뒤 | 이 호출은 끝났지만 initramfs 대기·콘솔·분기·키 로드는 아직 뒤에 있다. |
kernel_init_freeable() 반환 | 1573행 | 함수 내부 순서를 마쳤다. initmem 해제와 사용자 공간 init 실행은 아직 이 함수 밖이다. |
| 비동기 초기화 동기화 | 1438–1439행 | kernel_init()이 비동기 __init 작업 종료를 기다린다. |
| initmem 해제 | 1441–1446행 | 호출자에서 상태를 바꾸고 관련 정리를 거쳐 free_initmem() 호출. |
불변 조건과 오해
do_pre_smp_initcalls()은 smp_init()보다 앞에 있고, do_initcalls()은 do_basic_setup() 안에서 뒤에 호출된다.
prepare_namespace()는 이 함수에서 무조건 실행되는 단계가 아니다. 초기 init 경로에 접근할 수 없을 때만 호출된다.
kernel_init_freeable() 반환 뒤에 비동기 초기화 동기화가 있고 그 다음에 free_initmem()이 있다.
이 함수는 직접 kernel_execve()를 호출하지 않는다. 사용자 공간 init 실행 시도는 호출자의 나중 경로다.
소스 대조 방법
이 글의 검증은 소스 순서 검토다. 특정 기기에서 부팅을 실행하거나 trace를 측정한 결과로 해석하지 않는다. 원인을 좁힐 때는 로그 한 줄을 완료 신호로 보기보다 다음 경계의 전후 호출을 함께 확인한다.
| 궁금한 지점 | 먼저 볼 소스 | 확인할 구분 |
|---|---|---|
| PID 1이 언제 시작하는가 | rest_init()의 생성과 kernel_init():1428–1437 | 태스크 생성, 완료 대기, 이 함수 호출은 서로 다른 시점. |
| 초기화 호출이 어디서 갈리는가 | do_pre_smp_initcalls():1332–1339, do_basic_setup():1323–1330 | pre-SMP 호출과 do_initcalls() 호출을 분리. |
왜 prepare_namespace()가 보이지 않는가 | kernel_init_freeable():1554–1561 | init_eaccess() 결과에 따른 조건부 호출. |
| initmem은 언제 해제되는가 | kernel_init():1437–1446 | 이 함수가 반환한 뒤 비동기 초기화 동기화가 먼저 수행됨. |
다음에 확인할 질문
do_pre_smp_initcalls()가 순회하는 범위와do_initcalls()의 범위는 소스에서 어떻게 구분되는가?- 초기 사용자 공간 init 경로에 접근할 수 있는 경우와 없는 경우,
kernel_init_freeable()의 공통 후속 호출은 무엇인가? free_initmem()전에 실행하는async_synchronize_full()은 어떤 수명 경계를 지키는가?