Skip to content

Lesson 11: Build Your Own Linux

Óbuda University — Linux in Embedded Systems

"4 GB image for 5 processes. Is that engineering?"


Problem First

You prototyped on Raspberry Pi OS. It works. Ship it?

  • Boot takes 25 seconds — customer wants 5
  • Image is 4 GB — OTA update over cellular takes hours
  • 120 processes running at idle — you need 5
  • A field failure is unreproducible — "works on my Pi"

Stock distributions are for development. Products need custom images.


Today's Map

  • Block 1 (45 min): Custom Linux images: why custom images, Buildroot pipeline, Buildroot vs Yocto, toolchain, C library choices.
  • Block 2 (45 min): Design exercise: door access control, package selection, development tool choices, critique checklist.

The Five Reasons to Build

# Reason Stock Distro Problem
1 Boot time 15-35 s, loads everything
2 Reliability Writable rootfs corrupts on power loss
3 Determinism Kernel not configured for RT
4 Footprint 4 GB image, 1500 packages
5 Security Every package is an attack surface

Each reason alone can justify a custom build. In practice, you face several simultaneously.


Reason 1: Boot Time

A stock Raspberry Pi OS boots in 15-35 seconds.

It loads hundreds of packages, dozens of services, a full desktop environment — none of which your product uses.

A custom Buildroot image with only your application: 3-10 seconds.

No kernel optimization needed. Just remove what you do not need.

For appliances, boot time is a product requirement, not a convenience metric.


Boot Time: Real Consequences

  • Point-of-sale terminal — 30 s boot after power cut = frustrated customers
  • Factory display — 60 s recovery after watchdog reboot = lost monitoring
  • Medical device — slow boot = clinician loses trust
  • Kiosk / signage — visible boot screen = unprofessional

The fix is not "optimize boot." The fix is "stop loading things you do not need."


Reason 2: Reliability

Embedded devices live where power disappears without warning.

Stock distro: writable root filesystem. Power loss during write = corrupt OS. Device may not boot.

Custom image: read-only rootfs + overlayfs.

  +-------------------------------------------+
  |           overlayfs (merged view)          |
  +-------------------------------------------+
  |  Upper layer (RAM tmpfs)  |  Lower layer  |
  |  - logs, temp files       |  (read-only   |
  |  - runtime config         |   squashfs)   |
  |  - discarded on reboot    |  - immutable  |
  +---------------------------+---------------+
  |        Persistent partition (optional)     |
  |  - explicit fsync() for critical data      |
  +-------------------------------------------+

Base image cannot be corrupted. Device always boots.


Reason 3: Determinism

Stock kernel: optimized for throughput (servers, desktops).

Embedded control loop: needs predictable latency.

A custom kernel with PREEMPT_RT patches reduces worst-case scheduling latency from milliseconds to tens of microseconds.

Stock kernels do not include PREEMPT_RT. Building a custom kernel is the only way to enable it.


Reason 4: Footprint

Component Raspberry Pi OS Buildroot (BusyBox-only) Buildroot + Python + tools
Total image ~4.0 GB ~8–15 MB ~50–100 MB
Kernel ~25 MB ~5–8 MB ~8 MB
Root filesystem ~3.5 GB ~3–7 MB ~40–90 MB
Installed packages ~1500 ~1 (BusyBox) ~20–30
Processes at idle ~120 ~3–5 ~5–10

250x+ reduction in image size for a true minimal config. Even with Python and tools, it is still ~80x smaller. Not by compression — by not including things you do not need.


Why Footprint Matters

Smaller image = direct engineering benefits:

  • Faster SD card writes during manufacturing (seconds vs minutes)
  • Faster OTA updates (8–50 MB over cellular vs 4 GB)
  • Faster boot (less to load and initialize)
  • Smaller attack surface (fewer packages = fewer CVEs to track)

Every package you do not install is a package you never have to patch.


Reason 5: Security

1500 packages means 1500 potential vulnerability sources.

Each one needs tracking, patching, testing.

With a 20-package Buildroot image, you can:

  • List every binary on the device
  • Explain why each one is there
  • Audit the entire image in an afternoon

Packages you do not install cannot be exploited.


center

Embedded Linux distribution landscape: from general-purpose distros (Debian, Ubuntu) to embedded build systems (Buildroot, Yocto).


The Buildroot Pipeline

From configuration to flashable image


Buildroot: What It Does

Buildroot is an open-source build system that takes a configuration file and produces a complete flashable image.

Input: what you want (packages, kernel config, target architecture) Output: sdcard.img containing bootloader + kernel + device tree + rootfs

Everything is built from source. Every component version is pinned. The build is reproducible.


The Pipeline

  +----------+     +-----------+     +------------------+
  | defconfig |---->|  .config  |---->| Download Sources |
  +----------+     +-----------+     +------------------+
                    make menuconfig           |
                    (interactive)              v
                                     +------------------+
                                     | Build Toolchain  |
                                     +------------------+
                                              |
                                              v
                                     +------------------+
                                     | Build Packages   |
                                     +------------------+
                                              |
                                              v
                                     +------------------+
                                     |  Build Kernel    |
                                     +------------------+
                                              |
                                              v
                                     +------------------+
                                     | Build Root FS    |
                                     +------------------+
                                              |
                                              v
                                     +------------------+
                                     |   sdcard.img     |
                                     +------------------+

Pipeline: Step by Step

Step What Happens Output
defconfig Load a saved configuration .config
menuconfig Interactively adjust settings updated .config
Download Fetch source tarballs dl/ directory
Toolchain Build cross-compiler output/host/
Packages Build selected packages output/target/
Kernel Build kernel + device tree zImage, .dtb
Root FS Assemble filesystem image rootfs.ext4
Image Combine everything sdcard.img

One command: make. Output: a flashable image.


defconfig and .config

defconfig = a minimal file listing only non-default options. Small, readable, version-controllable.

.config = the full configuration with every option resolved. Generated from defconfig.

# Load a defconfig
make raspberrypi3_defconfig

# Customize interactively
make menuconfig

# Save your changes back to a defconfig
make savedefconfig

Always commit defconfig to version control. Never commit .config — it is generated.


What Goes Into the Image

  sdcard.img
  +--------------------------------------------------+
  |  Partition 1 (FAT32, ~50 MB)                     |
  |  +--------------------------------------------+  |
  |  |  bootloader  |  kernel  |  device tree     |  |
  |  +--------------------------------------------+  |
  +--------------------------------------------------+
  |  Partition 2 (ext4 or squashfs)                   |
  |  +--------------------------------------------+  |
  |  |  /bin  /sbin  /lib  /etc  /usr             |  |
  |  |  Root filesystem with selected packages    |  |
  |  +--------------------------------------------+  |
  +--------------------------------------------------+

Flash to SD card. Boot. That is it.


center

Components of a Linux-based embedded system: bootloader, kernel, root filesystem, and application — each built from source by the build system.


Build vs Install on Target

Not everything belongs in the image.

Bake into the image when: - Required every boot - Security / reproducibility matters - Startup time budget is strict

Install on target when: - Experimenting during development - Temporary debugging tools - Short-lived diagnostics

If you install the same tool on every device at every deploy, it should be in the image.


Build vs Install: Examples

Package Image or Target? Why
busybox Image Core utilities, needed every boot
dropbear (SSH) Image Remote access is a product feature
gdb Target only Debugging tool, not needed in production
strace Target only Diagnostics, temporary
tcpdump Target only Network debugging, remove before deploy
mosquitto Image MQTT broker is part of the application

Clear boundary: image = product, target = development.


Buildroot vs Yocto — The Two Build Systems You Will Encounter


Buildroot vs Yocto: Overview

Both produce flashable images from configuration. Both are open source.

Buildroot: simpler, faster, minimal. Like a Makefile.

Yocto: more flexible, scalable, complex. Like a build framework.

Choosing between them is one of the first architectural decisions in a new embedded Linux project. Switching later is expensive.


Buildroot vs Yocto: Comparison

Aspect Buildroot Yocto
Learning curve Low (menuconfig) High (BitBake, recipes, layers)
First build time 30-90 min 2-8 hours
Minimal image size 8-15 MB (BusyBox), 50-100 MB (with Python) 50-150 MB
Package management None (by design) Optional (opkg, dpkg, rpm)
Partial rebuilds Limited Excellent (sstate cache)
Best for Learning, appliances Complex products, multi-team

When to Choose Which

Start with Buildroot when: - Learning embedded Linux (this course) - Single-product, small team - Prototype or simple appliance - You want results in an afternoon

Move to Yocto when: - Multiple product variants from one codebase - Per-package customization needed - Large team collaborating on the build - Enterprise support required

Rule of thumb: Buildroot for learning and prototypes. Yocto when the product grows.


The Toolchain — The First Thing Buildroot Builds


What Is a Toolchain?

The set of programs that turns source code into binaries for your target hardware.

You write code on your laptop (x86_64). The code must run on the target (ARM).

The cross-toolchain compiles on your host for your target.

  +------------------+        +------------------+
  |   Your laptop    |        |   Raspberry Pi   |
  |   (x86_64)       |        |   (ARM)          |
  |                  |        |                  |
  |  source.c -----> | cross  |                  |
  |  arm-gcc ------> | compile|-----> binary     |
  |                  |  SSH   |                  |
  +------------------+--------+------------------+

Toolchain Components

Component What It Does Example
Compiler C/C++ to target machine code arm-linux-gnueabihf-gcc
Linker Combines objects + libs arm-linux-gnueabihf-ld
C library libc + syscall wrappers glibc, musl, uClibc-ng
Binutils Assembler, objdump, strip arm-linux-gnueabihf-objdump
Sysroot Headers + libs for target /usr/arm-linux-gnueabihf/

The Target Triplet

arm-linux-gnueabihf

  arm       -  linux      -  gnueabihf
   |            |              |
   |            |              +-- ABI: GNU EABI, hard-float
   |            +-- OS: Linux
   +-- Architecture: ARM

Every tool in the cross-toolchain carries this prefix so it does not conflict with your host's native gcc, ld, etc.

When you see arm-linux-gnueabihf-gcc, you know: ARM target, Linux OS, hardware floating point.


Native vs Cross Compilation

Native (on target) Cross (on host)
Speed Slow (Pi: limited CPU/RAM) Fast (laptop's full power)
Setup Simple: apt install gcc Requires cross-toolchain
Use case Quick tests, scripts Kernel, Buildroot, production
Industry standard No Yes

Nobody compiles a production kernel on a Raspberry Pi.

Buildroot provides the cross-toolchain automatically. You do not install it separately.


Try It Now: Inspect Your Toolchain (5 min)

Discover the toolchain already present on your Pi — identify architecture, C library, and binary format:

# What architecture is this binary?
file /bin/ls

# What shared libraries does Python need?
ldd /usr/bin/python3

# Identify the target triplet
gcc -dumpmachine

# Check the C library version
ldd --version

Find the target triplet in the file output. Does it match gcc -dumpmachine?

Tutorial: Buildroot Mini-Linux — Section 1: Toolchain Theory: Section 3: The Toolchain


C Library Choices

The C library is the most impactful toolchain decision. Every user-space program links against it.

glibc musl uClibc-ng
Size ~10 MB ~1 MB ~600 KB
POSIX conformance Full Nearly full Partial
Thread safety Full Full Partial
Default in Debian, Ubuntu Alpine Linux Legacy Buildroot
Best for Full-featured Minimal, embedded Legacy/tiny

musl saves ~9 MB per device. Over 10,000 devices, that is 90 GB less to deploy over the air.


C Library: Gotchas

Switching from glibc to musl is not free:

  • Locale handling differs — some programs assume glibc locale behavior
  • DNS resolution works differently (no NSS module support)
  • Some libraries assume glibc internals and break on musl

Always test your application on the actual target image — not on your Debian host.

The C library choice is made once and affects everything above it.


center

Reference model: host machine (cross-compilation) produces images for the target embedded device.


Reproducibility Quick Checks

Before shipping, can you answer yes to all four?

  1. Can you reproduce the same image from the same config?
  2. Do you track versions of every package per release?
  3. Can you explain every service enabled at boot?
  4. Is there a rollback plan for bad OTA updates?

If any answer is "no" — you are not ready for production.


Block 1 Summary

  • Stock distros are for development; products need custom images
  • Five reasons: boot time, reliability, determinism, footprint, security
  • Buildroot pipeline: defconfig -> .config -> toolchain -> packages -> kernel -> rootfs -> image
  • Buildroot vs Yocto: start simple (Buildroot), scale up (Yocto) when needed
  • Toolchain: cross-compilation is industry standard; C library choice matters

Block 2 — Design Exercise

"Door Access Control System"


The Scenario

You are building a door access control system.

Hardware: Raspberry Pi-class SBC, RFID reader, electric lock relay, network connection.

Requirements: - Boot in under 5 seconds - Survive power cuts without filesystem corruption - Receive remote configuration updates (new card lists, access rules) - Log access events locally and sync to server

Your job: design the image.


Exercise Step 1 — Image Packages

List 3 packages that must be baked into the image.

For each one, explain why it cannot be left out.

Think about: - What must run on every boot? - What is needed for the core product function? - What enables remote management?

Example format:

Package Why It Is Essential
? ?
? ?
? ?

Exercise Step 2 — Development Tools

List 3 tools to install only during development.

For each one, explain why it does not belong in the production image.

Think about: - What do you need for debugging that the end user never sees? - What adds attack surface without product value? - What increases image size unnecessarily?

Tool Why Not in Production
? ?
? ?
? ?

Exercise Step 3 — Success Criterion

Define one measurable success criterion with a specific number.

Not: "the system should boot fast" But: "cold boot to RFID-ready in under 4.5 seconds"

Good criteria are: - Measurable — you can test with a stopwatch or script - Specific — includes a target number - Relevant — directly tied to a product requirement

Write it as one sentence.


Example Answers (Do Not Show Until After)

Image packages:

Package Why Essential
dropbear SSH for remote config updates
busybox Core utilities, init, shell
RFID reader daemon Core product function

Development tools:

Tool Why Not in Production
gdb Debugging only, adds 5 MB
tcpdump Network diagnostics, attack surface
strace Syscall tracing, development only

Criterion: "Cold boot to RFID-ready state in under 4.5 seconds, measured from power-on to first successful card read."


Critique Checklist

When reviewing another team's design, ask:

  • Is every image package truly essential for every boot?
  • Are the dev tools really not needed in production?
  • Is the success criterion measurable with a clear pass/fail?
  • Did they consider security (SSH keys, firewall rules)?
  • Did they consider reliability (read-only rootfs, watchdog)?

Challenge each other. A good design survives scrutiny.


Common Mistakes

Mistake Why It Is Wrong
Including vim in production Use vi from busybox; save 30 MB
No read-only rootfs Power loss will corrupt the system
No watchdog A hung process bricks the device
SSH with password auth Brute-forceable; use key-only
No OTA rollback plan Bad update = bricked device in the field

Every unnecessary package is a liability.


Key Takeaways

  • Building Linux is an engineering decision, not a hobby
  • The reasons are performance, reliability, and security
  • Buildroot: defconfig -> toolchain -> packages -> kernel -> rootfs -> image
  • Cross-compilation is the industry standard
  • Know the boundary: image = product, target install = development
  • Start with Buildroot, scale to Yocto when needed

Hands-On Next

Try this in practice:

Tutorial: Buildroot Mini Linux Build a minimal Linux image with Buildroot. Flash it to an SD card. Compare boot time and image size to stock Raspberry Pi OS.

Measure the difference yourself. The numbers are more convincing than any slide.