이 코드는 어떤 문제를 푸나요?
한 CPU에서 이어지던 초기화 흐름을 이제 여러 태스크의 실행으로 나눕니다. 사용자 init으로 이어질 태스크와 커널 스레드를 만드는 kthreadd를 생성하고, 원래 부트 태스크는 idle 경로로 들어갑니다. 생성과 실행은 같은 순간이 아니므로 준비 완료 통지도 필요합니다.
읽을 범위: v6.6 · init/main.c · rest_init 680–727행입니다. 아래에 이 범위의 원문과 각 줄의 설명을 실었습니다. 주제 전체의 흐름과 다른 경로는 기존 분석에서 함께 읽으실 수 있습니다.
먼저 알아둘 개념
PID 1
kernel_init에서 시작해 나중에 사용자 init으로 바뀌는 태스크입니다. 처음부터 사용자 명령을 실행하는 것은 아닙니다.
completion
다른 실행 흐름이 준비되었음을 알리는 동기화 수단입니다. 여기서는 kthreadd 준비를 기다리는 쪽을 깨울 수 있습니다.
처음 읽을 때
코드를 읽을 때 user_mode_thread로 kernel_init을 시작할 태스크를 만듭니다. 그다음 어느 상태가 바뀌는지 각 줄에서 확인하세요.
더 깊이 살펴볼 때
이 경로의 호출 문맥, 잠금·인터럽트 상태, 오류 시 남는 자원을 함께 추적해 보세요. 호출된 함수가 수행하는 작업과 현재 함수가 직접 보장하는 범위를 구분하는 것이 중요합니다.
그림으로 보는 변화

1. init 생성
user_mode_thread로 kernel_init을 시작할 태스크를 만듭니다.
화살표는 태스크 생성 순서입니다.
2. kthreadd 생성
커널 스레드 관리 태스크를 생성하고 참조를 저장합니다.
태스크 포인터와 PID 숫자는 다릅니다.
3. 스케줄링 시작
준비 완료를 알리고 원래 태스크는 idle 경로로 갑니다.
태스크 사이 전환을 뜻하며 메모리 복사는 아닙니다.
rest_init를 한 줄씩 읽기
줄 번호는 v6.6 원문 기준입니다. 주석·빈 줄을 포함한 함수 전체를 먼저 보고, 그 아래에서 각 줄을 설명합니다.
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);
}noinline void __ref __noreturn rest_init(void)초기화 흐름을 init, kthreadd와 부트 CPU의 idle 실행으로 나눕니다. noinline은 별도 함수 형태를 유지하고 __ref는 참조가 필요한 코드 구역에 두며 __noreturn은 정상 반환하지 않음을 표시합니다.
struct task_struct *tsk;생성된 init 태스크를 PID로 찾아 플래그와 CPU 허용 범위를 바꿀 때 사용할 task_struct 포인터입니다. 포인터가 보관하는 것은 객체의 주소입니다. 이 선언만으로 대상 구조체나 문자열의 내용이 복사되지는 않습니다.
int pid;태스크 생성 함수가 반환한 PID 번호를 담습니다. 아래에서 이 번호를 task_struct 포인터로 다시 조회하므로 두 종류의 값을 구분합니다.
rcu_scheduler_starting();스케줄러 시작 단계로 RCU 상태를 전환하여 후속 태스크 활동을 추적하게 합니다.
pid = user_mode_thread(kernel_init, NULL, CLONE_FS);kernel_init부터 실행할 init 태스크를 만들고 PID를 받습니다. CLONE_FS는 현재 디렉터리·루트·umask를 담은 파일 시스템 문맥을 부모와 공유하라는 뜻입니다. 여기서 즉시 사용자 명령을 실행하는 것이 아니라 kernel_init의 준비를 거친 뒤 exec로 사용자 init을 실행합니다.
rcu_read_lock();참조하는 RCU 보호 객체의 수명이 읽기 도중 끝나지 않도록 읽기 구간을 시작합니다.
tsk = find_task_by_pid_ns(pid, &init_pid_ns);PID 숫자를 해당 namespace의 task_struct 포인터로 바꿔 찾습니다.
tsk->flags |= PF_NO_SETAFFINITY;후속 준비 전에 다른 코드가 init 태스크의 CPU 배치를 바꾸지 못하게 플래그를 추가합니다.
set_cpus_allowed_ptr(tsk, cpumask_of(smp_processor_id()));초기 init 태스크를 부트 CPU에 묶어 아직 준비 중인 다른 CPU에서 실행되지 않게 합니다.
rcu_read_unlock();RCU 보호 읽기 구간을 끝냅니다. 잠든 독자를 기다리는 일반 mutex unlock과는 다릅니다.
numa_default_policy();기본 메모리 노드 배치 정책을 준비합니다.
pid = kernel_thread(kthreadd, NULL, NULL, CLONE_FS | CLONE_FILES);커널 스레드 생성 요청을 처리할 kthreadd를 만듭니다. CLONE_FS는 루트·현재 디렉터리 등을, CLONE_FILES는 열린 파일 디스크립터 표를 부모와 공유하게 합니다. 앞에서 만든 init은 나중에 사용자 프로그램을 실행하지만 kthreadd는 커널 스레드로 남습니다.
rcu_read_lock();참조하는 RCU 보호 객체의 수명이 읽기 도중 끝나지 않도록 읽기 구간을 시작합니다.
kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns);PID 숫자를 해당 namespace의 task_struct 포인터로 바꿔 찾습니다.
rcu_read_unlock();RCU 보호 읽기 구간을 끝냅니다. 잠든 독자를 기다리는 일반 mutex unlock과는 다릅니다.
system_state = SYSTEM_SCHEDULING;시스템이 초기 스케줄링 단계에 들어갔음을 전역 상태에 기록합니다.
complete(&kthreadd_done);kthreadd가 준비되었음을 알립니다. 기다리는 init 경로가 후속 작업을 시작할 수 있습니다.
schedule_preempt_disabled();선점이 비활성인 조건에서 첫 스케줄링 전환을 수행합니다.
cpu_startup_entry(CPUHP_ONLINE);원래 부트 태스크를 CPU idle 실행 경로로 보냅니다.
함께 생각해 볼 질문
CLONE_FS가 있으면 주소 공간도 공유하나요?
CLONE_FS는 파일 시스템 관련 실행 문맥 공유입니다. 주소 공간 공유 여부는 CLONE_VM과 구분해야 합니다.
왜 PID를 다시 태스크 포인터로 찾나요?
생성 결과는 PID 번호입니다. 태스크의 플래그나 CPU 허용 집합을 바꾸려면 task_struct가 필요합니다.
원래 부트 태스크는 사라지나요?
부트 CPU의 idle 태스크로 실행을 이어 갑니다.
출처와 읽은 범위
Linux stable v6.6 · init/main.c
해당 버전 원본 파일 · 기존 코드 분석 · 설명 원고
