포스트

임베디드 스터디 - 리눅스 부팅 과정

임베디드 스터디 - 리눅스 부팅 과정

리눅스 부팅 개요

  • U-Boot가 커널 이미지를 메모리에 로드하고 진입점으로 점프한 이후부터, 첫 번째 사용자 프로세스(/sbin/init)가 실행되기까지의 과정
  • 크게 4단계로 구분됨
    1. zImage 자가 압축 해제
    2. 어셈블리 초기화
    3. start_kernel() — 커널 서브시스템 초기화
    4. rest_init() — 첫 번째 프로세스 생성 및 사용자 공간 진입

1단계 — zImage 자가 압축 해제

  • U-Boot가 zImage 진입점으로 점프하면 커널 본체 실행 전에 자가 압축 해제가 먼저 실행됨
    • zImage는 gzip으로 압축된 커널 본체 + 자가 압축 해제 코드로 구성됨
    • 압축 해제 코드가 메모리에 vmlinux(커널 본체)를 적재한 후 커널 진입점으로 점프
1
2
3
4
5
6
U-Boot → zImage 진입점으로 점프
              ↓
        [자가 압축 해제 코드 실행]
        gzip 압축 해제 → 메모리에 커널 본체 적재
              ↓
        커널 본체 실행 시작

2단계 — 어셈블리 초기화

  • C 코드(start_kernel())로 넘어가기 전에 어셈블리로 CPU/메모리 기본 환경을 구성함
    • 이 시점엔 OS가 없으므로 모든 초기화를 직접 수행해야 함
1
2
3
4
5
6
7
8
[어셈블리 초기화 단계]
├── CPU 모드 설정 (Exception Level 등)
├── MMU·캐시 비활성화    ← 초기엔 꺼둬야 안전
├── 스택 포인터(SP) 설정 ← C 함수 호출을 위해 필수
├── DRAM 초기화
└── BSS 영역 초기화      ← 전역변수 0으로 클리어
        ↓
start_kernel() 호출
  • 스택 포인터 설정이 가장 중요한 이유 : C 함수 호출 시 지역변수·리턴 주소·인자가 스택에 저장되므로, SP 없이는 start_kernel() 호출 자체가 불가능

3단계 — start_kernel()

  • 커널 핵심 서브시스템을 의존성 순서에 따라 초기화하는 C 함수
  • setup_arch()가 가장 먼저 실행되는 이유 : 메모리 맵을 알아야 이후 mm_init()이 버디 시스템을 구축할 수 있음
1
2
3
4
5
6
7
start_kernel()
├── setup_arch()     아키텍처 초기화 (MMU 활성화, 메모리 , Device Tree 파싱)
├── mm_init()        메모리 관리자 초기화 (버디 시스템, 슬랩 할당자)
├── trap_init()      예외·인터럽트 벡터 설정
├── sched_init()     스케줄러 초기화
├── time_init()      타이머·클럭 초기화
└── rest_init()       번째 프로세스 생성
  • mm_init()이 두 번째인 이유 : 스케줄러·인터럽트 초기화 과정에서 kmalloc 등 메모리 할당이 필요하므로, 메모리 관리자가 먼저 준비되어야 함

setup_arch() 세부 동작

  • arch/arm64/kernel/setup.c 등 아키텍처별 코드를 호출
  • Device Tree(.dtb) 파싱 → 보드 메모리 레이아웃, 주변장치 정보 파악
  • MMU 활성화 → 가상 주소 체계 전환

4단계 — rest_init()

  • start_kernel()의 마지막 단계로, 커널 최초의 프로세스들을 생성함
1
2
3
4
5
rest_init()
├── kernel_thread(kernel_init)   PID 1 생성
├── kernel_thread(kthreadd)      PID 2 생성
└── 자신은 PID 0 (idle 프로세스)  남음
         CPU   없을  실행되는 무한루프
PID이름역할
0idlerest_init() 자신. CPU 유휴 시 실행
1kernel_initinit 프로세스. 사용자 공간 진입점
2kthreadd커널 스레드 생성 데몬

kernel_init (PID 1) 동작

  • 커널 공간에서 시작해 최종적으로 사용자 공간의 /sbin/init을 실행하는 것이 목표
  • /sbin/init은 파일이므로 파일시스템이 마운트되어 있어야 접근 가능
    • 이 시점엔 아직 아무 파일시스템도 마운트되지 않은 상태 → initramfs 활용
1
2
3
4
5
6
7
8
9
10
kernel_init (PID 1)
        ↓
initramfs를 임시 루트 파일시스템으로 마운트
        ↓
initramfs 안의 초기화 스크립트 실행
(모듈 로드, 실제 루트 파일시스템 마운트 준비)
        ↓
실제 루트 파일시스템(ext4 등)으로 전환 (pivot_root)
        ↓
execve("/sbin/init") → 커널 공간 → 사용자 공간 전환
  • initramfs가 필요한 이유 : 스토리지 드라이버가 =m 모듈인 경우, 파일시스템 마운트 전에 로드가 불가능한 닭-달걀 문제를 해결 (Day 51·59 참고)

전체 부팅 흐름 요약

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
U-Boot
    ↓
zImage 압축 해제 (자가 압축 해제 코드)
    ↓
어셈블리 초기화 (SP 설정, DRAM, BSS)
    ↓
start_kernel()
    ├── setup_arch()   ← MMU, 메모리 맵, Device Tree
    ├── mm_init()      ← 버디 시스템, 슬랩
    ├── trap_init()    ← 인터럽트 벡터
    ├── sched_init()   ← 스케줄러
    └── time_init()    ← 타이머
    ↓
rest_init()
    ├── PID 0 : idle 프로세스
    ├── PID 1 : kernel_init
    └── PID 2 : kthreadd
    ↓
kernel_init (PID 1)
    ├── initramfs 마운트
    ├── 실제 루트 파일시스템으로 전환 (pivot_root)
    └── execve("/sbin/init") → 사용자 공간 진입
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.