전체 흐름
kernel_init()은 사용자 공간의 첫 프로그램을 고르는 마지막 커널 측 분기다. Linux v6.6의 순서는 ramdisk_execute_command, 명시적 execute_command, CONFIG_DEFAULT_INIT, 네 개의 기본 경로다. 후보가 성공하면 즉시 반환하므로 뒤 후보는 시도하지 않는다.
이 그림은 init/main.c:1462–1500의 코드 순서다. 실제 부팅에서 어느 파일이 실행됐는지는 기기 로그나 실행 환경을 별도로 확인해야 한다.
| 순위 | 후보 활성 조건 | 실패했을 때 |
|---|---|---|
| 1 · ramdisk | ramdisk_execute_command != NULL | 오류를 기록하고 다음 후보로 진행 |
| 2 · init= | execute_command != NULL | Requested init ... failed panic |
| 3 · 기본 설정 | CONFIG_DEFAULT_INIT[0] != '\0' | 오류를 기록하고 기본 경로로 진행 |
| 4 · 기본 경로 | 앞 단계에서 반환하거나 panic하지 않았을 때 | 네 경로 모두 실패하면 No working init found panic |
소스 코드 위치
이 글의 선택 순서와 반환 처리는 Linux v6.6 init/main.c에서 직접 확인할 수 있다. 아래의 두 발췌는 해당 파일의 연속된 원문 구간이다.
| 원문 위치 | 이 글에서 확인하는 것 | 읽을 때의 경계 |
|---|---|---|
| 1341–1368행 | run_init_process()와 try_to_run_init_process()의 호출·반환 | 실행 파일 내부 동작까지 설명하는 구간은 아님 |
| 1428–1460행 | kthreadd_done 대기, 초기화 완료 후 후보 검사 진입 | 후보 선택에 앞선 커널 준비 단계 |
| 1462–1501행 | 후보 순서와 단계별 오류 처리 | 실제 부팅 성공 경로를 입증하는 기록은 아님 |
| 1554–1561행 | 초기 ramdisk 경로 접근 검사 실패 시 포인터를 null로 바꾸고 namespace 준비 | /init을 언제나 시도한다고 단정할 수 없는 이유 |
대표 코드
실행 요청과 실패 로그
Linux v6.6init/main.c:1341–1368, 연속 원문 28행
static int run_init_process(const char *init_filename)
{
const char *const *p;
argv_init[0] = init_filename;
pr_info("Run %s as init process\n", init_filename);
pr_debug(" with arguments:\n");
for (p = argv_init; *p; p++)
pr_debug(" %s\n", *p);
pr_debug(" with environment:\n");
for (p = envp_init; *p; p++)
pr_debug(" %s\n", *p);
return kernel_execve(init_filename, argv_init, envp_init);
}
static int try_to_run_init_process(const char *init_filename)
{
int ret;
ret = run_init_process(init_filename);
if (ret && ret != -ENOENT) {
pr_err("Starting init: %s exists but couldn't execute it (error %d)\n",
init_filename, ret);
}
return ret;
}
라인별 주석
고정 Linux v6.6 init/main.c 1341–1368행. 왼쪽 번호는 위 발췌문의 줄 번호이며 원문과 대조한 행만 설명한다.
static int run_init_process(const char *init_filename)현재 init 태스크에서 init_filename이 지정한 사용자 프로그램 실행을 시도합니다. int 반환값은 kernel_execve의 결과이며 새 PID를 만드는 함수가 아닙니다.
const char *const *p;argv나 envp의 문자열 포인터 배열을 한 원소씩 읽을 순회 포인터입니다. *p가 현재 문자열 주소이고 NULL이면 배열 끝입니다. 포인터가 보관하는 것은 객체의 주소입니다. 이 선언만으로 대상 구조체나 문자열의 내용이 복사되지는 않습니다.
argv_init[0] = init_filename;사용자 프로그램이 받는 첫 번째 인자 포인터를 실행 파일 이름으로 정합니다. 문자열 전체를 복사하지 않습니다.
pr_info("Run %s as init process\n", init_filename);지금 어떤 사용자 init 파일의 실행을 시도하는지 기록합니다. 이 로그만으로 exec 성공까지 확인한 것은 아니며 실행 호출은 아래에 있습니다.
pr_debug(" with arguments:\n");인수 목록용 pr_debug 제목입니다. 해당 디버그 출력이 활성화된 환경에서만 로그에 보이며, 이 줄만으로 실제 출력 여부를 단정할 수 없습니다.
for (p = argv_init; *p; p++)argv_init의 첫 원소부터 NULL까지 이동합니다. 각 회차에서 문자열 내용이 아니라 문자열 포인터 하나를 골라 다음 줄에서 출력합니다.
pr_debug(" %s\n", *p);현재 배열 원소 *p가 가리키는 인수 문자열 하나를 pr_debug로 기록합니다. 디버그 출력 설정에 따라 로그에 보이지 않을 수 있습니다.
pr_debug(" with environment:\n");이후 pr_debug 출력이 인수 목록에서 환경 문자열 목록으로 바뀜을 나타내는 제목입니다. 해당 디버그 출력이 활성화된 경우에만 로그에 보입니다.
for (p = envp_init; *p; p++)envp_init의 첫 문자열부터 NULL 종결자 전까지 순회합니다. 환경 문자열을 수정하거나 새로 만드는 루프는 아닙니다.
pr_debug(" %s\n", *p);현재 배열 원소 *p가 가리키는 환경 문자열 하나를 pr_debug로 기록합니다. 디버그 출력 설정에 따라 로그에 보이지 않을 수 있습니다.
return kernel_execve(init_filename, argv_init, envp_init);커널이 마련한 파일명·인자·환경으로 현재 태스크의 실행 이미지를 바꿀 공통 exec 경로에 들어갑니다.
run_init_process()는 argv_init[0]에 선택한 파일명을 넣고 kernel_execve()의 반환값을 돌려준다. try_to_run_init_process()는 0이 아니면서 -ENOENT도 아닌 반환값만 별도 오류 로그로 남긴다. 실패를 성공으로 바꾸지는 않는다.
PID 1의 후보 선택
Linux v6.6init/main.c:1462–1501, 연속 원문 40행
if (ramdisk_execute_command) {
ret = run_init_process(ramdisk_execute_command);
if (!ret)
return 0;
pr_err("Failed to execute %s (error %d)\n",
ramdisk_execute_command, ret);
}
/*
* We try each of these until one succeeds.
*
* The Bourne shell can be used instead of init if we are
* trying to recover a really broken machine.
*/
if (execute_command) {
ret = run_init_process(execute_command);
if (!ret)
return 0;
panic("Requested init %s failed (error %d).",
execute_command, ret);
}
if (CONFIG_DEFAULT_INIT[0] != '\0') {
ret = run_init_process(CONFIG_DEFAULT_INIT);
if (ret)
pr_err("Default init %s failed (error %d)\n",
CONFIG_DEFAULT_INIT, ret);
else
return 0;
}
if (!try_to_run_init_process("/sbin/init") ||
!try_to_run_init_process("/etc/init") ||
!try_to_run_init_process("/bin/init") ||
!try_to_run_init_process("/bin/sh"))
return 0;
panic("No working init found. Try passing init= option to kernel. "
"See Linux Documentation/admin-guide/init.rst for guidance.");
}
ramdisk_execute_command 실패는 다음 후보로 이어지지만 execute_command 실패는 그 자리에서 panic한다. 기본 경로의 ||는 왼쪽부터 평가해 성공한 첫 후보에서 멈춘다.
함수별 분석
한 후보를 시도할 때의 실행 요청과, 다음 후보를 고르는 정책은 서로 다른 층이다. 두 함수의 반환값을 분리하면 실패 로그가 찍힌 뒤에도 다음 경로를 검사하는 이유가 보인다.
실행 요청
run_init_process(path) → argv_init[0] = path → kernel_execve(path, argv_init, envp_init)
후보 정책
kernel_init()이 후보별로 반환값을 해석한다. 기본 네 경로만 try_to_run_init_process()를 거친다.
try_to_run_init_process()의 로그 여부와 kernel_init()의 다음 분기는 별개다.
| 호출 | 반환값 0 | 0이 아닌 반환값 |
|---|---|---|
run_init_process() | 선택한 후보가 성공한 것으로 처리 | 호출자에게 오류값 전달 |
try_to_run_init_process() | 기본 경로 탐색 종료 | -ENOENT 이외만 별도 로그, 오류값은 그대로 반환 |
kernel_init()의 init= 분기 | 함수에서 반환 | 즉시 panic; 뒤 후보는 검사하지 않음 |
kernel_init()의 ramdisk·CONFIG 분기 | 함수에서 반환 | 오류 로그 후 다음 후보로 이동 |
구조체와 필드
이 부분의 중심은 새 구조체 생성이 아니라 후보 경로 문자열과 실행 인수다. execute_command는 init=, ramdisk_execute_command는 rdinit=에서 설정될 수 있다. v6.6 원문에서 후자의 초기값은 /init이다.
ramdisk_execute_command, execute_command, 설정 기본값 또는 고정 경로argv_init[0]run_init_process()가 이번 후보 경로로 갱신kernel_execve()파일명, argv_init, envp_init을 인자로 받음그림은 init/main.c:1341–1353에서 확인되는 인자 전달만 나타낸다. 실행 파일 형식이나 파일 시스템 내부 처리는 이 발췌만으로 설명하지 않는다.
실행 단계와 상태 변화
후보 검사는 초기화와 무관하게 시작되는 것이 아니다. kernel_init()은 kthreadd_done 완료를 기다리고 kernel_init_freeable()을 거친 뒤, 아래 후보 선택 구간에 도달한다. 그 과정에서 init_eaccess(ramdisk_execute_command)가 실패하면 ramdisk 후보를 null로 바꾼다.
init/main.c:1435kernel_init_freeable() · ramdisk 접근 검사init/main.c:1462–1500가운데 순서는 소스상의 제어 흐름이다. 사용자 공간에서 PID 1이 이후 무엇을 하는지는 이 글의 코드만으로 확인되지 않는다.
| 조건 | 소스가 보장하는 다음 동작 | 확인 범위 |
|---|---|---|
| ramdisk 후보 null | 첫 실행 분기를 건너뜀 | if (ramdisk_execute_command) |
| ramdisk 실행 오류 | 오류 로그 후 init= 분기로 진행 | 반환값을 기록하지만 panic하지 않음 |
| init= 실행 오류 | 그 자리에서 panic | CONFIG·기본 경로로 넘어가지 않음 |
| 기본 경로 하나 성공 | 단축 평가를 끝내고 반환 | 뒤 경로는 시도하지 않음 |
불변 조건과 실패 사례
kernel_init()이 끝나고, 0이 아니면 그 후보에 정해진 오류 분기로 간다. init= 실패와 다른 후보 실패를 같은 fallback으로 묶으면 원문과 달라진다./init은 초기값이지만, kernel_init_freeable()의 접근 검사 실패 시 포인터가 null이 될 수 있다. 따라서 “모든 부팅에서 /init이 실행된다”는 결론은 이 코드에서 나오지 않는다. 기본 네 경로가 모두 실패한 경우에만 마지막 No working init found panic에 도달한다.
계측과 검증
이 소스에 이미 있는 관찰 지점은 Run %s as init process, Failed to execute %s, Default init %s failed, Starting init: %s exists but couldn't execute it 로그와 두 panic 문자열이다. 해당 문구를 볼 때는 후보·반환값·다음 분기를 같이 읽어야 한다.
특히 try_to_run_init_process()는 -ENOENT에 대해 별도 오류 로그를 생략한다. 로그가 없다는 사실만으로 기본 경로 실행 성공을 단정할 수 없다. 이 글은 소스 분기만 검증했으며, 실제 부팅 로그나 실행 결과를 측정하지 않았다.
추가 확인 사항
- 사용 중인 커널 설정에서
CONFIG_DEFAULT_INIT이 비어 있는가? - 부팅 명령줄에
rdinit=또는init=이 있다면 어느 후보가 먼저 도달 가능한가? - ramdisk 후보의 접근 검사 결과와
ramdisk_execute_command의 값은 무엇인가? - 실제 로그의 마지막 후보와 오류 번호는 어느 소스 분기에 대응하는가?