# sched_init과 idle 준비: 실행할 일이 없을 때도 CPU 상태는 필요합니다

v6.6 / kernel/sched/core.c

스케줄러는 여러 작업 중 하나를 고르지만, 실행할 일반 작업이 하나도 없는 경우도 표현해야 합니다. CPU마다 idle task가 있는 이유입니다. sched_init 전체의 자료구조 초기화를 읽기 전에 init_idle 한 함수를 통해 task와 CPU의 실행 큐가 어떻게 연결되는지 살펴보겠습니다.

## init_idle

```c

void __init init_idle(struct task_struct *idle, int cpu)
{
#ifdef CONFIG_SMP
	struct affinity_context ac = (struct affinity_context) {
		.new_mask  = cpumask_of(cpu),
		.flags     = 0,
	};
#endif
	struct rq *rq = cpu_rq(cpu);
	unsigned long flags;

	__sched_fork(0, idle);

	raw_spin_lock_irqsave(&idle->pi_lock, flags);
	raw_spin_rq_lock(rq);

	idle->__state = TASK_RUNNING;
	idle->se.exec_start = sched_clock();
	/*
	 * PF_KTHREAD should already be set at this point; regardless, make it
	 * look like a proper per-CPU kthread.
	 */
	idle->flags |= PF_KTHREAD | PF_NO_SETAFFINITY;
	kthread_set_per_cpu(idle, cpu);

#ifdef CONFIG_SMP
	/*
	 * It's possible that init_idle() gets called multiple times on a task,
	 * in that case do_set_cpus_allowed() will not do the right thing.
	 *
	 * And since this is boot we can forgo the serialization.
	 */
	set_cpus_allowed_common(idle, &ac);
#endif
	/*
	 * We're having a chicken and egg problem, even though we are
	 * holding rq->lock, the CPU isn't yet set to this CPU so the
	 * lockdep check in task_group() will fail.
	 *
	 * Similar case to sched_fork(). / Alternatively we could
	 * use task_rq_lock() here and obtain the other rq->lock.
	 *
	 * Silence PROVE_RCU
	 */
	rcu_read_lock();
	__set_task_cpu(idle, cpu);
	rcu_read_unlock();

	rq->idle = idle;
	rcu_assign_pointer(rq->curr, idle);
	idle->on_rq = TASK_ON_RQ_QUEUED;
#ifdef CONFIG_SMP
	idle->on_cpu = 1;
#endif
	raw_spin_rq_unlock(rq);
	raw_spin_unlock_irqrestore(&idle->pi_lock, flags);

	/* Set the preempt count _outside_ the spinlocks! */
	init_idle_preempt_count(idle, cpu);

	/*
	 * The idle tasks have their own, simple scheduling class:
	 */
	idle->sched_class = &idle_sched_class;
	ftrace_graph_init_idle_task(idle, cpu);
	vtime_init_idle(idle, cpu);
#ifdef CONFIG_SMP
	sprintf(idle->comm, "%s/%d", INIT_TASK_COMM, cpu);
#endif
}

```

### 9250행

```c

void __init init_idle(struct task_struct *idle, int cpu)

```

이미 준비된 idle task와 대상 CPU 번호를 받아 둘을 연결합니다. 이 함수 자체가 새 task_struct를 할당하는 것은 아닙니다.

### 9252행

```c

#ifdef CONFIG_SMP

```

다중 CPU 지원 CONFIG_SMP가 켜진 빌드에서만 다음 affinity 설정 인자를 만듭니다. 실행 중 CPU 개수를 비교하는 if문이 아닙니다.

### 9253행

```c

	struct affinity_context ac = (struct affinity_context) {

```

CPU 허용 범위를 설정할 affinity_context 구조체를 초기화합니다. 다음 두 줄은 함수 호출이 아니라 구조체 필드 값입니다.

### 9254행

```c

		.new_mask  = cpumask_of(cpu),

```

대상 CPU 하나만 포함하는 마스크를 지정합니다. idle task를 임의의 CPU로 이동시키지 않으려는 설정입니다.

### 9255행

```c

		.flags     = 0,

```

이번 affinity 갱신에 추가 동작 플래그를 지정하지 않습니다.

### 9257행

```c

#endif

```

SMP 빌드에만 필요한 affinity 인자 선언 부분이 끝납니다.

### 9258행

```c

	struct rq *rq = cpu_rq(cpu);

```

대상 CPU의 runqueue를 찾습니다. 이후 초기 상태를 기록할 곳입니다.

### 9259행

```c

	unsigned long flags;

```

잠금 전의 인터럽트 허용 상태를 보관할 변수를 마련합니다.

### 9261행

```c

	__sched_fork(0, idle);

```

idle task의 스케줄링 entity와 실행 큐 관련 초기 필드를 준비합니다. 함수 이름에 fork가 있지만 여기서 새로운 task를 복제하는 시스템 호출은 아닙니다.

### 9263행

```c

	raw_spin_lock_irqsave(&idle->pi_lock, flags);

```

idle task의 pi_lock을 잡으면서 로컬 인터럽트 상태를 저장하고 차단합니다. task 관련 상태를 일관되게 바꾸기 위한 시작입니다.

### 9264행

```c

	raw_spin_rq_lock(rq);

```

대상 runqueue 잠금도 잡아 task와 rq의 연결을 보호합니다.

### 9266행

```c

	idle->__state = TASK_RUNNING;

```

idle task를 스케줄러가 실행 가능한 상태로 표시합니다. 실제 유용한 일을 하고 있다는 뜻은 아닙니다.

### 9267행

```c

	idle->se.exec_start = sched_clock();

```

실행 시간 계상의 기준 시작 시각을 넣습니다. 이후 경과 시간을 계산할 출발점입니다.

### 9272행

```c

	idle->flags |= PF_KTHREAD | PF_NO_SETAFFINITY;

```

커널 스레드임을 표시하고 일반적인 affinity 변경을 금지합니다. 기존 flags를 지우지 않도록 OR 대입을 사용합니다.

### 9273행

```c

	kthread_set_per_cpu(idle, cpu);

```

커널 스레드 관리 정보에서도 CPU 전용 task로 등록합니다.

### 9275행

```c

#ifdef CONFIG_SMP

```

다중 CPU 지원이 있는 빌드에서만 CPU 허용 범위 설정 코드를 포함합니다.

### 9282행

```c

	set_cpus_allowed_common(idle, &ac);

```

대상 CPU 하나를 지정한 마스크를 적용합니다. 부팅 시점이며 idle 초기화가 같은 task에 다시 호출될 수 있다는 조건을 고려해 공통 설정 함수를 사용합니다.

### 9283행

```c

#endif

```

SMP 전용 CPU 허용 범위 설정이 끝납니다.

### 9294행

```c

	rcu_read_lock();

```

다음 CPU 연결 갱신에서 요구되는 RCU 읽기 영역에 들어갑니다. 소스 주석은 부팅 초기 task_group 검사의 상황도 설명합니다.

### 9295행

```c

	__set_task_cpu(idle, cpu);

```

idle task가 속한 CPU 번호를 실제로 설정합니다. 단지 허용 마스크를 바꾸는 앞 호출과 구분됩니다.

### 9296행

```c

	rcu_read_unlock();

```

CPU 연결 갱신에 사용한 RCU 읽기 영역을 끝냅니다.

### 9298행

```c

	rq->idle = idle;

```

이 rq의 특별한 idle task 포인터를 기록합니다.

### 9299행

```c

	rcu_assign_pointer(rq->curr, idle);

```

현재 실행 task 포인터를 RCU 방식으로 게시합니다. 이후 읽는 코드가 일관된 포인터를 보도록 하는 표기입니다.

### 9300행

```c

	idle->on_rq = TASK_ON_RQ_QUEUED;

```

idle을 rq에 등록된 상태로 표시합니다. 일반 fair task의 목록 삽입을 호출한다는 뜻은 아닙니다.

### 9301행

```c

#ifdef CONFIG_SMP

```

다중 CPU 지원 빌드에서만 다음 on_cpu 필드를 설정합니다.

### 9302행

```c

	idle->on_cpu = 1;

```

이 초기화 단계에서 CPU 위의 task임을 표시합니다.

### 9303행

```c

#endif

```

SMP 전용 실행 CPU 표시가 끝납니다.

### 9304행

```c

	raw_spin_rq_unlock(rq);

```

runqueue 상태 갱신이 끝났으므로 rq 잠금을 풉니다.

### 9305행

```c

	raw_spin_unlock_irqrestore(&idle->pi_lock, flags);

```

task 잠금을 풀고 저장했던 인터럽트 상태를 복원합니다. 무조건 인터럽트를 켜는 것과 다릅니다.

### 9308행

```c

	init_idle_preempt_count(idle, cpu);

```

잠금 밖에서 idle의 초기 선점 카운트를 설정합니다. 소스가 요구한 순서를 그대로 지킵니다.

### 9313행

```c

	idle->sched_class = &idle_sched_class;

```

일반 fair나 RT 정책 대신 idle 전용 스케줄링 클래스를 연결합니다.

### 9314행

```c

	ftrace_graph_init_idle_task(idle, cpu);

```

idle task의 함수 그래프 추적 상태를 준비합니다. 추적 설정에 따라 실제 작업이 달라질 수 있습니다.

### 9315행

```c

	vtime_init_idle(idle, cpu);

```

idle에 대한 가상 CPU 시간 계상 상태를 초기화합니다.

### 9316행

```c

#ifdef CONFIG_SMP

```

SMP 빌드에서 CPU별로 구별되는 idle 이름을 만듭니다.

### 9317행

```c

	sprintf(idle->comm, "%s/%d", INIT_TASK_COMM, cpu);

```

CPU 번호가 포함된 이름을 넣어 로그나 task 목록에서 구분할 수 있게 합니다.

### 9318행

```c

#endif

```

CPU 번호를 붙이는 조건부 컴파일 부분이 끝납니다.

