임베디드 리눅스 빌드 시스템(Build System) (OpenWrt · Buildroot · Yocto)
임베디드 리눅스 개발의 핵심인 OpenWrt, Buildroot, Yocto/OpenEmbedded 3대 빌드 시스템을 종합 비교합니다. 각 시스템의 빌드 파이프라인(Pipeline), 패키지 인프라, 펌웨어(Firmware) 이미지 구조, 크로스 툴체인 전략, Device Tree 통합, BSP 개발 방법론, SDK 활용, 디버깅(Debugging) 워크플로, 실전 유스케이스까지 임베디드 빌드 시스템의 모든 것을 다룹니다.
- 임베디드 리눅스(Embedded Linux) — 라우터·카메라·산업용 단말 등 특수 목적 하드웨어에 올리는 리눅스. 데스크탑 대비 RAM·플래시가 수십~수백 배 제한되어 필요한 것만 포함한 최소 이미지를 만들어야 합니다.
- 크로스 컴파일(Cross Compile) — x86 PC(호스트)에서 ARM·MIPS 등 다른 CPU(타겟)용 바이너리를 생성하는 것. 타겟 장치에서 직접 빌드하면 수십 배 느리거나 불가능하기 때문입니다.
- 툴체인(Toolchain) — 크로스 컴파일러(GCC), 링커(Linker, binutils), C 라이브러리(musl/glibc)를 묶은 도구 세트. 호스트에서 타겟 바이너리를 만드는 핵심 도구입니다.
- sysroot — 타겟 환경의 헤더·라이브러리를 호스트에 미리 복사해 둔 디렉토리. 크로스 컴파일 시 컴파일러가 이 경로를 참조합니다.
- rootfs (루트 파일시스템) — 타겟에서 실행되는
/bin,/etc,/lib등 전체 파일 트리. 빌드 시스템이 최종적으로 조합해 이미지로 패키징합니다. - 펌웨어 이미지(Firmware Image) — 부트로더(Bootloader)+커널+rootfs를 플래시 메모리 레이아웃에 맞게 하나로 묶은 바이너리. 장치에 직접 플래시(flash)합니다.
- 피드(Feed) — OpenWrt에서 외부 패키지 저장소를 가리키는 용어. git 저장소 형태로 관리되며,
feeds update/install로 로컬에 가져옵니다. - 레시피(Recipe, .bb) — Yocto에서 패키지 하나를 빌드하는 방법(소스 URL·컴파일 옵션·설치 규칙)을 기술한 파일. Buildroot의
.mk와 동일한 역할입니다. - 레이어(Layer) — Yocto에서 관련 레시피·설정을 묶는 단위 디렉토리. BSP 레이어, 기능 레이어, 배포판 레이어를 쌓아(stack) 맞춤 배포판을 구성합니다.
- BSP (Board Support Package) — 특정 보드를 지원하는 부트로더·커널 설정·Device Tree·드라이버 묶음. 새 하드웨어를 지원할 때 가장 먼저 작성합니다.
핵심 요약
- OpenWrt — 라우터/네트워크 장비 특화 배포판+빌드 시스템. opkg 패키지 매니저, LuCI, UCI를 기본 제공합니다.
- Buildroot — Make+Kconfig 기반 간결한 빌드 프레임워크. 최소 수 MB 수준의 정적 이미지 생성에 강점입니다.
- Yocto / OpenEmbedded — BitBake 엔진과 레이어 아키텍처로 엔터프라이즈·벤더 BSP 통합에 적합합니다.
- 크로스 컴파일 — x86_64 호스트에서 ARM/MIPS 등 타겟 바이너리를 생성하는 공통 기반 기술입니다.
- BSP(Board Support Package) — 부트로더, 커널, Device Tree, 드라이버 묶음으로 특정 보드를 지원합니다.
단계별 이해
- 왜 크로스 컴파일인가
타겟은 리소스가 제한적이므로 호스트에서 크로스 툴체인으로 빌드해 시간을 크게 단축합니다. - 빌드 시스템이 자동화하는 것
툴체인 구축, 수백 개 패키지 크로스 컴파일, 루트 파일시스템(Filesystem)·커널·펌웨어 이미지 생성까지 이어집니다. - 세 시스템의 선택 기준
네트워크 장비는 OpenWrt, 최소 정적 이미지는 Buildroot, 대규모 벤더 BSP는 Yocto가 기본 출발점입니다. - defconfig로 첫 빌드
각 시스템의 보드별 defconfig/Target Profile을 선택해 menuconfig로 최소 변경 후 병렬 빌드를 돌립니다. - 산출물과 배포
커널 이미지, 루트 파일시스템, 부트로더, 패키지 피드를 묶어 플래시 가능한 펌웨어 이미지로 패키징하는 절차를 확인합니다.
3대 빌드 시스템 개요 및 비교
왜 임베디드 전용 빌드 시스템이 필요한가
데스크탑 리눅스에서는 apt install 한 줄로 소프트웨어를 설치할 수 있습니다. 그런데 임베디드 개발에서는 왜 OpenWrt·Buildroot·Yocto 같은 전용 빌드 시스템이 필요할까요? 세 가지 근본 이유가 있습니다.
| 문제 | 데스크탑 방식의 한계 | 임베디드 빌드 시스템의 해결 |
|---|---|---|
| 아키텍처 불일치 | x86 PC에서 만든 바이너리는 ARM·MIPS 장치에서 실행 불가합니다. | 크로스 툴체인을 자동으로 구축해 타겟 CPU용 바이너리를 생성합니다. |
| 리소스 제약 | 데스크탑 패키지는 수십~수백 MB, 임베디드 플래시는 4~32 MB 수준입니다. | 필요한 기능만 선택(menuconfig)해 수 MB짜리 최소 이미지를 만듭니다. |
| 재현성·통합 | 수백 개 패키지 간 버전 조합을 수동으로 맞추기 어렵습니다. | 전체 소스(툴체인~커널~애플리케이션)를 한 번의 make로 재현 가능하게 빌드합니다. |
크로스 컴파일이란
임베디드 빌드 시스템이 공통으로 사용하는 핵심 기술입니다. 세 가지 환경을 구분해야 합니다.
| 환경 | 예시 | 역할 |
|---|---|---|
| 빌드(Build) | x86_64 Ubuntu | 컴파일러 자체를 빌드하는 환경 (보통 호스트와 같음) |
| 호스트(Host) | x86_64 Ubuntu | 컴파일러가 실행되는 환경 — 개발자의 PC |
| 타겟(Target) | arm-linux-gnueabihf | 최종 바이너리가 실행될 환경 — 임베디드 장치 |
arch-vendor-os-abi 형태의 트리플렛이 붙습니다. 예를 들어 arm-linux-gnueabihf-gcc는 "ARM CPU, Linux OS, GNU ABI, 하드웨어 부동소수점" 타겟용 GCC를 의미합니다. 빌드 시스템은 이 툴체인을 자동으로 구축하거나 외부 것을 가져다 씁니다.
역사와 기원
세 시스템은 서로 다른 배경에서 탄생하여 고유한 철학과 강점을 발전시켰습니다.
- Buildroot (2001~) — uClibc 프로젝트의 빌드 인프라로 시작했습니다. Erik Andersen이 uClibc와 BusyBox를 쉽게 빌드할 수 있는 Makefile 기반 시스템을 만든 것이 시초입니다. 2009년 Peter Korsgaard가 메인테이너를 맡으면서 현대적 형태로 발전했습니다. "Keep it simple"을 핵심 철학으로, Make와 Kconfig만으로 전체 시스템을 구성합니다.
- OpenWrt (2004~) — Linksys가 WRT54G 라우터의 GPL 소스를 공개한 것이 계기입니다. 커뮤니티가 이 소스를 기반으로 완전한 라우터 운영체제를 만들기 시작했습니다. Buildroot에서 포크되었으나, 패키지 매니저(opkg), 웹 인터페이스(LuCI), 네트워크 구성 추상화(UCI) 등을 추가하며 독자적 생태계를 구축했습니다. 2016년 LEDE 프로젝트로 분기 후 2018년 재통합되었습니다.
- Yocto Project (2010~) — Linux Foundation이 OpenEmbedded 프로젝트(2003~)의 경험을 바탕으로 산업 표준 빌드 프레임워크를 목표로 출범했습니다. BitBake 빌드 엔진과 OpenEmbedded-Core(OE-Core) 메타데이터를 핵심으로, Intel, AMD, TI, NXP 등 주요 반도체 벤더가 공식 BSP 레이어를 제공합니다.
역사 타임라인 비교
| 연도 | OpenWrt | Buildroot | Yocto/OE |
|---|---|---|---|
| 2000 | uClibc 빌드 인프라 시작 | ||
| 2003 | 독립 프로젝트화 | OpenEmbedded 프로젝트 시작 | |
| 2004 | WRT54G GPL 소스 기반 탄생 | ||
| 2006 | Kamikaze (첫 공식 릴리스) | BitBake 엔진 안정화 | |
| 2009 | Peter Korsgaard 메인테이너 | ||
| 2010 | Backfire | 분기별 릴리스 시작 | Yocto Project 출범 (Linux Foundation) |
| 2012 | Attitude Adjustment | 1.0 Bernard → 1.2 Danny | |
| 2016 | LEDE 프로젝트 분기 | Cargo/Go 인프라 | 2.1 Krogoth |
| 2018 | LEDE와 OpenWrt 재통합 (18.06) | 2.5 Sumo | |
| 2020 | 19.07 → DSA 스위치 지원 | per-package-directories | 3.1 Dunfell (첫 LTS) |
| 2022 | 22.03 (Firewall4/nftables) | Meson 인프라 강화 | 4.0 Kirkstone (LTS, 2026-04-27 지원 종료) |
| 2024 | 23.05 LTS (7월) | 2024.02/05/08/11 분기별 릴리스 | 5.0 Scarthgap LTS (4월, 2028-04까지 지원) |
| 2025-02 | 24.10 첫 안정 릴리스 (커널 6.6, WiFi 7 초기 지원, MPTCP) | 2025.02/05/08/11 — SBOM(CycloneDX) 고도화, PEP 517 Python 지원 | 5.1 Styhead (10월, 비-LTS) |
| 2026-02 | 24.10.x 유지 | 2026.02 — Binutils 2.44 기본, 커널 헤더 6.19 기본, HPPA 아키텍처 추가 | 5.2 Walnascar (4월)·5.3 Whinlatter (개발 중) |
| 2026-03 | 25.12 첫 안정 릴리스 (25.12.0: 2026-03-05, 25.12.2: 2026-03-26) |
- OpenWrt 25.12 계열 — 2026년 4월 기준 최신 안정 시리즈는 25.12이며, 25.12.2가 최신 릴리스입니다(2026-03-26 공개). 이전 24.10 계열(24.10.0: 2025-02-06)은 유지보수 단계입니다.
- Buildroot 2026.02 (2026-03-05) — 커널 헤더 6.19.x 기본(6.17.x 제거), Binutils 2.44 기본·2.45 시리즈 추가. HPPA 아키텍처 추가·ARC 빅엔디안 제거.
utils/generate-cyclonedx개선(CVE 패치 annotation), AMD Versal2 VEK385·HP-9000·QEMU HPPA B160L·STM32h747i-disco defconfig 신규. 97명 컨트리뷰터, 1,500+ 변경. - Yocto 5.2 Walnascar (2025-04) — 비-LTS. gcc 15, glibc 2.41, 커널 6.12 계열 LTS 기본. systemd 257, Python 3.13 전면 채택.
- Yocto 5.0 Scarthgap LTS (2024-04) — 2028-04까지 지원(4년). gcc 13.3, glibc 2.39, 커널 6.6 LTS. 장기 상용 제품 타깃.
- Yocto 4.0 Kirkstone LTS (2022-05) — 현재 공개된 일정 기준 지원 종료일은 2026-04-27입니다. 마이그레이션 경로는 Scarthgap LTS(5.0) 또는 이후 LTS 계열을 검토하는 편이 안전합니다.
철학 비교
| 관점 | OpenWrt | Buildroot | Yocto |
|---|---|---|---|
| 설계 목표 | 네트워크 어플라이언스 OS | 최소한의 임베디드 시스템 | 유연한 엔터프라이즈 빌드 |
| 핵심 원칙 | 라우터에 완전한 리눅스를 | 단순하게 유지하라 | 재사용성과 확장성 |
| 빌드 도구 | Make + Kconfig | Make + Kconfig | BitBake + OE metadata |
| 패키지 매니저 | opkg (런타임 설치 가능) | 없음 (정적 이미지) | rpm/deb/ipk (선택) |
| 라이선스 | GPL-2.0 | GPL-2.0+ | MIT (Poky/BitBake) |
| 주요 사용처 | 라우터, AP, 스위치, NAS | IoT, 산업용 제어기, STB | 자동차, 항공, 의료, 반도체 벤더 SDK |
상세 비교표
| 항목 | OpenWrt | Buildroot | Yocto |
|---|---|---|---|
| 첫 빌드 시간 | ~1시간 | ~30분 | 2~4시간 |
| 재빌드 시간 | 수 분 (패키지 단위) | 수 분 (패키지 단위) | 수 초~분 (sstate-cache) |
| 최소 이미지 크기 | ~4 MB | ~2 MB | ~8 MB |
| 런타임 패키지 관리 | opkg (필수) | 없음 | rpm/deb/ipk (선택) |
| 기본 init | procd | BusyBox init/systemd/sysvinit | systemd/sysvinit |
| 기본 C 라이브러리 | musl | 선택 (musl/glibc/uclibc-ng) | glibc (기본) |
| 설정 방식 | Kconfig (menuconfig) | Kconfig (menuconfig) | .conf 파일 + BitBake |
| 패키지 수 | 5,000+ | 2,800+ | 수만 (레이어 합산) |
| SDK 제공 | SDK + Image Builder | SDK (output/host) | Standard/Extensible SDK |
| CI/CD 친화성 | 중간 | 높음 (단순) | 매우 높음 (sstate, hash equiv.) |
| 상업적 지원 | 커뮤니티 중심 | 커뮤니티 중심 | Wind River, Mentor, Siemens 등 |
| 주요 벤더 | 라우터/AP 제조사 | STMicro, Microchip | Intel, TI, NXP, AMD, Qualcomm |
| 호스트 디스크 | ~15 GB | ~5 GB | ~50 GB+ |
| 문서 품질 | Wiki 중심, 양호 | 매뉴얼 우수 | 방대 (Mega Manual) |
라이선스 컴플라이언스 워크플로
임베디드 제품의 GPL/LGPL 소스 코드 공개 의무를 처리하는 방법은 시스템마다 다릅니다:
| 시스템 | 라이선스 수집 명령 | 출력물 | 비고 |
|---|---|---|---|
| OpenWrt | make package/xxx/compile 시 자동 | bin/packages/에 소스 포함 가능 | CONFIG_SRC_TREE_OVERRIDE로 소스 보존 |
| Buildroot | make legal-info | output/legal-info/ — 라이선스, 소스 아카이브, 매니페스트 CSV | 가장 체계적; 호스트/타겟 분리, 재배포 소스 자동 수집 |
| Yocto | INHERIT += "archiver" in local.conf | tmp/deploy/sources/, tmp/deploy/licenses/ | ARCHIVER_MODE 옵션으로 패치(Patch) 포함 원본/수정 소스 선택 |
커뮤니티 지표 비교
| 지표 | OpenWrt | Buildroot | Yocto |
|---|---|---|---|
| GitHub Stars | ~20K | ~3K | ~3K (Poky) |
| 연간 커밋 수 | ~8,000 | ~3,000 | ~5,000 (OE-Core) |
| 활성 기여자 | ~300명/년 | ~200명/년 | ~400명/년 |
| 메일링 리스트 | 중간 활성 | 높은 활성 | 매우 높은 활성 |
| IRC/Discord | #openwrt (Libera) | #buildroot (OFTC) | #yocto (Libera) |
| 릴리스 주기 | ~12개월 | 3개월 | 6개월 |
OpenWrt 빌드 시스템 아키텍처
OpenWrt란 무엇인가
OpenWrt는 임베디드 리눅스 배포판인 동시에 빌드 시스템입니다. 이 이중 성격이 Buildroot·Yocto와 구별되는 핵심입니다.
| 성격 | 의미 | 구체적 예시 |
|---|---|---|
| 배포판(Distribution) | 완성된 라우터 OS. 설치하면 바로 동작합니다. | ipq806x 라우터에 OpenWrt 이미지를 플래시하면 즉시 웹 관리 화면(LuCI)이 뜹니다. |
| 빌드 시스템 | 커스텀 이미지를 처음부터 직접 빌드할 수 있습니다. | 소스를 받아 make menuconfig → make로 자신만의 펌웨어를 만듭니다. |
feeds update -a && feeds install -a로 이 패키지들을 로컬에 가져온 뒤 menuconfig에서 선택할 수 있게 됩니다. 핵심 저장소를 작고 빠르게 유지하면서도 수천 개의 추가 패키지를 지원하기 위한 설계입니다.
OpenWrt의 소스 트리는 라우터/네트워크 장비에 특화된 계층 구조를 가집니다.
핵심은 target/(하드웨어 지원), package/(소프트웨어 패키지), feeds/(외부 패키지 저장소) 3개 디렉토리입니다.
주요 디렉토리 역할
| 디렉토리 | 역할 | 핵심 파일 |
|---|---|---|
target/linux/ | 타겟 플랫폼별 커널 설정, 패치, DTS | config-6.6, patches-6.6/, dts/ |
target/sdk/ | SDK 이미지 생성 규칙 | Makefile |
package/ | 핵심 패키지 Makefile (base-files, kernel, network 등) | package/*/Makefile |
feeds/ | 외부 패키지 소스 (feeds install 후 심볼릭 링크) | feeds.conf.default |
toolchain/ | 크로스 컴파일러 빌드 (GCC, Binutils, musl/glibc) | toolchain/gcc/, toolchain/musl/ |
tools/ | 호스트 빌드 도구 (m4, autoconf, cmake, sstrip 등) | 각 도구별 Makefile |
include/ | 빌드 시스템 공통 Makefile 인프라 | package.mk, image.mk, kernel.mk |
scripts/ | 빌드 유틸리티 스크립트 | feeds, config/, download.pl |
target/subtarget/profile 계층 구조
OpenWrt는 3단계 계층으로 하드웨어를 분류합니다:
- Target — SoC 패밀리 (예:
ath79= Qualcomm Atheros AR7xxx/AR9xxx) - Subtarget — SoC 변형 (예:
generic,nand,tiny) - Profile — 개별 보드/제품 (예:
tplink_tl-wr1043nd-v1)
# target/linux/ath79/ 디렉토리 구조 예시
target/linux/ath79/
├── Makefile # 타겟 정의, BOARDNAME, FEATURES
├── config-6.6 # 커널 .config (subtarget 공용)
├── generic/ # subtarget: generic
│ ├── config-default # subtarget 기본 설정
│ └── target.mk # DEVICE_PACKAGES 등
├── nand/ # subtarget: nand (NAND 플래시 장비)
├── dts/ # Device Tree 소스 (.dts/.dtsi)
│ ├── ar9344_tplink_tl-wr1043nd-v2.dts
│ └── qca9558_tplink_archer-c7-v2.dts
├── files/ # 커널 소스에 추가할 파일
├── patches-6.6/ # 커널 패치 (순서번호_설명.patch)
│ ├── 0001-ath79-add-xxx.patch
│ └── 0002-ath79-fix-xxx.patch
└── image/ # 이미지 생성 규칙
└── Makefile # DEVICE_xxx 매크로로 프로파일 정의
UCI/procd/netifd/ubus 서브시스템
OpenWrt가 일반 임베디드 리눅스와 구별되는 핵심은 자체 서브시스템 스택입니다.
UCI(Unified Configuration Interface)는 /etc/config/ 디렉토리의 구조화된 설정 파일을 관리하며,
procd는 systemd 대신 사용하는 경량 init/서비스 매니저,
netifd는 네트워크 인터페이스를 추상화하는 데몬,
ubus는 시스템 내 IPC(프로세스(Process) 간 통신)를 담당하는 JSON-RPC 버스(Bus)입니다.
UCI 시스템 상세
# UCI 설정 파일 구조 (/etc/config/network 예시)
config interface 'loopback'
option device 'lo'
option proto 'static'
option ipaddr '127.0.0.1'
option netmask '255.0.0.0'
config interface 'lan'
option device 'br-lan'
option proto 'static'
option ipaddr '192.168.1.1'
option netmask '255.255.255.0'
config interface 'wan'
option device 'eth0.2'
option proto 'dhcp'
# UCI CLI 명령
uci show network # 전체 설정 보기
uci get network.lan.ipaddr # 특정 값 읽기
uci set network.lan.ipaddr='10.0.0.1' # 값 변경
uci commit network # 변경사항 저장
/etc/init.d/network restart # 서비스 재시작으로 적용
# procd 서비스 관리
/etc/init.d/dnsmasq start|stop|restart|enable|disable
service list # 실행 중인 서비스 목록 (JSON)
procd vs systemd 비교
| 항목 | procd (OpenWrt) | systemd (일반 리눅스) |
|---|---|---|
| 바이너리 크기 | ~200 KB | ~10 MB |
| 서비스 정의 | Shell 스크립트 + JSON | .service unit 파일 |
| 의존성 | 단순 (시작 순서 번호) | 복잡 (Wants, Requires, After) |
| 프로세스 감시 | respawn 지원 | Restart=on-failure 등 다양 |
| 샌드박스(Sandbox) | procd jail (seccomp, namespace) | 다양한 보안 옵션 |
| 핫플러그(Hotplug) | 내장 (hotplug.d/) | udev + systemd |
| 최소 RAM | ~1 MB | ~30 MB |
staging_dir 구조
staging_dir/은 OpenWrt 빌드 시스템의 핵심 중간 산출물 디렉토리입니다:
staging_dir/
├── host/ # 호스트 빌드 도구 (cmake, m4, pkg-config 등)
├── hostpkg/ # 호스트 패키지
├── toolchain-mips_24kc_gcc-13.3.0_musl/ # 크로스 툴체인
│ ├── bin/ # mips-openwrt-linux-musl-gcc 등
│ ├── include/ # 헤더 파일
│ └── lib/ # 크로스 컴파일된 라이브러리
└── target-mips_24kc_musl/ # 타겟 sysroot
├── usr/include/ # 타겟 헤더
├── usr/lib/ # 타겟 라이브러리 (.so, .a)
└── pkgconfig/ # pkg-config .pc 파일
OpenWrt 빌드 과정
빌드 단계 상세
Step 1: 피드 업데이트 및 설치
# feeds.conf.default 내용 확인
cat feeds.conf.default
# src-git packages https://git.openwrt.org/feed/packages.git;openwrt-23.05
# src-git luci https://git.openwrt.org/project/luci.git;openwrt-23.05
# src-git routing https://git.openwrt.org/feed/routing.git;openwrt-23.05
# src-git telephony https://git.openwrt.org/feed/telephony.git;openwrt-23.05
# 모든 피드 업데이트 (git clone/pull)
./scripts/feeds update -a
# 모든 피드 패키지를 빌드 트리에 심볼릭 링크
./scripts/feeds install -a
# 특정 패키지만 설치
./scripts/feeds install luci-app-openvpn
Step 2: 설정 (menuconfig)
# ncurses 기반 메뉴 설정
make menuconfig
# 설정 항목:
# Target System → (예: MediaTek Ralink MIPS)
# Subtarget → (예: MT7621 based boards)
# Target Profile → (예: Xiaomi Mi Router 4A Gigabit)
# [*] Base system → base-files, busybox, ...
# [*] LuCI → Collections → luci
# [M] Network → VPN → openvpn-openssl # M=모듈(IPK 생성)
# 커널 설정 확인/수정
make kernel_menuconfig
Step 3: 빌드 실행
# 전체 빌드 (상세 로그, CPU 코어 수 병렬)
make -j$(nproc) V=s
# 다운로드만 먼저 실행 (빌드 서버에서 유용)
make download -j8
# 특정 패키지만 재빌드
make package/network/services/dnsmasq/compile V=s
# 특정 패키지 정리 후 재빌드
make package/network/services/dnsmasq/{clean,compile} V=s
# 펌웨어 이미지만 재생성 (패키지 변경 없이)
make target/install V=s
# ccache 활성화 (재빌드 가속)
echo "CONFIG_CCACHE=y" >> .config
make defconfig
커널 모듈(Kernel Module) 컴파일
OpenWrt에서 커널 모듈은 kmod-* 패키지로 관리됩니다. 커널 모듈 패키지는 커널 버전과 정확히 일치해야 합니다:
# 커널 모듈 패키지 빌드 (kmod-usb-net 예시)
make package/kernel/linux/compile V=s
# 커널 모듈 패키지 정의 위치
package/kernel/linux/modules/
├── block.mk # kmod-loop, kmod-nbd 등
├── crypto.mk # kmod-crypto-aes 등
├── netdevices.mk # kmod-usb-net, kmod-tun 등
├── netsupport.mk # kmod-ipsec, kmod-wireguard 등
└── usb.mk # kmod-usb-core, kmod-usb-storage 등
# 모듈 이름 패턴: kmod-<기능명>
# 예: kmod-usb-storage, kmod-wireguard, kmod-nft-nat
다운로드 미러 및 재현 가능 빌드
# 다운로드 미러 설정 (.config)
CONFIG_DOWNLOAD_FOLDER="/shared/dl" # 공유 다운로드 캐시
CONFIG_LOCALMIRROR="https://mirror.example.com/openwrt/dl" # 로컬 미러
# 또는 환경변수로 지정
export DL_DIR=/shared/dl
# 재현 가능 빌드를 위한 체크리스트
# 1. .config 파일 버전 관리
# 2. feeds.conf.default에 커밋 해시 고정
# src-git packages https://git.openwrt.org/feed/packages.git^abc1234
# 3. dl/ 디렉토리 보존 (다운로드 소스 아카이브)
# 4. 호스트 도구 버전 통일 (Docker 권장)
빌드 오류 디버깅
# 단일 스레드로 상세 빌드 (오류 위치 확인)
make -j1 V=s 2>&1 | tee build.log
# 특정 패키지만 빌드 (의존성 문제 격리)
make package/xxx/compile V=s
# 빌드 디렉토리에서 직접 확인
ls build_dir/target-mips_24kc_musl/xxx-*/
# .config 변경 반영
make defconfig # 의존성 자동 해결
make oldconfig # 대화형 질문
CONFIG_CCACHE=y— 컴파일러 캐시로 재빌드 시간 대폭 단축CONFIG_DOWNLOAD_FOLDER— 공유 다운로드 캐시 디렉토리 지정make download -j8— 소스 다운로드를 먼저 병렬 실행- tmpfs에
build_dir/배치 — I/O 병목(Bottleneck) 해소 (16GB+ RAM 필요) CONFIG_AUTOREMOVE=y— 빌드 완료된 패키지의 빌드 디렉토리 자동 삭제로 디스크 절약
OpenWrt 패키지 시스템
OpenWrt의 패키지 시스템은 다른 임베디드 빌드 시스템과 차별화되는 핵심 기능입니다.
런타임 패키지 설치/제거가 가능한 opkg 패키지 매니저와 IPK 포맷을 사용합니다.
패키지 Makefile 예제: Hello World
# package/hello/Makefile
include $(TOPDIR)/rules.mk
PKG_NAME:=hello
PKG_VERSION:=1.0
PKG_RELEASE:=1
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
PKG_SOURCE_URL:=https://example.com/
PKG_HASH:=skip
include $(INCLUDE_DIR)/package.mk
define Package/hello
SECTION:=utils
CATEGORY:=Utilities
TITLE:=Hello World Example
DEPENDS:=+libc
MAINTAINER:=Developer <dev@example.com>
endef
define Package/hello/description
A simple Hello World package for OpenWrt.
endef
define Build/Compile
$(TARGET_CC) $(TARGET_CFLAGS) $(TARGET_LDFLAGS) \
-o $(PKG_BUILD_DIR)/hello $(PKG_BUILD_DIR)/hello.c
endef
define Package/hello/install
$(INSTALL_DIR) $(1)/usr/bin
$(INSTALL_BIN) $(PKG_BUILD_DIR)/hello $(1)/usr/bin/hello
endef
$(eval $(call BuildPackage,hello))
커널 모듈 패키지 패턴 (kmod-*)
OpenWrt의 커널 모듈은 독립 패키지로 분리되어 런타임 설치가 가능합니다:
# package/kernel/linux/modules/netdevices.mk (발췌)
define KernelPackage/usb-net
SUBMENU:=$(USB_MENU)
TITLE:=Kernel modules for USB-to-Ethernet
DEPENDS:=+kmod-mii +kmod-usb-core
KCONFIG:=CONFIG_USB_USBNET
FILES:=$(LINUX_DIR)/drivers/net/usb/usbnet.ko
AUTOLOAD:=$(call AutoLoad,61,usbnet)
endef
# 사용자가 menuconfig에서 kmod-usb-net을 선택하면
# → 커널 CONFIG_USB_USBNET=m 자동 설정
# → usbnet.ko 빌드 → IPK 생성
# → 타겟에서 opkg install kmod-usb-net 가능
LuCI 앱 패키지 패턴
# package/luci-app-myvpn/Makefile
include $(TOPDIR)/rules.mk
LUCI_TITLE:=LuCI support for MyVPN
LUCI_DEPENDS:=+myvpn +luci-base
include $(TOPDIR)/feeds/luci/luci.mk
# 필요한 것은 이것뿐 — luci.mk가 나머지를 자동 처리
$(eval $(call BuildPackage,luci-app-myvpn))
UCI 설정 통합 패턴
패키지가 UCI 설정을 사용하는 경우, 설치 시 기본 설정 파일을 제공합니다:
# Package/hello/install에 UCI 설정 포함
define Package/hello/install
$(INSTALL_DIR) $(1)/usr/bin
$(INSTALL_BIN) $(PKG_BUILD_DIR)/hello $(1)/usr/bin/hello
$(INSTALL_DIR) $(1)/etc/config
$(INSTALL_CONF) ./files/hello.conf $(1)/etc/config/hello
$(INSTALL_DIR) $(1)/etc/init.d
$(INSTALL_BIN) ./files/hello.init $(1)/etc/init.d/hello
endef
# files/hello.conf (UCI 설정 파일)
# config hello 'main'
# option enabled '1'
# option port '8080'
# files/hello.init (procd 서비스 스크립트)
# #!/bin/sh /etc/rc.common
# START=99
# USE_PROCD=1
# start_service() {
# procd_open_instance
# procd_set_param command /usr/bin/hello
# procd_set_param respawn
# procd_close_instance
# }
opkg 패키지 매니저
# 패키지 목록 업데이트
opkg update
# 패키지 설치
opkg install luci-app-openvpn
# 패키지 제거
opkg remove luci-app-openvpn
# 설치된 패키지 목록
opkg list-installed
# 패키지 정보 조회
opkg info dnsmasq
# 패키지가 제공하는 파일 확인
opkg files dnsmasq
# 의존성 확인
opkg depends luci
가상 패키지와 패키지 변형
OpenWrt는 같은 기능의 대안 패키지를 가상 패키지(virtual package)로 관리합니다:
# 예: OpenSSL vs wolfSSL — 둘 다 "libssl" 가상 패키지를 제공
# package/libs/openssl/Makefile
define Package/libopenssl
PROVIDES:=libssl
endef
# package/libs/wolfssl/Makefile
define Package/libwolfssl
PROVIDES:=libssl
endef
# 의존하는 패키지는 +libssl로 선언 → menuconfig에서 구현체 선택
opkg 오버레이(Overlay)와 플래시 공간 관리
# 플래시 사용량 확인
df -h /overlay
# Filesystem Size Used Avail Use% Mounted on
# /dev/mtdblock6 3.5M 1.2M 2.3M 34% /overlay
# 패키지 설치 전 공간 확인
opkg install --size luci-app-statistics
# Installing luci-app-statistics (40-r23456-abc1234) to root...
# Installing collectd (5.12.0-r23456) to root... (120 KB 필요)
# USB/SD로 opkg overlay 확장 (extroot)
# 1. USB 포맷: mkfs.ext4 /dev/sda1
# 2. /etc/config/fstab에 마운트 설정:
# config mount
# option target '/overlay'
# option device '/dev/sda1'
# option enabled '1'
# 3. 기존 overlay 복사: tar -C /overlay -cvf - . | tar -C /mnt/sda1 -xf -
# 4. reboot
피드(Feed) 저장소 구조
# /etc/opkg/distfeeds.conf (타겟 장비에서)
src/gz openwrt_core https://downloads.openwrt.org/releases/23.05.4/targets/ath79/generic/packages
src/gz openwrt_base https://downloads.openwrt.org/releases/23.05.4/packages/mips_24kc/base
src/gz openwrt_luci https://downloads.openwrt.org/releases/23.05.4/packages/mips_24kc/luci
src/gz openwrt_packages https://downloads.openwrt.org/releases/23.05.4/packages/mips_24kc/packages
# 저장소 디렉토리 구조
# packages/
# ├── Packages.gz # 패키지 인덱스 (압축)
# ├── Packages.sig # 서명
# ├── hello_1.0-1_mips_24kc.ipk
# └── ...
OpenWrt 펌웨어 이미지 구조
OpenWrt 펌웨어의 핵심은 SquashFS + JFFS2 오버레이 아키텍처입니다. 읽기 전용(Read-Only) SquashFS 위에 쓰기 가능한 JFFS2 오버레이를 결합하여, 최소 플래시 사용으로 설정 변경과 패키지 설치를 지원합니다.
이미지 유형
| 이미지 유형 | 파일명 패턴 | 용도 |
|---|---|---|
| sysupgrade.bin | *-sysupgrade.bin | 기존 OpenWrt에서 업그레이드용. 설정 보존 가능 |
| factory.bin | *-factory.bin | 제조사 펌웨어에서 최초 설치용. 벤더 헤더 포함 |
| initramfs | *-initramfs-kernel.bin | RAM 디스크 부팅. 디버깅/복구용 (플래시 미사용) |
| rootfs.img | *-squashfs-rootfs.img.gz | SquashFS 루트 이미지만 (별도 커널 필요) |
이미지 헤더 포맷
| 포맷 | 매직 넘버 | 용도 | 구조 |
|---|---|---|---|
| TRX | HDR0 | Broadcom 라우터 (레거시) | 헤더(28B) + kernel + rootfs [+ 추가 파티션] |
| uImage | 27 05 19 56 | U-Boot 레거시 이미지 | 64B 헤더 + 압축 커널 (로드 주소/엔트리 포인트 포함) |
| FIT (ITB) | FDT 구조 | 현대 장비 (권장) | Device Tree 기반 — 다중 커널/DTB/initrd, 서명 지원 |
| UBI | UBI# | NAND 장비 | UBI 볼륨 이미지 — wear leveling, bad block 관리 포함 |
FIT (Flattened Image Tree) 포맷 상세
최신 OpenWrt 타겟은 FIT 이미지를 사용합니다. FIT는 Device Tree 구조를 이용하여 커널, DTB, initrd를 하나의 서명 가능한 이미지로 결합합니다:
/* FIT 이미지 구조 (.its 파일) */
/dts-v1/;
/ {
description = "OpenWrt FIT Image";
images {
kernel-1 {
data = /incbin/("zImage");
type = "kernel";
arch = "arm";
compression = "none";
hash-1 { algo = "sha256"; };
};
fdt-1 {
data = /incbin/("my-board.dtb");
type = "flat_dt";
arch = "arm";
hash-1 { algo = "sha256"; };
};
};
configurations {
default = "config-1";
config-1 {
kernel = "kernel-1";
fdt = "fdt-1";
signature-1 {
algo = "sha256,rsa2048";
key-name-hint = "dev";
};
};
};
};
eMMC 장비 레이아웃
최신 고성능 라우터(MediaTek MT7986, Qualcomm IPQ807x 등)는 eMMC를 사용합니다:
# eMMC 파티션 레이아웃 (GPT)
# /dev/mmcblk0p1 boot0 — BL2 (ARM Trusted Firmware stage 1)
# /dev/mmcblk0p2 boot1 — FIP (BL31 + U-Boot)
# /dev/mmcblk0p3 env — U-Boot 환경변수
# /dev/mmcblk0p4 factory — 칼리브레이션 데이터 (WiFi, MAC)
# /dev/mmcblk0p5 kernel — FIT 이미지 (커널 + DTB)
# /dev/mmcblk0p6 rootfs — SquashFS 루트 파일시스템
# /dev/mmcblk0p7 rootfs_data — ext4/f2fs overlay (쓰기 가능)
# 이미지 크기 최적화 팁
# 1. 불필요한 커널 모듈 제거 (kmod-* 최소화)
# 2. LuCI 대신 CLI만 사용 시 ~1MB 절약
# 3. SquashFS XZ 압축으로 30-50% 절약 (기본 활성화)
# 4. strip 레벨: CONFIG_USE_SSTRIP=y (초공격적 strip)
sysupgrade 메커니즘
# 펌웨어 업그레이드 (설정 보존)
sysupgrade -v /tmp/firmware-sysupgrade.bin
# 설정 초기화하며 업그레이드
sysupgrade -n /tmp/firmware-sysupgrade.bin
# 업그레이드 전 설정 백업
sysupgrade -b /tmp/backup.tar.gz
# 복구: firstboot (overlay 초기화)
firstboot -y && reboot
# Failsafe 모드: 부팅 시 버튼 누르기 → 192.168.1.1:22 접속
# overlay를 마운트하지 않아 /rom만으로 부팅
Buildroot 아키텍처
Buildroot가 실제로 무엇을 해주는가
Buildroot는 "처음부터 끝까지 한 번의 make로"라는 철학의 빌드 자동화 프레임워크입니다. 아래 다섯 단계를 자동으로 수행합니다.
| 단계 | Buildroot가 하는 일 | 출력 위치 |
|---|---|---|
| ① 소스 다운로드 | 패키지별 tarball·git을 dl/에 캐시합니다. | dl/ |
| ② 툴체인 구축 | GCC·binutils·C 라이브러리를 타겟 아키텍처용으로 빌드합니다. | output/host/ |
| ③ 패키지 크로스 컴파일 | 선택한 패키지를 의존성 순서대로 크로스 컴파일합니다. | output/target/ |
| ④ rootfs 조합 | 컴파일된 파일들을 /bin, /etc, /lib 구조로 조립합니다. | output/target/ |
| ⑤ 이미지 생성 | ext4·squashfs·cpio 등 원하는 포맷으로 패키징합니다. | output/images/ |
menuconfig로 구성하고 표준 make로 빌드합니다. 2,800개 이상의 패키지가 모두 같은 패턴의 .mk 파일로 정의되어 있어 새 패키지 추가 진입 장벽이 낮습니다.
Buildroot는 "Make를 사용하여 임베디드 리눅스 시스템을 간단하게 생성합니다"는 철학을 따릅니다. 전체가 Makefile과 Kconfig로 구성되어 있으며, 추가 빌드 도구(BitBake 같은)가 필요 없습니다.
소스 트리 구조
buildroot/
├── Makefile # 최상위 Makefile
├── Config.in # 최상위 Kconfig
├── package/ # 패키지 정의 (2800+ 패키지)
│ ├── busybox/ # BusyBox
│ │ ├── busybox.mk # 빌드 레시피
│ │ ├── Config.in # Kconfig 옵션
│ │ └── busybox.config # BusyBox 설정
│ ├── openssl/
│ │ ├── openssl.mk
│ │ └── Config.in
│ └── pkg-generic.mk # 공통 패키지 인프라
├── board/ # 보드별 설정/스크립트
│ ├── qemu/ # QEMU 보드
│ │ └── arm-vexpress/
│ │ ├── linux.config # 커널 설정
│ │ ├── rootfs-overlay/ # 루트FS 오버레이
│ │ └── post-build.sh # 빌드 후 스크립트
│ └── raspberrypi/ # Raspberry Pi
├── configs/ # 사전 정의된 defconfig
│ ├── qemu_arm_vexpress_defconfig
│ ├── raspberrypi4_64_defconfig
│ └── ... # 200+ defconfig
├── dl/ # 다운로드 캐시
├── output/ # 빌드 출력 (아래에서 상세)
├── boot/ # 부트로더 패키지 (U-Boot, GRUB, Barebox)
├── linux/ # 커널 빌드 규칙
├── toolchain/ # 툴체인 빌드/외부 정의
├── system/ # 시스템 설정 (hostname, init, overlay 등)
├── fs/ # 파일시스템 이미지 생성 (ext4, squashfs, cpio 등)
├── support/ # 빌드 지원 스크립트
└── docs/ # 문서
시스템 스켈레톤 (system/)
Buildroot의 system/ 디렉토리는 루트 파일시스템의 기본 구조를 정의합니다:
# system/skeleton/ — 기본 루트FS 구조
system/skeleton/
├── etc/
│ ├── fstab # 파일시스템 마운트 테이블
│ ├── group # 기본 그룹 (root, daemon, ...)
│ ├── hostname # BR2_TARGET_GENERIC_HOSTNAME
│ ├── hosts # 로컬 호스트 매핑
│ ├── inittab # BusyBox init 설정
│ ├── issue # 로그인 배너
│ ├── passwd # 기본 사용자 (root)
│ ├── profile # 셸 프로필
│ ├── protocols # 프로토콜 번호
│ ├── resolv.conf # DNS 설정
│ ├── services # 서비스 포트 매핑
│ └── shadow # 패스워드 해시 (BR2_TARGET_GENERIC_ROOT_PASSWD)
└── usr/
# 시스템 설정 옵션 (menuconfig → System configuration)
BR2_TARGET_GENERIC_HOSTNAME="my-device" # 호스트 이름
BR2_TARGET_GENERIC_ISSUE="Welcome to MyDevice" # 로그인 배너
BR2_TARGET_GENERIC_ROOT_PASSWD="secret" # root 패스워드
BR2_TARGET_GENERIC_GETTY_PORT="ttyS0" # 시리얼 콘솔 포트
BR2_TARGET_GENERIC_GETTY_BAUDRATE="115200" # 시리얼 속도
# init 시스템 선택
BR2_INIT_BUSYBOX=y # BusyBox init (기본, 가장 작음)
# BR2_INIT_SYSV=y # sysvinit
# BR2_INIT_SYSTEMD=y # systemd (glibc 필요)
# BR2_INIT_NONE=y # init 없음 (커스텀)
Kconfig 시스템
Buildroot의 모든 설정은 BR2_* 변수로 관리됩니다:
# 설정 방법들
make menuconfig # ncurses 메뉴
make nconfig # 대안 ncurses
make xconfig # Qt GUI
make gconfig # GTK GUI
# defconfig 워크플로
make qemu_arm_vexpress_defconfig # 사전 설정 적용
make menuconfig # 추가 커스터마이징
make savedefconfig # 변경 저장 → configs/
# 주요 설정 카테고리 (BR2_*)
# BR2_TOOLCHAIN_* — 툴체인 옵션
# BR2_LINUX_KERNEL_* — 커널 설정
# BR2_PACKAGE_* — 패키지 선택
# BR2_TARGET_ROOTFS_* — 루트FS 이미지 형식
# BR2_ROOTFS_OVERLAY — 루트FS 오버레이 경로
# BR2_ROOTFS_POST_BUILD_SCRIPT — 빌드 후 스크립트
툴체인 선택
| 옵션 | 설명 | 장점 | 단점 |
|---|---|---|---|
| Internal | Buildroot가 직접 GCC+binutils+C lib 빌드 | 완전한 제어, 최신 버전 | 빌드 시간 증가 (~20분) |
| External prebuilt | Linaro, ARM 등 사전 빌드 툴체인 사용 | 빠른 빌드 시작 | 버전 제한 |
| External custom | crosstool-NG 등으로 직접 빌드한 툴체인 | 완전 맞춤화 | 별도 관리 필요 |
Buildroot 빌드 과정
빌드 명령어 상세
# 기본 빌드
make -j$(nproc)
# 특정 패키지 조작
make busybox-menuconfig # BusyBox 설정
make linux-menuconfig # 커널 설정
make uboot-menuconfig # U-Boot 설정
# 패키지 재빌드
make busybox-rebuild # 소스 변경 후 재빌드
make busybox-reconfigure # configure부터 재실행
make busybox-dirclean # 완전 삭제 후 처음부터
# 시각화
make graph-depends # 의존성 그래프 (output/graphs/)
make graph-build # 빌드 시간 그래프
make graph-size # 이미지 크기 분석
# 법적 정보 수집
make legal-info # 라이선스, 소스 코드 아카이브 생성
# SDK 생성 (외부 개발용)
make sdk # output/images/에 SDK 타르볼
per-package-directories 모드
Buildroot 2020.02부터 도입된 BR2_PER_PACKAGE_DIRECTORIES는 top-level 병렬 빌드를 가능하게 합니다:
# 활성화: menuconfig → Build options → Use per-package directories
BR2_PER_PACKAGE_DIRECTORIES=y
# 기존 방식: 모든 패키지가 하나의 staging/target 공유
# → 패키지 간 빌드 순서 직렬화 필요
# per-package 방식: 각 패키지가 독립적인 staging/target 보유
# → 의존성 없는 패키지 동시 빌드 가능
# → 재빌드 시 영향 범위 최소화
# 디렉토리 구조 변화
output/
├── per-package/
│ ├── busybox/
│ │ ├── host/ # busybox 전용 호스트 도구
│ │ ├── staging/ # busybox 전용 sysroot
│ │ └── target/ # busybox 전용 타겟
│ ├── openssl/
│ │ ├── host/
│ │ ├── staging/
│ │ └── target/
│ └── ...
└── target/ # 최종 통합 루트FS
ccache 통합
# ccache 활성화: menuconfig → Build options → Enable compiler cache
BR2_CCACHE=y
BR2_CCACHE_DIR="/shared/buildroot-ccache" # 공유 캐시 디렉토리
# ccache 통계 확인
make ccache-stats
# 효과: 전체 리빌드 시 50-80% 시간 단축 (캐시 히트 시)
graph-size 이미지 크기 분석
# 이미지 크기 분석 실행
make graph-size
# 결과물: output/graphs/
# ├── graph-size.pdf # 패키지별 크기 파이 차트
# ├── package-size-stats.csv # CSV 데이터
# └── file-size-stats.csv # 파일별 크기
# 크기 최적화 워크플로
# 1. make graph-size 실행
# 2. 큰 패키지 확인 (보통 glibc > openssl > busybox > python)
# 3. 불필요 패키지 제거 또는 musl로 전환
# 4. BR2_STRIP_strip=y (기본) → 바이너리 strip
O= 변수로 여러 설정을 동시에 빌드할 수 있습니다:
make O=/path/to/output1 qemu_arm_vexpress_defconfig,
make O=/path/to/output2 raspberrypi4_64_defconfig.
각 output 디렉토리가 독립적인 빌드 환경을 가집니다.
Buildroot 패키지 인프라
Buildroot는 다양한 빌드 시스템에 맞춘 패키지 인프라(infrastructure)를 제공합니다. 개발자는 적절한 인프라를 선택하여 최소한의 코드로 패키지를 정의할 수 있습니다.
패키지 인프라 유형
| 인프라 | Makefile 매크로(Macro) | 대상 빌드 시스템 | 예시 패키지 |
|---|---|---|---|
| generic | $(eval $(generic-package)) | 커스텀 Makefile | BusyBox, Linux 커널 |
| autotools | $(eval $(autotools-package)) | configure/make/make install | coreutils, glib |
| cmake | $(eval $(cmake-package)) | CMake | systemd, json-c |
| meson | $(eval $(meson-package)) | Meson + Ninja | pipewire, gstreamer |
| python | $(eval $(python-package)) | setuptools/pip | python-requests |
| golang | $(eval $(golang-package)) | Go modules | containerd, etcd |
| cargo | $(eval $(cargo-package)) | Rust Cargo | ripgrep |
| kconfig | $(eval $(kconfig-package)) | Kconfig 기반 | BusyBox, Linux |
Generic 패키지 예제
# package/hello/hello.mk
HELLO_VERSION = 1.0
HELLO_SOURCE = hello-$(HELLO_VERSION).tar.gz
HELLO_SITE = https://example.com/downloads
HELLO_LICENSE = MIT
HELLO_LICENSE_FILES = LICENSE
HELLO_DEPENDENCIES = host-pkgconf openssl
define HELLO_BUILD_CMDS
$(MAKE) $(TARGET_CONFIGURE_OPTS) -C $(@D)
endef
define HELLO_INSTALL_TARGET_CMDS
$(INSTALL) -D -m 0755 $(@D)/hello $(TARGET_DIR)/usr/bin/hello
endef
$(eval $(generic-package))
CMake 패키지 예제
# package/myapp/myapp.mk
MYAPP_VERSION = 2.0
MYAPP_SITE = $(call github,myorg,myapp,v$(MYAPP_VERSION))
MYAPP_LICENSE = Apache-2.0
MYAPP_INSTALL_STAGING = YES
MYAPP_DEPENDENCIES = openssl zlib
MYAPP_CONF_OPTS = \
-DBUILD_TESTS=OFF \
-DWITH_SSL=ON
$(eval $(cmake-package))
가상 패키지 (Virtual Packages)
Buildroot의 가상 패키지는 동일한 기능을 제공하는 대안 패키지를 추상화합니다:
# package/jpeg/Config.in — 가상 패키지 정의
config BR2_PACKAGE_HAS_JPEG
bool # "provides" 마커
config BR2_PACKAGE_PROVIDES_JPEG
string
default "libjpeg" if BR2_PACKAGE_LIBJPEG
default "jpeg-turbo" if BR2_PACKAGE_JPEG_TURBO
# 의존하는 패키지:
# MYAPP_DEPENDENCIES = jpeg ← 가상 패키지명으로 의존
# → menuconfig에서 libjpeg 또는 jpeg-turbo 중 선택
호스트 패키지 (host-xxx)
# 호스트 패키지는 빌드 호스트에서 실행되는 도구를 빌드
# 패키지명에 host- 접두사 사용
HOST_MYAPP_DEPENDENCIES = host-pkgconf host-openssl
define HOST_MYAPP_BUILD_CMDS
$(MAKE) -C $(@D)
endef
define HOST_MYAPP_INSTALL_CMDS
$(INSTALL) -D $(@D)/mytool $(HOST_DIR)/bin/mytool
endef
$(eval $(host-generic-package)) # host- 접두사 인프라
사용자/권한 관리 (FOO_USERS, FOO_PERMISSIONS)
# package/myapp/myapp.mk — 사용자/그룹/권한 설정
MYAPP_USERS = myapp -1 myapp -1 * /var/lib/myapp - - My Application User
# 형식: username uid group gid password home shell groups comment
# -1은 자동 할당
MYAPP_PERMISSIONS = /etc/myapp.conf f 0600 root myapp - - - - -
MYAPP_PERMISSIONS += /var/lib/myapp d 0750 myapp myapp - - - - -
# systemd 서비스 설치 패턴
define MYAPP_INSTALL_INIT_SYSTEMD
$(INSTALL) -D -m 644 $(MYAPP_PKGDIR)/myapp.service \
$(TARGET_DIR)/usr/lib/systemd/system/myapp.service
endef
# sysvinit 서비스 설치 패턴
define MYAPP_INSTALL_INIT_SYSV
$(INSTALL) -D -m 755 $(MYAPP_PKGDIR)/S99myapp \
$(TARGET_DIR)/etc/init.d/S99myapp
endef
Config.in 구조
# package/hello/Config.in
config BR2_PACKAGE_HELLO
bool "hello"
depends on BR2_TOOLCHAIN_HAS_THREADS
select BR2_PACKAGE_OPENSSL
help
A simple Hello World application.
https://example.com/hello
BR2_EXTERNAL: 외부 트리
회사/프로젝트 전용 패키지와 설정을 Buildroot 소스 트리 외부에 관리할 수 있습니다:
# 외부 트리 구조
my-external/
├── Config.in # 외부 패키지 Kconfig
├── external.mk # 외부 패키지 포함
├── external.desc # 이름, 설명
├── configs/ # 커스텀 defconfig
│ └── my_board_defconfig
├── package/ # 커스텀 패키지
│ └── my-app/
│ ├── my-app.mk
│ └── Config.in
└── board/ # 보드 설정
└── my-company/
└── my-board/
├── linux.config
├── rootfs-overlay/
├── post-build.sh
└── genimage.cfg
# 사용법
make BR2_EXTERNAL=/path/to/my-external menuconfig
# 또는
make BR2_EXTERNAL=/path/to/my-external my_board_defconfig
Yocto/OpenEmbedded 아키텍처
Yocto의 핵심 개념 — 레이어·레시피·BitBake
Yocto를 처음 접하면 용어가 낯설어 진입 장벽이 높습니다. 세 가지 핵심 개념만 이해하면 구조가 보입니다.
| 개념 | 쉬운 설명 | 파일 형식 |
|---|---|---|
| 레시피(Recipe) | "이 패키지를 어떻게 빌드하는가"를 기술한 스크립트. Buildroot의 .mk, OpenWrt의 패키지 Makefile과 동일한 역할입니다. |
.bb (BitBake recipe) |
| 레이어(Layer) | 관련 레시피·설정 파일의 묶음 디렉토리. 레고 블록처럼 여러 레이어를 쌓아 최종 이미지를 구성합니다. BSP 레이어(하드웨어), 기능 레이어(보안·네트워크), 제품 레이어(내 프로젝트)로 분리해 재사용합니다. | meta-xxx/ 디렉토리 |
| BitBake | 레시피를 파싱하고 의존성 그래프를 분석해 병렬 태스크(Task)로 실행하는 빌드 엔진. Make와 달리 레시피 간 공유 상태를 캐시(sstate-cache)로 관리해 증분 빌드를 지원합니다. | Python/Shell 실행 엔진 |
Yocto Project는 가장 유연하고 확장성 있는 임베디드 빌드 프레임워크입니다. 핵심은 레이어 아키텍처로, 독립적인 메타데이터 레이어를 쌓아 올려 맞춤형 배포판을 구성합니다.
Poky 구성요소
Poky는 Yocto의 레퍼런스 배포판으로, 다음으로 구성됩니다:
- BitBake — 태스크 실행 엔진 (Python/Shell 레시피 파싱, 의존성 해결, 병렬 실행)
- OpenEmbedded-Core (OE-Core) — 핵심 recipe/class/conf (meta/ 디렉토리)
- meta-poky — Poky 배포판 정책 (distro.conf)
- meta-yocto-bsp — 레퍼런스 BSP (QEMU, BeagleBone 등)
DISTRO_FEATURES vs MACHINE_FEATURES vs IMAGE_FEATURES
| 변수 | 설정 위치 | 역할 | 예시 |
|---|---|---|---|
| DISTRO_FEATURES | distro.conf | 배포판 수준 기능 (소프트웨어 정책) | systemd wifi bluetooth ipv6 pam |
| MACHINE_FEATURES | machine.conf | 하드웨어 기능 (보드가 제공하는 것) | serial apm usbhost wifi gpu |
| IMAGE_FEATURES | 이미지 recipe/local.conf | 이미지에 포함할 기능 세트 | debug-tweaks ssh-server-openssh tools-debug |
# DISTRO_FEATURES 활용 — 배포판 정책
# systemd를 init으로 사용:
DISTRO_FEATURES:append = " systemd"
DISTRO_FEATURES_BACKFILL_CONSIDERED:append = " sysvinit"
VIRTUAL-RUNTIME_init_manager = "systemd"
# MACHINE_FEATURES 활용 — 하드웨어 능력
# meta-raspberrypi/conf/machine/raspberrypi4-64.conf:
MACHINE_FEATURES = "apm usbhost wifi bluetooth vfat ext2 screen touchscreen"
# IMAGE_FEATURES 활용 — 이미지 구성
IMAGE_FEATURES += "debug-tweaks" # root 비밀번호 없이 로그인 (개발용)
IMAGE_FEATURES += "ssh-server-openssh" # SSH 서버 포함
IMAGE_FEATURES += "tools-debug" # gdb, strace 등 디버그 도구
multiconfig 지원
Yocto의 multiconfig는 하나의 빌드 환경에서 여러 머신/배포판을 동시에 빌드합니다:
# conf/multiconfig/board-a.conf
MACHINE = "board-a"
DISTRO = "my-distro"
# conf/multiconfig/board-b.conf
MACHINE = "board-b"
DISTRO = "my-distro"
# local.conf
BBMULTICONFIG = "board-a board-b"
# 빌드
bitbake mc:board-a:core-image-minimal mc:board-b:core-image-minimal
kas 설정 도구
kas는 Yocto 프로젝트의 레이어/설정을 YAML로 선언적 관리하는 도구입니다:
# kas-project.yml
header:
version: 14
machine: raspberrypi4-64
distro: poky
repos:
poky:
url: "https://git.yoctoproject.org/poky"
branch: scarthgap
layers:
meta:
meta-poky:
meta-raspberrypi:
url: "https://git.yoctoproject.org/meta-raspberrypi"
branch: scarthgap
local_conf_header:
custom: |
IMAGE_INSTALL:append = " openssh python3"
PACKAGE_CLASSES = "package_rpm"
target: core-image-full-cmdline
# kas 사용
pip install kas
kas build kas-project.yml # 전체 빌드
kas shell kas-project.yml # 빌드 환경 셸 진입
kas checkout kas-project.yml # 레이어 클론만
레이어 아키텍처
| 레이어 | 설명 | 제공처 |
|---|---|---|
meta/ (OE-Core) | 기본 recipe, class, qemu BSP | OE 커뮤니티 |
meta-poky | Poky 배포판 정책 | Yocto Project |
meta-openembedded | 추가 패키지 (networking, python, perl 등) | OE 커뮤니티 |
meta-ti-bsp | Texas Instruments BSP (AM335x, AM62x 등) | TI |
meta-raspberrypi | Raspberry Pi BSP | 커뮤니티 |
meta-intel | Intel BSP (x86, Edison 등) | Intel |
meta-freescale | NXP/Freescale BSP (i.MX, Layerscape) | NXP |
meta-security | 보안 도구 (SELinux, IMA, AppArmor) | 커뮤니티 |
meta-virtualization | 컨테이너(Container), 하이퍼바이저(Hypervisor) (Docker, KVM) | 커뮤니티 |
Recipe (.bb) 구조
# recipes-example/hello/hello_1.0.bb
SUMMARY = "Hello World Application"
DESCRIPTION = "A simple Hello World for Yocto"
LICENSE = "MIT"
LIC_FILES_CHKSUM = "file://LICENSE;md5=..."
SRC_URI = "https://example.com/hello-${PV}.tar.gz"
SRC_URI[sha256sum] = "abc123..."
DEPENDS = "openssl"
RDEPENDS:${PN} = "libssl"
S = "${WORKDIR}/hello-${PV}"
inherit autotools pkgconfig
EXTRA_OECONF = "--with-ssl"
do_install:append() {
install -d ${D}${sysconfdir}
install -m 0644 ${WORKDIR}/hello.conf ${D}${sysconfdir}/
}
주요 설정 변수
# build/conf/local.conf
MACHINE = "raspberrypi4-64" # 타겟 머신
DISTRO = "poky" # 배포판
PACKAGE_CLASSES = "package_rpm" # 패키지 형식 (rpm/deb/ipk)
IMAGE_INSTALL:append = " openssh vim strace gdb"
DISTRO_FEATURES:append = " systemd wifi bluetooth"
DISTRO_FEATURES:remove = "sysvinit"
BB_NUMBER_THREADS = "8"
PARALLEL_MAKE = "-j 8"
SSTATE_DIR = "/opt/yocto/sstate"
DL_DIR = "/opt/yocto/downloads"
# build/conf/bblayers.conf
BBLAYERS = " \
/path/to/poky/meta \
/path/to/poky/meta-poky \
/path/to/meta-openembedded/meta-oe \
/path/to/meta-raspberrypi \
/path/to/meta-my-product \
"
Yocto 빌드 과정
핵심 BitBake 명령어
# 환경 초기화 (반드시 매 세션마다)
source oe-init-build-env [build-dir]
# 이미지 빌드
bitbake core-image-minimal
bitbake core-image-sato
bitbake core-image-full-cmdline
# 특정 recipe 빌드
bitbake busybox
bitbake -c compile busybox
bitbake -c devshell busybox
bitbake -c cleansstate busybox
# 레이어 관리
bitbake-layers show-layers
bitbake-layers add-layer ../meta-xxx
bitbake-layers show-recipes
# 의존성 분석
bitbake -g core-image-minimal
bitbake -e busybox | grep ^SRC_URI
# SDK 생성
bitbake -c populate_sdk core-image-minimal
bitbake -c populate_sdk_ext core-image-minimal
Hash Equivalence Server
Yocto 3.1+의 Hash Equivalence Server는 동일한 출력을 생성하는 다른 입력 해시(Hash)를 매핑(Mapping)하여 불필요한 재빌드를 추가 방지합니다:
# local.conf에서 활성화
BB_HASHSERVE = "auto"
BB_SIGNATURE_HANDLER = "OEEquivHash"
# 원리: A 패키지의 입력이 변경되었지만 출력 바이너리가 동일한 경우
# → B 패키지(A에 의존)를 재빌드하지 않음
# → 예: 주석만 변경된 소스 파일, 환경변수 순서 변경 등
재현 가능 빌드 (Reproducible Builds)
# Yocto 3.4+: 재현 가능 빌드 기본 활성화
# local.conf:
BUILD_REPRODUCIBLE_BINARIES = "1"
# 빌드 히스토리 추적 (변경사항 모니터링)
INHERIT += "buildhistory"
BUILDHISTORY_COMMIT = "1"
# 결과: buildhistory/ 디렉토리에 git 이력으로
# - 패키지 목록 변경
# - 파일 크기/권한 변경
# - 의존성 변경 추적
크로스 컴파일 툴체인 비교
툴체인 삼각관계: 호스트 · 타겟 · sysroot
크로스 컴파일 환경에는 항상 세 가지 역할이 존재합니다. 이 관계를 이해해야 툴체인 오류를 읽을 수 있습니다.
| 역할 | 설명 | 실제 예시 |
|---|---|---|
| 호스트(Host) | 컴파일러가 실행되는 개발 PC | Ubuntu 22.04 / x86_64 |
| 타겟(Target) | 최종 바이너리가 실행될 임베디드 장치 | OpenWrt 라우터 / ARM Cortex-A7 |
| sysroot | 타겟 환경의 헤더(.h)와 라이브러리(.so)를 호스트에 복사한 디렉토리. 컴파일러가 이 경로를 참조해 타겟용 바이너리를 생성합니다. |
staging_dir/target-arm_cortex-a7_musl/ |
/usr/lib)에서 찾으면 안 됩니다. ARM용으로 컴파일된 libpthread.so를 x86용 코드에 링크하면 동작하지 않기 때문입니다. sysroot는 타겟용 바이너리만 모아 놓은 격리(Isolation)된 루트 디렉토리로, --sysroot= 옵션으로 컴파일러에 전달합니다. 빌드 시스템은 이 sysroot를 자동으로 구성하고 관리합니다.
임베디드 빌드 시스템의 핵심은 크로스 컴파일 툴체인입니다. 특히 C 라이브러리 선택은 바이너리 크기, 호환성, 성능에 큰 영향을 미칩니다. 크로스 컴파일의 이론적 배경은 LFS 문서를 참고하세요.
ABI 호환성 매트릭스
| ARM 옵션 | 접미사 | 설명 | 호환성 |
|---|---|---|---|
| Hard-float (VFPv3) | gnueabihf / musleabihf | 하드웨어 FPU 사용, 부동소수점 인자를 FPU 레지스터(Register)로 전달 | Cortex-A7+ 필수, 가장 빠름 |
| Soft-float | gnueabi / musleabi | 소프트웨어 부동소수점 에뮬레이션 | FPU 없는 프로세서, 느림 |
| AArch64 | aarch64-linux-gnu | 64비트 ARM, 항상 하드웨어 FP | ARMv8-A+ |
- cannot find -lxxx — sysroot에 해당 라이브러리 미설치.
PKG_CONFIG_SYSROOT_DIR확인 - GLIBC_2.xx not found — 타겟 glibc 버전이 빌드 시 사용한 것보다 낮음
- Illegal instruction — 타겟 CPU가 지원하지 않는 명령어 생성 (예: Cortex-A7에서 Cortex-A72 코드)
- dynamic linker not found — 정적/동적 링킹(Dynamic Linking) 불일치, 또는 sysroot의 ld.so 경로 불일치
정적 vs 동적 링킹 트레이드오프
| 항목 | 정적 링킹(Static Linking) | 동적 링킹 |
|---|---|---|
| 바이너리 크기 | 큼 (라이브러리 포함) | 작음 (공유 라이브러리(Shared Library) 참조) |
| 총 시스템 크기 | 적은 프로그램: 작음 / 많은 프로그램: 큼 | 라이브러리 공유로 절약 |
| 보안 업데이트 | 모든 바이너리 재빌드 필요 | 라이브러리만 교체 |
| 배포 편의성 | 단일 파일, 의존성 없음 | .so 파일 함께 배포 필요 |
| 사용 사례 | initramfs, rescue 도구, 단일 앱 장비 | 일반 임베디드 시스템 |
시스템별 툴체인 전략
OpenWrt 툴체인
# OpenWrt는 항상 내부 빌드 (musl 기본, glibc 선택 가능)
make menuconfig
# → Advanced configuration → Toolchain Options
# → C Library implementation (musl / glibc)
# 결과물
staging_dir/toolchain-mips_24kc_gcc-13.3.0_musl/bin/
├── mips-openwrt-linux-musl-gcc
├── mips-openwrt-linux-musl-g++
├── mips-openwrt-linux-musl-ld
└── mips-openwrt-linux-musl-strip
Buildroot 툴체인
# 내부 빌드 (Internal)
make menuconfig
# → Toolchain → Toolchain type: Internal toolchain
# → C library: musl / glibc / uClibc-ng
# 외부 프리빌트 (Linaro ARM)
# → Toolchain type: External toolchain
# → Toolchain: Linaro ARM 2024.xx
# 결과물 (내부 빌드 시)
output/host/bin/
├── arm-buildroot-linux-musleabihf-gcc
├── arm-buildroot-linux-musleabihf-g++
└── ...
Yocto 툴체인/SDK
# Standard SDK 생성
bitbake -c populate_sdk core-image-minimal
# SDK 설치
./poky-glibc-x86_64-...-toolchain-4.0.sh -d /opt/yocto-sdk
# SDK 환경 설정
source /opt/yocto-sdk/environment-setup-cortexa72-poky-linux
# 크로스 컴파일
$CC -o hello hello.c # $CC = aarch64-poky-linux-gcc + sysroot flags
# Extensible SDK (eSDK) — devtool 포함
bitbake -c populate_sdk_ext core-image-minimal
Sysroot 구조
# 공통 sysroot 구조 (모든 시스템)
sysroot/
├── usr/
│ ├── include/ # 타겟 시스템 헤더
│ │ ├── linux/ # 커널 헤더
│ │ ├── stdio.h # C 라이브러리 헤더
│ │ └── openssl/ # 패키지 헤더
│ └── lib/ # 타겟 라이브러리
│ ├── libc.so # C 라이브러리
│ ├── libssl.so # OpenSSL
│ └── pkgconfig/ # pkg-config 메타데이터
└── lib/
└── ld-musl-mips.so.1 # 동적 링커
커널 빌드 커스터마이징
각 빌드 시스템은 고유한 방식으로 커널 빌드를 관리합니다. 커널 설정, 패치 적용, Device Tree 생성, 버전 관리 방법이 모두 다릅니다.
OpenWrt 커널 커스터마이징
# 커널 설정 파일 위치
target/linux/ath79/config-6.6 # 타겟별 기본 설정
target/linux/generic/config-6.6 # 공통 설정
# 커널 패치
target/linux/ath79/patches-6.6/
├── 0001-ath79-add-new-board.patch
└── 0002-ath79-fix-pcie.patch
# 커널 소스에 추가할 파일 (drivers, DTS 등)
target/linux/ath79/files/
├── arch/mips/dts/my-board.dts
└── drivers/net/phy/my-phy.c
# 커널 menuconfig
make kernel_menuconfig
# 변경사항을 config-6.6에 반영
make kernel_oldconfig
# 특정 커널 모듈만 빌드
make target/linux/compile V=s
Buildroot 커널 커스터마이징
# menuconfig에서 커널 설정
make menuconfig
# → Kernel
# → Kernel version: 6.6.x (또는 custom tarball)
# → Kernel configuration: Using a custom config file
# → Configuration file path: board/my-company/my-board/linux.config
# → Custom kernel patches: board/my-company/my-board/patches/
# 커널 menuconfig 직접 실행
make linux-menuconfig
# 커널 설정 저장 (defconfig 형태)
make linux-update-defconfig
# 커널만 재빌드
make linux-rebuild
Yocto 커널 커스터마이징
# 커널 recipe 위치 확인
bitbake -e virtual/kernel | grep ^FILE=
# 커널 menuconfig
bitbake -c menuconfig virtual/kernel
# 커널 설정 fragment 방식 (권장)
# meta-my-layer/recipes-kernel/linux/linux-yocto_%.bbappend
FILESEXTRAPATHS:prepend := "${THISDIR}/files:"
SRC_URI += "file://my-config.cfg"
SRC_URI += "file://0001-my-driver.patch"
# files/my-config.cfg (config fragment)
CONFIG_MY_DRIVER=m
CONFIG_USB_GADGET=y
# KERNEL_FEATURES (Yocto 고유 기능)
# 커널 기능을 논리적으로 그룹화한 .scc 파일
KERNEL_FEATURES:append = " features/netfilter/netfilter.scc"
KERNEL_FEATURES:append = " cfg/virtio.scc"
# DTB 지정
KERNEL_DEVICETREE = "broadcom/bcm2711-rpi-4-b.dtb"
# 커널 devshell (소스 디렉토리에서 직접 작업)
bitbake -c devshell virtual/kernel
out-of-tree 커널 모듈 패턴
| 시스템 | 패턴 | 예시 |
|---|---|---|
| OpenWrt | package/kernel/my-module/Makefile + KernelPackage | define KernelPackage/my-module ... FILES:=$(LINUX_DIR)/drivers/my-module.ko endef |
| Buildroot | package/my-module/my-module.mk + kernel-module infra | $(eval $(kernel-module)) $(eval $(generic-package)) |
| Yocto | inherit module in recipe | inherit module — do_compile은 자동으로 make -C ${STAGING_KERNEL_DIR} M=${S} |
커널 버전 고정 전략
# OpenWrt: 타겟별 커널 버전은 include/kernel-version.mk에서 관리
# → 사용자 변경 비권장 (타겟-커널 호환성 보장)
# Buildroot: menuconfig에서 지정
BR2_LINUX_KERNEL_CUSTOM_VERSION=y
BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE="6.6.30"
# 또는 커스텀 tarball/git:
BR2_LINUX_KERNEL_CUSTOM_TARBALL=y
BR2_LINUX_KERNEL_CUSTOM_TARBALL_LOCATION="https://example.com/linux-6.6.30.tar.xz"
# Yocto: PREFERRED_VERSION으로 고정
PREFERRED_VERSION_linux-yocto = "6.6%" # 6.6.x 시리즈
# 또는 정확한 커밋 고정:
SRCREV = "abc1234..." # in linux-yocto bbappend
.cfg fragment를, Buildroot에서는 BR2_LINUX_KERNEL_CONFIG_FRAGMENT_FILES를,
OpenWrt에서는 config-* 파일에 delta만 기록하는 방식을 권장합니다.
커스텀 보드 지원 (BSP)
새 보드를 처음 만났을 때 — 어디서 시작하는가
BSP(Board Support Package)는 특정 하드웨어 보드에서 리눅스가 동작하게 만드는 모든 것의 묶음입니다. 새 보드를 처음 지원할 때 막막하게 느껴지는 이유는 작업 범위가 넓기 때문입니다. 아래 순서로 접근하면 단계를 나눌 수 있습니다.
| 단계 | 해야 할 일 | 확인 방법 |
|---|---|---|
| ① 유사 보드 찾기 | 같은 SoC(칩셋)를 사용하는 기존 지원 보드를 찾습니다. 90% 이상의 설정을 재사용할 수 있습니다. | SoC 데이터시트에서 칩 이름 확인 → 빌드 시스템에서 검색 |
| ② 시리얼 콘솔 확보 | UART 핀을 찾아 115200bps로 연결합니다. U-Boot 메시지가 나오면 포팅의 50%가 완료된 것입니다. | picocom -b 115200 /dev/ttyUSB0 |
| ③ Device Tree 작성 | 보드의 하드웨어 구성(CPU, 메모리, 주변장치 연결)을 .dts로 기술합니다. SoC 공통 .dtsi를 include하고 보드 특이 사항만 추가합니다. |
cat /proc/device-tree/model 로 로드 확인 |
| ④ 커널 설정 최소화 | 유사 보드 defconfig에서 불필요한 드라이버를 제거합니다. 이더넷·플래시·UART 드라이버가 핵심입니다. | dmesg | grep -i error 로 누락 드라이버 확인 |
| ⑤ 플래시 레이아웃 정의 | 부트로더·커널·rootfs 파티션 크기와 시작 주소를 결정합니다. SoC 부팅 요구 사항을 데이터시트에서 확인합니다. | cat /proc/mtd 또는 lsblk |
| ⑥ 빌드 시스템에 등록 | 위 산출물을 OpenWrt target/, Buildroot board/, Yocto meta-bsp/에 추가합니다. |
make menuconfig에서 보드가 보이는지 확인 |
tftpboot 명령으로 네트워크에서 커널과 rootfs를 RAM에 직접 로드해 테스트하면 플래시 없이 빠르게 반복할 수 있습니다. 커널이 완전히 안정화된 이후에 플래시 레이아웃을 확정하는 것이 효율적입니다.
새로운 하드웨어 보드를 빌드 시스템에 통합하려면 BSP(Board Support Package)를 작성해야 합니다. BSP는 부트로더, 커널 설정, Device Tree, 보드별 드라이버, 플래시 레이아웃을 포함합니다.
BSP 검증 체크리스트
- 시리얼 콘솔 출력 확인 (U-Boot → 커널 → 로그인 프롬프트)
- 커널 부팅 시 Device Tree 올바르게 로드 확인:
cat /proc/device-tree/model - 주요 하드웨어 초기화: 이더넷, WiFi, USB, GPIO, I2C, SPI
- 플래시 파티션 레이아웃 확인:
cat /proc/mtd또는lsblk - 부트로더 환경변수 정상 작동:
fw_printenv - Watchdog 타이머(Timer) 설정 확인 (타이머 미설정 시 재부팅 루프)
- 전원 관리(Power Management)/서스펜드 테스트 (해당하는 경우)
시스템별 U-Boot 커스터마이징
# OpenWrt: U-Boot은 target/linux/*/image/ 에서 관리
# 대부분 장비는 제조사 U-Boot 사용 (교체 비권장)
# Buildroot: boot/uboot/uboot.mk
make menuconfig
# → Bootloaders → U-Boot
# → U-Boot board name: my_board
# → U-Boot custom config file: board/my-company/my-board/uboot.config
make uboot-menuconfig # U-Boot 설정
make uboot-rebuild # U-Boot만 재빌드
# Yocto: u-boot recipe bbappend
# meta-my-bsp/recipes-bsp/u-boot/u-boot_%.bbappend
UBOOT_MACHINE = "my_board_defconfig"
SRC_URI += "file://0001-add-my-board.patch"
SRC_URI += "file://my-board-env.txt"
Buildroot BSP 추가
# board/my-company/my-board/
board/my-company/my-board/
├── linux.config # 커널 defconfig
├── uboot.config # U-Boot defconfig (선택)
├── rootfs-overlay/ # 루트FS에 복사될 파일
│ └── etc/
│ ├── network/interfaces
│ └── init.d/S99myapp
├── post-build.sh # 빌드 후 스크립트
├── post-image.sh # 이미지 생성 후 스크립트
├── genimage.cfg # SD 카드 이미지 레이아웃
└── readme.txt
# configs/my_board_defconfig
BR2_arm=y
BR2_cortex_a7=y
BR2_TOOLCHAIN_EXTERNAL=y
BR2_LINUX_KERNEL=y
BR2_LINUX_KERNEL_CUSTOM_CONFIG_FILE="board/my-company/my-board/linux.config"
BR2_LINUX_KERNEL_DTS_SUPPORT=y
BR2_LINUX_KERNEL_CUSTOM_DTS_PATH="board/my-company/my-board/my-board.dts"
BR2_TARGET_ROOTFS_EXT2=y
BR2_ROOTFS_OVERLAY="board/my-company/my-board/rootfs-overlay"
BR2_ROOTFS_POST_BUILD_SCRIPT="board/my-company/my-board/post-build.sh"
BR2_ROOTFS_POST_IMAGE_SCRIPT="board/my-company/my-board/post-image.sh"
Yocto BSP 레이어 생성
# BSP 레이어 구조
meta-my-bsp/
├── conf/
│ ├── layer.conf
│ └── machine/
│ └── my-board.conf
├── recipes-bsp/
│ ├── u-boot/
│ │ └── u-boot_%.bbappend
│ └── my-firmware/
│ └── my-firmware_1.0.bb
├── recipes-kernel/
│ └── linux/
│ ├── linux-yocto_%.bbappend
│ └── files/
│ ├── my-board.cfg
│ └── 0001-add-driver.patch
└── wic/
└── my-board.wks
# conf/machine/my-board.conf
MACHINE_FEATURES = "serial apm ext2 usbhost wifi"
KERNEL_IMAGETYPE = "zImage"
KERNEL_DEVICETREE = "my-board.dtb"
SERIAL_CONSOLES = "115200;ttyS0"
PREFERRED_PROVIDER_virtual/kernel = "linux-yocto"
IMAGE_FSTYPES = "wic ext4"
WKS_FILE = "my-board.wks"
UBOOT_MACHINE = "my_board_defconfig"
Device Tree 통합
Device Tree는 하드웨어 구성을 커널에 전달하는 데이터 구조로, 임베디드 리눅스 빌드 시스템에서 핵심적인 역할을 합니다. 각 시스템은 DTS(Device Tree Source) 파일의 위치와 빌드 방식이 다릅니다. 상세한 Device Tree 문법과 구조는 Device Tree 문서를 참고하세요.
시스템별 DTS 관리
| 시스템 | DTS 위치 | 설정 방법 | 빌드 방법 |
|---|---|---|---|
| OpenWrt | target/linux/xxx/dts/ | 자동 (profile에 따라 선택) | 커널 빌드 시 자동 컴파일 |
| Buildroot | board/*/ 또는 커널 소스 내 | BR2_LINUX_KERNEL_CUSTOM_DTS_PATH | make linux-rebuild |
| Yocto | meta-*/recipes-kernel/linux/files/ | KERNEL_DEVICETREE in machine.conf | bitbake virtual/kernel |
DT Overlay 런타임 로딩
# DT Overlay를 런타임에 적용 (ConfigFS 방식)
# 1. 오버레이 컴파일
dtc -I dts -O dtb -o /tmp/my-overlay.dtbo my-overlay.dts
# 2. ConfigFS를 통해 적용
mkdir -p /sys/kernel/config/device-tree/overlays/my-overlay
cat /tmp/my-overlay.dtbo > /sys/kernel/config/device-tree/overlays/my-overlay/dtbo
# 3. 제거
rmdir /sys/kernel/config/device-tree/overlays/my-overlay
# fdtoverlay 도구로 오프라인 병합 (이미지 빌드 시)
fdtoverlay -i base.dtb -o merged.dtb overlay1.dtbo overlay2.dtbo
# U-Boot에서 오버레이 적용
fdt addr ${fdt_addr}
fdt resize 8192
fdt apply ${overlay_addr}
DTS 디버깅
# 컴파일된 DTB 역컴파일
dtc -I dtb -O dts -o decompiled.dts my-board.dtb
# 런타임 DT 확인
cat /sys/firmware/devicetree/base/compatible
ls /sys/firmware/devicetree/base/
# Yocto에서 커스텀 DTS 추가
# recipes-kernel/linux/linux-yocto_%.bbappend
SRC_URI += "file://my-board.dts;subdir=git/arch/arm64/boot/dts/my-vendor"
KERNEL_DEVICETREE += "my-vendor/my-board.dtb"
디버깅 및 개발 워크플로
OpenWrt 개발 도구
SDK와 Image Builder
# SDK: 패키지를 독립적으로 크로스 컴파일
tar xf openwrt-sdk-23.05-ath79-generic_gcc-13.3.0_musl.Linux-x86_64.tar.xz
cd openwrt-sdk-*/
./scripts/feeds update -a && ./scripts/feeds install -a
cp -r my-package package/
make package/my-package/compile V=s
# Image Builder: 사전 빌드된 패키지로 이미지만 재조합
tar xf openwrt-imagebuilder-23.05-ath79-generic.Linux-x86_64.tar.xz
cd openwrt-imagebuilder-*/
make image PROFILE=tplink_tl-wr1043nd-v2 \
PACKAGES="luci luci-app-openvpn -ppp -ppp-mod-pppoe" \
FILES=files/
원격 디버깅 (GDB)
# 타겟에서 gdbserver 실행
opkg install gdbserver
gdbserver :9000 /usr/bin/my-app
# 호스트에서 GDB 연결
./staging_dir/toolchain-*/bin/mips-openwrt-linux-musl-gdb
(gdb) target remote 192.168.1.1:9000
(gdb) set sysroot ./staging_dir/target-mips_24kc_musl/
(gdb) break main
(gdb) continue
Buildroot 개발 도구
# 디버그 빌드 활성화 + NFS root
make menuconfig
# → Build options → build packages with debugging symbols
# NFS root로 빠른 반복 개발
# U-Boot: setenv bootargs "root=/dev/nfs nfsroot=192.168.1.100:/path/to/output/target ip=dhcp"
# QEMU 통합 테스트
make qemu_arm_vexpress_defconfig && make
./output/images/start-qemu.sh
Yocto 개발 도구
# devtool: 가장 강력한 개발 도구
devtool modify busybox
# → workspace/sources/busybox/에 소스 체크아웃
devtool build busybox
devtool deploy-target busybox root@192.168.1.100
devtool finish busybox meta-my-layer
# devshell: 빌드 환경에서 직접 작업
bitbake -c devshell busybox
# QEMU 테스트
runqemu qemuarm64 nographic
Valgrind/AddressSanitizer 활용
# Buildroot에서 Valgrind 사용
make menuconfig
# → Target packages → Debugging → valgrind
make
# 타겟에서:
valgrind --leak-check=full /usr/bin/my-app
# AddressSanitizer (GCC -fsanitize=address)
# Buildroot: MYAPP_CONF_OPTS에 추가
MYAPP_CFLAGS = -fsanitize=address -fno-omit-frame-pointer
# 주의: ASan은 ~2x 메모리 오버헤드, 임베디드에서는 RAM 충분한 경우만
# Yocto에서 ASan 활성화
# local.conf:
EXTRA_OECMAKE:append:pn-myapp = " -DSANITIZE_ADDRESS=ON"
커널 크래시 덤프(Dump) 수집
# ramoops: RAM 기반 크래시 로그 (재부팅 후 확인)
# Device Tree에 예약 메모리 추가:
# reserved-memory { ramoops@... { compatible = "ramoops"; ... }; };
# 부팅 후 확인:
cat /sys/fs/pstore/console-ramoops-0 # 이전 부팅의 커널 로그
# OpenWrt에서 로그 확인
logread # 시스템 로그
dmesg | tail -50 # 커널 링 버퍼
# coredump 수집 (Buildroot/Yocto)
# /proc/sys/kernel/core_pattern에 경로 설정
echo "/tmp/core.%e.%p" > /proc/sys/kernel/core_pattern
ulimit -c unlimited
# 크래시 발생 시 /tmp/core.myapp.1234 생성
# 호스트에서 분석:
arm-buildroot-linux-gnueabihf-gdb output/build/myapp-1.0/myapp /tmp/core.myapp.1234
시리얼 콘솔과 공통 디버깅
# 시리얼 콘솔 연결
picocom -b 115200 /dev/ttyUSB0
# strace로 시스템 콜 추적
strace -f -e trace=network ./my-app
# 네트워크 성능 측정
iperf3 -s # 서버 모드 (타겟)
iperf3 -c 192.168.1.1 -t 30 # 클라이언트 (호스트)
# 디스크 I/O 성능 측정
dd if=/dev/zero of=/tmp/test bs=1M count=100 oflag=direct
CI/CD 통합 패턴
# Buildroot CI (가장 단순) — .gitlab-ci.yml
build:
script:
- make my_board_defconfig
- make -j$(nproc)
artifacts:
paths:
- output/images/
# OpenWrt CI
build:
script:
- ./scripts/feeds update -a && ./scripts/feeds install -a
- cp my-config .config && make defconfig
- make download -j8 && make -j$(nproc) V=s
# Yocto CI (sstate-cache 활용이 핵심)
build:
script:
- source oe-init-build-env
- bitbake core-image-minimal
cache:
paths:
- sstate-cache/
- downloads/
성능 비교 및 선택 가이드
성능 비교 (동일 하드웨어: 16코어, 64GB RAM, NVMe)
| 항목 | OpenWrt | Buildroot | Yocto |
|---|---|---|---|
| 첫 빌드 시간 | 45~90분 | 15~45분 | 120~240분 |
| 패키지 1개 재빌드 | 1~5분 | 1~5분 | 10초~2분 (sstate) |
| 이미지 재생성 | 2~5분 | 1~3분 | 5~15분 |
| 호스트 디스크 사용 | 10~20 GB | 3~10 GB | 50~150 GB |
| 호스트 RAM 권장 | 4 GB+ | 2 GB+ | 8 GB+ (16 GB 권장) |
| 최소 이미지 크기 | ~4 MB | ~2 MB | ~8 MB |
| 일반 이미지 크기 | 8~16 MB | 4~32 MB | 50~500 MB |
엔지니어링 비용 분석
| 항목 | OpenWrt | Buildroot | Yocto |
|---|---|---|---|
| 초기 학습 시간 | 1~2주 | 2~5일 | 2~4주 |
| 새 패키지 추가 | 30분~2시간 | 15분~1시간 | 1~4시간 |
| 새 보드 BSP | 1~3일 | 1~2일 | 2~5일 |
| 메이저 업그레이드 | 1~3일 | 1~2일 | 3~10일 |
| CI/CD 구축 | 1일 | 반나절 | 2~3일 |
시스템 간 마이그레이션 경로
- Buildroot → Yocto: 가장 흔한 경로. 프로토타입(Buildroot)에서 양산(Yocto)으로. Buildroot의 패키지 목록(.config)을 Yocto IMAGE_INSTALL로 매핑. 커널 config는 그대로 재사용 가능.
- OpenWrt → Buildroot: 네트워크 장비에서 범용 장비로. UCI/procd/LuCI 대체 필요. 커널 패치는 Buildroot에서도 사용 가능.
- Buildroot → OpenWrt: 비권장. OpenWrt의 타겟 지원이 없으면 포팅 비용이 높음.
- Yocto → Buildroot: 규모 축소 시. recipe를 .mk로 변환하는 수작업 필요.
선택 매트릭스
| 유스케이스 | 추천 | 이유 |
|---|---|---|
| WiFi 라우터/AP | OpenWrt | WiFi 드라이버(ath10k, mt76), LuCI, UCI 네트워크 설정 |
| 네트워크 스위치 | OpenWrt | DSA(Distributed Switch Architecture) 기본 지원 |
| IoT 센서 게이트웨이 | Buildroot | 최소 크기, 빠른 빌드, 단순 커스터마이징 |
| 산업용 HMI/PLC | Buildroot 또는 Yocto | Buildroot(프로토타입), Yocto(양산) |
| 자동차 IVI | Yocto | AGL(Automotive Grade Linux), meta-ivi |
| 의료 기기 | Yocto | 인증, 재현성, 장기 지원 |
| 반도체 벤더 SDK | Yocto | TI, NXP, Intel 공식 BSP 레이어 |
| 교육/학습용 | Buildroot | 가장 쉬운 학습 곡선, 명확한 구조 |
| 셋톱박스/미디어 | Buildroot | Kodi, FFmpeg 패키지 지원, 단순 구조 |
| 대규모 팀 프로젝트 | Yocto | 레이어 분리, 재사용성, 벤더 협업 |
실전 예제
유스케이스 1: WiFi 라우터 (OpenWrt)
# MediaTek MT7621 기반 라우터 빌드 (OpenWrt 23.05)
git clone -b openwrt-23.05 https://github.com/openwrt/openwrt.git
cd openwrt
./scripts/feeds update -a && ./scripts/feeds install -a
make menuconfig
# Target System: MediaTek Ralink MIPS
# Subtarget: MT7621 based boards
# Target Profile: Xiaomi Mi Router 4A Gigabit Edition
# LuCI → Collections → luci
# Network → VPN → wireguard-tools (선택)
make download -j8 && make -j$(nproc) V=s
ls bin/targets/ramips/mt7621/
# openwrt-23.05-ramips-mt7621-xiaomi_mi-router-4a-gigabit-squashfs-sysupgrade.bin
유스케이스 2: IoT 센서 게이트웨이 (Buildroot)
# Raspberry Pi Zero W 기반 센서 게이트웨이
git clone https://github.com/buildroot/buildroot.git
cd buildroot
make raspberrypi0w_defconfig
make menuconfig
# Target packages → Interpreter languages → python3
# Target packages → Libraries → Networking → libmosquitto (MQTT)
# Filesystem images → ext4 root filesystem
make -j$(nproc)
sudo dd if=output/images/sdcard.img of=/dev/sdX bs=4M
유스케이스 3: 산업용 게이트웨이 (Yocto)
# TI AM62x 기반 산업 게이트웨이 (Yocto scarthgap)
git clone -b scarthgap https://git.yoctoproject.org/poky
git clone -b scarthgap https://git.yoctoproject.org/meta-ti
git clone -b scarthgap https://git.openembedded.org/meta-openembedded
cd poky && source oe-init-build-env build-am62x
bitbake-layers add-layer ../meta-openembedded/meta-oe
bitbake-layers add-layer ../meta-ti/meta-ti-bsp
cat >> conf/local.conf <<'EOF'
MACHINE = "am62xx-evm"
DISTRO_FEATURES:append = " systemd wifi"
IMAGE_INSTALL:append = " openssh python3 mosquitto can-utils"
EOF
bitbake core-image-full-cmdline
CI 빌드용 Dockerfile 예시
# Dockerfile.buildroot-ci
FROM ubuntu:22.04
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update && apt-get install -y \
build-essential gcc g++ binutils patch gawk bzip2 \
unzip wget curl git file python3 python3-distutils \
rsync bc cpio libncurses-dev zlib1g-dev libssl-dev \
cmake perl-modules ca-certificates \
&& rm -rf /var/lib/apt/lists/*
RUN useradd -m builder
USER builder
WORKDIR /home/builder
# 사용법:
# docker build -t buildroot-ci -f Dockerfile.buildroot-ci .
# docker run -v $(pwd)/buildroot:/home/builder/buildroot buildroot-ci \
# sh -c "cd buildroot && make my_board_defconfig && make -j$(nproc)"
펌웨어 업데이트 전략: A/B 파티션
펌웨어 업데이트 도구 비교
| 도구 | 특징 | 시스템 호환 |
|---|---|---|
| sysupgrade | OpenWrt 내장 업그레이드, 설정 보존 가능 | OpenWrt |
| SWUpdate | 이중 복사(A/B) 업데이트, 서명 검증, 웹 UI | Buildroot, Yocto |
| RAUC | A/B 슬롯, D-Bus API, Casync 델타 업데이트 | Yocto (meta-rauc) |
| Mender | OTA 클라우드 서비스, A/B 업데이트, 모니터링 | Yocto (meta-mender) |
| OSTree | Git-like 파일시스템 스냅샷, 원자적(Atomic) 업데이트 | Yocto (meta-updater) |
# SWUpdate A/B 업데이트 예시
swupdate -i my-firmware-update.swu
# RAUC 예시
rauc install my-update-bundle.raucb
rauc status # 슬롯 상태 확인
# Secure Boot 체인 예시 (Yocto + RAUC)
# 1. 인증서 생성: openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem
# 2. 번들 서명: rauc bundle --cert=cert.pem --key=key.pem --signing-keyring=ca.pem
# 3. 디바이스에서 검증: /etc/rauc/system.conf에 인증서 경로 설정
관련 문서
| 문서 | 관련 주제 |
|---|---|
| Linux From Scratch (LFS) | 크로스 컴파일 이론, 툴체인 구축 원리, sysroot 개념 |
| 빌드 시스템 | Makefile, CMake, Autotools, Meson 등 빌드 도구 기초 |
| JFFS2 | OpenWrt overlay 파일시스템, NOR 플래시 저널링(Journaling) |
| SquashFS | OpenWrt 읽기 전용 루트 파일시스템, 압축 옵션 |
| Device Tree | DTS 문법, DTB 컴파일, 오버레이, 바인딩 |
| 부팅 과정(Boot Process) | U-Boot, 커널 부팅, initramfs, init 시스템 |
| 개발 환경 설정 | 크로스 컴파일 환경 구축, 호스트 도구 설정 |
| DSA 태깅 | OpenWrt 스위치 칩 지원, VLAN 태깅 |
| 무선 네트워크 | WiFi 드라이버(ath10k, mt76), cfg80211, mac80211 |
| GCC | 크로스 컴파일러 옵션, 타겟 아키텍처 지정 |
| ELF | 크로스 컴파일된 바이너리 형식, 동적 링킹 |
| UFS/eMMC | 임베디드 스토리지, 플래시 메모리 관리(Memory Management) |
| BusyBox | 멀티콜 바이너리 아키텍처, 애플릿 시스템, init, initramfs 활용 |
- OpenWrt 공식 문서 (openwrt.org)
- OpenWrt 개발자 가이드 (openwrt.org)
- OpenWrt 빌드 시스템 사용법 (openwrt.org)
- OpenWrt 빌드 시스템 핵심 개념 (openwrt.org)
- OpenWrt 패키지 개발 가이드 (openwrt.org)
- Buildroot 사용자 매뉴얼 (buildroot.org)
- Buildroot 문서 및 교육 자료 (buildroot.org)
- Yocto Project 문서 포털 (yoctoproject.org)
- Yocto Project 빠른 시작 가이드 (yoctoproject.org)
- Yocto 개발자 매뉴얼 (yoctoproject.org)
- BitBake 사용자 매뉴얼 (yoctoproject.org)
- Yocto 커널 개발 매뉴얼 (yoctoproject.org)
- Yocto BSP 개발자 가이드 (yoctoproject.org)
- OpenEmbedded Layer Index (openembedded.org)
- OpenEmbedded 위키 (openembedded.org)
- 커널 Device Tree 문서 (kernel.org)
- Device Tree 사양서 (devicetree.org)
- U-Boot 공식 문서 (u-boot.org)
- Embedded Linux Wiki (elinux.org)
- 크로스 컴파일 툴체인 개요 (elinux.org)
- crosstool-NG 문서 — 크로스 툴체인 빌더 (crosstool-ng.github.io)
- kas — Yocto 프로젝트 설정 자동화 도구 (github.com)
- SWUpdate — 임베디드 OTA 업데이트 (sbabic.github.io)
- RAUC — 안전한 펌웨어 업데이트 프레임워크 (rauc.io)