Boot / Init · Linux v6.6 · source + diagram note

rest_init()

PID 1과 kthreadd 생성, 완료 신호로 여는 PID 1 경로, 부트 CPU의 idle 진입을 Linux v6.6 코드 순서로 읽는다.

전체 흐름

rest_init()은 start_kernel() 뒤의 부팅 경로에서 PID 1용 kernel_init과 kthreadd를 만든 다음, 부트 CPU의 idle 실행으로 들어간다. 여기서 중요한 구분은 스레드 생성 순서, PID 1의 진행 허가, 스케줄러가 실제로 실행 기회를 주는 시점이다. 세 시점을 하나의 화살표로 합치면 부팅 동작을 잘못 읽게 된다.

그림 1. rest_init() 호출 순서와 각 호출이 남기는 조건
01RCU 시작 단계rcu_scheduler_starting()
02PID 1 생성user_mode_thread(kernel_init, …)
03초기 CPU 고정PID 1을 부트 CPU에 임시 고정
04kthreadd 생성kthreadd_task 포인터 설정
05상태 전환SYSTEM_SCHEDULING
06대기 관문 해제complete(&kthreadd_done)
07부트 idle의 첫 scheduleschedule_preempt_disabled()
08idle 루프 진입cpu_startup_entry()

번호는 rest_init()을 실행하는 부트 idle 태스크의 소스 순서다. PID 1과 kthreadd가 각 번호 사이의 어느 순간에 실제로 CPU를 얻는지는 이 순서만으로 확정할 수 없다.

소스 코드 위치

기준은 Linux v6.6의 init/main.c:680–727이다. PID 1 쪽 대기는 같은 파일의 kernel_init():1428–1437에서 확인할 수 있다. 초기 CPU 고정이 풀리는 다음 단계는 kernel_init_freeable()의 sched_init_smp() 호출과 연결된다.

생성과 실행을 분리

user_mode_thread()와 kernel_thread()는 새 태스크를 만든다. 그 태스크가 즉시 실행됐다는 증거는 아니다.

서로 다른 실행 주체

이 함수의 마지막 두 호출은 부트 idle 태스크 경로다. PID 1은 별도의 kernel_init()에서 완료 신호를 기다린다.

대표 코드

첫 발췌는 rest_init() 전체이며, 두 번째는 kernel_init()의 시작 부분이다. 부트 경로가 보낸 완료 신호를 PID 1이 확인한 뒤 나머지 초기화로 진행한다.

부트 경로: rest_init()

Linux v6.6 init/main.c:680–727

noinline void __ref __noreturn rest_init(void)
{
	struct task_struct *tsk;
	int pid;

	rcu_scheduler_starting();
	/*
	 * We need to spawn init first so that it obtains pid 1, however
	 * the init task will end up wanting to create kthreads, which, if
	 * we schedule it before we create kthreadd, will OOPS.
	 */
	pid = user_mode_thread(kernel_init, NULL, CLONE_FS);
	/*
	 * Pin init on the boot CPU. Task migration is not properly working
	 * until sched_init_smp() has been run. It will set the allowed
	 * CPUs for init to the non isolated CPUs.
	 */
	rcu_read_lock();
	tsk = find_task_by_pid_ns(pid, &init_pid_ns);
	tsk->flags |= PF_NO_SETAFFINITY;
	set_cpus_allowed_ptr(tsk, cpumask_of(smp_processor_id()));
	rcu_read_unlock();

	numa_default_policy();
	pid = kernel_thread(kthreadd, NULL, NULL, CLONE_FS | CLONE_FILES);
	rcu_read_lock();
	kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns);
	rcu_read_unlock();

	/*
	 * Enable might_sleep() and smp_processor_id() checks.
	 * They cannot be enabled earlier because with CONFIG_PREEMPTION=y
	 * kernel_thread() would trigger might_sleep() splats. With
	 * CONFIG_PREEMPT_VOLUNTARY=y the init task might have scheduled
	 * already, but it's stuck on the kthreadd_done completion.
	 */
	system_state = SYSTEM_SCHEDULING;

	complete(&kthreadd_done);

	/*
	 * The boot idle thread must execute schedule()
	 * at least once to get things moving:
	 */
	schedule_preempt_disabled();
	/* Call into cpu_idle with preempt disabled */
	cpu_startup_entry(CPUHP_ONLINE);
}

PID 1 경로: kernel_init()의 대기

Linux v6.6 init/main.c:1428–1437

static int __ref kernel_init(void *unused)
{
	int ret;

	/*
	 * Wait until kthreadd is all set-up.
	 */
	wait_for_completion(&kthreadd_done);

	kernel_init_freeable();

함수별 분석

PID 1을 먼저 만들어 번호를 확보하지만, PID 1이 커널 스레드 생성을 계속 진행하기 전에 kthreadd 준비 조건을 세워야 한다. kernel_init()의 대기는 이 두 사실을 이어 주는 관문이다.

그림 2. 소스 순서와 PID 1의 대기 관문
부트 idle 태스크
PID 1 생성kthreadd 생성·포인터 설정완료 신호첫 schedule → idle
PID 1 · kernel_init
생성됨완료 조건 확인·필요하면 대기진행 조건 충족kernel_init_freeable()

complete()는 대기 조건을 충족시킨다. 호출이 돌아온 시점에 PID 1이나 kthreadd가 이미 실행됐다고 보장하는 동기식 실행 호출은 아니다.

부트 경로의 호출직접 확인되는 변화아직 단정할 수 없는 것
user_mode_thread(kernel_init, …)PID 1용 태스크를 먼저 생성PID 1이 다음 코드까지 실행했는지
kernel_thread(kthreadd, …)kthreadd 태스크를 만들고 kthreadd_task 포인터를 설정kthreadd가 이미 CPU를 얻었는지
complete(&kthreadd_done)PID 1의 대기 조건을 충족PID 1이 즉시 깨어 실행됐는지
schedule_preempt_disabled()부트 idle 태스크가 명시적으로 스케줄러에 진입어느 새 태스크가 먼저 실행될지

태스크와 공유 상태

kernel_init은 PID 1이 실행할 함수, kthreadd는 별도 커널 태스크의 함수다. kthreadd_task는 그 태스크를 가리키는 포인터이며, kthreadd_done은 PID 1의 진행을 조절하는 completion이다. 함수, 태스크, 포인터, 동기화 객체를 같은 종류의 “필드”로 취급하면 관계가 흐려진다.

그림 3. PID 1의 초기 CPU 제한이 바뀌는 시점
rest_init()PID 1 생성 직후PF_NO_SETAFFINITY 설정, 허용 CPU를 부트 CPU로 제한
kernel_init()완료 신호 이후kernel_init_freeable()의 초기화 단계 진행
sched_init_smp()SMP 스케줄러 준비소스 주석에 따라 init의 허용 CPU가 비격리 CPU로 갱신됨

처음의 제한은 sched_init_smp() 전에는 태스크 이동이 제대로 준비되지 않았기 때문이다. 이것을 PID 1의 영구 CPU 고정으로 읽으면 안 된다.

이름종류와 역할읽을 때 주의할 점
kernel_initPID 1이 시작할 함수wait_for_completion() 뒤에 나머지 초기화로 진행
kthreadd / kthreadd_task커널 스레드용 태스크 함수 / 생성한 태스크의 포인터포인터 설정은 실행 완료 확인이 아님
kthreadd_donePID 1의 대기 조건을 알리는 completion이름의 “done”을 kthreadd 실행 완료로 확대 해석하지 않음
system_stateSYSTEM_SCHEDULING으로 갱신되는 전역 상태검사 활성화 시점이며 즉시 문맥 전환을 뜻하지 않음

실행 단계와 상태 변화

rest_init()은 돌아오지 않는 함수다. 새로 만든 태스크들이 뒤따라 실행할 경로와, 부트 idle 태스크가 들어갈 경로를 나란히 남긴다. 특히 complete()와 첫 schedule()은 서로 다른 단계다.

그림 4. 소스로 증명되는 사건과 실행 관측이 필요한 사건
소스가 보이는 순서PID 1 생성 → kthreadd 생성·포인터 설정 → 완료 신호 → 부트 idle의 schedule → idle 진입
실행 시 확인할 순서PID 1의 첫 실행, completion 대기/재개 시점, kthreadd의 첫 실행, 실제 문맥 전환 상대

왼쪽은 rest_init()의 호출 순서다. 오른쪽의 실제 실행 타이밍은 구성과 스케줄링에 좌우되며 이 함수의 소스만으로 고정할 수 없다.

소스 지점성립하는 조건다음 경로
user_mode_thread() 반환PID 1 생성 요청이 끝남부트 CPU 허용 집합 설정
kthreadd_task 설정새 kthreadd 태스크의 포인터를 저장SYSTEM_SCHEDULING 설정
complete() 호출PID 1의 completion 대기를 통과할 조건 제공부트 idle의 명시적 스케줄 호출
cpu_startup_entry() 호출부트 CPU가 idle 경로로 진입rest_init() 호출자로 돌아오지 않음

해석할 때 지킬 조건

PID 1은 먼저 만든다

소스 주석은 PID 1을 얻기 위해 init을 먼저 생성한다고 설명한다. 그 뒤 kthreadd를 만들기 전에 init이 계속 진행하면 커널 스레드 생성에서 문제가 생길 수 있다.

대기는 준비 조건이다

kernel_init()은 kthreadd_done을 기다린다. 완료 신호는 필요한 설정을 마쳤다는 관문이며 kthreadd가 한 번 실행했다는 기록은 아니다.

CPU 제한은 초기 단계용이다

sched_init_smp() 전의 태스크 이동 제약 때문에 init을 잠시 부트 CPU에 묶는다. 이후 CPU 허용 집합을 갱신하는 경로를 함께 읽는다.

부트 경로는 idle로 간다

부트 idle 태스크가 한 번 schedule()하도록 호출한 뒤 cpu_startup_entry()에 들어간다. PID 1의 함수 경로와 혼동하지 않는다.

계측과 검증

소스는 순서와 대기 조건을 설명하지만 실제 실행 타이밍을 측정하지는 않는다. 특정 보드에서 시간관계를 확인하려면 커널 설정과 초기 부팅 시점에 수집 가능한 관측 지점을 먼저 확인해야 한다.

확인할 질문가능한 관측해석 한계
두 태스크는 어떤 순서로 생성됐나?초기 부팅 시점의 태스크 생성 추적을 켠 구성에서 생성 이벤트 비교생성 이벤트만으로 첫 실행 시점은 알 수 없음
PID 1은 언제 대기를 통과했나?해당 커널 빌드에서 kernel_init()의 대기 전후 계측완료 신호 시점과 PID 1의 재실행 시점은 다를 수 있음
부트 idle이 누구에게 CPU를 넘겼나?초기 스케줄 전환을 수집할 수 있을 때 전환 대상 확인수집되지 않은 초기 이벤트를 사후 ps로 복원할 수 없음

이 글에는 특정 보드의 부팅 trace나 실행 시간 측정값을 제시하지 않는다.

추가 확인 사항

  • 사용하는 커널 구성에서 PID 1이 complete() 전에 실행된다면, kernel_init()의 대기 지점에서 어떤 상태로 멈추는가?
  • system_state = SYSTEM_SCHEDULING을 스케줄러 최초 실행과 동일시하면 어떤 순서 오류가 생기는가?
  • 초기 CPU 고정과 sched_init_smp() 뒤의 CPU 허용 집합을 실제 시스템에서 어떻게 구분해 관찰할 것인가?