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.

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.

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.

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?
- Can you reproduce the same image from the same config?
- Do you track versions of every package per release?
- Can you explain every service enabled at boot?
- 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.