Yocto는 무엇이고 왜 필요하게 되었을까?
미리 설명하자면, 임베디드 리눅스 시스템을 제품에 맞게 구성하는데 불편함이 있었기 때문이다.
하드웨어를 구성한 뒤, 그 하드웨어에 맞는 툴체인을 구성한다. 그리고 부트로더로 커널을 메모리로 로드하고 커널 빌드를 진행. 파일시스템을 구성해야 기본적인 임베디드 리눅스 시스템이 구성된다.
이러한 복잡한 과정을 손쉽게 지원하도록 여러가지 자동 빌드 시스템들이 나오게 되었고, 그 중 Yocto를 보편적으로 쓴다고 알고 있다.
하드웨어 (Hardware/BSP)
툴체인 (Cross Toolchain)
gcc, glibc/musl, binutils, sysroot, headers 포함.부트로더 (Bootloader)
커널 빌드 (Linux Kernel)
make menuconfig)..dts/.dtb) 작성으로 하드웨어 맵 정의.zImage, Image, uImage)를 생성.파일시스템 (Root Filesystem)
하드웨어 별 CPU의 어셈블리어가 다르기 때문에, 각 하드웨어 환경에 맞게 컴파일해주는 컴파일러 세트가 필요하다.
toolchain = compiler + assembler + linker + libc + headers + binutils + debugger(gdb)우분투(x86)의 툴체인으로는 라즈베리파이 하드웨어(ARM64) 환경에 사용할 수 있는 바이너리를 만들지 못하므로, 해당 환경에 알맞는 툴체인을 가져와 빌드하였던 것이다.
대부분의 툴체인은 SoC 또는 보드 벤더사에서 이미 제공하는 경우가 있다. 혹은 Yocto를 통한 자동빌드로 툴체인을 구성할 수 있음.
시스템이 동작할 수 있도록, 전원이 들어오면 커널을 메모리에 로드하고 실행시키는 역할.
마찬가지로 아키텍처마다 부트로더는 다르게 동작한다.
부트로더가 커널을 실행시키기 위해 전달해야 하는 레지스터 설정, 메모리 레이아웃, 파라미터 포맷(ATAG, DTB 등), 그리고 CPU 초기화 상태(MMU, 모드 전환 등)가 아키텍처마다 다르기 때문.
x86/x86_64는 전통적으로 BIOS/UEFI 기반으로 부팅돼.
→ 그래서 GRUB 같은 부트로더가 많이 쓰임.
→ BIOS 인터럽트 호출, MBR 파티션 구조 등 x86 전용 부팅 규약에 맞춰 동작.
ARM, ARM64 계열은 이런 BIOS가 없고,
SoC 제조사가 제공하는 ROM 코드 → 2단계 부트로더(U-Boot 등) 체계로 동작해.
→ 따라서 U-Boot, Barebox, ATF + U-Boot 구조가 흔함.
RISC-V는 OpenSBI → U-Boot → Linux 구조로 부팅돼.
(OpenSBI가 일종의 펌웨어/하이퍼바이저 역할을 함)
즉, 각 아키텍처의 부팅 초기 환경과 인터페이스가 다르기 때문에, 그에 맞는 부트로더가 필요.
임베디드 장치에서는 보통 ARM 하드웨어를 주로 사용하니, (그리고 라즈베리파이가 ARM64) U-Boot 부트로더를 통해 장비를 부팅시켜볼 것이다.
scp를 통해 통신을 위해 필요⭐️ 전체 흐름: ROM 코드 → SPL → TPL → 커널
Kernel image: 실제 OS 커널FDT: 디바이스 트리 → 어떤 하드웨어가 있는지 커널에 알려주는 데이터 구조initramfs: 초기 램 디스크, 루트 파일 시스템처럼 사용jump to kernel), 이후 제어권은 운영체제 커널로 넘어감위 과정을 진행하여 부트로더는 커널에게 제어권을 넘긴다.
부트로더가 커널로 제어를 넘길 때 전달해야 하는 정보는 아래와 같음
| 항목 | 의미 | 커널이 필요한 이유 |
|---|---|---|
| Machine Number | 어떤 보드인지 식별 (DTB 미지원 시) | 보드별 초기화 코드 선택 |
| HW 기본 정보 | RAM, 클럭 등 감지 정보 | 메모리/타이머 설정 |
| 커널 명령줄 | 커널 실행 옵션(/proc/cmdline) | 루트FS, 콘솔 등 지정 |
| DTB 위치/크기 | 하드웨어 구성 트리 | 드라이버 초기화 정보 |
| initrd 위치/크기 | 임시 루트FS | 초기 부팅 환경 구성 |
임베디드 리눅스 시스템에서 커널이 하드웨어를 이해하기 위한 “지도” 역할
예전(ARMv7 이전)에는 커널이 보드별 하드웨어 정보 및 초기화 코드를 직접 포함
그래서 생긴 게 Device Tree (DT) — “하드웨어 구성을 커널 코드가 아니라 외부 데이터로 분리"
텍스트 기반 소스 파일(.dts, Device Tree Source)
-> 빌드 시 바이너리(.dtb, Device Tree Blob) 로 변환
-> 부트로더가 커널로 전달.
| 확장자 | 설명 | 사용 시점 |
|---|---|---|
.dts | Device Tree Source (텍스트) | 사람이 편집 |
.dtsi | include용 하위 트리 (공통 부분) | 코드 재사용 |
.dtb | Device Tree Blob (바이너리) | 부트로더 → 커널 전달 시 |
/ {
model = "Raspberry Pi 4 Model B";
compatible = "raspberrypi,4-model-b";
memory@0 {
device_type = "memory";
reg = <0x0 0x40000000>; // 시작 주소, 크기
};
cpus {
cpu@0 {
compatible = "arm,cortex-a72";
reg = <0>;
};
};
soc {
serial@7e201000 {
compatible = "brcm,bcm2835-uart";
reg = <0x7e201000 0x1000>;
interrupts = <2>;
};
};
};
| 요소 | 설명 |
|---|---|
| 노드(node) | 하드웨어 장치 하나를 표현 (serial@7e201000) |
| 프로퍼티(property) | 해당 장치의 속성 (reg, interrupts, compatible) |
| 레지스터(reg) | 장치의 물리 주소와 크기 (<base size>) |
| compatible | 어떤 드라이버가 이 장치를 다룰 수 있는지 식별 |
| interrupts | 인터럽트 번호 정보 |
| aliases / chosen | 루트 노드 하위의 특수 노드 (부트 파라미터 등) |
부트로더(U-Boot 등)는 부팅 시 커널로 DTB 파일의 메모리 주소를 넘겨줘.
커널은 이걸 읽어들여 “어떤 장치가 있고, 어떤 드라이버를 올려야 하는지”를 결정함.
bootz 0x80000000 - 0x81000000
# 커널: 0x80000000
# DTB: 0x81000000
compatible 프로퍼티를 보고,예:
DTB에compatible = "brcm,bcm2835-uart"→
커널은of_match_table에 같은 문자열을 가진 드라이버를 찾아 UART 초기화
/boot/ 디렉터리 아래 .dtb 파일 형태로 저장되어 있음/boot/bcm2711-rpi-4-b.dtb)arch/arm/boot/dts/ 에 존재U-Boot Manual : https://docs.u-boot.org/en/latest/
U-Boot 내에서 사용하는 쉘 커맨드도 굉장히 다양하게 존재한다.

그 중 몇 가지 명령어에 대해 사용해보고 어떤 기능을 하는지 알아보았음
bootm : 운영체제 부팅을하는데 이미지 파일을 사용하는 명령어
유사한 걸로 bootz가 있는데, zimageformat 형식을 받을 수 있음.
→ 이미지 파일을 압축한 걸로 부팅.
echo : uboot의 환경변수를 확인하는 용도로 사용한다. (bash 문법과 유사)
md : memory context를 덤프뜨는데 사용하는 명령어.
U-Boot도 커널과 같은 ‘Kconfig 기반 빌드 시스템’을 사용한다
본인 보드 아키텍처에 맞게 스타터, 클럭 수 등을 uboot로 설정할 수 있음.
커널 소스코드 다운 후 make menuconfig 를 통해 gui 환경에서 옵션을 선택했듯, uboot도 마찬가지로 선택 가능하다.
여기서 Space/Enter 키로 설정을 켜거나 끄면 그 결과가 .config 파일에 반영됨.
이 .config는 결국 U-Boot 소스 빌드 시, Makefile이 읽어서 어떤 드라이버를 포함할지, 어떤 초기화 코드를 빌드할지를 결정.
U-Boot도 리눅스 커널처럼 다음의 구조를 공유합니다.
| 구성요소 | 역할 |
|---|---|
| Makefile | 전체 빌드 플로우 관리 (make all, make clean 등) |
| Kconfig | 설정 옵션 정의 (CPU, 클럭, 부트 장치 등) |
| defconfig | 특정 보드의 기본 설정값(초기 config) |
| .config | 실제 빌드에 사용되는 최종 설정 파일 (Kconfig에서 생성됨) |
즉, U-Boot은 커널과 동일한 Kconfig 메커니즘으로 설정 값을 관리하고,
make를 수행하면 scripts/kconfig/ 내부의 같은 툴(conf, menuconfig)을 이용해
사용자가 선택한 설정 → .config로 반영하는 구조.
보드에 맞는 defconfig를 먼저 선택해서 초기값을 로드
예를 들어 라즈베리파이용 U-Boot 빌드라면:
make rpi_4_defconfig
configs/rpi_4_defconfig 파일을 읽어.config로 복사..config 파일은 실제 빌드 시 U-Boot이 참고하는 설정 테이블.
| 단계 | 설명 |
|---|---|
| 1단계 | 어셈블리 초기화 (start.S) - 레지스터/메모리 설정 |
| 2단계 | 메모리 컨트롤러 설정, U-Boot Relocate |
| 3단계 | CPU, 보드, 인터럽트, 환경변수, 시리얼 초기화 |
| 4단계 | DRAM, Flash, 디바이스 초기화 |
| 5단계 | 메인 루프 진입, 명령어 처리 및 커널 부트 준비 |
ARM 보드는 BIOS/UEFI가 없음
→ 메모리 초기화 직접 해야 함
보드마다 하드웨어 다름
→ 보드별 초기화 필요 (board_init)
리얼 콘솔 꼭 필요 (디버깅용)
→ serial_init 필수
U-Boot Relocate 필요 (SRAM이 작기 때문에 DRAM으로 이동, SPL -> TPL)
→ 고성능 환경 준비
메인 루프에서 커널을 로드하거나 CLI 명령 처리 가능
부트로더가 커널을 메모리로 잘 로드했다면 제어권을 커널로 넘기게 된다.
$ make menuconfig : 빌드할 커널에 대한 세팅 진행. 필요한 모듈을 선택한다.
$ make ARCH=*** ####_defconfig : *** 아키텍처에 대해 #### 보드의 기본세팅으로 빌드
make -j# ARCH=arm64 dtbs CROSS_COMPILE=aarch64-linux-gnu- modules
➡️ 커널 모듈(.ko) 파일을 컴파일하는 단계
| 항목 | 의미 |
|---|---|
make | GNU Make를 실행 |
-j# | 병렬 빌드 (CPU 코어 수만큼 # 입력, 예: -j8) |
ARCH=arm64 | ARM 64비트 아키텍처용 커널을 빌드 |
CROSS_COMPILE=aarch64-linux-gnu- | aarch64 크로스컴파일러 사용 (aarch64-linux-gnu-gcc, ld, as 등) |
dtbs | 디바이스 트리 바이너리(.dtb)도 함께 빌드 (선택사항) |
modules | 커널 모듈(.ko)만 별도로 빌드하라는 의미 |
즉, .config 설정에 따라 선택된 커널 모듈들만 컴파일하는 make 명령.
결과적으로 drivers/, fs/, net/ 등의 서브디렉터리 아래에 .ko 파일들이 생성.
생성된 .ko 파일은 insmod 명령을 통해 커널에 삽입할 수 있음.
make -j# ARCH=arm64 dtbs CROSS_COMPILE=aarch64-linux-gnu- modules_install
➡️ 빌드된 모듈을 시스템의 루트파일시스템에 설치하는 단계
| 항목 | 의미 |
|---|---|
modules_install | /lib/modules/<kernel-version>/ 디렉토리에 모듈들을 복사 |
kernel-version | uname -r과 동일한 버전명 (예: 6.1.36) |
| 설치 경로 | 보통 INSTALL_MOD_PATH 변수로 변경 가능 |
예를 들어 루트FS를 SD카드 이미지에 미리 준비했다면:
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- \
INSTALL_MOD_PATH=/path/to/rootfs modules_install
→ /path/to/rootfs/lib/modules/6.1.36/ 에 .ko 파일들이 설치.
| 단계 | 명령 | 설명 |
|---|---|---|
| ① | make modules | 커널 모듈(드라이버 등)을 컴파일 |
| ② | make modules_install | 빌드된 모듈을 /lib/modules/<ver>/ 경로에 설치 |
아주 간단한, 소형의 임베디드 시스템 구현을 위한 루트파일시스템은 busybox 라는 툴을 통해 구성.
이렇게 시스템을 구성하는데에는
하드웨어 구성부터 툴체인, 부트로더, 커널 빌드, 루트파일시스템까지 많은 부분을 신경써야했다.
이를 yocto를 이용하면 손쉽게 해결할 수 있다.
다음 시간엔 본격적으로 yocto를 알아보자