Introduction: a Typical Embedded System

It's not always clear what separates ordinary Linux from embedded Linux. This article takes a look at the parts that make up a typical embedded system, starting with the bootloader and ending with end-user applications.

The very first step in starting an embedded Linux system does not involve
Linux at all. Instead, the processor is reset and starts executing
code from a given location. This location contains a bootloader that
initializes the device and sets up the basic necessities. When everything
has been prepared, the Linux kernel is loaded and started. The kernel
then initializes all the devices before mounting the filesystems and
starting the userspace applications.

The Linux kernel and userspace are not merely a simple blob that is
loaded and run. The kernel consists of a system-specific configuration
and usually some tweaked initialization code. The userspace holds
software libraries, data and several applications, all interacting to
form a system. Each of these components is handpicked for the task and
device in question in order to get a compact and well-performing system.
Figure 1 shows the basic sequence of events.

Figure 1. An Embedded Linux System Booting

The Bootloader

The bootloader is among the first pieces of software to run on the system. It
basically has two tasks: initialize the system and load the
kernel. The initialization can be to set up a UART to be used as a serial
debug console and to configure the system's memory controller. For
instance, if your system is using an SDRAM, you probably will have to
set up the controller with regard to the memory's physical features. This
includes page sizes, the number of columns, supported read and write
widths, latencies and so on. In these days of portable devices, there is
usually a plethora of settings for saving power when it comes to
memory.

In addition to the basic tasks required by the bootloader, it is typical
to provide some sort of command prompt where common low-level tasks can
be carried out. These tasks usually include peeking and poking at random
memory addresses, downloading and storing a Linux kernel image in Flash
and setting bootargs for the kernel to interpret.

Examples of common bootloaders for embedded systems are Das U-Boot
and RedBoot. Both support the basic tasks—meaning they can manage
Flash, networking and serial communication. They also are available for
several processor platforms, such as x86, ARM, PowerPC and more. You
can add your own commands to both of them as well. This makes it possible to
debug custom hardware without involving Linux, reducing the complexity
of the system during the testing phase.

The Linux Kernel

The kernel itself is not very different from an ordinary desktop
kernel. However, there are two major differences. First is the
initialization, which often is system-specific. Second is that you
probably know exactly what hardware will be used, so
you can include all the drivers as part of the kernel and avoid the
need for modules (unless you have proprietary drivers, of course).

When starting a desktop or a server system, the common scenario is that
the kernel probes for hardware and loads the corresponding drivers
as modules. This makes it possible to add hardware and still have
a working system. You also can add drivers for new hardware without
having to recompile the entire kernel. On an embedded system, you can
optimize boot time by including all drivers in the kernel, but also by
hard-coding parts of the available hardware, avoiding the need to
probe for all devices and settings.

Returning to the standard PC, each machine starts and looks about the
same during initialization. In the embedded case, each piece of hardware is
unique,
and you generally have to initialize the custom hardware. This means
you actually will have to write code to set up your kernel for your
board, which is usually easier than you think. For starters,
lots of boards already are supported in the Linux kernel, and you usually can
choose one of
those as a starting point. Second, there are drivers for the most common
peripherals, and again, you typically can find a good starting point,
even when you have to create something of your own. So, the process is
more or less to study the data sheets for your board and express what you learn to
the kernel (something that can be both intimidating and daunting).

Embedded systems often are more limited than your average computer
when it comes to system resources, so it is important to
keep your kernel's footprint small. That, in turn, makes the kernel
configuration stage important. By limiting configuration to a minimum,
you can save those extra bytes needed to fit everything in.