Embedded Linux Fundamentals Embedded Linux – Kernel, Structure, Toolchain

Updated on 2023-08-07 From Diplom-Ingenieur (FH) Andreas Klinger 8 min Reading Time

Related Vendor

When Linux is used in an embedded system, the resources are significantly more limited than in a desktop or server system. What exactly matters in Embedded Linux regarding the kernel, bootloader, or root filesystem?

(Image: Clipdealer)
(Image: Clipdealer)

What does a Linux system consist of?

Linux systems consist of three components: bootloader, Linux kernel, and root filesystem.

The bootloader's task during booting is to initialize the hardware, copy the kernel into RAM, supply it with necessary information, and initiate its startup. Once this is done, its execution path transitions into the kernel boot process.

The Linux kernel includes memory management, scheduling, inter-process communication, and a huge selection of drivers. Any access to hardware is done using kernel drivers, at least to configure it.

At the end of the boot process, the kernel mounts the root filesystem. This is referred to as "mounting." It is visible from the root directory /. The init daemon is located in it. This continues the boot process in user space. To do this, additional filesystems are mounted, system settings are made, and daemons (background processes) are started. It also launches login programs, which start a shell after authenticating a user.

How do Embedded Linux systems differ from Desktop and Server Linux systems? To answer this question, the required components will be described below and a comparison between Standard and Embedded Linux will be made.

Bootloader in Embedded Linux

Which bootloader is used depends on the architecture. On Intel systems, the bootloader grub is most commonly used, while on non-Intel systems, u-boot or barebox are usually used. This division is not strictly defined and the bootloaders can theoretically run on other architectures as well. However, this has little practical relevance.

The main reason for this is likely the way in which Intel architectures deliver hardware information to the bootloader and the Linux kernel. With Intel, there is the BIOS, which supplies this information to the software via ACPI. Based on this, the software can then load and use the corresponding drivers. This makes it possible for the same bootloader executable to be used on different boards of the Intel architecture, as is the case with grub.

If there is no generic information source from which the hardware configuration can be read, the bootloader must have at least some of this information hard compiled into it. This is the case with the bootloaders u-boot and barebox, which originate from the microcontroller sector. These are always created for a specific (sub-)architecture, often even for a specific board with a specific hardware configuration.

Therefore, whether it is an embedded, desktop, or server system plays a subordinate role in the choice of the bootloader.

Structure of an Embedded Linux System.
(Image:Andreas Klinger)

Compilation of Kernel and Root Filesystem

For compiling an Embedded Linux system, there are two contrasting approaches. With the Top-Down approach, a distribution that already contains a large number of software packages is reduced to the necessities of the target system.

This naturally only works if a distribution is available for the respective architecture. For embedded systems, for instance, there is the system elbe, which operates according to this principle.

In contrast, the Bottom-Up approach involves building the entire system from the sources. However, this is not usually done completely "From-The-Scratch." Most developers are supported by a build system. This system's task is to automate the creation process from the cross-development toolchain to the bootloader, kernel, and root filesystem. The developer selects the architecture, software packages, versions, and so on, and leaves the building process to the build system. Examples of this include buildroot and yocto.

Toolchain for Embedded Linux

With the Bottom-Up approach, a development toolchain is needed, with which it is possible to create the components. Very often, the build is done on a development system with a different architecture than the target system. In this case, a cross-development toolchain is required. These toolchains are included in the build systems.

In addition to the compiler and the binutils (assembler, linker, objdump, ...), the toolchain also includes the libraries of the target system, against which the programs of the root filesystem are linked. Especially the so-called C-Library is of central importance here.

Subscribe to the newsletter now

Don't Miss out on Our Best Content

By clicking on „Subscribe to Newsletter“ I agree to the processing and use of my data according to the consent form (please expand for details) and accept the Terms of Use. For more information, please see our Privacy Policy. The consent declaration relates, among other things, to the sending of editorial newsletters by email and to data matching for marketing purposes with selected advertising partners (e.g., LinkedIn, Google, Meta)

Unfold for details of your consent

For desktop and server systems, the GNU C Library, shortly glibc, is almost exclusively used as the C-Library. It always contains the latest features offered by the kernel.

In Embedded Linux, glibc can also be used. However, in cases where the size of this library is a concern, because a small system is needed, it is often too large. For this purpose, there are leaner re-implementations. These are much smaller due to the omission of debugging, tracing, and profiling functionalities. They are also configurable in their range of functions.

The consequence, however, is that programs in the root filesystem must always be linked against exactly the correct C library. Otherwise, they may not function properly. On the other hand, the glibc is kept binary compatible, so binary programs can be exchanged. This works only to a limited extent with the stripped-down libraries. Examples of small C libraries include uClibc and musl.

Mainline kernel, associated patch, or a manufacturer kernel? Which kernel can be used for an embedded system depends significantly on how well the manufacturer or community has managed to make and publish architectural adjustments and drivers.
(Image:Andreas Klinger)

Embedded Linux Kernel

Distributions, as they are primarily used for desktop and server systems, deliver both a Linux kernel and a root filesystem. The kernels contained therein are initially mainline kernels plus a frequently very large number of patches. These patches are introduced to fix bugs or close security vulnerabilities and are distributed with the distribution. Compiling and creating these patches is an important task for the distribution manufacturers.

Distribution kernels are generally not suitable for embedded systems. This is partly due to the architecture used. On the other hand, even with the same architecture, embedded boards are usually more specific, and their hardware configuration is often not covered by available distributions.

Which kernel can be used for an embedded system depends significantly on how well the manufacturer or the community has succeeded in submitting architecture adjustments and drivers upstream. In other words, whether kernel adjustments were made at all, and if so, whether these were then incorporated into the mainline kernel. At this point, there are major differences between SOC and board manufacturers.

In an ideal scenario, one could take a mainline kernel and then have the freedom to choose which version to use, whether to possibly apply a real-time patch, or whether to simply switch to a newer version in the future.

If this is not possible, one often relies on a so-called Manufacturer Kernel. This is a mainline kernel that has been modified through patches by the chip manufacturer and further by the board manufacturer, so that the kernel runs on the respective board. In this case, one is dependent on the available versions and it is very difficult to switch to a different (more recent) kernel version if it is not also provided by the manufacturer. Experience has shown that in such cases, only a few kernel versions are provided and eventually, no new versions will be made available in the future.

As a developer, you're then stuck with this kernel and have little maneuvering room. Thus, it can happen that you need a driver, want to use a new kernel feature, or need to patch a security vulnerability. In all these cases, integrating existing solutions into your own system is difficult and usually involves significant development effort.

Even before selecting a board, based on the extent of necessary patches and the depth of changes, one can recognize how far using it will deviate from the mainline and thus give up degrees of freedom.

Root Filesystem

The root filesystem consists of a directory system with the most important programs as well as device nodes and libraries. The required programs can either be the original programs or reduced variants. In embedded Linux systems, reduced variants are very often used for space reasons. These are then often created using Busybox. This is a re-implementation of the most important programs with the goal of minimizing the footprint. This is achieved by omitting functionality. Moreover, the range of programs and their features is configurable.

Another choice concerns the init daemon. In desktop systems, systemd has now become the standard. For embedded systems, the classic System-V-Init and the similar but reduced Busybox-Init are alternatives.

It is common to create two different systems for the same hardware in embedded Linux. One variant is intended to become the actual product, and a second variant is for development purposes. The development variant includes an adapted kernel and programs for debugging and tracing. This is because the selection of programs required for the finished system is usually very limited.

Embedded Linux Distributions

In the case of standard Linux, distributions are used in most instances. These include not only the compilation of software packages but also their own package management, directory structure, and so on.

The counterpart for Embedded Linux are so-called build systems. With them, entire Embedded Linux systems can be built, meaning they cover the entire creation process from the cross-development toolchain, through the bootloader, the Linux kernel, to the root filesystem. The complete creation process can be mapped with them.

It is possible within the build system to specify all necessary provisions, such as the architecture, which compiler version, which libraries, which programs should go into the root filesystem, and so on. Examples include crosstool-ng, buildroot, yocto, and elbe.

With crosstool-ng, a cross-toolchain for a target system can be created. The other components (bootloader, kernel, root filesystem) cannot be created with it.

With buildroot, the complete creation process with all components is represented. This is set up through a semi-graphical interface and executed with Makefiles.

yocto, on the other hand, works with so-called recipes. These contain the creation instructions for the individual components. During compilation, these recipes are processed with the tool bitbake.

With elbe, the embedded system is not created "from the scratch" but instead, the target system is created from Debian packages in a virtualized environment using debootstrap.

Summary

Desktop or server Linux systems are covered by a range of distributions and are comparatively homogeneous in their structure. Embedded Linux systems, on the other hand, are much more heterogeneous. Reasons for this are to be found in the available hardware and the requirements for the finished embedded system, which can lead to very different systems.