Boot / Init · Linux v6.6 · PID 1 source map

init process 실행

PID 1이 /init, init=, 기본 설정과 표준 경로 중 어떤 프로그램을 시도하는지 Linux v6.6 원문으로 따라간다.

전체 흐름

kernel_init()은 사용자 공간의 첫 프로그램을 고르는 마지막 커널 측 분기다. Linux v6.6의 순서는 ramdisk_execute_command, 명시적 execute_command, CONFIG_DEFAULT_INIT, 네 개의 기본 경로다. 후보가 성공하면 즉시 반환하므로 뒤 후보는 시도하지 않는다.

그림 1. PID 1의 init 실행 후보를 검사하는 순서

이 그림은 init/main.c:1462–1500의 코드 순서다. 실제 부팅에서 어느 파일이 실행됐는지는 기기 로그나 실행 환경을 별도로 확인해야 한다.

순위후보 활성 조건실패했을 때
1 · ramdiskramdisk_execute_command != NULL오류를 기록하고 다음 후보로 진행
2 · init=execute_command != NULLRequested 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.6 init/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행. 왼쪽 번호는 위 발췌문의 줄 번호이며 원문과 대조한 행만 설명한다.

01static int run_init_process(const char *init_filename)

현재 init 태스크에서 init_filename이 지정한 사용자 프로그램 실행을 시도합니다. int 반환값은 kernel_execve의 결과이며 새 PID를 만드는 함수가 아닙니다.

03 const char *const *p;

argv나 envp의 문자열 포인터 배열을 한 원소씩 읽을 순회 포인터입니다. *p가 현재 문자열 주소이고 NULL이면 배열 끝입니다. 포인터가 보관하는 것은 객체의 주소입니다. 이 선언만으로 대상 구조체나 문자열의 내용이 복사되지는 않습니다.

05 argv_init[0] = init_filename;

사용자 프로그램이 받는 첫 번째 인자 포인터를 실행 파일 이름으로 정합니다. 문자열 전체를 복사하지 않습니다.

06 pr_info("Run %s as init process\n", init_filename);

지금 어떤 사용자 init 파일의 실행을 시도하는지 기록합니다. 이 로그만으로 exec 성공까지 확인한 것은 아니며 실행 호출은 아래에 있습니다.

07 pr_debug(" with arguments:\n");

인수 목록용 pr_debug 제목입니다. 해당 디버그 출력이 활성화된 환경에서만 로그에 보이며, 이 줄만으로 실제 출력 여부를 단정할 수 없습니다.

08 for (p = argv_init; *p; p++)

argv_init의 첫 원소부터 NULL까지 이동합니다. 각 회차에서 문자열 내용이 아니라 문자열 포인터 하나를 골라 다음 줄에서 출력합니다.

09 pr_debug(" %s\n", *p);

현재 배열 원소 *p가 가리키는 인수 문자열 하나를 pr_debug로 기록합니다. 디버그 출력 설정에 따라 로그에 보이지 않을 수 있습니다.

10 pr_debug(" with environment:\n");

이후 pr_debug 출력이 인수 목록에서 환경 문자열 목록으로 바뀜을 나타내는 제목입니다. 해당 디버그 출력이 활성화된 경우에만 로그에 보입니다.

11 for (p = envp_init; *p; p++)

envp_init의 첫 문자열부터 NULL 종결자 전까지 순회합니다. 환경 문자열을 수정하거나 새로 만드는 루프는 아닙니다.

12 pr_debug(" %s\n", *p);

현재 배열 원소 *p가 가리키는 환경 문자열 하나를 pr_debug로 기록합니다. 디버그 출력 설정에 따라 로그에 보이지 않을 수 있습니다.

13 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.6 init/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한다. 기본 경로의 ||는 왼쪽부터 평가해 성공한 첫 후보에서 멈춘다.

함수별 분석

한 후보를 시도할 때의 실행 요청과, 다음 후보를 고르는 정책은 서로 다른 층이다. 두 함수의 반환값을 분리하면 실패 로그가 찍힌 뒤에도 다음 경로를 검사하는 이유가 보인다.

그림 2. 호출과 오류 처리의 두 층

실행 요청

run_init_process(path) → argv_init[0] = path → kernel_execve(path, argv_init, envp_init)

반환값을 호출자에게 그대로 전달

후보 정책

kernel_init()이 후보별로 반환값을 해석한다. 기본 네 경로만 try_to_run_init_process()를 거친다.

init= 실패는 panic · 나머지 실패는 조건별로 계속

try_to_run_init_process()의 로그 여부와 kernel_init()의 다음 분기는 별개다.

호출반환값 00이 아닌 반환값
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이다.

그림 3. 파일명과 인수가 실행 요청에 전달되는 경로

그림은 init/main.c:1341–1353에서 확인되는 인자 전달만 나타낸다. 실행 파일 형식이나 파일 시스템 내부 처리는 이 발췌만으로 설명하지 않는다.

실행 단계와 상태 변화

후보 검사는 초기화와 무관하게 시작되는 것이 아니다. kernel_init()은 kthreadd_done 완료를 기다리고 kernel_init_freeable()을 거친 뒤, 아래 후보 선택 구간에 도달한다. 그 과정에서 init_eaccess(ramdisk_execute_command)가 실패하면 ramdisk 후보를 null로 바꾼다.

그림 4. 후보 선택 이전의 문턱과 선택 이후의 결과
kthreadd 대기init/main.c:1435
kernel 초기화kernel_init_freeable() · ramdisk 접근 검사
init 후보 선택init/main.c:1462–1500
성공 또는 panic후보별 반환값 분기

가운데 순서는 소스상의 제어 흐름이다. 사용자 공간에서 PID 1이 이후 무엇을 하는지는 이 글의 코드만으로 확인되지 않는다.

조건소스가 보장하는 다음 동작확인 범위
ramdisk 후보 null첫 실행 분기를 건너뜀if (ramdisk_execute_command)
ramdisk 실행 오류오류 로그 후 init= 분기로 진행반환값을 기록하지만 panic하지 않음
init= 실행 오류그 자리에서 panicCONFIG·기본 경로로 넘어가지 않음
기본 경로 하나 성공단축 평가를 끝내고 반환뒤 경로는 시도하지 않음

불변 조건과 실패 사례

핵심 조건: 후보를 하나 실행할 때마다 반환값 0이면 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의 값은 무엇인가?
  • 실제 로그의 마지막 후보와 오류 번호는 어느 소스 분기에 대응하는가?