Electronics Guide

Embedded Linux

Embedded Linux is the practice of turning a kernel written for general-purpose computing into a product: a router, a programmable logic controller, a medical imaging console, a network camera. The source is the same source that runs data centers, but almost nothing else resembles installing a desktop distribution. There is no BIOS, no installer, and frequently no disk. The engineer assembles the boot chain, describes the hardware to the kernel, builds every package in the root file system, decides how the image is stored and how it will be replaced in the field ten years from now, and answers for the licenses of several thousand source components.

This article sits beside the real-time operating system pages because the first decision a designer makes is which of the two to use. Linux brings a memory management unit and hardware-enforced process isolation, a mature networking stack, storage layers that handle wear leveling and power loss, drivers for displays and cameras, a package ecosystem containing essentially every library an application might need, and a workforce already fluent in it. It asks in exchange for tens of megabytes of RAM rather than tens of kilobytes, a boot sequence measured in seconds, and a worst-case timing profile that can be measured but not proven. A kernel such as those in Open-Source RTOS Platforms inverts every term of that trade.

The sections below follow the shape of a real project: the boot chain, the device tree, kernel configuration, the root file system and the medium it lives on, the two dominant build systems, real-time behavior, and the product concerns that decide whether a device is still maintainable in its eighth year. Deadline and schedulability theory appears in RTOS Fundamentals, and the commercially supported real-time Linux distributions in Commercial RTOS Platforms.

Where Linux Fits Beside a Real-Time Kernel

The distinction that matters is not speed. Both kernels service an interrupt quickly on a good day. The distinction is what can be said about the bad day. A small kernel is a library linked into the application, with a call graph an engineer can read end to end, documented worst-case execution times for each system call, and a scheduler whose behavior under overload is a design parameter. Linux is tens of millions of lines of source with dynamic allocation, deferred work, page faults, and a scheduler tuned for aggregate throughput across a decade of server workloads. Measurement can characterize it. Analysis cannot bound it.

Set against that, the capability difference is often decisive. A product that must terminate TLS, serve a web interface, decode video, mount a file system that survives an unannounced power cut, and run an application written by another team is a Linux product; attempting it on a microcontroller kernel means reimplementing large parts of Linux badly. A product that closes a current loop at twenty kilohertz with microsecond jitter and nothing else is not, and forcing it to be one produces a system that works in the laboratory and fails in the field.

The resource floor is the other boundary. A usable Linux system wants a memory management unit, external DRAM measured in tens of megabytes, and non-volatile storage measured in tens to hundreds of megabytes, which adds a DDR interface and two memory devices to the bill of materials. On a product selling in the millions, that cost frequently settles the argument before any software consideration is heard. Most interesting products avoid the choice entirely, running Linux on an application processor and an RTOS on a companion core.

The Boot Chain

Boot ROM and the First-Stage Loader

Execution begins in mask ROM inside the system-on-chip, programmed at the foundry and unmodifiable. The boot ROM samples strapping pins or one-time-programmable fuses to determine the boot device, then reads a small image from a fixed offset of it: a defined sector of eMMC or an SD card, the start of SPI-NOR flash, or the first good block of raw NAND. Most boot ROMs also implement a recovery path over USB or a serial port, which is how a board with blank flash is brought up on the bench and how a bricked unit is revived on the production line.

The critical constraint is that DRAM does not work yet. The memory controller has not been configured and the DDR interface has not been trained, so the first image must execute entirely from on-chip SRAM, typically between sixty-four kilobytes and a few hundred kilobytes. A full bootloader does not fit. U-Boot answers with the secondary program loader, universally abbreviated SPL: a cut-down build that configures the phase-locked loops, initializes the memory controller, runs the vendor's DDR calibration routine where one is required, then copies the full bootloader into DRAM and jumps to it.

On 64-bit Arm platforms the chain also includes Arm Trusted Firmware-A. Its BL31 component installs itself permanently at the highest exception level as the secure monitor, providing the Power State Coordination Interface that Linux later calls to bring secondary cores online, and U-Boot runs after it as the non-secure BL33 payload. A board that hangs before any console output appears is usually failing in the SPL or a Trusted Firmware stage.

U-Boot Proper

U-Boot, formally Das U-Boot, is the de facto bootloader for embedded Linux on Arm, RISC-V, MIPS, and PowerPC. It is distributed under the GNU General Public License version 2 or later and releases roughly quarterly using calendar version numbers, a convention adopted in 2008, so that a release such as v2026.01 names its own date. General bootloader design is covered in Bootloader Development.

Running from DRAM with a working console, U-Boot provides a command interpreter, storage and network drivers, and a scripting mechanism. Its task is to locate a kernel image, a device tree blob, and optionally an initramfs; load them to the correct physical addresses; assemble a kernel command line; and transfer control. The flattened image tree, or FIT, is the container format worth adopting from the outset: one image holds the kernel, device tree blobs, and initramfs, each hashed, with named configurations selecting which combination to boot. Because the whole structure can be signed, FIT is also U-Boot's mechanism for verified boot.

The Environment and the Handover

U-Boot keeps its configuration in an environment of name-value pairs stored in a raw sector of eMMC or SPI-NOR, a UBI volume, or a file. It holds the boot command, the kernel arguments, and, in most update schemes, the variables recording which system slot is active and how many times it has been tried. Because those variables are written by the running system during an update, the environment must be redundant, with two copies and a validity marker, so that a power failure part-way through a save leaves a readable copy behind. Devices bricked by a non-redundant environment are a recurring and entirely avoidable field failure.

The handover follows a documented per-architecture protocol. On 64-bit Arm, U-Boot places the kernel image and the device tree blob in memory, sets register x0 to the blob's physical address, and branches to the kernel entry point with caches and interrupts in a defined state. The kernel command line arrives through the device tree's chosen node. From that instant the bootloader is gone, and everything the kernel knows about the hardware it learns from the blob it was handed.

The Device Tree

The device tree is a data structure describing the hardware of a board: which peripherals exist, at which addresses, on which buses, with which interrupts, clocks, regulators, and pin multiplexing. It is compiled from a readable source syntax into a flattened binary blob, handed to the kernel at boot, and parsed to instantiate devices, each matched to a driver through a compatible string. The concept descends from Open Firmware, standardized as IEEE 1275, and is now specified independently by devicetree.org, whose Devicetree Specification reached version 0.4 on June 28, 2023.

What it replaced explains why it matters. Before device tree, each Arm board carried a C source file inside the kernel tree, under arch/arm/mach-*, that registered every platform device by hand, so the Arm portion of the kernel accumulated thousands of nearly identical board files. The volume of that churn drew a well-publicized objection from Linus Torvalds in 2011, and the conversion followed over the next several kernel cycles. The argument is simply stated: the mapping from a driver to the hardware it drives is board data, not program logic, and putting board data in the kernel image forces a code change and a kernel release for what is properly a configuration change.

For a fragmented landscape the consequence is structural. Dozens of vendors ship system-on-chip families sharing almost no board-level layout, and thousands of derivatives vary a regulator or a display panel; one kernel binary serves all of them if the differences live in a separate file. A modern Arm build accordingly produces one Image and several hundred blobs. The contract between a tree and a driver is the binding, the documented properties a compatible string accepts. Bindings now live in the kernel tree as YAML schemas, so a build can validate sources against them mechanically. Overlays extend the mechanism to hardware not known at build time, such as Raspberry Pi HATs and BeagleBone capes.

Two caveats temper the promise. The device tree describes hardware but not policy, and the boundary is argued case by case. And although binding stability is an explicit goal, bindings do change, so a blob and a kernel are versioned together in most products. Shipping the two as a single signed unit, which FIT makes natural, avoids the problem.

Kernel Configuration and Footprint

The kernel is configured through Kconfig, the system behind make menuconfig. A configuration is thousands of symbols, most inherited from a defconfig that a vendor or the architecture maintainers supply. The first useful discipline is make savedefconfig, which reduces a configuration to only the symbols differing from the defaults, producing a file small enough to review and to keep in the product's own repository.

A vendor defconfig is written to make an evaluation board demonstrate everything, so it compiles dozens of protocols, filesystems, and peripheral families the product does not contain. Trimming it pays three ways: the image is smaller and therefore faster to read and decompress, the boot sequence spends less time in initcalls for absent hardware, and the attack surface shrinks with the code. The commands make localmodconfig and make localyesconfig narrow a configuration to the modules actually loaded on a running system.

Drivers can be compiled in or built as loadable modules. Building everything in produces one self-contained image, removes the need for an initramfs to reach the root file system, and eliminates module loading from the boot path, at the cost of a larger image. Modules keep the image small and defer driver initialization, which helps when one image serves several hardware variants. Many designs settle on a hybrid: whatever is required to mount the root file system is built in, and everything else loads after the application has started.

Kernel image compression is a genuine trade. The LZ4 family decompresses fastest and compresses least; XZ compresses most and decompresses slowest; Zstandard sits between with a tunable level. Where the image is read over a slow serial NOR interface, aggressive compression wins because read time dominates; where it comes from eMMC at high speed, a fast decompressor or an uncompressed image can boot sooner. Beyond that, size comes out of driver classes, filesystems, and the networking stack rather than anywhere clever.

Mainline Kernels, Vendor Trees, and the Cost of Divergence

The kernel a silicon vendor supplies with a board support package is rarely the kernel from kernel.org. It is a fork of some earlier long-term-support release carrying hundreds to many thousands of out-of-tree patches: drivers for the vendor's peripherals, power management for its clock tree, and often source-available or binary drivers for the graphics processor, video codec, and camera image signal processor. For the first months of a project it is unambiguously the fastest path to a working system.

The cost arrives later and compounds. A forked kernel does not receive upstream fixes. When a vulnerability is found in a core subsystem, the fix lands in the stable branches of maintained kernels, and applying it to the fork is manual work that someone must perform, verify, and repeat for every subsequent fix. When the product needs a newer kernel, the thousands of patches must be re-ported onto the new base. Teams that skipped several kernel generations have found the re-port to be a multi-engineer-month project undertaken under schedule pressure.

The alternative is a mainline kernel, built from kernel.org sources with a small and reviewable set of local patches. Whether that option exists is a property of the chosen silicon. Several Arm system-on-chip families have extensive mainline support for the processor, memory, storage, and networking; what remains out of tree is usually the graphics stack, video codecs, and camera pipelines. Assessing mainline status before the silicon is selected is among the highest-leverage decisions available, because the choice of chip is in practice the choice of kernel maintenance burden. Android's Generic Kernel Image project shows the problem is tractable at scale, defining a stable module interface between core kernel and vendor modules.

Building the Root File System

Once the kernel mounts a root file system it executes one program, conventionally /sbin/init, and everything after that is userspace of the engineer's own construction. Modern builds usually adopt the merged-/usr arrangement, in which the top-level directories are symbolic links into /usr, so that the entire system can be a single read-only mount.

BusyBox and the Userland

A conventional Unix userland is several hundred separate programs. BusyBox replaces most of them with one multi-call binary that inspects the name by which it was invoked and dispatches to the corresponding applet, reached through a symbolic link per command. Because the applets share code and are compiled for size, a complete shell environment, an init, file and text utilities, and network clients fit in a few hundred kilobytes. BusyBox is licensed under the GNU General Public License version 2 only; Toybox, under permissive 0BSD terms, occupies the same niche.

The C Library Decision

Every program links against a C library, and the choice is one of the few decisions genuinely difficult to revisit, because it fixes the application binary interface of the entire system and is baked into the cross toolchain, whose construction is treated in Cross-Compilation Toolchains.

The GNU C Library, glibc, is the reference implementation and the compatibility baseline. Licensed under the GNU Lesser General Public License version 2.1 or later, it implements the full range of POSIX and GNU extensions and is what essentially all proprietary binaries expect. It is also the largest option by a wide margin. The musl C library, under permissive MIT terms, was written for size, static linking, and strict standards conformance; its cost is compatibility, since glibc extensions may need patching, the name service switch is absent, and proprietary glibc binaries will not run. uClibc-ng persists mainly on processors without a memory management unit. A system running third-party binaries takes glibc; a size-constrained source-built system takes musl.

Init and Device Management

BusyBox init reading an inittab is a few kilobytes and starts a fixed list of programs in order, which suffices for a device with three daemons. Systemd, at the other end, provides dependency-based parallel startup, socket and device activation, service supervision with restart policies, control-group resource limits, watchdog handling, and structured logging, at the cost of a much larger footprint. Supervisors such as runit and s6 sit between. The choice matters because supervision is a reliability feature: a device whose application exits must restart it and escalate to a reboot if that does not help.

Device nodes in /dev are created by devtmpfs and then managed by a userspace helper applying naming rules, permissions, and module autoloading: BusyBox mdev, eudev, or systemd-udevd. An initial RAM file system, a cpio archive embedded in the kernel image or loaded by the bootloader, runs before the real root is mounted and is where integrity verification and encrypted volume setup happen.

Storage Media and File System Choices

Storage is where embedded Linux diverges most sharply from the desktop, because the medium is flash: writes are made in pages, erases in much larger blocks, blocks wear out after a bounded number of erase cycles, and a power failure during a program or erase can leave a block indeterminate. The general treatment appears in File Systems for Embedded Devices; what follows is how the pieces assemble on Linux.

Raw Flash: MTD, UBI, and UBIFS

Raw NAND and NOR flash present the bare array to software. Linux exposes them through the memory technology device layer, which handles the erase-program interface and error correction and leaves bad-block management, wear leveling, and power-fail robustness to the layers above. The modern arrangement puts UBI, the unsorted block images layer, directly on the MTD device; UBI presents logical erase blocks whose mapping to physical blocks it manages itself, wear-leveling across the volume set and retiring bad blocks transparently. UBIFS above it supplies write-back caching, compression, and tolerance of unexpected power loss. It was merged into the mainline kernel in Linux 2.6.27, released in October 2008.

Its predecessor JFFS2 remains appropriate on small NOR partitions but scans the entire device at mount, so mount duration grows with capacity and becomes untenable past a few tens of megabytes. UBI has an analogous startup cost, since attaching a volume requires reading erase-block headers, and the fastmap feature exists to shorten it. On a device with a boot-time requirement and a large NAND part, attach time is a line item engineers routinely forget to measure.

Managed Flash: eMMC and UFS

Managed flash puts a controller in the same package as the array. It runs a flash translation layer that performs wear leveling, error correction, and bad-block retirement internally, and presents an ordinary block device to the host. This removes an entire category of software work and is why eMMC dominates designs above a few hundred megabytes, with UFS appearing where bandwidth demands it.

The convenience comes with opacity. The translation layer is vendor firmware, its behavior on power loss is documented only in general terms, and its wear-leveling policy cannot be inspected. Two mitigations are standard: prefer eMMC to removable SD cards for anything the product depends on, since consumer cards vary enormously in controller quality; and run power-cut testing on the production part over thousands of interrupted writes. The standard also supplies hardware-selectable boot partitions and the replay-protected memory block, suited to monotonic counters for anti-rollback protection.

Matching File Systems to Roles

On a block device the choice is driven by whether a partition is written and by how it fails. The ext4 file system is the mature general-purpose choice for writable partitions, and is worth mounting with noatime on flash to eliminate a class of gratuitous writes. F2FS is a log-structured file system designed for storage sitting on a flash translation layer, contributed by Samsung and merged in Linux 3.8 in 2013; by writing sequentially it cooperates with rather than fights the translation layer, and it is widely deployed on Android user data partitions.

SquashFS is a compressed read-only file system long used for system images, storing an entire root file system as one compressed, immutable blob. EROFS is the newer design for the same role. It first appeared in Linux 4.19 and moved out of the staging area in Linux 5.4, and supports LZ4, MicroLZMA, DEFLATE, and Zstandard compression selectable per inode. Its distinguishing choice is fixed-size output compression, producing uniform compressed blocks from variable-sized input, which improves random-read performance relative to schemes that compress fixed-size inputs. The kernel documentation frames its purpose as immutable golden images built once and deployed unchanged.

The Read-Only Root with an Overlay

Standard practice for reliable devices is a read-only root with writable layers stacked on top. The system image is a SquashFS or EROFS volume mounted read-only. Directories that must be writable are provided by overlayfs, using that image as the lower layer and either a tmpfs, for state that need not survive a reboot, or a small ext4 or F2FS partition for state that must. Application and user data live on a separate writable partition.

Four benefits follow. The system image cannot be corrupted by a power failure, because nothing writes to it. It can be verified cryptographically, because it is immutable. It can be replaced atomically during an update, because it is a single blob. And a factory reset becomes an erase of the writable layers. The cost is discipline: every program that expects to write somewhere unexpected must be found during development, which is why the read-only root belongs in the first week of a project and not the last.

Endurance planning belongs here too. The usual cause of premature flash wear in the field is logging, and a daemon writing verbosely can consume the endurance budget of a small partition in months. Keep volatile logs in RAM, rotate what is persisted, and calculate expected write volume per day against the endurance rating of the part before the design is frozen.

Build Systems: Yocto and Buildroot

Assembling a bootloader, a kernel, a C library, and several hundred userspace packages by hand is possible exactly once. Every serious project uses a build system that cross-compiles the whole stack from source, reproducibly, from a checked-in configuration. Two dominate, and they differ in philosophy rather than capability. The broader practice of automating builds and releases is treated in Build Automation and Deployment.

Yocto and OpenEmbedded

The Yocto Project supplies the release process, testing infrastructure, and reference distribution around OpenEmbedded, whose core metadata and BitBake task executor do the work. The unit of metadata is the recipe: a file describing where to fetch a component's source, which patches to apply, and how to configure, compile, install, and package it. Recipes are grouped into layers, which stack in a defined priority order, and a layer can add recipes, override variables, or extend an existing recipe through an append file without modifying it.

Two capabilities justify the complexity. Yocto builds packages, in RPM, Debian, or ipk format, and assembles images from them, so a product can ship a package feed and build several image variants from one set of recipes. And because every component enters through a recipe declaring its license and source, the build produces license manifests and can generate SPDX bills of materials as a normal output. Yocto releases twice a year and designates the April release as long-term support: Scarthgap 5.0 of April 2024 carries support to April 2028, and Wrynose 6.0 of April 2026 to April 2030.

The costs are real. The learning curve is steep, and the metadata language has variable-override and task-dependency behaviors that reward study. A first build compiles everything from source, occupying tens of gigabytes of disk and hours of processor time, though the shared-state cache makes later builds incremental and can be shared across a build farm.

Buildroot

Buildroot takes the opposite position. It is a set of makefiles and Kconfig files producing a cross toolchain, kernel, bootloader, and root file system image, configured through the same menuconfig interface the kernel uses. It releases quarterly with calendar version numbers such as 2026.05 and designates one series each year for long-term support.

Its virtues are directness and speed of comprehension. The configuration is one file a person can read, adding a package means a short makefile fragment and a Config.in entry, and a newcomer can produce a booting image on a supported board in an afternoon. The resulting root file system contains only what was selected, with no package manager on the target. For a single product with a fixed image and a small team this is frequently the correct engineering choice.

Its limitations follow from the same design. Because Buildroot does not track per-package dependencies with the granularity a package manager requires, the project's own guidance is that a configuration change may require a full rebuild. There is no package feed, so field updates are whole-image updates. Supporting several products with divergent configurations means maintaining several configurations, where Yocto's layers would express the shared parts once. Compliance is served by the legal-info target, whose output is less structured than Yocto's manifests and SPDX documents.

Choosing Between Them

The honest comparison rests on three axes. On learning curve Buildroot wins decisively; a team can be productive in days rather than weeks. On scaling to a product family Yocto wins decisively, because layers express variation without duplication and package-based images allow per-component updates. On compliance output Yocto is ahead by a clear margin, which matters as customers and regulators increasingly ask for a software bill of materials as a condition of sale. Both build from pinned sources with recorded hashes, though the Yocto Project has invested more in bit-for-bit reproducibility. Other options occasionally fit better: debootstrap for a Debian userland, OpenWrt for network devices, and Android where its application ecosystem is required.

Real-Time Behavior on Linux

This is the section that determines whether the rest of the article is relevant to a given project. Linux offers real-time capability that is genuinely good and genuinely different in kind from what a small kernel provides, and the difference is worth stating precisely.

Preemption Models and PREEMPT_RT

The mainline kernel offers a range of preemption models chosen at configuration time, from no involuntary preemption in kernel mode, appropriate for throughput-oriented servers, through voluntary preemption points, to full kernel preemption for low-latency desktop and audio work. Recent kernels can also select among several of these at boot.

PREEMPT_RT is the model beyond those. It converts most kernel spinlocks into sleeping mutexes with priority inheritance, moves nearly all interrupt handlers into schedulable kernel threads, and makes software interrupt processing preemptible. Far less kernel code then runs with preemption disabled, so a high-priority task waiting on an event is delayed by a shorter and far more predictable interval. Maintained out of tree for roughly two decades, it was merged into the mainline kernel in Linux 6.12, released on November 17, 2024; the final enabling piece was a rewrite of the printk subsystem. Real-time capability is therefore now a configuration option in a stock kernel rather than a patch set to forward-port.

Scheduling and Measurement

Linux implements the POSIX real-time policies SCHED_FIFO and SCHED_RR across priorities 1 through 99, which run ahead of all normal tasks, and SCHED_DEADLINE, an earliest-deadline-first policy in which a task declares a runtime, a period, and a deadline and the kernel admits it only if the set remains feasible. One default surprises newcomers: to prevent a runaway real-time task from locking the system out, the kernel throttles the real-time classes to a fraction of each period, conventionally ninety-five percent. A thread that busy-waits is therefore interrupted at a regular interval by what looks like an inexplicable gap.

The standard instrument is cyclictest, from the rt-tests suite. It wakes threads on a fixed period and records how late each wake actually was; the maximum over a long run is the figure quoted as scheduling latency. A latency figure without its test conditions is meaningless. The maximum depends on the processor, the interrupt load, power management settings, and the run length, since the interesting number is a rare outlier. A responsible measurement states the hardware, the kernel configuration, the tuning, the load, and the duration. Two companions matter: hwlatdetect finds latency caused by firmware beneath the operating system, and the osnoise and timerlat tracers attribute latency to its source.

Stated carefully, a well-tuned PREEMPT_RT system on suitable hardware achieves worst-case scheduling latencies in the range of tens of microseconds, where a general-purpose configuration on the same hardware shows outliers one or two orders of magnitude larger. Tuning is not optional to reach that range. It normally includes isolating processor cores from the general scheduler, directing interrupts away from those cores, disabling frequency scaling and deep idle states, locking the application's memory with mlockall and pre-faulting its stacks, and eliminating dynamic allocation, page faults, and unbounded blocking from the critical path. The throughput cost is real and should be budgeted.

What Linux Still Cannot Do

Two limits remain after all tuning. The first is analytical: there is no worst-case execution time analysis of the Linux kernel, and there will not be one. A measured maximum over a long run is strong evidence, not a bound, and the difference matters when a deadline miss is a safety event. The second is evidentiary: the certification packages that safety standards expect are far harder to assemble for a codebase of Linux's size and rate of change.

Where those limits bite, the architecture changes rather than the tuning. The common answer is asymmetric multiprocessing on one chip: an application cluster running Linux alongside a real-time core, typically a Cortex-R or Cortex-M class processor, running an RTOS or bare-metal firmware on its own memory. Linux loads and starts the companion firmware through the remoteproc framework and exchanges messages over shared memory and hardware mailboxes using RPMsg, with the OpenAMP project supplying a common implementation on both sides. The deterministic work runs where determinism is achievable, and Linux handles connectivity, storage, and the user interface.

The stronger version is a separate microcontroller in its own package, connected over SPI, UART, or CAN. It costs a part and a second firmware image and buys complete isolation: the controller keeps running while Linux reboots, and no shared cache or interconnect couples the two. For certified safety functions this separation is frequently the only architecture a certification body accepts without argument. A third approach, the co-kernel design exemplified by Xenomai, runs a small real-time core beneath Linux and treats Linux as its lowest-priority task, at the cost of a separate patch set and a distinct programming interface.

Reducing Boot Time

A device that must display something within two seconds of power-on, or respond on a fieldbus after a supply interruption, treats boot time as a specification. Meeting it requires measuring every stage, because intuition about where the time goes is usually wrong. The stages are the boot ROM, which is fixed and occasionally surprisingly long; the first-stage loader including DRAM training; the bootloader's delay and its image loads; kernel decompression; kernel initcalls; and userspace from init to the first useful output. A general-purpose output pin toggled at stage boundaries and observed on an oscilloscope gives a timeline no software timestamp can dispute.

The reductions that pay best, in rough order of return: set the bootloader's interactive delay to zero in production images, since a countdown of a second or two is often the largest single item; consider U-Boot's Falcon mode, in which the first-stage loader boots the kernel directly; match kernel compression to storage speed; eliminate the initramfs when the root driver can be built in; build in every driver on the critical path; reduce console output, since a serial console at a modest baud rate consumes real time; use initcall_debug to find expensive initialization functions; and start the application that satisfies the requirement before the rest of userspace.

Secure Boot and Verified Root File Systems

A device that accepts field updates, or that can be opened, needs assurance that the software it runs is the software its manufacturer shipped. The mechanism is a chain of verification in which each stage authenticates the next before transferring control, anchored in something an attacker cannot rewrite. The general architecture, including measured boot and remote attestation, is treated in Secure Boot and Attestation; what follows is how it lands on Linux.

The anchor is the boot ROM together with one-time-programmable fuses holding the hash of a public key. Once those fuses are burned, the boot ROM loads only a first-stage image signed by the corresponding private key. Each vendor implements this with its own naming and image format, and the tooling is rarely portable. The first-stage loader then verifies the bootloader, and the bootloader verifies the kernel, device tree, and initramfs, which is what the signed FIT image provides: U-Boot holds the verification public key in its own control device tree and refuses to boot a configuration whose signature does not check.

The root file system needs a different mechanism, being far too large to hash in its entirety before use. The device-mapper verity target computes a Merkle tree of hashes over the block device at build time; at runtime the kernel verifies each block as it is read, with the root hash passed in from the verified boot chain. Any modification is detected when the block is read, not at some later scan. It originated in ChromeOS, was merged into the mainline kernel in Linux 3.4 in 2012, and requires an immutable root file system. Writable data is protected instead by dm-crypt with a key sealed to hardware.

Three operational realities should be designed in before fuses are burned. An attacker who can install an old signed image can reintroduce a fixed vulnerability, so a monotonic counter in fuses or in the eMMC replay-protected memory block, checked by the bootloader, is required. The signing key must be held where it cannot leak, and the scheme must allow revocation. And a fully secured unit will not boot an unsigned kernel, so debug builds, engineering units, and the failure analysis workflow for field returns need a deliberate answer before production. Separately, the kernel's version 2 license permits signed kernels, but version 3 components in a locked-down consumer product raise an installation-information question for counsel.

Field Updates and Image Strategy

Every connected device will need new software after it ships, and the update mechanism is a load-bearing part of the design rather than a feature added at the end. The security dimension is developed in Firmware Update Security. The architectural question is what unit gets replaced. Package-based updating replaces individual components, which is bandwidth-efficient but makes the software on a device a function of its update history, so a fleet drifts into many configurations. Image-based updating replaces the whole system partition: every device that applied a given update is bit-for-bit identical, and validation covers exactly what is deployed. This is why it has become the default, and why a read-only compressed root file system suits it.

A/B Partitions

The dominant scheme keeps two complete copies of the system, conventionally slots A and B. The device runs from one slot while an update is written to the other, so the running system is never modified. When the write completes and its integrity is verified, the bootloader is told to try the inactive slot at the next boot and a boot-attempt counter is set. If the new system reaches a healthy state the application confirms it and the slot is marked good; if it does not, the counter runs out and the bootloader falls back to the previous slot, still intact.

The cost is two copies of the system partition. The benefit is that no single failure, including a power loss at any instant or a new image that boots but does not work, leaves an unbootable device. Where a technician visit costs more than the device, this is not a luxury. Cheaper variants exist: a single slot with a minimal recovery image saves storage but leaves a real window during which a power failure strands the device in recovery, and delta updates cut bandwidth by shipping a binary difference. Android's virtual A/B uses copy-on-write snapshots to obtain the safety of two slots without the full storage cost.

Mechanism and Frameworks

The bootloader holds the state machine. In a U-Boot system the slot selection, the boot-attempt counter, and the update-pending flag are environment variables that the running system writes and the bootloader reads and decrements, which is what makes a redundant environment mandatory. Mature open-source frameworks implement the full flow, including SWUpdate, RAUC, and Mender; OSTree offers a different model in which the file system is versioned like a repository. Two points close the topic. Updating the bootloader is the riskiest operation a device performs, which is partly why hardware-switchable eMMC boot partitions exist. And the update path must be exercised throughout development, from the oldest shipped version forward.

Licensing, Compliance, and the Maintenance Tail

Source Obligations

The kernel is licensed under version 2 of the GNU General Public License, BusyBox under the same license, U-Boot under version 2 or later, glibc under the Lesser General Public License version 2.1 or later, and musl under MIT terms. Distributing a device containing copyleft software is distribution of that software, and the corresponding source must be made available. Version 2 offers three routes, and the second is the one embedded products almost always take: accompanying the product with a written offer, valid for at least three years, to supply the complete corresponding source to any third party for no more than the cost of distribution.

That three-year term is a term of the license itself, and its consequence is underestimated. The obligation attaches to each shipped version, so the source archive for every release must be preserved and retrievable for three years after the last unit carrying it shipped, and it must be the exact source, including patches and build scripts sufficient to reproduce the binary. Reconstructing that archive years later from an undocumented build environment is the failure mode, and archiving at release time prevents it. The Lesser General Public License adds a requirement for glibc: the user must be able to relink against a modified library, which dynamic linking satisfies.

Compliance is best treated as a build output rather than a document. Yocto produces per-image license manifests and can generate SPDX bills of materials; Buildroot's legal-info target produces a license report and collects package source. Scanning tools such as FOSSology and ScanCode verify what the metadata claims, and the OpenChain specification, published as ISO/IEC 5230, describes a process standard for an auditable compliance program. Enforcement is not hypothetical: the BusyBox litigation of the late 2000s established that these obligations are pursued.

Long-Term Support Kernels

The kernel community designates certain releases as long-term support, maintained with backported fixes long after mainline has moved on. As of this writing the maintained longterm branches are 6.18, released November 30, 2025, and 6.12, released November 17, 2024, both projected to December 2028; 6.6, released October 29, 2023, and 6.1, released December 11, 2022, both projected to December 2027; and 5.15 and 5.10, released in October 2021 and December 2020, both projected to December 2026. The projections are explicitly not guarantees and have been extended before.

Read that against a typical industrial product lifetime of ten to fifteen years and the problem is immediate. A device shipping today on a vendor board support package built from a 5.10 kernel runs a base whose upstream maintenance ends within months, after which the manufacturer inherits the whole burden. Vulnerability reporting sharpens the point: since becoming a CVE numbering authority in early 2024, the kernel community has assigned identifiers at a volume that makes per-vulnerability triage impractical for most organizations. Tracking the stable branch of a maintained LTS kernel is therefore both more defensible and cheaper than cherry-picking fixes into a frozen fork.

Three practices follow. Choose silicon whose mainline support lets the product track an upstream LTS kernel rather than a vendor fork. Schedule and budget a kernel migration during the product's life, since a planned migration costs a fraction of an emergency one. And treat the security-update commitment as a specification with a stated duration: the European Union's Cyber Resilience Act, which entered into force in December 2024 with obligations phasing in over the following years, makes support for security updates across a product's expected lifetime a legal requirement in that market.

Conclusion

Embedded Linux is best understood as an engineering discipline built on a general-purpose kernel, and the discipline is where the difficulty lives. The source is free and well documented. The work is assembling a boot chain that survives a power failure, a device tree that matches the board, a root file system that cannot corrupt itself, an update path that cannot brick a unit, and a maintenance plan still valid in year eight. The choices hardest to revisit are made earliest: the silicon decides whether the product tracks a mainline kernel, the C library fixes the binary interface of userspace, and a read-only root chosen in week one is what makes verified boot and atomic updates possible later.

On the question that places this article beside the real-time operating system pages, the answer is clear. With PREEMPT_RT in the mainline kernel since 6.12, Linux offers worst-case latencies an engineer can measure in tens of microseconds and defend with evidence, alongside a networking, storage, and display stack no small kernel can match. What it does not offer is a proof. Where a deadline is genuinely hard, or a certification body must be satisfied, the answer is not to tune Linux further but to move that function to a real-time core or a separate microcontroller. Most successful products do exactly that.

Related Topics