BSP 이미지 패키징 — Flash Layout·Partition·GPT 설계
#한 줄 요약
flash image는 섹터 단위의 layout입니다. 한 번 양산되면 바꾸기 어렵습니다. 파티션 layout과 partition table 형식을 처음부터 잘 잡는 것이 핵심입니다.
빌드 시스템이 toolchain, kernel, rootfs를 다 만들어 줘도 그것들이 어떻게 한 binary로 묶이는가는 별도 문제입니다. SD 카드의 0번 섹터, eMMC의 boot1 파티션, NAND의 MTD partition table이 다 다릅니다. 잘못 설계하면 OTA를 못 하는 BSP가 만들어집니다.
이번 글은 partition table 종류, A/B 슬롯, GPT 구조, genimage로 이미지를 조립하는 방법을 다룹니다.
#Partition Table 종류
| 형식 | 최대 파티션 | 최대 크기 | 적용 |
|---|---|---|---|
| MBR | 4 (primary) | 2TB | 단순한 SD 카드 |
| GPT | 128 | 9.4ZB | 표준 (UEFI 포함) |
| BCT / TOC1 | SoC 특정 | SoC 특정 | NVIDIA, Mediatek |
| MTD partitions | kernel cmdline | N/A | raw NAND/NOR flash |
eMMC와 SD 카드에서는 GPT를 자주 사용하지만, 부트 ROM·펌웨어 요구사항이나 기존 제품 호환성에 따라 MBR 또는 SoC 전용 레이아웃을 사용할 수도 있습니다. raw NAND는 보통 MTD partition을 씁니다. SoC가 자체 boot header 형식을 요구하는 경우(NVIDIA Tegra, MediaTek)도 있으므로, 새 BSP는 먼저 vendor 부트 레이아웃을 확인해야 합니다.
#GPT 구조
GPT는 LBA 단위로 다음을 배치합니다.
LBA 0 : Protective MBR (legacy tool 호환)LBA 1 : GPT Header (primary)LBA 2~33 : Partition Entry Array (128 entries × 128B = 16KB)LBA 34~ : Partition data...LBA -33 ~ -2 : Partition Entry Array (backup)LBA -1 : GPT Header (backup)핵심 특징을 짚습니다.
- 16KB 예약. LBA 0
33이 partition table용입니다. 033 = 34 LBA = 17KB이지만 보통 첫 partition을 최소 1MB부터 시작하도록 정렬합니다. - backup GPT. 디스크 끝에 동일 정보가 한 번 더 있습니다. primary가 깨져도 복구 가능합니다.
- partition UUID. 각 파티션은 고유 UUID를 갖습니다. kernel의
root=PARTUUID=...로 참조하면 sda/mmcblk0/nvme0n1 같은 device 이름 변화에 흔들리지 않습니다.
sgdisk나 parted로 확인할 수 있습니다.
$ sgdisk -p /dev/mmcblk0Disk /dev/mmcblk0: 30543872 sectors, 14.6 GiBLogical sector size: 512 bytesDisk identifier (GUID): A1B2C3D4-E5F6-7890-ABCD-EF1234567890First usable sector is 34, last usable sector is 30543838Total free space is 2014 sectors (1007.0 KiB)
Number Start End Size Code Name 1 2048 67583 32.0 MiB 8300 boot 2 67584 2164735 1024.0 MiB 8300 rootfs-a 3 2164736 4261887 1024.0 MiB 8300 rootfs-b 4 4261888 30543838 12.5 GiB 8300 data#A/B Partition Layout
OTA를 안전하게 하려면 두 개의 rootfs 슬롯이 필요합니다. 업데이트 중 전원이 끊겨도 이전 슬롯으로 부팅 가능해야 하기 때문입니다.
GPT├─ p1: bootloader-env (1MB) # U-Boot 환경, 부팅 슬롯 정보├─ p2: boot-a (32MB) # kernel + dtb + initramfs (slot A)├─ p3: boot-b (32MB) # kernel + dtb + initramfs (slot B)├─ p4: rootfs-a (1GB) # rootfs (slot A)├─ p5: rootfs-b (1GB) # rootfs (slot B)├─ p6: persist (256MB) # 영구 데이터 (factory cert 등)├─ p7: data (remainder) # 사용자 데이터A/B 슬롯의 동작 순서는 다음과 같습니다.
- 부팅 직후: 부트로더가
BOOT_SLOT환경 변수를 읽음. 기본 A. - 슬롯 A의 boot와 rootfs를 mount.
- 정상 부팅 완료 시 application이
BOOT_OK=1을 씀. - OTA agent가 슬롯 B에 새 이미지를 씀.
- 검증 후
BOOT_SLOT=B로 변경 +TRY_COUNT=3설정. - 재부팅. B 슬롯이 부팅됨.
- B가 정상 부팅하면
BOOT_OK=1. 실패하면 부트로더가TRY_COUNT감소 후 A로 fallback.
U-Boot 환경 변수로 다음과 같이 구현합니다.
bootcount=0bootlimit=3altbootcmd=run boot_altbootcmd=run boot_main
boot_main=if test "$boot_slot" = "B"; then run boot_b; else run boot_a; fiboot_a=load mmc 0:2 ${kernel_addr_r} boot/Image; load mmc 0:4 ...; bootmboot_b=load mmc 0:3 ${kernel_addr_r} boot/Image; load mmc 0:5 ...; bootmboot_alt=if test "$boot_slot" = "B"; then setenv boot_slot A; else setenv boot_slot B; fi; saveenv; resetbootcount와 bootlimit은 U-Boot가 자동으로 다루는 변수입니다. 부팅 시 +1, application의 bootcount=0로 reset. limit 초과 시 altbootcmd 실행.
#SoC 부트 헤더와 첫 섹터
SoC마다 부트 ROM이 읽는 첫 영역이 다릅니다. GPT가 이 영역을 침범하면 부팅이 안 됩니다.
| SoC | 부트 영역 | 위치 |
|---|---|---|
| i.MX 6/7 | IVT + DCD + bootloader | offset 1024 byte |
| i.MX 8M | container header | offset 32KB |
| STM32MP1 | FSBL header | offset 0 (SD/eMMC) |
| RK3399 | IDB header | sector 64 (32KB) |
| Tegra | BCT | sector 0 |
| TI AM335x | MLO | FAT 파티션 또는 raw |
GPT의 partition 1 시작 위치를 SoC 부트 영역 뒤로 옮겨야 합니다. 보통 첫 partition을 1MB 또는 8MB부터 시작하는 방식이 안전합니다.
i.MX 8M의 예입니다.
0x000000 (0KB) : (사용 안 함)0x008000 (32KB) : flash.bin (SPL + ATF + U-Boot, ~2MB)0x100000 (1MB) : GPT primary header0x100200 (1MB+) : Partition entry array0x800000 (8MB) : partition 1 (boot)...GPT primary header가 1MB offset에 있으면 표준 GPT 도구로 인식이 안 될 수 있습니다. SoC vendor가 권장하는 layout을 따르거나, GPT를 8MB 이후에 두고 그 앞은 unused 영역으로 비워 두는 패턴이 흔합니다.
#genimage로 이미지 조립
Pengutronix의 genimage가 Buildroot/Yocto와 무관하게 standalone으로 자주 쓰입니다. 한 cfg 파일로 multi-partition disk image를 만듭니다.
image boot.vfat { vfat { files = { "Image", "mybsp.dtb", "boot.scr", } } size = 32M}
image rootfs.ext4 { ext4 { label = "rootfs" } mountpoint = "/" size = 1G}
image sdcard.img { hdimage { partition-table-type = "gpt" }
partition u-boot { in-partition-table = "no" image = "u-boot.bin" offset = 32K size = 2M }
partition boot-a { partition-type-uuid = U image = "boot.vfat" }
partition boot-b { partition-type-uuid = U image = "boot.vfat" }
partition rootfs-a { partition-type-uuid = L partition-uuid = "12345678-9abc-def0-1234-56789abcdef0" image = "rootfs.ext4" }
partition rootfs-b { partition-type-uuid = L partition-uuid = "12345678-9abc-def0-1234-56789abcdef1" image = "rootfs.ext4" }
partition data { partition-type-uuid = L size = 4G }}in-partition-table = "no"는 raw 영역(SoC가 읽는 부트 영역)에 쓰지만 GPT에는 표시하지 않는다는 뜻입니다.
빌드:
$ genimage \ --inputpath output/images \ --outputpath output/images \ --rootpath output/target \ --config genimage.cfgINFO: vfat(boot.vfat): cmd: "MAKEDOSFS=mkfs.fat ..."INFO: ext4(rootfs.ext4): cmd: "mkfs.ext4 ..."INFO: hdimage(sdcard.img): writing GPTsdcard.img를 dd나 bmaptool로 SD에 굽습니다.
$ sudo dd if=sdcard.img of=/dev/sdX bs=4M status=progress conv=fsyncbmaptool은 빈 영역을 건너뛰어 훨씬 빠릅니다.
$ bmaptool create -o sdcard.bmap sdcard.img$ sudo bmaptool copy sdcard.img /dev/sdX#Android Sparse Image
Android는 .simg (sparse image) 형식을 씁니다. 0으로 채워진 큰 영역을 메타데이터로만 표시해 전송량을 줄입니다. fastboot로 flash 할 때 사용됩니다.
# raw → sparse$ img2simg rootfs.ext4 rootfs.simg
# sparse → raw$ simg2img rootfs.simg rootfs.ext4
$ fastboot flash rootfs rootfs.simg16GB raw image가 1GB sparse로 줄어드는 일이 흔합니다. 양산 line에서 flash 속도에 직접 영향이 있습니다.
#Raw NAND/NOR Flash (MTD)
raw flash는 GPT가 없습니다. MTD partition을 kernel cmdline 또는 device tree로 정의합니다.
mtdparts=spi-nor:512K(u-boot),128K(env),64K(dtb),4M(kernel),-(rootfs)또는 DT:
&spi0 { flash@0 { compatible = "jedec,spi-nor"; reg = <0>; spi-max-frequency = <40000000>;
partitions { compatible = "fixed-partitions"; #address-cells = <1>; #size-cells = <1>;
partition@0 { label = "u-boot"; reg = <0x0 0x80000>; }; partition@80000 { label = "env"; reg = <0x80000 0x20000>; }; partition@a0000 { label = "kernel"; reg = <0xa0000 0x400000>; }; partition@4a0000 { label = "rootfs"; reg = <0x4a0000 0xb60000>; }; }; };};NAND rootfs는 ubinize로 UBI volume을 만들어 둡니다. UBI는 wear-leveling과 bad block management를 해 줍니다.
$ mkfs.ubifs -r rootfs/ -m 2048 -e 124KiB -c 1000 -o rootfs.ubifs$ ubinize -o rootfs.ubi -m 2048 -p 128KiB ubinize.cfgraw 쓰기는 flash_erase + nandwrite:
$ flash_erase /dev/mtd4 0 0$ nandwrite -p /dev/mtd4 rootfs.ubi#양산 라인의 flashing 도구
| 도구 | SoC | 인터페이스 |
|---|---|---|
uuu | NXP i.MX | USB |
fastboot | Qualcomm, MediaTek, Android | USB |
dfu-util | STM, Allwinner | USB DFU |
STM32CubeProgrammer | STM32 | USB / UART / JTAG |
RKDevTool | Rockchip | USB |
sunxi-fel | Allwinner | USB FEL |
bmaptool | 일반 | SD card |
i.MX의 uuu 예입니다.
$ cat << 'EOF' > flash.uuuuuu_version 1.4.0
SDP: boot -f flash.binSDPU: write -f u-boot.itb -offset 0x57c00SDPU: jump
FB: ucmd setenv fastboot_buffer 0x42000000FB: flash gpt sdcard.gptFB: flash boot-a boot.vfatFB: flash rootfs-a rootfs.ext4FB: doneEOF
$ sudo uuu flash.uuu양산 line이라면 PC + USB hub + 보드 8대 같은 setup에서 동시 flash가 표준입니다. uuu, fastboot 모두 multi-board 모드를 지원합니다.
#자주 하는 실수
GPT primary가 SoC 부트 영역과 겹침. i.MX 8M의 SPL은 32KB부터, GPT는 보통 1MB부터. 첫 partition은 8MB 이후로 두는 것이 안전합니다.
partition UUID 미사용. kernel cmdline에 root=/dev/mmcblk0p2로 박으면 SD가 USB로 바뀌면 panic. 항상 PARTUUID=.
A/B 슬롯에서 환경 변수 동기화 실패. 두 슬롯이 같은 bootloader-env partition을 공유해야 하는데 각 slot의 rootfs에 따로 두면 inconsistent. env는 별도 partition.
dd 대신 USB writer GUI 사용. Windows의 일부 GUI tool이 GPT를 깨뜨립니다. 항상 dd + sync나 bmaptool.
bmap 없이 raw image 전송. 16GB image 전체를 보내는 건 낭비입니다. bmap은 사용된 영역만 표시합니다.
sparse image 직접 mount 시도. .simg는 그냥 mount 안 됩니다. simg2img로 변환 후.
#정리
- 첫 섹터의 의미는 SoC마다 다릅니다. SoC vendor 가이드의 layout을 출발점으로 삼아야 합니다.
- GPT가 표준입니다. MBR은 legacy SD 카드 정도, raw NAND는 MTD partition.
- A/B 슬롯은 OTA 안전성의 출발점입니다. boot 슬롯과 rootfs 슬롯을 짝지어 두 세트 둡니다.
- partition UUID로 root를 지정해야 device 이름 변화에 영향을 받지 않습니다.
genimage는 단일 cfg로 multi-partition disk image를 만드는 표준 도구입니다.bmaptool은 빈 영역을 건너뛰어 이미지 기록 시간을 줄일 수 있습니다. 실제 절감 폭은 이미지의 사용 영역과 저장 장치·USB 경로에 따라 달라집니다.- Android sparse image는 fastboot 환경에서 큰 image의 전송 시간을 단축합니다.
- 양산 flashing은 SoC별 전용 도구(uuu, fastboot, RKDevTool, dfu-util)를 PC line에서 multi-board로 운영합니다.
#다음 편 예고
Ch 18: OTA와 field recovery에서는 A/B 슬롯을 활용한 OTA, RAUC/SWUpdate/Mender 같은 update agent, 그리고 현장 복구 시나리오를 다룹니다.
#관련 항목
- Ch 16: Buildroot/Yocto와 rootfs — 빌드 산출물의 출처
- Ch 18: OTA와 field recovery — A/B 슬롯의 활용
- Bootloader 시리즈 — 환경 변수 — U-Boot env partition
- Buildroot Practical — 이미지 조립 — Buildroot 측 깊이
BSP Development · 17 of 21
- 1 BSP의 본질 분해 — 새 보드 부팅을 위한 코드의 자리
- 2 SoC 데이터시트 읽기 — Pin Mux·Clock·Memory Map 파악법
- 3 새 보드 Device Tree 설계 — node·property·phandle 작성 흐름
- 4 Pin Mux와 Clock Tree 분석 — 보드 부팅의 첫 두 관문
- 5 DDR 매개변수 결정 — 보드별 Timing·Training 추출
- 6 U-Boot 새 보드 포팅 — defconfig·board.c·DTS 작성 흐름
- 7 TF-A·TrustZone 통합 — BL31·secure world·SMC 흐름 적용
- 8 Linux 커널 BSP 설정 — defconfig·Kconfig·DT 통합
- 9 Multi-core SMP Bring-up — PSCI·Secondary CPU 깨우기
- 10 첫 부팅 추적 — 0%부터 login prompt까지의 단계 분석
- 11 부트로그 디버깅 — earlyprintk·loglevel·serial 추적
- 12 BSP 드라이버 추가 — 보드별 Peripheral 통합 흐름
- 13 BSP Power Management — Suspend/Resume·Runtime PM·Regulator
- 14 BSP Thermal과 Watchdog — Trip Point·Cooling Device·Hardware Reset
- 15 BSP 부트 시간 최적화 — Bootchart·initcall_debug·Parallel Init
- 16 BSP RootFS 통합 — Buildroot·Yocto와 보드별 패키지 묶기
- 17 BSP 이미지 패키징 — Flash Layout·Partition·GPT 설계
- 18 BSP OTA와 Field Recovery — A/B 슬롯·롤백·BootCount
- 19 BSP Stability Testing — Stress·Soak·Power Cycle 시나리오
- 20 BSP 양산 환경 구축 — CI/CD·재현 가능 빌드·서명
- 21 BSP 유지보수 — 업스트림 기여·커널 버전업·LTS 전략
관련 글
BSP OTA와 Field Recovery — A/B 슬롯·롤백·BootCount
현장 배포된 보드의 펌웨어 업데이트와 복구 — RAUC/SWUpdate 통합, recovery 파티션, USB recovery.
같은 시리즈에서 이어 읽기
BSP Stability Testing — Stress·Soak·Power Cycle 시나리오
양산 BSP의 안정성 검증 — stress 도구, 장기간 soak, thermal 사이클, EMC 영향.
같은 시리즈에서 이어 읽기
임베디드 Flash Layout 설계 — partition·NAND·eMMC·UBI 비교
양산 firmware의 flash layout 설계 — 부트로더·env·kernel·rootfs·OTA slot의 sizing과 NAND/eMMC/UBI 차이.
공통 태그 기반 추천
이 글을 참조하는 글 (5)
- Buildroot post-build·post-image 심화 — rootfs 최종 수정 흐름 — Buildroot Practical
- BSP 양산 환경 구축 — CI/CD·재현 가능 빌드·서명 — BSP Development
- BSP OTA와 Field Recovery — A/B 슬롯·롤백·BootCount — BSP Development
- BSP RootFS 통합 — Buildroot·Yocto와 보드별 패키지 묶기 — BSP Development
- BSP 부트 시간 최적화 — Bootchart·initcall_debug·Parallel Init — BSP Development