전체 흐름
start_kernel()은 타이머 인터럽트와 IRQ 초기화에 앞서 sched_init()을 호출해 scheduler의 초기 자료구조를 마련한다. 이 함수는 가능한 CPU의 runqueue 구성요소를 초기화하고 부트 CPU의 current를 idle task로 설정한다. SMP domain·NUMA를 포함한 후기 설정은 훨씬 뒤의 smp_init() 다음 sched_init_smp()에서 한다.
sched_init() 뒤 scheduler가 동작할 기초는 있지만, 이 시점에 SMP scheduling domain이 전부 설정됐다는 뜻은 아니다.
for_each_possible_cpu는 각 rq의 지정 필드·class별 queue를 설정한다. 구조체 전체를 memset(0) 한다고 읽으면 안 된다.
sched_init()start_kernel() 초기, IRQ·timer보다 앞smp_init()CPU bring-up 시점sched_init_smp()SMP 빌드에서 domain·NUMA 등 후기 설정init/main.c:935–946, 1539–1542. ④의 상세 작업은 CONFIG_SMP에 따라 달라진다. 비-SMP 구현은 granularity 설정만 한다.
소스 코드 위치
Linux v6.6에서 대조할 파일
init/main.c — sched_init과 후기 sched_init_smp 호출 순서
kernel/sched/core.c — sched_init, init_idle, sched_init_smp 구현
kernel/sched/fair.c — init_cfs_rq와 fair class 세부 구현
아래 발췌는 사이트가 보존한 v6.6 core.c의 표기된 연속 행이며, 호출 순서는 보존 init/main.c에서 확인했다. core.c 파일 전체가 공식 v6.6 원본과 바이트 단위로 같은지는 입증되지 않았으므로 공식 태그도 함께 대조한다.
공식 태그: core.c · init/main.c · fair.c
대표 코드
기존 페이지의 첫 코드 박스는 sched_init_smp(), 둘째는 __sched_fork()에서 시작해 제목과 다른 함수를 보여 주었다. 아래는 함수 시작과 필요한 구간을 나눠 놓은 보존 소스 발췌다. 생략된 중간 분기는 이어지는 원문에서 확인한다.
sched_init(): class 배치 검사
core.c 9909–9922행void __init sched_init(void)
{
unsigned long ptr = 0;
int i;
/* Make sure the linker didn't screw up */
BUG_ON(&idle_sched_class != &fair_sched_class + 1 ||
&fair_sched_class != &rt_sched_class + 1 ||
&rt_sched_class != &dl_sched_class + 1);
#ifdef CONFIG_SMP
BUG_ON(&dl_sched_class != &stop_sched_class + 1);
#endif
wait_bit_init();BUG_ON은 linker가 배치한 scheduling class 순서를 검증한다. init_idle()이 전체 class 순서를 만든다는 설명은 맞지 않는다.
sched_init(): 가능한 CPU의 rq 구성요소
core.c 9973–9985행 for_each_possible_cpu(i) {
struct rq *rq;
rq = cpu_rq(i);
raw_spin_lock_init(&rq->__lock);
rq->nr_running = 0;
rq->calc_load_active = 0;
rq->calc_load_update = jiffies + LOAD_FREQ;
init_cfs_rq(&rq->cfs);
init_rt_rq(&rq->rt);
init_dl_rq(&rq->dl);
#ifdef CONFIG_FAIR_GROUP_SCHED
INIT_LIST_HEAD(&rq->leaf_cfs_rq_list);각 possible CPU에서 lock과 nr_running, CFS·RT·DL queue 등을 초기화한다. 이 발췌는 루프 앞부분이며 CONFIG_FAIR_GROUP_SCHED, CONFIG_SMP 등의 뒤쪽 분기까지 대표하지 않는다.
sched_init(): 부트 idle와 완료 표시
core.c 10075–10098행 /*
* Make us the idle thread. Technically, schedule() should not be
* called from this thread, however somewhere below it might be,
* but because we are the idle thread, we just pick up running again
* when this runqueue becomes "idle".
*/
init_idle(current, smp_processor_id());
calc_load_update = jiffies + LOAD_FREQ;
#ifdef CONFIG_SMP
idle_thread_set_boot_cpu();
balance_push_set(smp_processor_id(), false);
#endif
init_sched_fair_class();
psi_init();
init_uclamp();
preempt_dynamic_init();
scheduler_running = 1;
}init_idle(current, smp_processor_id())는 부트 CPU의 current를 idle task로 만드는 호출이다. 이어 fair class·PSI·uclamp·dynamic preempt를 준비한 뒤 scheduler_running=1을 쓴다. 이 값만으로 후기 SMP domain 완료를 뜻하지 않는다.
init_idle(): 인자와 초기 상태
core.c 9250–9267행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();대상 idle task와 CPU의 rq를 받아 __sched_fork() 기본값을 적용하고 task/rq 잠금으로 진입한다. __sched_fork() 자체가 init_idle()의 정의는 아니다.
init_idle(): rq 연결과 idle class
core.c 9294–9319행 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
}rq->idle, rq->curr, idle->on_rq를 설정한다. idle_sched_class 대입은 잠금 해제와 preempt-count 설정 뒤에 나온다. CPU affinity와 on_cpu, comm 관련 일부 행은 SMP 조건부다.
후기 sched_init_smp()의 조건부 구현
core.c 9851–9888행void __init sched_init_smp(void)
{
sched_init_numa(NUMA_NO_NODE);
/*
* There's no userspace yet to cause hotplug operations; hence all the
* CPU masks are stable and all blatant races in the below code cannot
* happen.
*/
mutex_lock(&sched_domains_mutex);
sched_init_domains(cpu_active_mask);
mutex_unlock(&sched_domains_mutex);
/* Move init over to a non-isolated CPU */
if (set_cpus_allowed_ptr(current, housekeeping_cpumask(HK_TYPE_DOMAIN)) < 0)
BUG();
current->flags &= ~PF_NO_SETAFFINITY;
sched_init_granularity();
init_sched_rt_class();
init_sched_dl_class();
sched_smp_initialized = true;
}
static int __init migration_init(void)
{
sched_cpu_starting(smp_processor_id());
return 0;
}
early_initcall(migration_init);
#else
void __init sched_init_smp(void)
{
sched_init_granularity();
}
#endif /* CONFIG_SMP */CONFIG_SMP 분기는 NUMA/domain, class 후기 설정과 sched_smp_initialized를 다룬다. #else에서는 granularity만 설정한다. sched_init() 초기 경로와 합치지 않는다.
함수별 분석
sched_init()은 전체 rq를 무조건 0으로 지우는 함수가 아니다. class 배치와 조건부 root task group을 처리하고, CPU별 queue 필드를 초기화한 다음 부트 CPU idle task를 연결한다. 후기 토폴로지는 별도 호출에서 완성된다.
sched_init() 안에서 확인되는 네 관문BUG_ON으로 linker 순서 검사init_idle(current,...) 후 fair/PSI/uclamp, scheduler_running=1core.c:9909–10098. 이는 조건부 코드를 포함한 주요 구간의 요약이며 함수 내부의 모든 필드를 열거하지 않는다.
1. class와 그룹
BUG_ON은 class 객체 주소의 linker 순서를 확인한다. CONFIG_FAIR_GROUP_SCHED, CONFIG_RT_GROUP_SCHED, CONFIG_CGROUP_SCHED는 root task group 코드를 바꾼다. 조건부 배열용 kzalloc(..., GFP_NOWAIT) 결과는 바로 검사하지 않으므로 할당 성공을 전제하지 않는다.
2. CPU별 rq
for_each_possible_cpu는 class별 rq를 초기화한다. SMP 분기는 rq->sd/rd에 먼저 NULL을 대입하고 같은 루프에서 rq_attach_root(rq, &def_root_domain)을 호출한다. 이 초기 root domain 연결과 후기 scheduling domain 구성은 구분한다.
3. 부트 idle와 후기 설정
초기 함수는 현재 부트 CPU의 idle 기준을 세운다. 나중에 smp_init() 뒤 실행되는 sched_init_smp()는 SMP 빌드에서 domain/NUMA를 설정한다. 비-SMP 분기는 같은 작업을 하는 축약판이 아니다.
| 위치 | 코드에서 확인할 일 | 범위 제한 |
|---|---|---|
sched_init 시작 | class 주소 순서 BUG_ON | init_idle의 class 순서 생성 아님 |
| CPU별 루프 | 특정 rq 필드와 class별 queue 초기화 | 전체 rq memset 아님 |
| 부트 idle | init_idle(current,...) | 모든 CPU의 idle task 생성 아님 |
| 후기 SMP | sched_init_smp 조건부 domain 설정 | 초기 sched_init과 별도 시점 |
구조체와 필드
root task group, CPU별 rq, 부트 CPU idle task를 한 줄의 생성 체인으로 보지 않는다. 설정별로 존재하는 root 자료구조와 rq 구성요소를 먼저 분리해야 한다.
sched_init()이 만지는 세 층rqCFS·RT·DL queue, lock, SMP 상태 등current현재 부트 CPU의 idle task로 rq에 연결CONFIG_SMP 루프: rq->sd/rd = NULL 대입 → rq_attach_root(...) 호출. 전체 scheduling domain 구성은 후기 sched_init_smp()에서 다룬다.core.c:9930–9971, 9973–10081. 그림의 세 층은 동일한 객체가 아니며 모두 같은 방식으로 할당되거나 공개되는 상태도 아니다.
| 대상 | 초기화에서 맡는 역할 | 설정 또는 시점 제한 |
|---|---|---|
| class 객체 주소 | BUG_ON으로 linker 배치 확인 | CONFIG_SMP에서 stop class 검사 추가 |
root_task_group | group scheduler용 root 필드·리스트 | FAIR/RT/CGROUP 빌드 옵션별 |
rq | CPU별 실행 대기 구조와 clock·balance 필드 | SMP/NO_HZ/SCHED_CORE 필드 조건부 |
init_task/current | 부트 CPU idle로 연결되는 task | 모든 CPU의 idle task와 같지 않음 |
실행 단계와 상태 변화
init_idle()은 task 기본값부터 rq 연결과 idle class 지정까지 한 함수에서 다룬다. idle class 지정이 rq 연결보다 먼저라고 단정하지 말고, 소스에서 저장 위치와 잠금 경계를 확인한다.
init_idle()의 부트 CPU task 연결__sched_forkidle task의 scheduler 필드 기본값rq->idle, rq->curr, on_rq 설정idle_sched_class 지정core.c:9250–9319. 일부 CPU affinity·on_cpu·이름 설정은 CONFIG_SMP 조건부다. 부트 CPU 호출은 core.c:10081이다.
| 단계 | 보존 소스에서 확인되는 변화 | 넘겨짚지 말 것 |
|---|---|---|
| class 검사 | linker 주소 배치 검증 | runtime class 객체를 여기서 생성한다는 뜻 아님 |
| rq 루프 | possible CPU의 지정 필드·queue 초기화 | 전체 rq zeroing 아님 |
| 부트 idle | rq->idle/curr와 idle class 설정 | 모든 CPU idle task 완료 아님 |
scheduler_running=1 | 초기 함수의 끝 표시 | SMP domain·NUMA 후기 설정 완료 아님 |
불변 조건과 놓치기 쉬운 분기
sched_init()의 BUG_ON은 linker class 배치를 검사한다. init_idle()은 한 task에 idle_sched_class를 지정한다.
루프는 가능한 CPU마다 특정 queue와 상태를 초기화한다. 설명에 전체 rq 메모리 zeroing을 끼워 넣지 않는다.
root group, SMP, NO_HZ, SCHED_CORE 관련 필드와 함수 경로는 각 CONFIG_* 조건 아래에서만 성립한다.
scheduler_running=1 이후에도 smp_init()와 sched_init_smp()가 남는다. 초기 함수의 완료를 full topology와 동일시하지 않는다.
계측과 검증
정적 코드에서 확인한 초기화 순서와 부팅 뒤 관찰 가능한 scheduler 상태를 분리한다. sched_init()은 initcall이 아닌 직접 호출이며, v6.6 init/main.c에서 ftrace_init()·early_trace_init()(930·933행)는 이 함수보다 앞서지만, 일반 trace event가 사용 가능해지는 trace_init()(964행)는 뒤에 나온다.
| 자료 | 확인 가능한 것 | 한계 |
|---|---|---|
init/main.c, core.c | 호출 순서와 CONFIG별 코드 분기 | 특정 보드의 실행 성공 증명은 아님 |
| 커널 빌드 설정 | SMP, group scheduler, NO_HZ 선택 | 실제 runtime field 값은 별도 확인 |
/proc/sched_debug | 제공되는 빌드라면 부팅 뒤 rq/domain 상태 | 초기 함수 내부 단계 기록 아님 |
sched_switch event | 사용 가능할 때 이후 context switch | ftrace:sched_init 기본 event나 초기 단계 증거 아님 |
grep -E 'CONFIG_(SMP|FAIR_GROUP_SCHED|RT_GROUP_SCHED|CGROUP_SCHED)=' /boot/config-$(uname -r)
test -r /proc/sched_debug && head -n 30 /proc/sched_debug배포판의 config 경로와 /proc/sched_debug 제공 여부는 다를 수 있다.
추가 확인 사항
- 현재 커널 빌드에서 root task group과 SMP/NO_HZ/SCHED_CORE 조건부 코드는 각각 포함되는가?
- 부트 CPU의
current가rq->idle과rq->curr에 연결되는 정확한 지점은 어디인가? - 해당 아키텍처에서 다른 CPU의 idle task 준비와 후기 scheduling domain 연결은 어느 경로에서 이루어지는가?
- 부팅 후의 rq/domain 출력과 초기 소스 순서를 혼동하지 않으려면 어떤 별도 관측이 필요한가?