포스트

임베디드 스터디 - 커널 빌드 과정

임베디드 스터디 - 커널 빌드 과정

커널 빌드 개요

  • 리눅스 커널은 수천 개의 소스 파일로 구성되어 있어 단순한 gcc 명령으로 빌드 불가
  • 이를 관리하기 위해 Kbuild 시스템을 사용함
    • 계층적 Makefile 구조로 각 서브디렉토리를 독립적으로 관리
    • ARCH, CROSS_COMPILE 환경변수로 타겟 플랫폼을 유연하게 전환
    • Kconfig 시스템으로 포함할 기능을 선택적으로 구성

ARCH · CROSS_COMPILE 환경변수

  • 임베디드 커널 빌드는 호스트(x86)에서 타겟(ARM 등)용 바이너리를 만드는 크로스 컴파일 환경
  • ARCH : 빌드 시 참조할 arch/ 하위 디렉토리를 결정함
1
2
3
ARCH=arm    → arch/arm/     참조 (ARMv7, Cortex-A/R)
ARCH=arm64  → arch/arm64/   참조 (ARMv8, AArch64)
ARCH=x86    → arch/x86/     참조
  • CROSS_COMPILE : 크로스 컴파일러 툴체인의 접두사를 지정함
    • 접두사 뒤에 gcc, ld, objcopy 등을 붙여 툴체인 전체를 자동 조합
1
2
3
4
5
6
7
8
9
# ARM64 타겟 커널 빌드 예시
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu-
# → CC  = aarch64-linux-gnu-gcc
# → LD  = aarch64-linux-gnu-ld
# → OBJCOPY = aarch64-linux-gnu-objcopy

# ARMv7 타겟 커널 빌드 예시
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf-
# → CC  = arm-linux-gnueabihf-gcc
  • Makefile에 컴파일러를 하드코딩하지 않고 환경변수로 분리하여 타겟 전환 시 Makefile 수정 불필요

Kbuild 시스템 — 계층적 Makefile 구조

  • 최상위 Makefile이 모든 파일을 직접 관리하면 유지보수가 불가능함
  • Kbuild는 각 서브디렉토리에 독립적인 Makefile을 두는 계층 구조로 이를 해결함
    • CMake의 add_subdirectory() 와 동일한 개념
1
2
3
4
5
6
7
8
9
10
linux/
├── Makefile                  ← 최상위: ARCH/CROSS_COMPILE 처리, 전체 조율
├── drivers/
│   ├── Makefile              ← 하위 디렉토리 나열
│   └── tty/
│       └── serial/
│           └── Makefile      ← 빌드 대상 파일 선언
└── arch/
    └── arm64/
        └── Makefile          ← ARM64 전용 빌드 규칙
  • 각 하위 Makefile은 obj-y / obj-m 으로 빌드 대상만 선언
1
2
3
4
# drivers/tty/serial/Makefile 예시
obj-y   += pl011.o                       # 항상 커널에 포함
obj-m   += some_drv.o                    # 커널 모듈(.ko)로 빌드
obj-$(CONFIG_SERIAL_PL011) += pl011.o   # Kconfig 설정에 따라 조건부 포함

Kconfig · menuconfig · defconfig · .config

  • CONFIG_ 변수는 Kconfig 시스템에서 생성됨
  • 각 디렉토리의 Kconfig 파일이 설정 항목(드라이버 포함 여부 등)을 정의하고, 개발자가 선택한 결과가 .config에 저장됨
명령/파일역할
Kconfig각 디렉토리의 설정 항목 정의 파일
make menuconfigKconfig를 읽어 TUI로 Y/N/M 선택
make defconfig특정 보드용 기본 설정 로드 (예: make bcm2711_defconfig)
.config선택 결과 저장 파일 — 빌드 시 참조
1
2
3
4
# .config 내용 예시
CONFIG_SERIAL_PL011=y         # 커널에 정적 포함 → obj-y
CONFIG_USB_SERIAL=m           # 모듈로 빌드      → obj-m
# CONFIG_INFINIBAND is not set  # 제외
  • obj-$(CONFIG_SERIAL_PL011).config 값이 대입되어 obj-y 또는 obj-m 이 됨
1
2
3
4
5
6
7
Kconfig 정의
    ↓
make menuconfig / defconfig   ← 개발자 선택
    ↓
.config 생성
    ↓
Kbuild가 .config 참조 → obj-y / obj-m 결정 → 빌드

빌드 결과물 — vmlinux · zImage · uImage

  • 커널 빌드 결과물은 사용 목적에 따라 세 가지 형태로 변환됨
1
2
3
4
5
6
7
vmlinux
    ↓ ELF 헤더 제거 + objcopy
Image (순수 바이너리)
    ↓ gzip 압축 + 자가 압축 해제 코드 추가
zImage
    ↓ mkimage로 U-Boot 헤더(64바이트) 추가
uImage
결과물형식특징주요 용도
vmlinuxELF심볼·디버그 정보 포함, 미압축커널 디버깅 (gdb vmlinux)
zImage압축 바이너리gzip 압축, 자가 압축 해제 코드 내장ARM 임베디드 표준 부팅
uImageU-Boot 래핑64바이트 헤더(CRC·로드주소·진입점) 추가U-Boot bootm 명령으로 부팅
  • vmlinuxvmvirtual memory의 약자

전체 빌드 흐름

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
1. make bcm2711_defconfig      ← 타겟 보드 기본 설정 로드
       ↓
2. make menuconfig              ← 필요 시 추가 설정 조정
       ↓
3. .config 생성
       ↓
4. make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- -j$(nproc)
       ↓
   Kbuild가 계층적 Makefile 순회
   .config 참조 → obj-y/obj-m 결정
   aarch64-linux-gnu-gcc 로 각 .o 컴파일
       ↓
5. 링크 → vmlinux (ELF)
       ↓
6. objcopy → Image → zImage / mkimage → uImage
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.