BSP 부트 시간 최적화 — Bootchart·initcall_debug·Parallel Init
#한 줄 요약
부트 시간 최적화는 추측이 아니라 측정으로 시작합니다. 각 단계의 시간을 ms 단위로 쪼개 본 다음, 가장 비싼 단계부터 깎아내려 갑니다.
자동차 인포테인먼트는 후방 카메라를 2초 안에 표시해야 합니다. 산업용 컨트롤러는 정전 복구 후 1초 안에 안전 상태로 돌아와야 합니다. 가전 제품은 사용자가 버튼을 누른 직후 LED가 켜져야 합니다. 부팅이 5초 걸리는 BSP는 이런 요구를 통과하지 못합니다.
이번 글은 ROM에서 application 첫 줄까지의 전 구간을 측정 가능한 단계로 나눕니다. 그리고 각 단계에서 시간을 깎는 실전 기법을 정리합니다.
#단계별 시간 모델
부트는 다음 단계로 나뉩니다. 다음 그림은 각 단계의 시간 비중을 시각화합니다.
| 단계 | 일반 BSP | 최적화 후 |
|---|---|---|
| ROM → SPL | 50~200ms | 50~80ms |
| SPL → U-Boot | 100~500ms | 30~100ms |
| U-Boot → kernel load | 200ms~2s | 50~200ms |
| Kernel decompress + init | 1~3s | 300~800ms |
| Initramfs → real rootfs | 200~800ms | 0~100ms |
| init → application | 1~5s | 200~500ms |
| 합계 | 3~10s | 0.6~1.5s |
각 단계를 측정하지 않고는 어디를 깎을지 알 수 없습니다. 측정 도구를 먼저 갖추는 것이 출발점입니다.
#측정 — printk timestamps와 bootchart
커널은 빌드 옵션 하나로 부팅 로그에 시간을 박아 줍니다.
CONFIG_PRINTK_TIME=y또는 부팅 명령줄에 printk.time=1을 넣으면 dmesg 출력의 모든 줄에 [초.마이크로초] prefix가 붙습니다.
[ 0.000000] Booting Linux on physical CPU 0x0[ 0.234521] random: get_random_bytes called from start_kernel[ 0.512344] PCI: bus0: Fast back to back transfers enabled[ 1.234567] systemd[1]: Detected architecture arm.여기서 0초는 kernel 시작 시점입니다. 그 앞 단계는 별도로 봐야 합니다. U-Boot에서는 bootstage report가 단계별 시간을 보여 줍니다.
=> bootstage reportTimer summary in microseconds (12 records): Mark Elapsed Stage 0 0 reset 218,453 218,453 board_init_f 345,221 126,768 board_init_r 421,003 75,782 eth_common_init 498,221 77,218 bootm_start 523,144 24,923 start_kernelsystemd가 init이면 systemd-analyze가 userspace 단계를 분해합니다.
$ systemd-analyzeStartup finished in 1.234s (kernel) + 2.567s (userspace) = 3.801smulti-user.target reached after 2.234s in userspace.
$ systemd-analyze blame 1.234s NetworkManager.service 876ms ModemManager.service 543ms udev-trigger.service 321ms my-app.servicebootchart는 동일 정보를 간트 차트로 그려 줍니다. 의존성과 병렬 실행 여부가 한눈에 보입니다.
# BuildrootBR2_PACKAGE_BOOTCHART=y# 또는 systemd-bootchart 사용systemd-bootchart --no-syslog --output=/tmp/bootchart.svgftrace의 boot_tracer는 커널 내부의 do_initcall을 추적합니다.
CONFIG_BOOT_TRACER=y# kernel cmdline: initcall_debugdmesg에 모든 initcall이 시간과 함께 찍힙니다.
[ 0.512] calling pci_subsys_init+0x0/0x60 @ 1[ 0.745] initcall pci_subsys_init+0x0/0x60 returned 0 after 232 msecs232ms 걸린 pci_subsys_init이 PCIe를 쓰지 않는 보드라면 제거 후보입니다.
#부트 시간 측정 스크립트 예시
자동화된 측정을 위해 시리얼 로그를 파싱하는 스크립트입니다.
#!/bin/bash# boot-time-analyze.sh - dmesg에서 부트 단계별 시간 추출
# 커널 부트 완료 시점KERNEL_DONE=$(dmesg | grep "Freeing unused kernel" | awk '{print $1}' | tr -d '[]')
# systemd 도달 시점SYSTEMD_START=$(dmesg | grep "systemd\[1\]: Detected" | awk '{print $1}' | tr -d '[]')
# 가장 느린 initcall 상위 10개echo "=== Slowest initcalls ==="dmesg | grep "initcall.*returned 0 after" | \ sed 's/.*initcall \(.*\) returned 0 after \([0-9]*\) msecs/\2 \1/' | \ sort -rn | head -10
echo ""echo "Kernel boot: ${KERNEL_DONE}s"echo "systemd reached: ${SYSTEMD_START}s"U-Boot의 bootstage 정보를 환경 변수로 내보내 커널에서 전체 부트 시간을 추적할 수도 있습니다.
=> bootstage stash 0x83000000 0x1000=> setenv bootargs "${bootargs} bootstage.stash=0x83000000,0x1000"#SPL과 U-Boot 단계 — Falcon mode
U-Boot가 대화형 셸을 제공하는 비용은 작지 않습니다. 환경 변수 로드, console 초기화, USB 스캔 같은 단계가 다 부팅 시간을 잡아먹습니다. Falcon mode는 SPL이 바로 커널을 로드하도록 합니다. 평소에는 SPL → kernel, 특수 키를 누르거나 부팅 실패 시에만 full U-Boot로 떨어집니다.
CONFIG_SPL_OS_BOOT=yCONFIG_SPL_FALCON_BOOT_MMC=y# Falcon mode 준비 - kernel과 dtb를 SPL이 기대하는 위치에 둠=> spl export fdt $kernel_addr_r - $fdt_addr_r=> mmc write $fdt_addr_r 0x500 0x80Falcon mode는 U-Boot의 유연성과 부팅 속도를 맞바꿉니다. 양산 펌웨어에 권장됩니다.
DRAM 초기화 조기 종료, console 끄기, 불필요한 드라이버 제외도 누적되면 큽니다.
# defconfig에서 제거할 후보CONFIG_CMD_NET=n # 네트워크 부팅 안 함CONFIG_USB_STORAGE=n # USB 부팅 안 함CONFIG_CMD_USB=nCONFIG_CONSOLE_MUX=nCONFIG_HUSH_PARSER=n # 양산이면 셸 자체를 빼도 됨이더넷 PHY autoneg은 1~3초가 걸리므로 부팅 경로에 절대 두지 말아야 합니다.
#Kernel 단계 — 슬림화
커널 decompression 자체가 무거운 단계입니다. zImage는 gzip, Image.gz는 동일 압축이고 Image는 비압축입니다. 비압축은 디스크 공간을 더 쓰지만 decompress 시간이 0입니다.
# arch/arm/boot/Makefilemake Image # 비압축 - 빠른 부팅make zImage # gzip - 균형make lzImage # lz4 - decompress 빠름, 크기 중간initcall_debug로 시간 잡아먹는 드라이버를 찾아 모듈로 빼거나 제거합니다.
$ dmesg | grep "returned 0 after" | sort -k 11 -n -r | head[ 1.234] initcall ip_auto_config returned 0 after 700 msecs[ 0.987] initcall mmc_blk_init returned 0 after 234 msecs[ 0.745] initcall pci_subsys_init returned 0 after 232 msecsip_auto_config가 700ms 걸리는 보드는 IP DHCP를 부트 cmdline에서 받고 있을 가능성이 큽니다. rootfs를 NFS가 아니라 eMMC로 바꾸면 사라집니다.
kernel cmdline의 quiet와 loglevel=0은 console 출력 자체를 줄입니다. 직렬 출력은 의외로 느립니다. 115200bps면 한 줄 80자가 6.9ms입니다. dmesg가 100줄이면 700ms가 console로 빠집니다.
# 디버깅용console=ttyS0,115200 earlycon loglevel=7
# 양산용console=null loglevel=0 quietroot는 PARTUUID=로 지정하면 rootfs 검색 단계의 random poll이 짧아집니다.
root=PARTUUID=12345678-02 rootwaitrootwait은 MMC가 준비될 때까지 대기합니다. 없으면 panic으로 빠질 수 있습니다.
#Initramfs — 최소화 또는 제거
표준 distribution은 initramfs를 거쳐 real rootfs로 switch_root 합니다. 임베디드는 rootfs가 항상 같은 자리에 있으므로 initramfs 자체가 불필요한 경우가 많습니다.
# kernel configCONFIG_INITRAMFS_SOURCE="" # 비움initramfs를 꼭 써야 한다면(예: dm-verity 검증) busybox 정적 빌드 + 최소 init 스크립트로 100KB 이내로 만듭니다.
#!/bin/sh# /init - initramfs의 진입점mount -t proc none /procmount -t sysfs none /sysmount /dev/mmcblk0p2 /newrootumount /proc /sysexec switch_root /newroot /sbin/init#Userspace — systemd 의존성 정리
systemd의 multi-user.target은 기본적으로 많은 서비스에 의존합니다. 임베디드에서는 대부분 불필요합니다.
$ systemctl disable systemd-resolved.service$ systemctl disable ModemManager.service$ systemctl disable NetworkManager-wait-online.service$ systemctl mask wpa_supplicant.serviceNetworkManager-wait-online.service는 자주 5~30초 timeout으로 박힙니다. 네트워크가 application 시작 후 올라와도 되는 시스템에서는 mask가 안전합니다.
systemd 자체가 무거우면 BusyBox init이나 finit, OpenRC를 대안으로 검토합니다. systemd → BusyBox init만 바꿔도 userspace 부팅이 1~2초 빨라집니다.
application을 직접 init으로 등록하는 극단도 가능합니다.
init=/usr/bin/my-app이 경우 my-app이 mount, network, log 같은 모든 setup을 책임집니다. 부팅 0.5초 안에 application LED가 켜져야 하는 케이스에서 검토할 만합니다.
#실전 사례 — STM32MP1에서 1초 이내
ST가 공개한 STM32MP157 0-to-login 최적화는 단계별 숫자를 보여 줍니다.
| 단계 | 기본 | 최적화 |
|---|---|---|
| ROM bootloader | 60ms | 60ms |
| FSBL (TF-A BL2) | 250ms | 80ms |
| SSBL (U-Boot) | 700ms | 60ms (Falcon) |
| Kernel | 1900ms | 480ms |
| Userspace | 2100ms | 320ms |
| 합계 | 5010ms | 1000ms |
깎은 항목은 다음과 같습니다.
- TF-A BL2에서 사용하지 않는 STORAGE/USB 드라이버 제외
- U-Boot Falcon mode 적용
- Kernel
Image.gz→Image(비압축) - Initramfs 제거, rootfs PARTUUID 직접 root
- systemd → BusyBox init
- application의 의존 라이브러리를 LTO + strip
#자주 하는 실수
측정 없이 깎기. quiet 하나 넣고 만족하는 경우가 많습니다. dmesg 안 보고 PHY autoneg 1초를 그대로 두는 BSP가 흔합니다.
release에서 console 끄기 잊기. loglevel=7이면 매 줄마다 직렬 송신에 시간이 듭니다. 양산에서는 0으로.
Falcon mode 실패 경로 미준비. Falcon이 실패하면 보드가 벽돌이 됩니다. SPL이 fallback으로 U-Boot 전체를 로드할 수 있도록 환경을 짜둡니다.
Userspace에서 sleep 박기. 어떤 service가 race condition을 피하려고 sleep 2를 박아두는 경우가 있습니다. 부팅 2초가 사라집니다. systemd After=/Wants=로 해결합니다.
autoneg을 부팅 경로에. 이더넷 PHY autoneg은 1.5~3초입니다. application이 네트워크를 필요로 한다면 application 시작 후 비동기로 처리합니다.
#정리
- 부트 시간 최적화는 측정으로 시작합니다.
printk.time=1,bootstage report,systemd-analyze blame이 기본 도구입니다. - 가장 큰 한 덩어리부터 깎습니다. 1초짜리 PHY autoneg 한 개가 0.1초짜리 10개보다 우선입니다.
- U-Boot Falcon mode는 부트로더 단계 시간을 한 자릿수 ms로 끌어내립니다.
- Kernel은
Image비압축 + initcall_debug로 무거운 driver 제거 +quiet loglevel=0이 기본 조합입니다. - Initramfs는 보안 검증이 필요하지 않다면 제거하고 PARTUUID로 직접 root mount 합니다.
- systemd는 무겁습니다. BusyBox init이나 application init으로 대체하는 극단을 검토하세요.
- STM32MP1, i.MX8M, RK3568 같은 메인스트림 SoC에서 1초 이내 부팅이 실현 가능합니다.
#다음 편 예고
Ch 16: Buildroot/Yocto와 BSP에서는 BSP를 빌드 시스템(Buildroot/Yocto)에 통합하는 방법을 다룹니다. 외부 트리, 메타레이어, 보드별 오버레이를 설계합니다.
#관련 항목
- Ch 14: 디버깅 도구 — 측정 도구가 같이 묶입니다
- Ch 17: 이미지 패키징 — 파티션 layout이 부트 경로와 맞물립니다
- Bootloader 시리즈 — Falcon Mode — U-Boot 측 깊이 있는 다룸
- Modern Embedded Recipes — Boot 최적화 — recipe 묶음
BSP Development · 15 of 21
- 1BSP의 본질 분해 — 새 보드 부팅을 위한 코드의 자리
- 2SoC 데이터시트 읽기 — Pin Mux·Clock·Memory Map 파악법
- 3새 보드 Device Tree 설계 — node·property·phandle 작성 흐름
- 4Pin Mux와 Clock Tree 분석 — 보드 부팅의 첫 두 관문
- 5DDR 매개변수 결정 — 보드별 Timing·Training 추출
- 6U-Boot 새 보드 포팅 — defconfig·board.c·DTS 작성 흐름
- 7TF-A·TrustZone 통합 — BL31·secure world·SMC 흐름 적용
- 8Linux 커널 BSP 설정 — defconfig·Kconfig·DT 통합
- 9Multi-core SMP Bring-up — PSCI·Secondary CPU 깨우기
- 10첫 부팅 추적 — 0%부터 login prompt까지의 단계 분석
- 11부트로그 디버깅 — earlyprintk·loglevel·serial 추적
- 12BSP 드라이버 추가 — 보드별 Peripheral 통합 흐름
- 13BSP Power Management — Suspend/Resume·Runtime PM·Regulator
- 14BSP Thermal과 Watchdog — Trip Point·Cooling Device·Hardware Reset
- 15BSP 부트 시간 최적화 — Bootchart·initcall_debug·Parallel Init
- 16BSP RootFS 통합 — Buildroot·Yocto와 보드별 패키지 묶기
- 17BSP 이미지 패키징 — Flash Layout·Partition·GPT 설계
- 18BSP OTA와 Field Recovery — A/B 슬롯·롤백·BootCount
- 19BSP Stability Testing — Stress·Soak·Power Cycle 시나리오
- 20BSP 양산 환경 구축 — CI/CD·재현 가능 빌드·서명
- 21BSP 유지보수 — 업스트림 기여·커널 버전업·LTS 전략