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

v6.18.37 / 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)
{
	struct affinity_context ac = (struct affinity_context) {
		.new_mask  = cpumask_of(cpu),
		.flags     = 0,
	};
	struct rq *rq = cpu_rq(cpu);
	unsigned long flags;

	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);

	/*
	 * No validation and serialization required at boot time and for
	 * setting up the idle tasks of not yet online CPUs.
	 */
	set_cpus_allowed_common(idle, &ac);
	/*
	 * 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;
	rq_set_donor(rq, idle);
	rcu_assign_pointer(rq->curr, idle);
	idle->on_rq = TASK_ON_RQ_QUEUED;
	idle->on_cpu = 1;
	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);
	sprintf(idle->comm, "%s/%d", INIT_TASK_COMM, cpu);
}

```

### 7964행

```c

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

```

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

### 7966행

```c

	struct affinity_context ac = (struct affinity_context) {

```

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

### 7967행

```c

		.new_mask  = cpumask_of(cpu),

```

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

### 7968행

```c

		.flags     = 0,

```

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

### 7970행

```c

	struct rq *rq = cpu_rq(cpu);

```

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

### 7971행

```c

	unsigned long flags;

```

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

### 7973행

```c

	raw_spin_lock_irqsave(&idle->pi_lock, flags);

```

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

### 7974행

```c

	raw_spin_rq_lock(rq);

```

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

### 7976행

```c

	idle->__state = TASK_RUNNING;

```

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

### 7977행

```c

	idle->se.exec_start = sched_clock();

```

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

### 7982행

```c

	idle->flags |= PF_KTHREAD | PF_NO_SETAFFINITY;

```

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

### 7983행

```c

	kthread_set_per_cpu(idle, cpu);

```

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

### 7989행

```c

	set_cpus_allowed_common(idle, &ac);

```

앞에서 만든 마스크를 task의 허용 CPU 정보에 반영합니다. 부팅 중 또는 아직 온라인이 아닌 CPU 준비라는 호출 조건을 전제로 합니다.

### 8000행

```c

	rcu_read_lock();

```

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

### 8001행

```c

	__set_task_cpu(idle, cpu);

```

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

### 8002행

```c

	rcu_read_unlock();

```

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

### 8004행

```c

	rq->idle = idle;

```

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

### 8005행

```c

	rq_set_donor(rq, idle);

```

초기 donor도 idle로 설정합니다. 이 버전의 자원 계상 대상과 실행 상태를 함께 준비합니다.

### 8006행

```c

	rcu_assign_pointer(rq->curr, idle);

```

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

### 8007행

```c

	idle->on_rq = TASK_ON_RQ_QUEUED;

```

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

### 8008행

```c

	idle->on_cpu = 1;

```

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

### 8009행

```c

	raw_spin_rq_unlock(rq);

```

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

### 8010행

```c

	raw_spin_unlock_irqrestore(&idle->pi_lock, flags);

```

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

### 8013행

```c

	init_idle_preempt_count(idle, cpu);

```

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

### 8018행

```c

	idle->sched_class = &idle_sched_class;

```

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

### 8019행

```c

	ftrace_graph_init_idle_task(idle, cpu);

```

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

### 8020행

```c

	vtime_init_idle(idle, cpu);

```

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

### 8021행

```c

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

```

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

