운영체제 기본/x86-64 Linux의 BIOS 부팅/PID 1 생성
시작 상태: 부트 태스크가 start_kernel()의 주요 초기화를 마치고 rest_init()에 도달했다.
끝 상태: 별도로 생성된 PID 1이 kernel_init()에서 나머지 커널 초기화를 마치고 사용자 프로그램을 선택할 준비를 한다. 기존 PID 0은 idle 경로로 들어간다.
기준은 Linux v6.12다. 함수 호출과 그 함수를 시작점으로 하는 태스크 생성은 별개의 작업이다.
1. PID 0이 PID 1로 바뀌는 것은 아니다
rest_init()은 먼저 user_mode_thread(kernel_init, NULL, CLONE_FS)를 호출해 PID 1을 만든다. 이어 kernel_thread()로 kthreadd(PID 2)를 만든다. 기존 부트 태스크는 스케줄러를 거쳐 cpu_startup_entry()의 idle 경로로 진행한다.[1]
즉 rest_init()이 자기 스택에서 평범하게 kernel_init()을 호출하고 그대로 init이 되는 구조가 아니다. 새 태스크에는 별도 실행 문맥이 생긴다. 이후 어떤 태스크가 CPU를 사용할지는 스케줄링에 따라 달라진다. PID 1은 새로 생겼지만 기존 PID 0이 사라진 것은 아니다.[2]
동기화와 중간 처리를 생략한 태스크 분기:
PID 0: start_kernel → rest_init ─────────────→ idle
├─ 새 PID 1 → kernel_init
└─ 새 PID 2 → kthreadd
2. user_mode_thread인데 왜 커널 함수를 실행하는가
함수 이름의 user mode를 “이 호출 직후 CPU가 사용자 모드가 된다”로 읽으면 안 된다. user_mode_thread()는 시작 함수와 인자를 kernel_clone_args에 담아 kernel_clone()에 넘긴다. 이 경우 시작 함수는 커널 내부 함수인 kernel_init()이다.[2]
kernel_thread()와의 중요한 차이는 kthread 속성을 설정하지 않는다는 점이다. 계속 커널 스레드로 남을 태스크와 나중에 사용자 실행 이미지를 갖게 될 태스크의 생성 방식이 구별된다. 실제로 kernel_execve()는 PF_KTHREAD가 설정된 태스크의 호출을 거부한다. 따라서 “PID 1은 처음에 커널 코드를 실행한다”는 설명은 맞지만, 이를 곧바로 “일반 커널 스레드를 만든 뒤 exec한다”로 바꾸면 v6.12 구현과 어긋난다.[2][3]
권한 수준과 태스크 종류도 같은 말이 아니다. 사용자 프로그램을 실행할 태스크 역시 커널에 진입해 커널 코드를 실행할 수 있다. PID 1은 아직 첫 사용자 실행 이미지를 얻기 전이다. 보호 경계 자체는 보호를 참고한다.
3. 먼저 만든 PID 1을 왜 기다리게 하는가
PID 1이라는 번호를 얻도록 init을 먼저 생성하지만, init이 진행하며 사용할 커널 스레드 생성 기반인 kthreadd는 그다음 만들어진다. 단순히 소스에서 생성 호출을 두 줄 연속 적었다고 새 태스크들이 반드시 원하는 순서로 실행되는 것은 아니다.[1]
이 의존 관계를 표현하는 것이 kthreadd_done completion이다. completion은 한쪽이 작업 완료를 알리고 다른 쪽이 그 사건을 기다리는 동기화 수단이다. kernel_init()은 시작하자마자 기다리고, rest_init()은 kthreadd 준비 후 완료를 알린다. “태스크를 먼저 만들었다”와 “필요한 기반이 준비되었으니 진행해도 된다”를 분리한 것이다.[1]
4. PID 1에서 커널 초기화를 계속한다
대기가 끝나면 kernel_init_freeable()이 SMP와 나머지 초기화를 진행한다. 여기에는 do_basic_setup()을 통한 initcall 실행도 포함된다.[1]
initcall은 부팅 중 실행할 초기화 함수를 정해진 단계에 등록하는 방식이다. 소스의 각 부분이 등록한 함수들을 초기화 경로가 순회하며 실행하므로, start_kernel() 안에 모든 드라이버의 함수 이름이 직접 나열될 필요는 없다. 예를 들어 rootfs에 initramfs를 푸는 populate_rootfs()도 rootfs_initcall로 등록되어 있다.[1][4]
kernel_init_freeable()에서 돌아온 뒤에도 비동기 초기화의 완료를 기다린다. 그래야 아직 실행 중인 초기화 코드가 있는데 그 코드를 담은 메모리를 먼저 해제하는 일이 없다. 이후 초기화용 메모리를 정리하고, 다음 장에서 다룰 init 선택과 exec로 진행한다.[1]
소스 탐색: rest_init · user_mode_thread · kernel_init.
이전: 커널 초기화 · 다음: init 실행과 사용자 모드 진입
- ↑ 1.0 1.1 1.2 1.3 1.4 1.5 v6.12 init/main.c, rest_init.
- ↑ 2.0 2.1 2.2 v6.12 kernel/fork.c, user_mode_thread, kernel_thread, kernel_clone.
- ↑ v6.12 fs/exec.c, kernel_execve.
- ↑ v6.12 init/initramfs.c, populate_rootfs 및 rootfs_initcall 등록.