이 코드는 어떤 문제를 푸나요?
init 태스크가 시스템을 사용자 공간으로 넘기기 전에 CPU와 드라이버, 루트 파일 시스템을 준비합니다. 함수 이름의 freeable은 이 단계의 초기화 코드를 나중에 회수할 수 있다는 뜻입니다. initramfs 준비처럼 다른 작업이 끝나기를 기다리는 단계도 포함합니다.
읽을 범위: v6.18.37 · init/main.c · kernel_init_freeable 1563–1622행입니다. 아래에 이 범위의 원문과 각 줄의 설명을 실었습니다. 주제 전체의 흐름과 다른 경로는 기존 분석에서 함께 읽으실 수 있습니다.
먼저 알아둘 개념
initcall
커널에 등록된 초기화 함수들을 정해진 단계에 따라 실행하는 체계입니다.
initramfs
초기 사용자 공간과 루트 파일들을 제공하는 메모리 기반 파일 시스템입니다. 내용을 준비한 뒤 프로그램 접근을 확인해야 합니다.
처음 읽을 때
코드를 읽을 때 사용할 CPU와 메모리 노드, 작업 큐를 준비합니다. 그다음 어느 상태가 바뀌는지 각 줄에서 확인하세요.
더 깊이 살펴볼 때
이 경로의 호출 문맥, 잠금·인터럽트 상태, 오류 시 남는 자원을 함께 추적해 보세요. 호출된 함수가 수행하는 작업과 현재 함수가 직접 보장하는 범위를 구분하는 것이 중요합니다.
그림으로 보는 변화

1. 실행 자원 준비
사용할 CPU와 메모리 노드, 작업 큐를 준비합니다.
위에서 아래로 초기화 순서를 보여 줍니다.
2. 후속 초기화
SMP와 initcall을 실행하여 장치·서비스를 준비합니다.
실제 드라이버 초기화는 여러 함수에 나뉩니다.
3. 사용자 공간 준비
initramfs 완료를 기다리고 init 실행 경로를 확인합니다.
프로그램 실행 직전의 준비이며 이 함수 자체가 exec를 끝내지는 않습니다.
kernel_init_freeable를 한 줄씩 읽기
줄 번호는 v6.18.37 원문 기준입니다. 주석·빈 줄을 포함한 함수 전체를 먼저 보고, 그 아래에서 각 줄을 설명합니다.
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();
do_pre_smp_initcalls();
lockup_detector_init();
smp_init();
sched_init_smp();
workqueue_init_topology();
async_init();
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
*/
int ramdisk_command_access;
ramdisk_command_access = init_eaccess(ramdisk_execute_command);
if (ramdisk_command_access != 0) {
pr_warn("check access for rdinit=%s failed: %i, ignoring\n",
ramdisk_execute_command, ramdisk_command_access);
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();
}static noinline void __init kernel_init_freeable(void)init 태스크에서 SMP와 장치·초기 루트 파일 시스템 준비를 진행합니다. __init으로 묶인 이 코드의 메모리는 호출 즉시가 아니라 초기화 완료 뒤 별도 단계에서 회수할 수 있습니다.
gfp_allowed_mask = __GFP_BITS_MASK;이후 할당에서 사용할 수 있는 GFP 동작의 제한을 풀어 정상 할당 정책을 허용합니다.
set_mems_allowed(node_states[N_MEMORY]);init 태스크가 사용할 수 있는 메모리 노드 집합을 넓힙니다.
cad_pid = get_pid(task_pid(current));PID 객체의 참조를 얻어 이후에도 유효한 대상으로 유지합니다.
smp_prepare_cpus(setup_max_cpus);사용할 CPU 개수에 맞춰 보조 CPU의 초기화 준비를 수행합니다.
workqueue_init();실제 작업을 실행할 worker 기반을 준비합니다.
init_mm_internals();메모리 관리의 CPU별 작업 큐를 만들고, SMP에서는 CPU가 추가·제거될 때 메모리 통계를 정리할 콜백과 주기 갱신을 연결합니다. procfs를 쓰면 buddyinfo·vmstat·zoneinfo 같은 조회 파일도 등록합니다. 앞의 workqueue_init 뒤에 실행되어 실제 작업을 예약할 기반을 갖춥니다. v6.18.37에서는 vm sysctl 항목도 여기서 등록합니다.
do_pre_smp_initcalls();보조 CPU를 시작하기 전에 실행해야 하는 초기화 함수들을 호출합니다.
lockup_detector_init();CPU가 오랫동안 진행하지 못하는 상황을 감지할 기반을 준비합니다.
smp_init();보조 CPU들을 온라인으로 올리는 SMP 초기화를 수행합니다.
sched_init_smp();여러 CPU 사이 스케줄링 관계를 구성합니다.
workqueue_init_topology();CPU 토폴로지에 맞춰 작업 큐의 실행 배치를 정리합니다.
async_init();비동기 초기화 작업 관리 기반을 준비합니다.
padata_init();순서를 유지하며 병렬 작업을 처리하는 padata 기반을 준비합니다.
page_alloc_init_late();CPU·일반 할당 기반이 갖춰진 뒤 페이지 할당기의 후반 초기화를 수행합니다.
do_basic_setup();드라이버 등 기본 서브시스템의 initcall 초기화를 실행합니다.
kunit_run_all_tests();구성된 KUnit 테스트를 실행합니다. 모든 시스템에서 항상 같은 테스트가 포함되지는 않습니다.
wait_for_initramfs();비동기로 풀고 있는 initramfs 작업이 끝나기를 기다립니다.
console_on_rootfs();초기 루트 파일 시스템의 콘솔을 준비합니다.
int ramdisk_command_access;ramdisk init 프로그램에 접근할 수 있는지 확인한 결과를 보관합니다. 0은 성공이며 실패한 경우 같은 오류 코드를 경고에 출력하기 위해 별도 변수에 둡니다.
ramdisk_command_access = init_eaccess(ramdisk_execute_command);지정 init 프로그램에 접근할 수 있는지 확인하고 결과를 기록합니다.
if (ramdisk_command_access != 0) {ramdisk init 접근 검사가 실패한 경우입니다. 해당 실행 경로를 포기하고 prepare_namespace로 일반 루트 파일 시스템 준비를 이어 갑니다.
pr_warn("check access for rdinit=%s failed: %i, ignoring\n",사용하려던 ramdisk init 경로와 접근 실패 이유를 경고합니다. 단순 출력 뒤에 끝내지 않고 아래에서 경로를 NULL로 지워 사용하지 않게 합니다.
ramdisk_execute_command, ramdisk_command_access);진단 문구에 실제 실행 파일 경로와 접근 검사 오류 코드를 넣습니다. 나중에 경로 자체가 잘못된 것인지 권한 등 다른 원인인지 확인할 단서입니다. 앞줄에서 시작한 호출이나 식에 이어지는 부분이므로, 이 줄만으로 별도의 함수 호출이 생기지는 않습니다.
ramdisk_execute_command = NULL;접근할 수 없었던 ramdisk init 경로를 사용하지 않도록 포인터를 지웁니다. 다음 prepare_namespace가 후속 루트 준비를 맡습니다.
prepare_namespace();ramdisk init 경로를 사용하지 못한 경우 루트 파일 시스템 등 후속 실행 환경을 준비합니다.
integrity_load_keys();사용 가능한 초기 파일 시스템에서 무결성 검증 키를 읽는 준비를 수행합니다.
함께 생각해 볼 질문
__init이면 호출 즉시 메모리가 사라지나요?
아닙니다. 섹션 분류를 붙이는 것이며 초기화 완료 후 별도 단계에서 회수합니다.
wait_for_initramfs는 왜 필요한가요?
비동기로 준비되는 초기 파일 시스템의 내용이 사용 가능해질 때까지 기다리기 때문입니다.
rdinit 접근에 실패하면 무엇을 하나요?
지정한 ramdisk 실행 경로를 포기하고 prepare_namespace로 루트 준비를 이어 갑니다.
출처와 읽은 범위
Linux stable v6.18.37 · init/main.c
해당 버전 원본 파일 · 기존 코드 분석 · 설명 원고
