연구 관점
start_kernel()의 큰 축은 소스에 적힌 호출 순서입니다. 호출되었다는 사실만으로 해당 하위 시스템의 모든 기능이 준비되었다고 단정하지 않습니다.
아래 도식은 Linux v6.6 init/main.c:869–1068의 경계를 표시합니다. arch_call_rest_init()는 아키텍처가 재정의할 수 있는 약한 함수이며, 이 파일의 기본 구현은 rest_init()을 호출합니다.
초기화 순서
부팅 중 핵심 경계를 앞에서 뒤로 읽습니다. 각 칸의 숫자는 init/main.c 원문 행이며, 이 도식은 실행 시간이나 CPU 간 동시성을 뜻하지 않습니다.
local_irq_disable()882행setup_arch()893행 · 내부 동작은 여기서 확정 불가mm_core_init() → sched_init()928 → 940행early_irq_init() → time_init()971–980행local_irq_enable()995행console_init(), fork_init(), vfs_caches_init()1004, 1045, 1052행arch_call_rest_init()1068행rest_init()약한 기본 구현 821–824행 · 재정의 가능번호는 원문에 적힌 호출 선후 관계만 나타냅니다. rest_init() 직행은 약한 기본 구현의 경로입니다.
| 원문 위치 | 직접 확인되는 호출 | 이 파일만으로 확정할 수 없는 점 |
|---|---|---|
| 882–893 | IRQ를 끄고 setup_arch() 호출 | 아키텍처가 구성한 메모리·DT·CPU 세부 상태 |
| 928–940 | mm_core_init() 뒤 sched_init() | 각 allocator 및 스케줄러 자료구조의 내부 준비 단계 |
| 971–995 | IRQ·timer·timekeeping 초기화 뒤 IRQ 활성 | 첫 인터럽트가 실제로 도착한 시각 |
| 1068, 821–824 | arch_call_rest_init(); 약한 기본 구현은 rest_init() 호출 | 아키텍처별 재정의 여부와 그 구현의 추가 동작 |
상태 변화
timekeeping_init()은 IRQ를 켜서 생기는 호출이 아닙니다. v6.6 원문에서는 timekeeping_init()이 979행, local_irq_enable()이 995행입니다.
local_irq_disable()882행early_irq_init()init_IRQ()tick_init()timekeeping_init()time_init()971–980행local_irq_enable()995행초기화 함수 호출 시각과 하드웨어 이벤트 발생 시각은 다릅니다. IRQ 활성만으로 timer interrupt가 이미 발생했다고 추론할 수 없습니다.
| 읽을 명제 | v6.6 본문으로 확인 가능한 범위 |
|---|---|
sched_init()은 timer interrupt 전 | 935–940행 주석과 995행의 IRQ 활성 순서가 이를 뒷받침합니다. 같은 주석은 전체 topology 구성이 나중임을 명시합니다. |
timekeeping_init()은 IRQ 활성 전 | 979행에서 호출, 995행에서 local_irq_enable() 호출. |
| IRQ 활성 후 실행할 수 있는 코드 | 이 파일은 997행 이후 늦은 초기화 호출을 보여줍니다. 특정 IRQ나 scheduler tick의 실제 실행 시각은 보여주지 않습니다. |
경계별 확인
소스가 보장하는 순서와 실기기에서 관찰할 현상을 나눠 읽습니다. 아래 비교는 오류 증상을 특정 초기화 함수에 자동으로 귀속하지 않습니다.
setup_arch() → mm_core_init() → sched_init()893 → 928 → 940행timekeeping_init() → local_irq_enable()979 → 995행| 질문 | 먼저 볼 원문 | 다음 검증 |
|---|---|---|
| 초기 메모리 호출 순서가 맞나? | 920–940행의 주석과 호출 목록 | mm_core_init() 구현과 아키텍처 메모리 설정 |
| 타이머 초기화가 IRQ보다 먼저인가? | 971–980행, 994–995행 | 실제 타이머 이벤트는 대상 시스템 trace |
| init 프로세스 생성으로 어떻게 넘어가나? | 1068행, 821–824행, 680–727행 | 아키텍처가 약한 함수를 재정의했는지 확인 |
원문에서 세울 수 있는 불변 조건
local_irq_disable()(882) 뒤의 초기화가local_irq_enable()(995)보다 앞에 적혀 있습니다. 경로가 조기에 IRQ를 켜는지 942–944, 992행에서도 검사합니다.mm_core_init()(928) 뒤에sched_init()(940)을 호출합니다. 이것만으로 모든 메모리·스케줄러 기능의 완성 시점을 단정하지 않습니다.timekeeping_init()(979)은 IRQ 활성화(995) 전 호출입니다.arch_call_rest_init()(1068)의 약한 기본 구현(821–824)은rest_init()으로 이어집니다. 아키텍처별 재정의는 별도 확인이 필요합니다.- 기본 경로의
rest_init()(680–727)은 PID 1용 thread,kthreadd를 순서대로 생성하고 boot idle 경로로 들어갑니다.
읽기와 검증의 범위
arch_call_rest_init()1068행rest_init()821–824행user_mode_thread(kernel_init) → kernel_thread(kthreadd)691 → 704행complete() → schedule_preempt_disabled() → cpu_startup_entry()718 → 724 → 726행kernel_init()은 kthreadd_done completion을 기다립니다(1432–1437행).| 검증 대상 | 이 글의 근거 | 추가로 필요한 자료 |
|---|---|---|
| 정적 호출 순서 | Linux v6.6 init/main.c의 표시 행 | 없음 |
| 아키텍처별 진입·override | 약한 기본 구현의 존재만 확인 | 대상 아키텍처 소스와 설정 |
| 실행 시간·첫 IRQ·작업 전환 | 이 소스만으로 확인 불가 | 대상 시스템의 실행 trace |
원본 코드
이 글은 Linux v6.6의 init/main.c를 기준으로 씁니다.
아래 raw 파일은 홈페이지 폴더에 같이 둔 로컬 복사본입니다.
head.S 다음에 무슨 일이 생기나
아키텍처 진입 코드가 start_kernel()으로 넘긴 뒤의 공통 C 초기화 순서를
이 글에서 읽습니다. 진입 직전의 레지스터·MMU·스택 상태는 해당 아키텍처의
head.S와 빌드 설정을 별도로 대조해야 합니다.
이 함수는 memory, scheduler, IRQ, time, VFS, process, security, network namespace
초기화 호출의 순서를 보여줍니다. driver 초기화와 initcall은 뒤의
kernel_init_freeable() → do_basic_setup() 경로에서 살핍니다.
각 함수의 내부 결과는 이 파일의 호출 한 줄보다 더 넓은 근거가 필요합니다.
869–899: start_kernel 진입부
L869-L899: boot CPU와 arch setup 전 준비
asmlinkage __visible __init __no_sanitize_address __noreturn __no_stack_protector
void start_kernel(void)
{
char *command_line;
char *after_dashes;
set_task_stack_end_magic(&init_task);
smp_setup_processor_id();
debug_objects_early_init();
init_vmlinux_build_id();
cgroup_init_early();
local_irq_disable();
early_boot_irqs_disabled = true;
/*
* Interrupts are still disabled. Do necessary setups, then
* enable them.
*/
boot_cpu_init();
page_address_init();
pr_notice("%s", linux_banner);
early_security_init();
setup_arch(&command_line);
setup_boot_config();
setup_command_line(command_line);
setup_nr_cpu_ids();
setup_per_cpu_areas();
smp_prepare_boot_cpu(); /* arch-specific boot-cpu hooks */
boot_cpu_hotplug_init();
설명: C 코드에 들어왔지만 아직 일반 커널 런타임은 아닙니다. IRQ는 꺼져 있고, allocator와 scheduler도 완전히 준비되지 않았습니다.
더 읽기: 함수 선언의 __init은 초기화 section, __noreturn은 호출자에게 복귀하지 않는 함수임을 나타냅니다. 아키텍처별 호출부의 실제 명령과 후속 처리는 해당 head.S 원문에서 별도로 확인해야 합니다.
set_task_stack_end_magic(&init_task)는 875행에서 호출됩니다. stack overflow 감지 등 세부 동작은 이 함수 구현을 확인해야 하며, 이 호출만으로 현재 태스크의 다른 상태를 단정할 수 없습니다.
smp_setup_processor_id()(876)와 boot_cpu_init()(889)이 초기 호출 목록에 있습니다. boot CPU 식별 관련 내부 상태는 각각의 구현에서 확인합니다.
setup_arch(&command_line)는 아키텍처별 구현으로 들어가는 호출입니다. 이 파일만으로 DTB·paging·CPU feature 등 개별 결과를 확정할 수 없으므로, 대상 아키텍처의 구현을 함께 봐야 합니다.
901-918: kernel command line 처리
L901-L918: early param과 일반 param 분리
pr_notice("Kernel command line: %s\n", saved_command_line);
/* parameters may set static keys */
jump_label_init();
parse_early_param();
after_dashes = parse_args("Booting kernel",
static_command_line, __start___param,
__stop___param - __start___param,
-1, -1, NULL, &unknown_bootoption);
print_unknown_bootoptions();
if (!IS_ERR_OR_NULL(after_dashes))
parse_args("Setting init args", after_dashes, NULL, 0, -1, -1,
NULL, set_init_arg);
if (extra_init_args)
parse_args("Setting extra init args", extra_init_args,
NULL, 0, -1, -1, NULL, set_init_arg);
/* Architectural and non-timekeeping rng init, before allocator init */
random_init_early(command_line);
설명: 커널 command line을 early param, kernel param, init process 인자로 나눠 처리합니다.
더 읽기: parse_early_param() 호출은 904행, 일반 parse_args() 호출은 905행입니다. 특정 옵션의 등록 방식은 그 옵션의 선언부를 확인해야 합니다.
parse_args("Booting kernel", ...)는 905–908행에서 __start___param과 __stop___param 범위를 받습니다. 다른 옵션 처리 경로까지 이 한 호출에 포함된다고 단정하지 않습니다.
command line의 -- 뒤쪽은 커널이 아니라 userspace init에 넘길 인자로 취급됩니다. 그래서 after_dashes를 다시 set_init_arg로 parse합니다. bring-up 중 init argument가 이상하게 들어간다면 이 경계를 확인해야 합니다.
920-940: early memory와 scheduler
L920-L940: page allocator 전후의 큰 초기화
/*
* These use large bootmem allocations and must precede
* initalization of page allocator
*/
setup_log_buf(0);
vfs_caches_init_early();
sort_main_extable();
trap_init();
mm_core_init();
poking_init();
ftrace_init();
/* trace_printk can be enabled here */
early_trace_init();
/*
* Set up the scheduler prior starting any interrupts (such as the
* timer interrupt). Full topology setup happens at smp_init()
* time - but meanwhile we still have a functioning scheduler.
*/
sched_init();
설명: 메모리와 scheduler가 여기서 처음 큰 형태를 갖춥니다. 이 이후부터 커널은 훨씬 일반적인 실행 환경에 가까워집니다.
더 읽기: setup_log_buf와 vfs_caches_init_early는 page allocator가 완전히 서기 전에 큰 bootmem allocation을 써야 해서 앞에 옵니다. 순서가 그냥 취향이 아니라 allocator 준비 단계와 직접 연결됩니다.
mm_core_init()은 928행에서 호출됩니다. 앞선 주석은 큰 bootmem allocation을 사용하는 호출을 page allocator 초기화보다 먼저 두어야 한다고 설명합니다. allocator 내부 단계는 mm_core_init() 구현에서 따로 확인해야 합니다.
935–939행 주석은 timer interrupt 전에 sched_init()을 호출하고, 전체 topology setup은 뒤의 smp_init() 때 이뤄진다고 설명합니다. 이는 본문의 호출 순서와 주석에 근거한 설명이며, 개별 scheduler 자료구조의 완성 시점을 뜻하지는 않습니다.
945-980: IRQ, timer, timekeeping
L945-L980: interrupt와 time 기반 시설
radix_tree_init();
maple_tree_init();
/*
* Set up housekeeping before setting up workqueues to allow the unbound
* workqueue to take non-housekeeping into account.
*/
housekeeping_init();
/*
* Allow workqueue creation and work item queueing/cancelling
* early. Work item execution depends on kthreads and starts after
* workqueue_init().
*/
workqueue_init_early();
rcu_init();
/* Trace events are available after this */
trace_init();
if (initcall_debug)
initcall_debug_enable();
context_tracking_init();
/* init some links before init_ISA_irqs() */
early_irq_init();
init_IRQ();
tick_init();
rcu_init_nohz();
init_timers();
srcu_init();
hrtimers_init();
softirq_init();
timekeeping_init();
time_init();
설명: IRQ와 시간 기반 subsystem이 여기서 순서대로 올라옵니다. 이 순서가 맞아야 뒤에서 IRQ를 켤 수 있습니다.
더 읽기: rcu_init()은 961행에서 호출됩니다. RCU 내부 단계와 read-side 사용 가능 조건은 RCU 구현 및 설정에 따라 확인해야 합니다.
971–972행에서 early_irq_init(), init_IRQ()를 차례로 호출합니다. IRQ descriptor와 interrupt controller의 실제 구성은 각각의 구현 및 아키텍처 소스에서 확인해야 합니다.
973–980행의 tick_init(), init_timers(), hrtimers_init(), timekeeping_init(), time_init() 호출은 모두 995행의 IRQ 활성화보다 앞섭니다. 각 시간 하위 시스템의 구현은 이 호출 목록만으로 설명되지 않습니다.
softirq_init()도 이 구간 978행에 있습니다. 실제 softirq 종류와 실행 시점은 관련 구현 및 실행 trace를 따로 확인해야 합니다.
981-1068: IRQ enable 이후 core subsystem
L981-L1068: console, process, VFS, namespace, security, net
/* This must be after timekeeping is initialized */
random_init();
/* These make use of the fully initialized rng */
kfence_init();
boot_init_stack_canary();
perf_event_init();
profile_init();
call_function_init();
WARN(!irqs_disabled(), "Interrupts were enabled early\n");
early_boot_irqs_disabled = false;
local_irq_enable();
kmem_cache_init_late();
/*
* HACK ALERT! This is early. We're enabling the console before
* we've done PCI setups etc, and console_init() must be aware of
* this. But we do want output early, in case something goes wrong.
*/
console_init();
if (panic_later)
panic("Too many boot %s vars at `%s'", panic_later,
panic_param);
lockdep_init();
/*
* Need to run this when irqs are enabled, because it wants
* to self-test [hard/soft]-irqs on/off lock inversion bugs
* too:
*/
locking_selftest();
#ifdef CONFIG_BLK_DEV_INITRD
if (initrd_start && !initrd_below_start_ok &&
page_to_pfn(virt_to_page((void *)initrd_start)) < min_low_pfn) {
pr_crit("initrd overwritten (0x%08lx < 0x%08lx) - disabling it.\n",
page_to_pfn(virt_to_page((void *)initrd_start)),
min_low_pfn);
initrd_start = 0;
}
#endif
setup_per_cpu_pageset();
numa_policy_init();
acpi_early_init();
if (late_time_init)
late_time_init();
sched_clock_init();
calibrate_delay();
arch_cpu_finalize_init();
pid_idr_init();
anon_vma_init();
#ifdef CONFIG_X86
if (efi_enabled(EFI_RUNTIME_SERVICES))
efi_enter_virtual_mode();
#endif
thread_stack_cache_init();
cred_init();
fork_init();
proc_caches_init();
uts_ns_init();
key_init();
security_init();
dbg_late_init();
net_ns_init();
vfs_caches_init();
pagecache_init();
signals_init();
seq_file_init();
proc_root_init();
nsfs_init();
cpuset_init();
cgroup_init();
taskstats_init_early();
delayacct_init();
acpi_subsystem_init();
arch_post_acpi_subsys_init();
kcsan_init();
/* Do the rest non-__init'ed, we're now alive */
arch_call_rest_init();
설명: IRQ를 켠 뒤부터는 subsystem 초기화가 넓게 펼쳐집니다. 여기서 커널 전체 토픽이 거의 다 갈라집니다.
local_irq_enable()은 995행의 명시적 경계입니다. 1011–1016행 주석은 locking_selftest()를 IRQ 활성화 뒤에 호출하는 이유를 설명합니다. 실제 첫 IRQ 수신 시각은 이 파일만으로 알 수 없습니다.
999–1003행 주석은 console_init()이 PCI setup 전에 이르게 실행되는 이유를 설명합니다. 어떤 콘솔이 실제로 등록되는지는 구성과 드라이버에 달려 있습니다.
cred_init()(1044), fork_init()(1045), signals_init()(1054)은 process 관련 구현으로 들어가는 호출입니다. 내부 자료구조의 준비 범위는 각각의 함수에서 확인해야 합니다.
vfs_caches_init()(1052), pagecache_init()(1053), proc_root_init()(1056)은 파일시스템 관련 초기화의 입구입니다. 상세 결과는 각 구현에서 대조해야 합니다.
net_ns_init()은 1051행에서 호출됩니다. 네트워크 장치와 protocol의 실제 등록 경로는 다른 소스와 설정에 따라 확인해야 합니다.
680-727: rest_init(), pid 1과 kthreadd
L680-L727: init thread, kthreadd, idle thread
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이 될 init thread와 kernel thread를 낳는 kthreadd가 만들어집니다. boot CPU는 이후 idle loop로 들어갑니다.
더 읽기: user_mode_thread(kernel_init, ...)가 먼저 호출됩니다. init task가 pid 1을 얻어야 하기 때문입니다. 다만 이 thread가 곧바로 kthread를 만들려고 하면 kthreadd가 아직 없으므로, 뒤에서 completion으로 동기화합니다.
kernel_thread(kthreadd, ...)는 704행에서 호출되고, 반환된 PID로 찾은 task를 kthreadd_task에 저장합니다(705–707행). 이후 kthread 생성의 세부 경로는 다른 구현에서 확인해야 합니다.
system_state = SYSTEM_SCHEDULING(716), complete()(718), schedule_preempt_disabled()(724), cpu_startup_entry()(726)이 소스 순서입니다. 이 순서와 PID 1·kthreadd의 실제 CPU 실행 시각은 구분해야 합니다.
1323-1330: driver_init과 initcall
L1323-L1330: do_basic_setup()
static void __init do_basic_setup(void)
{
cpuset_init_smp();
driver_init();
init_irq_proc();
do_ctors();
do_initcalls();
}
설명: do_basic_setup()의 본문은 driver_init() 뒤에 do_initcalls()를 호출합니다. 이 함수는 start_kernel() 안에 직접 나타나는 호출이 아니라, 뒤의 PID 1 kernel_init_freeable() 경로에서 호출됩니다(1547행).
driver_init()은 1326행에서 호출됩니다. driver core의 구체적인 bus·class·device 준비 범위는 해당 구현에서 확인해야 합니다.
do_initcalls()는 1329행에 있습니다. level 순서와 각 initcall의 등록 위치는 이 함수의 구현과 호출 대상을 더 추적해야 합니다.
특정 장치의 probe가 실행되지 않는 원인은 이 호출 순서만으로 결정할 수 없습니다. 해당 드라이버의 등록·매칭·defer 경로를 별도 대조해야 합니다.
1432-1500: kernel_init과 userspace init 실행
L1432-L1500: initmem 해제 후 /init 또는 /sbin/init 실행
/*
* Wait until kthreadd is all set-up.
*/
wait_for_completion(&kthreadd_done);
kernel_init_freeable();
/* need to finish all async __init code before freeing the memory */
async_synchronize_full();
system_state = SYSTEM_FREEING_INITMEM;
kprobe_free_init_mem();
ftrace_free_init_mem();
kgdb_free_init_mem();
exit_boot_config();
free_initmem();
mark_readonly();
/*
* Kernel mappings are now finalized - update the userspace page-table
* to finalize PTI.
*/
pti_finalize();
system_state = SYSTEM_RUNNING;
numa_default_policy();
rcu_end_inkernel_boot();
do_sysctl_args();
if (ramdisk_execute_command) {
ret = run_init_process(ramdisk_execute_command);
if (!ret)
return 0;
pr_err("Failed to execute %s (error %d)\n",
ramdisk_execute_command, ret);
}
/*
* We try each of these until one succeeds.
*
* The Bourne shell can be used instead of init if we are
* trying to recover a really broken machine.
*/
if (execute_command) {
ret = run_init_process(execute_command);
if (!ret)
return 0;
panic("Requested init %s failed (error %d).",
execute_command, ret);
}
if (CONFIG_DEFAULT_INIT[0] != '\0') {
ret = run_init_process(CONFIG_DEFAULT_INIT);
if (ret)
pr_err("Default init %s failed (error %d)\n",
CONFIG_DEFAULT_INIT, ret);
else
return 0;
}
if (!try_to_run_init_process("/sbin/init") ||
!try_to_run_init_process("/etc/init") ||
!try_to_run_init_process("/bin/init") ||
!try_to_run_init_process("/bin/sh"))
return 0;
panic("No working init found. Try passing init= option to kernel. "
"See Linux Documentation/admin-guide/init.rst for guidance.");
설명: 커널 초기화가 끝나면 init section을 해제하고, 마지막으로 userspace init을 실행합니다.
kernel_init()은 1435행에서 kthreadd_done을 기다립니다. rest_init()은 kthreadd_task를 설정한 뒤 718행에서 completion을 완료합니다. 이것은 실행 순서의 동기화이며 kthreadd가 이미 CPU에서 실행되었다는 증거는 아닙니다.
kernel_init_freeable()은 별도 함수입니다. SMP, workqueue, initcall 관련 세부 호출은 해당 함수 본문에서 확인할 수 있으며, 이 발췌 구간만으로 그 결과를 단정하지 않습니다.
free_initmem()은 1446행에서 호출됩니다. 초기화 전용 메모리가 해제되는 범위와 시점은 해당 함수 구현과 아키텍처에 따라 확인해야 합니다.
1462–1500행에서 ramdisk init 시도 뒤 init= 지정 명령을 시도합니다. 지정한 init= 실행이 실패하면 1480행에서 panic하므로 그 뒤 기본 init 후보로 계속 넘어가지 않습니다. 지정이 없으면 CONFIG_DEFAULT_INIT과 표준 경로 후보를 순서대로 검사합니다.