Most of the information available in this book, and the examples presented as exercises, have one thing in common: the fact that they are freely available for anyone to access. This book tries to offer guidance to you on how to interact with existing and freely available packages that could help an embedded engineer, such as you, and at the same time, also try to arouse your curiosity to learn more.
The main advantage of open source is represented by the fact that it permits developers to concentrate more on their products and their added value. Having an open source product offers access to a variety of new possibilities and opportunities, such as reduced costs of licensing, increased skills, and knowledge of a company. The fact that a company uses an open source product that most people have access to, and can understand its working, implies budget savings. The money saved could be used in other departments, such as hardware or acquisitions.
Usually, there is a misconception about open source having little or no control over a product. However, the opposite is true. The open source system, in general, offers full control over software, and we are going to demonstrate this. For any software, your open source project resides on a repository that offers access for everyone to see. Since you're the person in charge of a project, and its administrator as well, you have all the right in the world to accept the contributions of others, which lends them the same right as you, and this basically gives you the freedom to do whatever you like. Of course, there could be someone who is inspired by your project and could do something that is more appreciated by the open source community. However, this is how progress is made, and, to be completely honest, if you are a company, this kind of scenario is almost invalid. Even in this case, this situation does not mean the death of your project, but an opportunity instead. Here, I would like to present the following quote:
"If you want to build an open source project, you can't let your ego stand in the way. You can't rewrite everybody's patches, you can't second-guess everybody, and you have to give people equal control." | ||
--– Rasmus Lerdorf |
Instead of a conclusion, I would also like to present a quote from the person who forms the core of this process, the man who offered us Linux and kept it open source:
"I think, fundamentally, open source does tend to be more stable software. It's the right way to do things." | ||
--– Linus Torvalds |
Now that the benefits of open source have been introduced to you, I believe we can go through a number of examples of embedded systems, hardware, software, and their components. For starters, embedded devices are available anywhere around us: take a look at your smartphone, car infotainment system, microwave oven, or even your MP3 player. Of course, not all of them qualify to be Linux operating systems, but they all have embedded components that make it possible for them to fulfill their designed functions.
For Linux to be run on any device hardware, you will require some hardware-dependent components that are able to abstract the work for hardware-independent ones. The boot loader, kernel, and toolchain contain hardware-dependent components that make the performance of work easier for all the other components. For example, a BusyBox developer will only concentrate on developing the required functionalities for his application, and will not concentrate on hardware compatibility. All these hardware-dependent components offer support for a large variety of hardware architectures for both 32 and 64 bits. For example, the U-Boot implementation is the easiest to take as an example when it comes to source code inspection. From this, we can easily visualize how support for a new device can be added.
We will now try to do some of the little exercises presented previously, but before moving further, I must present the computer configuration on which I will continue to do the exercises, to make sure that that you face as few problems as possible. I am working on an Ubuntu 14.04 and have downloaded the 64-bit image available on the Ubuntu website at http://www.ubuntu.com/download/desktop
Information relevant to the Linux operation running on your computer can be gathered using this command:
uname –srmpio
The preceding command generates this output:
Linux 3.13.0-36-generic x86_64 x86_64 x86_64 GNU/Linux
The next command to gather the information relevant to the Linux operation is as follows:
Now, moving on to exercises, the first one requires you fetch the git
repository sources for the U-Boot package:
Some of the directories that can be visualized are arch
/arc
and arch
/metag
.
From the toolchain point of view, the hardware-dependent component is represented by the GNU C Library, which is, in turn, usually represented by glibc
. This provides the system call interface that connects to the kernel architecture-dependent code and further provides the communication mechanism between these two entities to user applications. System calls are presented inside the sysdeps
directory of the glibc
sources if the glibc
sources are cloned, as follows:
These packages are also very easily accessible using the Yocto Project's poky
repository. As mentioned at https://www.yoctoproject.org/about:
"The Yocto Project is an open source collaboration project that provides templates, tools and methods to help you create custom Linux-based systems for embedded products regardless of the hardware architecture. It was founded in 2010 as a collaboration among many hardware manufacturers, open-source operating systems vendors, and electronics companies to bring some order to the chaos of embedded Linux development."
Most of the interaction anyone has with the Yocto Project is done through the Poky build system, which is one of its core components that offers the features and functionalities needed to generate fully customizable Linux software stacks. The first step needed to ensure interaction with the repository sources would be to clone them:
git clone -b dizzy http://git.yoctoproject.org/git/poky
After the sources are present on your computer, a set of recipes and configuration files need to be inspected. The first location that can be inspected is the U-Boot recipe, available at meta/recipes-bsp/u-boot/u-boot_2013.07.bb
. It contains the instructions necessary to build the U-Boot package for the corresponding selected machine. The next place to inspect is in the recipes available in the kernel. Here, the work is sparse and more package versions are available. It also provides some bbappends
for available recipes, such as meta/recipes-kernel/linux/linux-yocto_3.14.bb
and meta-yocto-bsp/recipes-kernel/linux/linux-yocto_3.10.bbappend
. This constitutes a good example for one of the kernel package versions available when starting a new build using BitBake.
Toolchain construction is a big and important step for host generated packages. To do this, a set of packages are necessary, such as gcc
, binutils
, glibc
library
, and kernel headers
, which play an important role. The recipes corresponding to this package are available inside the meta/recipes-devtools/gcc/
, meta/recipes-devtools/binutils
, and meta/recipes-core/glibc
paths. In all the available locations, a multitude of recipes can be found, each one with a specific purpose. This information will be detailed in the next chapter.
We will now try to do some of the little exercises presented previously, but before moving further, I must present the computer configuration on which I will continue to do the exercises, to make sure that that you face as few problems as possible. I am working on an Ubuntu 14.04 and have downloaded the 64-bit image available on the Ubuntu website at http://www.ubuntu.com/download/desktop
Information relevant to the Linux operation running on your computer can be gathered using this command:
uname –srmpio
The preceding command generates this output:
Linux 3.13.0-36-generic x86_64 x86_64 x86_64 GNU/Linux
The next command to gather the information relevant to the Linux operation is as follows:
Now, moving on to exercises, the first one requires you fetch the git
repository sources for the U-Boot package:
Some of the directories that can be visualized are arch
/arc
and arch
/metag
.
From the toolchain point of view, the hardware-dependent component is represented by the GNU C Library, which is, in turn, usually represented by glibc
. This provides the system call interface that connects to the kernel architecture-dependent code and further provides the communication mechanism between these two entities to user applications. System calls are presented inside the sysdeps
directory of the glibc
sources if the glibc
sources are cloned, as follows:
These packages are also very easily accessible using the Yocto Project's poky
repository. As mentioned at https://www.yoctoproject.org/about:
"The Yocto Project is an open source collaboration project that provides templates, tools and methods to help you create custom Linux-based systems for embedded products regardless of the hardware architecture. It was founded in 2010 as a collaboration among many hardware manufacturers, open-source operating systems vendors, and electronics companies to bring some order to the chaos of embedded Linux development."
Most of the interaction anyone has with the Yocto Project is done through the Poky build system, which is one of its core components that offers the features and functionalities needed to generate fully customizable Linux software stacks. The first step needed to ensure interaction with the repository sources would be to clone them:
git clone -b dizzy http://git.yoctoproject.org/git/poky
After the sources are present on your computer, a set of recipes and configuration files need to be inspected. The first location that can be inspected is the U-Boot recipe, available at meta/recipes-bsp/u-boot/u-boot_2013.07.bb
. It contains the instructions necessary to build the U-Boot package for the corresponding selected machine. The next place to inspect is in the recipes available in the kernel. Here, the work is sparse and more package versions are available. It also provides some bbappends
for available recipes, such as meta/recipes-kernel/linux/linux-yocto_3.14.bb
and meta-yocto-bsp/recipes-kernel/linux/linux-yocto_3.10.bbappend
. This constitutes a good example for one of the kernel package versions available when starting a new build using BitBake.
Toolchain construction is a big and important step for host generated packages. To do this, a set of packages are necessary, such as gcc
, binutils
, glibc
library
, and kernel headers
, which play an important role. The recipes corresponding to this package are available inside the meta/recipes-devtools/gcc/
, meta/recipes-devtools/binutils
, and meta/recipes-core/glibc
paths. In all the available locations, a multitude of recipes can be found, each one with a specific purpose. This information will be detailed in the next chapter.
on to exercises, the first one requires you fetch the git
repository sources for the U-Boot package:
Some of the directories that can be visualized are arch
/arc
and arch
/metag
.
From the toolchain point of view, the hardware-dependent component is represented by the GNU C Library, which is, in turn, usually represented by glibc
. This provides the system call interface that connects to the kernel architecture-dependent code and further provides the communication mechanism between these two entities to user applications. System calls are presented inside the sysdeps
directory of the glibc
sources if the glibc
sources are cloned, as follows:
These packages are also very easily accessible using the Yocto Project's poky
repository. As mentioned at https://www.yoctoproject.org/about:
"The Yocto Project is an open source collaboration project that provides templates, tools and methods to help you create custom Linux-based systems for embedded products regardless of the hardware architecture. It was founded in 2010 as a collaboration among many hardware manufacturers, open-source operating systems vendors, and electronics companies to bring some order to the chaos of embedded Linux development."
Most of the interaction anyone has with the Yocto Project is done through the Poky build system, which is one of its core components that offers the features and functionalities needed to generate fully customizable Linux software stacks. The first step needed to ensure interaction with the repository sources would be to clone them:
git clone -b dizzy http://git.yoctoproject.org/git/poky
After the sources are present on your computer, a set of recipes and configuration files need to be inspected. The first location that can be inspected is the U-Boot recipe, available at meta/recipes-bsp/u-boot/u-boot_2013.07.bb
. It contains the instructions necessary to build the U-Boot package for the corresponding selected machine. The next place to inspect is in the recipes available in the kernel. Here, the work is sparse and more package versions are available. It also provides some bbappends
for available recipes, such as meta/recipes-kernel/linux/linux-yocto_3.14.bb
and meta-yocto-bsp/recipes-kernel/linux/linux-yocto_3.10.bbappend
. This constitutes a good example for one of the kernel package versions available when starting a new build using BitBake.
Toolchain construction is a big and important step for host generated packages. To do this, a set of packages are necessary, such as gcc
, binutils
, glibc
library
, and kernel headers
, which play an important role. The recipes corresponding to this package are available inside the meta/recipes-devtools/gcc/
, meta/recipes-devtools/binutils
, and meta/recipes-core/glibc
paths. In all the available locations, a multitude of recipes can be found, each one with a specific purpose. This information will be detailed in the next chapter.
GNU/Linux, or Linux as it's commonly known, represents a name that has a long line of tradition behind it, and is one of the most important unions of open source software. Shortly, you will be introduced to the history of what is offered to people around the world today and the choice available in terms of selecting personal computer operating systems. Most of all, we will look at what is offered to hardware developers and the common ground available for the development of platforms.
Meanwhile, in 1991, a Finnish student started working on another kernel as a hobby while attending the University of Helsinki. He also got help from various programmers who contributed to the cause over the Internet. That student's name was Linus Torvalds and, in 1992, his kernel was combined with the GNU system. The result was a fully functional operating system called GNU/Linux that was free and open source. The most common form of the GNU system is usually referred to as a GNU/Linux system, or even a Linux distribution, and is the most popular variant of GNU. Today, there are a great number of distributions based on GNU and the Linux kernel, and the most widely used ones are: Debian, Ubuntu, Red Hat Linux, SuSE, Gentoo, Mandriva, and Slackware. This image shows us how the two components of Linux work together:
- Host machine: This is the machine where all the development tools reside. Outside the Yocto world, these tools are represented by a corresponding toolchain cross-compiled for a specific target and its necessary applications sources and patches. However, for an Yocto user, all these packages, and the preparation work involved, is reduced to automatized tasks executed before the actual work is performed. This, of course, has to be prioritized adequately.
- Target machine: This is the embedded system on which the work is done and tested. All the software available on the target is usually cross-compiled on the host machine, which is a more powerful and more efficient environment. The components that are usually necessary for an embedded device to boot Linux and operate various application, involve using a bootloader for basic initiation and loading of the Linux kernel. This, in turn, initializes drivers and the memory, and offers services for applications to interact with through the functions of the available C libraries.
To have a functional Linux operating system on an development board, a developer first needs to make sure that the kernel, bootloader, and board corresponding drives are working properly before starting to develop and integrate other applications and libraries.
In the previous section, the benefits of having an open source environment were presented. Taking a look at how embedded development was done before the advent of the Yocto Project offers a complete picture of the benefits of this project. It also gives an answer as to why it was adopted so quickly and in such huge numbers.
The most notable solutions are Buildroot, Linux Target Image Builder (LTIB), Scratchbox, OpenEmbedded, Yocto, and Angstrom. However, Scratchbox doesn't seem to be active anymore, with the last commit being done in April 2012. LTIB was the preferred build tool for Freescale and it has lately moved more toward Yocto; in a short span of time, LTIB may become deprecated also.
Buildroot as a tool tries to simplify the ways in which a Linux system is generated using a cross-compiler. Buildroot is able to generate a bootloader, kernel image, root filesystem, and even a cross-compiler. It can generate each one of these components, although in an independent way, and because of this, its main usage has been restricted to a cross-compiled toolchain that generates a corresponding and custom root filesystem. It is mainly used in embedded devices and very rarely for x86 architectures; its main focus being architectures, such as ARM, PowerPC, or MIPS. As with every tool presented in this book, it is designed to run on Linux, and certain packages are expected to be present on the host system for their proper usage. There are a couple of mandatory packages and some optional ones as well.
There is a list of mandatory packages that contain the certain packages, and are described inside the Buildroot manual available at http://buildroot.org/downloads/manual/manual.html. These packages are as follows:
which
sed
make
(version 3.81 or any later ones)binutils
build-essential
(required for Debian-based systems only)gcc
(version 2.95 or any later ones)g++
(version 2.95 or any later ones)bash
patch
gzip
bzip2
perl
(version 5.8.7 or any later ones)tar
cpio
python
(version 2.6 or 2.7 ones)unzip
rsync
wget
Beside these mandatory packages, there are also a number of optional packages. They are very useful for the following:
- Source fetching tools: In an official tree, most of the package retrieval is done using
wget
fromhttp
,https
, or evenftp
links, but there are also a couple of links that need a version control system or another type of tool. To make sure that the user does not have a limitation to fetch a package, these tools can be used:bazaar
cvs
git
mercurial
rsync
scp
subversion
- Interface configuration dependencies: They are represented by the packages that are needed to ensure that the tasks, such as kernel, BusyBox, and U-Boot configuration, are executed without problems:
ncurses5
is used for the menuconfig interfaceqt4
is used for thexconfig
interfaceglib2
,gtk2
, andglade2
are used for thegconfig
interface
- Java related package interaction: This is used to make sure that when a user wants to interact with the Java Classpath component, that it will be done without any hiccups:
javac
: this refers to the Java compilerjar
: This refers to the Java archive tool
- Graph generation tools: The following are the graph generation tools:
graphviz
to usegraph-depends
and<pkg>-graph-depends
python-matplotlib
to usegraph-build
- Documentation generation tools: The following are the tools necessary for the documentation generation process:
asciidoc
, version 8.6.3 or higherw3m
python
with theargparse
module (which is automatically available in 2.7+ and 3.2+ versions)dblatex
(necessary for pdf manual generation only)
Buildroot releases are made available to the open source community at http://buildroot.org/downloads/ every three months, specifically in February, May, August, and November, and the release name has the buildroot-yyyy-mm
format. For people interested in giving Buildroot a try, the manual described in the previous section should be the starting point for installing and configuration. Developers interested in taking a look at the Buildroot source code can refer to http://git.buildroot.net/buildroot/.
Note
Before cloning the Buildroot source code, I suggest taking a quick look at http://buildroot.org/download. It could help out anyone who works with a proxy server.
Next, there will be presented a new set of tools that brought their contribution to this field and place on a lower support level the Buildroot project. I believe that a quick review of the strengths and weaknesses of these tools would be required. We will start with Scratchbox and, taking into consideration that it is already deprecated, there is not much to say about it; it's being mentioned purely for historical reasons. Next on the line is LTIB, which constituted the standard for Freescale hardware until the adoption of Yocto. It is well supported by Freescale in terms of Board Support Packages (BSPs) and contains a large database of components. On the other hand, it is quite old and it was switched with Yocto. It does not contain the support of new distributions, it is not used by many hardware providers, and, in a short period of time, it could very well become as deprecated as Scratchbox. Buildroot is the last of them and is easy to use, having a Makefile
base format and an active community behind it. However, it is limited to smaller and simpler images or devices, and it is not aware of partial builds or packages.
The next tools to be introduced are very closely related and, in fact, have the same inspiration and common ancestor, the OpenEmbedded project. All three projects are linked by the common engine called Bitbake and are inspired by the Gentoo Portage build tool. OpenEmbedded was first developed in 2001 when the Sharp Corporation launched the ARM-based PDA, and SL-5000 Zaurus, which run Lineo, an embedded Linux distribution. After the introduction of Sharp Zaurus, it did not take long for Chris Larson to initiate the OpenZaurus Project, which was meant to be a replacement for SharpROM, based on Buildroot. After this, people started to contribute many more software packages, and even the support of new devices, and, eventually, the system started to show its limitations. In 2003, discussions were initiated around a new build system that could offer a generic build environment and incorporate the usage models requested by the open source community; this was the system to be used for embedded Linux distributions. These discussions started showing results in 2003, and what has emerged today is the Openembedded project. It had packages ported from OpenZaurus by people, such as Chris Larson, Michael Lauer, and Holger Schurig, according to the capabilities of the new build system.
The Yocto Project is the next evolutionary stage of the same project and has the Poky build system as its core piece, which was created by Richard Purdie. The project started as a stabilized branch of the OpenEmbedded project and only included a subset of the numerous recipes available on OpenEmbedded; it also had a limited set of devices and support of architectures. Over time, it became much more than this: it changed into a software development platform that incorporated a fakeroot replacement, an Eclipse plug-in, and QEMU-based images. Both the Yocto Project and OpenEmbedded now coordinate around a core set of metadata called OpenEmbedded-Core (OE-Core).
The Yocto Project is sponsored by the Linux Foundation, and offers a starting point for developers of Linux embedded systems who are interested in developing a customized distribution for embedded products in a hardware-agnostic environment. The Poky build system represents one of its core components and is also quite complex. At the center of all this lies Bitbake, the engine that powers everything, the tool that processes metadata, downloads corresponding source codes, resolves dependencies, and stores all the necessary libraries and executables inside the build directory accordingly. Poky combines the best from OpenEmbedded with the idea of layering additional software components that could be added or removed from a build environment configuration, depending on the needs of the developer.
As shown in this example, it is easy to obtain a Linux image that can be later used for testing inside a QEMU environment. There are a number of images footprints available that will vary from a shell-accessible minimal image to an LSB compliant image with GNOME Mobile user interface support. Of course, that these base images can be imported in new ones for added functionalities. The layered structure that Poky has is a great advantage because it adds the possibility to extend functionalities and to contain the impact of errors. Layers could be used for all sort of functionalities, from adding support for a new hardware platform to extending the support for tools, and from a new software stack to extended image features. The sky is the limit here because almost any recipe can be combined with another.
All this is possible because of the Bitbake engine, which, after the environment setup and the tests for minimal systems requirements are met, based on the configuration files and input received, identifies the interdependencies between tasks, the execution order of tasks, generates a fully functional cross-compilation environment, and starts building the necessary native and target-specific packages tasks exactly as they were defined by the developer. Here is an example with a list of the available tasks for a package:
The metadata modularization is based on two ideas—the first one refers to the possibility of prioritizing the structure of layers, and the second refers to the possibility of not having the need for duplicate work when a recipe needs changes. The layers are overlapping. The most general layer is meta, and all the other layers are usually stacked over it, such as meta-yocto
with Yocto-specific recipes, machine specific board support packages, and other optional layers, depending on the requirements and needs of developers. The customization of recipes should be done using bbappend
situated in an upper layer. This method is preferred to ensure that the duplication of recipes does not happen, and it also helps to support newer and older versions of them.
An example of the organization of layers is found in the previous example that specified the list of the available tasks for a package. If a user is interested in identifying the layers used by the test
build setup in the previous exercise that specified the list of the available tasks for a package, the bblayers.conf
file is a good source of inspiration. If cat
is done on this file, the following output will be visible:
The complete command for doing this is:
Here is a visual mode for the layered structure of a more generic build directory:
Yocto as a project offers another important feature: the possibility of having an image regenerated in the same way, no matter what factors change on your host machine. This is a very important feature, taking into consideration not only that, in the development process, changes to a number of tools, such as autotools
, cross-compiler
, Makefile
, perl
, bison
, pkgconfig
, and so on, could occur, but also the fact that parameters could change in the interaction process with regards to a repository. Simply cloning one of the repository branches and applying corresponding patches may not solve all the problems. The solution that the Yocto Project has to these problems is quite simple. By defining parameters prior to any of the steps of the installation as variables and configuration parameters inside recipes, and by making sure that the configuration process is also automated, will minimize the risks of manual interaction are minimized. This process makes sure that image generation is always done as it was the first time.
The Yocto project contributes to making reliable embedded Linux development and because of its dimensions, it is used for lots of things, ranging from board support packages by hardware companies to new software solutions by software development companies. Yocto is not a perfect tool and it has certain drawbacks:
a tool tries to simplify the ways in which a Linux system is generated using a cross-compiler. Buildroot is able to generate a bootloader, kernel image, root filesystem, and even a cross-compiler. It can generate each one of these components, although in an independent way, and because of this, its main usage has been restricted to a cross-compiled toolchain that generates a corresponding and custom root filesystem. It is mainly used in embedded devices and very rarely for x86 architectures; its main focus being architectures, such as ARM, PowerPC, or MIPS. As with every tool presented in this book, it is designed to run on Linux, and certain packages are expected to be present on the host system for their proper usage. There are a couple of mandatory packages and some optional ones as well.
There is a list of mandatory packages that contain the certain packages, and are described inside the Buildroot manual available at http://buildroot.org/downloads/manual/manual.html. These packages are as follows:
which
sed
make
(version 3.81 or any later ones)binutils
build-essential
(required for Debian-based systems only)gcc
(version 2.95 or any later ones)g++
(version 2.95 or any later ones)bash
patch
gzip
bzip2
perl
(version 5.8.7 or any later ones)tar
cpio
python
(version 2.6 or 2.7 ones)unzip
rsync
wget
Beside these mandatory packages, there are also a number of optional packages. They are very useful for the following:
- Source fetching tools: In an official tree, most of the package retrieval is done using
wget
fromhttp
,https
, or evenftp
links, but there are also a couple of links that need a version control system or another type of tool. To make sure that the user does not have a limitation to fetch a package, these tools can be used:bazaar
cvs
git
mercurial
rsync
scp
subversion
- Interface configuration dependencies: They are represented by the packages that are needed to ensure that the tasks, such as kernel, BusyBox, and U-Boot configuration, are executed without problems:
ncurses5
is used for the menuconfig interfaceqt4
is used for thexconfig
interfaceglib2
,gtk2
, andglade2
are used for thegconfig
interface
- Java related package interaction: This is used to make sure that when a user wants to interact with the Java Classpath component, that it will be done without any hiccups:
javac
: this refers to the Java compilerjar
: This refers to the Java archive tool
- Graph generation tools: The following are the graph generation tools:
graphviz
to usegraph-depends
and<pkg>-graph-depends
python-matplotlib
to usegraph-build
- Documentation generation tools: The following are the tools necessary for the documentation generation process:
asciidoc
, version 8.6.3 or higherw3m
python
with theargparse
module (which is automatically available in 2.7+ and 3.2+ versions)dblatex
(necessary for pdf manual generation only)
Buildroot releases are made available to the open source community at http://buildroot.org/downloads/ every three months, specifically in February, May, August, and November, and the release name has the buildroot-yyyy-mm
format. For people interested in giving Buildroot a try, the manual described in the previous section should be the starting point for installing and configuration. Developers interested in taking a look at the Buildroot source code can refer to http://git.buildroot.net/buildroot/.
Note
Before cloning the Buildroot source code, I suggest taking a quick look at http://buildroot.org/download. It could help out anyone who works with a proxy server.
Next, there will be presented a new set of tools that brought their contribution to this field and place on a lower support level the Buildroot project. I believe that a quick review of the strengths and weaknesses of these tools would be required. We will start with Scratchbox and, taking into consideration that it is already deprecated, there is not much to say about it; it's being mentioned purely for historical reasons. Next on the line is LTIB, which constituted the standard for Freescale hardware until the adoption of Yocto. It is well supported by Freescale in terms of Board Support Packages (BSPs) and contains a large database of components. On the other hand, it is quite old and it was switched with Yocto. It does not contain the support of new distributions, it is not used by many hardware providers, and, in a short period of time, it could very well become as deprecated as Scratchbox. Buildroot is the last of them and is easy to use, having a Makefile
base format and an active community behind it. However, it is limited to smaller and simpler images or devices, and it is not aware of partial builds or packages.
The next tools to be introduced are very closely related and, in fact, have the same inspiration and common ancestor, the OpenEmbedded project. All three projects are linked by the common engine called Bitbake and are inspired by the Gentoo Portage build tool. OpenEmbedded was first developed in 2001 when the Sharp Corporation launched the ARM-based PDA, and SL-5000 Zaurus, which run Lineo, an embedded Linux distribution. After the introduction of Sharp Zaurus, it did not take long for Chris Larson to initiate the OpenZaurus Project, which was meant to be a replacement for SharpROM, based on Buildroot. After this, people started to contribute many more software packages, and even the support of new devices, and, eventually, the system started to show its limitations. In 2003, discussions were initiated around a new build system that could offer a generic build environment and incorporate the usage models requested by the open source community; this was the system to be used for embedded Linux distributions. These discussions started showing results in 2003, and what has emerged today is the Openembedded project. It had packages ported from OpenZaurus by people, such as Chris Larson, Michael Lauer, and Holger Schurig, according to the capabilities of the new build system.
The Yocto Project is the next evolutionary stage of the same project and has the Poky build system as its core piece, which was created by Richard Purdie. The project started as a stabilized branch of the OpenEmbedded project and only included a subset of the numerous recipes available on OpenEmbedded; it also had a limited set of devices and support of architectures. Over time, it became much more than this: it changed into a software development platform that incorporated a fakeroot replacement, an Eclipse plug-in, and QEMU-based images. Both the Yocto Project and OpenEmbedded now coordinate around a core set of metadata called OpenEmbedded-Core (OE-Core).
The Yocto Project is sponsored by the Linux Foundation, and offers a starting point for developers of Linux embedded systems who are interested in developing a customized distribution for embedded products in a hardware-agnostic environment. The Poky build system represents one of its core components and is also quite complex. At the center of all this lies Bitbake, the engine that powers everything, the tool that processes metadata, downloads corresponding source codes, resolves dependencies, and stores all the necessary libraries and executables inside the build directory accordingly. Poky combines the best from OpenEmbedded with the idea of layering additional software components that could be added or removed from a build environment configuration, depending on the needs of the developer.
As shown in this example, it is easy to obtain a Linux image that can be later used for testing inside a QEMU environment. There are a number of images footprints available that will vary from a shell-accessible minimal image to an LSB compliant image with GNOME Mobile user interface support. Of course, that these base images can be imported in new ones for added functionalities. The layered structure that Poky has is a great advantage because it adds the possibility to extend functionalities and to contain the impact of errors. Layers could be used for all sort of functionalities, from adding support for a new hardware platform to extending the support for tools, and from a new software stack to extended image features. The sky is the limit here because almost any recipe can be combined with another.
All this is possible because of the Bitbake engine, which, after the environment setup and the tests for minimal systems requirements are met, based on the configuration files and input received, identifies the interdependencies between tasks, the execution order of tasks, generates a fully functional cross-compilation environment, and starts building the necessary native and target-specific packages tasks exactly as they were defined by the developer. Here is an example with a list of the available tasks for a package:
The metadata modularization is based on two ideas—the first one refers to the possibility of prioritizing the structure of layers, and the second refers to the possibility of not having the need for duplicate work when a recipe needs changes. The layers are overlapping. The most general layer is meta, and all the other layers are usually stacked over it, such as meta-yocto
with Yocto-specific recipes, machine specific board support packages, and other optional layers, depending on the requirements and needs of developers. The customization of recipes should be done using bbappend
situated in an upper layer. This method is preferred to ensure that the duplication of recipes does not happen, and it also helps to support newer and older versions of them.
An example of the organization of layers is found in the previous example that specified the list of the available tasks for a package. If a user is interested in identifying the layers used by the test
build setup in the previous exercise that specified the list of the available tasks for a package, the bblayers.conf
file is a good source of inspiration. If cat
is done on this file, the following output will be visible:
The complete command for doing this is:
Here is a visual mode for the layered structure of a more generic build directory:
Yocto as a project offers another important feature: the possibility of having an image regenerated in the same way, no matter what factors change on your host machine. This is a very important feature, taking into consideration not only that, in the development process, changes to a number of tools, such as autotools
, cross-compiler
, Makefile
, perl
, bison
, pkgconfig
, and so on, could occur, but also the fact that parameters could change in the interaction process with regards to a repository. Simply cloning one of the repository branches and applying corresponding patches may not solve all the problems. The solution that the Yocto Project has to these problems is quite simple. By defining parameters prior to any of the steps of the installation as variables and configuration parameters inside recipes, and by making sure that the configuration process is also automated, will minimize the risks of manual interaction are minimized. This process makes sure that image generation is always done as it was the first time.
The Yocto project contributes to making reliable embedded Linux development and because of its dimensions, it is used for lots of things, ranging from board support packages by hardware companies to new software solutions by software development companies. Yocto is not a perfect tool and it has certain drawbacks:
to be introduced are very closely related and, in fact, have the same inspiration and common ancestor, the OpenEmbedded project. All three projects are linked by the common engine called Bitbake and are inspired by the Gentoo Portage build tool. OpenEmbedded was first developed in 2001 when the Sharp Corporation launched the ARM-based PDA, and SL-5000 Zaurus, which run Lineo, an embedded Linux distribution. After the introduction of Sharp Zaurus, it did not take long for Chris Larson to initiate the OpenZaurus Project, which was meant to be a replacement for SharpROM, based on Buildroot. After this, people started to contribute many more software packages, and even the support of new devices, and, eventually, the system started to show its limitations. In 2003, discussions were initiated around a new build system that could offer a generic build environment and incorporate the usage models requested by the open source community; this was the system to be used for embedded Linux distributions. These discussions started showing results in 2003, and what has emerged today is the Openembedded project. It had packages ported from OpenZaurus by people, such as Chris Larson, Michael Lauer, and Holger Schurig, according to the capabilities of the new build system.
The Yocto Project is the next evolutionary stage of the same project and has the Poky build system as its core piece, which was created by Richard Purdie. The project started as a stabilized branch of the OpenEmbedded project and only included a subset of the numerous recipes available on OpenEmbedded; it also had a limited set of devices and support of architectures. Over time, it became much more than this: it changed into a software development platform that incorporated a fakeroot replacement, an Eclipse plug-in, and QEMU-based images. Both the Yocto Project and OpenEmbedded now coordinate around a core set of metadata called OpenEmbedded-Core (OE-Core).
The Yocto Project is sponsored by the Linux Foundation, and offers a starting point for developers of Linux embedded systems who are interested in developing a customized distribution for embedded products in a hardware-agnostic environment. The Poky build system represents one of its core components and is also quite complex. At the center of all this lies Bitbake, the engine that powers everything, the tool that processes metadata, downloads corresponding source codes, resolves dependencies, and stores all the necessary libraries and executables inside the build directory accordingly. Poky combines the best from OpenEmbedded with the idea of layering additional software components that could be added or removed from a build environment configuration, depending on the needs of the developer.
As shown in this example, it is easy to obtain a Linux image that can be later used for testing inside a QEMU environment. There are a number of images footprints available that will vary from a shell-accessible minimal image to an LSB compliant image with GNOME Mobile user interface support. Of course, that these base images can be imported in new ones for added functionalities. The layered structure that Poky has is a great advantage because it adds the possibility to extend functionalities and to contain the impact of errors. Layers could be used for all sort of functionalities, from adding support for a new hardware platform to extending the support for tools, and from a new software stack to extended image features. The sky is the limit here because almost any recipe can be combined with another.
All this is possible because of the Bitbake engine, which, after the environment setup and the tests for minimal systems requirements are met, based on the configuration files and input received, identifies the interdependencies between tasks, the execution order of tasks, generates a fully functional cross-compilation environment, and starts building the necessary native and target-specific packages tasks exactly as they were defined by the developer. Here is an example with a list of the available tasks for a package:
The metadata modularization is based on two ideas—the first one refers to the possibility of prioritizing the structure of layers, and the second refers to the possibility of not having the need for duplicate work when a recipe needs changes. The layers are overlapping. The most general layer is meta, and all the other layers are usually stacked over it, such as meta-yocto
with Yocto-specific recipes, machine specific board support packages, and other optional layers, depending on the requirements and needs of developers. The customization of recipes should be done using bbappend
situated in an upper layer. This method is preferred to ensure that the duplication of recipes does not happen, and it also helps to support newer and older versions of them.
An example of the organization of layers is found in the previous example that specified the list of the available tasks for a package. If a user is interested in identifying the layers used by the test
build setup in the previous exercise that specified the list of the available tasks for a package, the bblayers.conf
file is a good source of inspiration. If cat
is done on this file, the following output will be visible:
The complete command for doing this is:
Here is a visual mode for the layered structure of a more generic build directory:
Yocto as a project offers another important feature: the possibility of having an image regenerated in the same way, no matter what factors change on your host machine. This is a very important feature, taking into consideration not only that, in the development process, changes to a number of tools, such as autotools
, cross-compiler
, Makefile
, perl
, bison
, pkgconfig
, and so on, could occur, but also the fact that parameters could change in the interaction process with regards to a repository. Simply cloning one of the repository branches and applying corresponding patches may not solve all the problems. The solution that the Yocto Project has to these problems is quite simple. By defining parameters prior to any of the steps of the installation as variables and configuration parameters inside recipes, and by making sure that the configuration process is also automated, will minimize the risks of manual interaction are minimized. This process makes sure that image generation is always done as it was the first time.
The Yocto project contributes to making reliable embedded Linux development and because of its dimensions, it is used for lots of things, ranging from board support packages by hardware companies to new software solutions by software development companies. Yocto is not a perfect tool and it has certain drawbacks:
A toolchain represents a compiler and its associated utilities that are used with the purpose of producing kernels, drivers, and applications necessary for a specific target. A toolchain usually contains a set of tools that are usually linked to each other. It consists of gcc
, glibc
, binutils
, or other optional tools, such as a debugger optional compiler, which is used for specific programming languages, such as C++, Ada, Java, Fortran, or Objective-C.
In a toolchain environment, three different machines are available:
These three machine are used to generate four different toolchain build procedures:
- A native toolchain: This is usually available on a normal Linux distribution or on your normal desktop system. This is usually compiled and run, and generates code for the same architecture.
- A cross-native toolchain: This represents a toolchain built on one system, though it runs and produces a binary code for the target system. A normal use case is when a native
gcc
is needed on the target system without building it on the target platform. - A cross-compilation toolchain: This is the most widespread toolchain type used for embedded development. It is compiled and run on an architecture type, usually x86, and produces a binary code for the target architecture.
- A cross-canadian build: This represents a process that involves building a toolchain on system A. This toolchain is then run on another system, such as B, which produces a binary code for a third system, called C. This is one of the most underused build processes.
Toolchains represent a list of tools that make the existence of most of great projects available today possible. This includes open source projects as well. This diversity would not have been possible without the existence of a corresponding toolchain. This also happens in the embedded world where newly available hardware needs the components and support of a corresponding toolchain for its Board Support Package (BSP).
The GNU toolchain is a term used for a collection of programming tools under the GNU Project umbrella. This suite of tools is what is normally called a toolchain, and is used for the development of applications and operating systems. It plays an important role in the development of embedded systems and Linux systems, in particular.
The following projects are included in the GNU toolchain:
- GNU make: This represents an automation tool used for compilation and build
- GNU Compiler Collection (GCC): This represents a compiler's suite that is used for a number of available programming languages
- GNU Binutils: This contains tools, such as linkers, assemblers, and so on - these tools are able to manipulate binaries
- GNU Bison: This is a parser generator
- GNU Debugger (GDB): This is a code debugging tool
- GNU m4: This is an m4 macro processor
- GNU build system (autotools): This consists of the following:
- Autoconf
- Autoheaders
- Automake
- Libtool
The projects included in the toolchain is described in the following diagram:
An embedded development environment needs more than a cross-compilation toolchain. It needs libraries and it should target system-specific packages, such as programs, libraries, and utilities, and host specific debuggers, editors, and utilities. In some cases, usually when talking about a company's environment, a number of servers host target devices, and an certain hardware probes are connected to the host through Ethernet or other methods. This emphasizes the fact that an embedded distribution includes a great number of tools, and, usually, a number of these tools require customization. Presenting each of these will take up more than a chapter in a book.
I will start by the introducing the first item on this list, the GNU Binutils package. Developed under the GNU GPL license, it represents a set of tools that are used to create and manage binary files, object code, assembly files, and profile data for a given architecture. Here is a list with the functionalities and names of the available tools for GNU Binutils package:
- The GNU linker, that is
ld
- The GNU assembler, that is
as
- A utility that converts addresses into filenames and line numbers, that is
addr2line
- A utility to create, extract, and modify archives, that is
ar
- A tool used to listing the symbols available inside object files, that is
nm
- Copying and translating object files, that is
objcopy
- Displaying information from object files, that is
objdump
- Generating an index to for the contents of an archive, that is
ranlib
- Displaying information from any ELF format object file, that is
readelf
- Listing the section sizes of an object or archive file, that is
size
- Listing printable strings from files, that is
strings
- Discarding the symbols utility that is
strip
- Filtering or demangle encoded C++ symbols, that is
c++filt
- Creating files that build use DLLs, that is
dlltool
- A new, faster, ELF-only linker, which is still in beta testing, that is
gold
- Displaying the profiling information tool, that is
gprof
- Converting an object code into an NLM, that is
nlmconv
- A Windows-compatible message compiler, that is
windmc
- A compiler for Windows resource files, that is
windres
The majority of these tools use the Binary File Descriptor (BFD) library for low-level data manipulation, and also, many of them use the opcode
library to assemble and disassemble operations.
In the toolchain generation process, the next item on the list is represented by kernel headers, and are needed by the C library for interaction with the kernel. Before compiling the corresponding C library, the kernel headers need to be supplied so that they can offer access to the available system calls, data structures, and constants definitions. Of course, any C library defines sets of specifications that are specific to each hardware architecture; here, I am referring to application binary interface (ABI).
As a general rule, ABI must be respected for its interaction with external components. However, with regard to interaction with its internal modules, the user is free to do whatever he or she wants. Basically, they are able to reinvent the ABI and form their own dependence on the limitations of the machine. The simple example here is related to various citizens who belong to their own country or region, because they learned and know the language of that region since they were born. Hence, they are able to understand one another and communicate without problems. For an external citizen to be able to communicate, he or she will need to know the language of a region, and being in this community seems natural, so it will not constitute a problem. Compilers are also able to design their own custom calling conventions where they know the limitations of functions that are called within a module. This exercise is typically done for optimization reasons. However, this can be considered an abuse of the ABI term.
The GNU Compiler Collection, also known as GCC, represents a compiler system that constitutes the key component of the GNU toolchain. Although it was originally named the GNU C Compiler, due to the fact that it only handled the C programming language, it soon begun to represent a collection of languages, such as C, C++, Objective C, Fortran, Java, Ada, and Go, as well as the libraries for other languages (such as libstdc++
, libgcj
, and so on).
This changed in 1997, when a group of developers gathered as the Experimental/Enhanced GNU Compiler System (EGCS) workgroup started merging several forks in one project. They had so much success in this venture, and gathered so many features, that they made Free Software Foundation (FSF) halt their development of GCC version 2 and appointed EGCS the official GCC version and maintainers by April 1999. They united with each other with the release of GCC 2.95. More information on the history and release history of the GNU Compiler Collection can be found at https://www.gnu.org/software/gcc/releases.html and http://en.wikipedia.org/wiki/GNU_Compiler_Collection#Revision_history.
The GCC interface is similar to the Unix convention, where users call a language-specific driver, which interprets arguments and calls a compiler. It then runs an assembler on the resulting outputs and, if necessary, runs a linker to obtain the final executable. For each language compiler, there is a separate program that performs the source code read.
The process of obtaining an executable from source code has some execution steps. After the first step, an abstract syntax tree is generated and, in this stage, compiler optimization and static code analysis can be applied. The optimizations and static code analysis can be both applied on architecture-independent GIMPLE or its superset GENERIC representation, and also on architecture-dependent Register Transfer Language (RTL) representation, which is similar to the LISP language. The machine code is generated using pattern-matching algorithm, which was written by Jack Davidson and Christopher Fraser.
Each available frontend generated a tree from the given source code. Using this abstract tree form, different languages can share the same backend. Initially, GCC used Look-Ahead LR (LALR) parsers, which were generated using Bison, but over time, it moved on to recursive-descendent parsers for C, C++, and Objective-C in 2006. Today, all available frontends use handwritten recursive-descendent parsers.
GENERIC is a more complex intermediate representation, while GIMPLE is a simplified GENERIC and targets all the frontends of GCC. Languages, such as C, C++ or Java frontends, directly produce GENERIC tree representations in the frontend. Others use different intermediate representations that are then parsed and converted to GENERIC representations.
The middle stage representation of GCC involves code analysis and optimization, and works independently in terms of a compiled language and the target architecture. It starts from the GENERIC representation and continues to the Register Transfer Language (RTL) representation. The optimization mostly involves jump threading, instruction scheduling, loop optimization, sub expression elimination, and so on. The RTL optimizations are less important than the ones done through GIMPLE representations. However, they include dead code elimination, global value numbering, partial redundancy elimination, sparse conditional constant propagation, scalar replacement of aggregates, and even automatic vectorization or automatic parallelization.
The last element that needs to be introduced here is the C library. It represents the interface between a Linux kernel and applications used on a Linux system. At the same time, it offers aid for the easier development of applications. There are a couple of C libraries available in this community:
The choice of the C library used by the GCC compiler will be executed in the toolchain generation phase, and it will be influenced not only by the size and application support offered by the libraries, but also by compliance of standards, completeness, and personal preference.
The first library that we'll discuss here is the glibc
library, which is designed for performance, compliance of standards, and portability. It was developed by the Free Software Foundation for the GNU/Linux operating system and is still present today on all GNU/Linux host systems that are actively maintained. It was released under the GNU Lesser General Public License.
The glibc
library was initially written by Roland McGrath in the 1980s and it continued to grow until the 1990s when the Linux kernel forked glibc
, calling it Linux libc
. It was maintained separately until January 1997 when the Free Software Foundation released glibc 2.0
. The glibc 2.0
contained so many features that it did not make any sense to continue the development of Linux libc
, so they discontinued their fork and returned to using glibc
. There are changes that are made in Linux libc
that were not merged into glibc
because of problems with the authorship of the code.
The glibc
library is quite large in terms of its dimensions and isn't a suitable fit for small embedded systems, but it provides the functionality required by the Single UNIX Specification (SUS), POSIX, ISO C11, ISO C99, Berkeley Unix interfaces, System V Interface Definition, and the X/Open Portability Guide, Issue 4.2, with all its extensions common with X/Open System Interface compliant systems along with X/Open UNIX extensions. In addition to this, GLIBC also provides extensions that have been deemed useful or necessary while developing GNU.
Since 2009, Debian and a number of its derivations chose to move from the GNU C Library to eglibc
. This might be because there is a difference in licensing between GNU LGPL and eglibc
, and this permits them to accept patches that glibc
developers my reject. Since 2014, the official eglibc
homepage states that the development of eglibc
was discontinued because glibc
had also moved to the same licensing, and also, the release of Debian Jessie meant that it had moved back to glibc
. This also happened in the case of Yocto support when they also decided to make glibc
their primary library support option.
The newlib
library is another C library developed with the intention of being used in embedded systems. It is a conglomerate of library components under free software licenses. Developed by Cygnus Support and maintained by Red Hat, it is one of the preferred versions of the C library used for non-Linux embedded systems.
Bionic is a derivate of the BSD C library developed by Google for Android based on the Linux kernel. Its development is independent of Android code development. It is licensed as 3-clause BSD license and its goals are publically available. These include the following:
- Small size: Bionic is smaller in size compared to
glibc
- Speed: This has designed CPUs that work at low frequencies
- BSD license: Google wished to isolate Android apps from GPL and LGPL licenses, and this is the reason it moved to a non-copyleft license which are as follows:
- Android is based on a Linux kernel which is based on a GPLv2 license
glibc
is based on LGPL, which permits the linking of dynamic proprietary libraries but not with static linking
It also has a list of restrictions compared to glibc
, as follows:
- It does not include C++ exception handling, mainly because most the code used for Android is written in Java.
- It does not have wide character support.
- It does not include a Standard Template library, although it can be included manually.
- It functions within Bionic POSIX and even system call headers are wrappers or stubs for Android -specific functions. This may lead to odd behavior sometimes.
- When Android 4.2 released, it included support for
glibc
FORTIFY_SOURCE
features. These features are very often used in Yocto, and embedded systems in general, but are only present in thegcc
version for Android devices with ARM processors.
The next C library that will be discussed is musl
. It is a C library intended for use with Linux operating systems for embedded and mobile systems. It has a MIT license and was developed with the idea of having a clean, standard-compliant libc
, which is time efficient, since it's been developed from scratch. As a C library, it is optimized for the linking of static libraries. It is compatible with C99 standard and POSIX 2008, and implements Linux, glibc
, and BSD non-standard functions.
Next, we'll discuss
uClibc
, which is a C standard library designed for Linux embedded systems and mobile devices. Although initially developed for μClinux and designed for microcontrollers, it gathered track and became the weapon of choice for anyone who's has limited space on their device. This has become popular due to the following reasons:
- It focuses on size rather than performance
- It has a GNU Lesser General Public License (LGPL) free license
- It is much smaller the glibc and reduces compilation time
- It has high configurability due to the fact that many of its features can be enabled using a
menuconfig
interface similar to the one available on packages, such as Linux kernel, U-Boot, or even BusyBox
The dietlibc
library is a standard C library that was developed by Felix von Leitner and released under the GNU GPL v2 license. Although it also contains some commercial licensed components, its design was based on the same idea as uClibc
: the possibility of compiling and linking software while having the smallest size possible. It has another resemblance to uClibc
; it was developed from scratch and has only implemented the most used and known standard functions. Its primary usage is mainly in the embedded devices market.
The last in the C libraries list is the klibc
standard C library. It was developed by H. Peter Anvin and it was developed to be used as part of the early user space during the Linux startup process. It is used by the components that run the the kernel startup process but aren't used in the kernel mode and, hence, they do not have access to the standard C library.
The development of klibc
started in 2002 as an initiative to remove the Linux initialization code outside a kernel. Its design makes it suitable for usage in embedded devices. It also has another advantage: it is optimized for small size and correctness of data. The klibc
library is loaded during the Linux startup process from initramfs (a temporary Ram filesystem) and is incorporated by default into initramfs using the mkinitramfs
script for Debian and Ubuntu-based filesystems. It also has access to a small set of utilities, such as mount
, mkdir
, dash
, mknod
, fstype
, nfsmount
, run-init
and so on, which are very useful in the early init stage.
When generating a toolchain, the first thing that needs to be done is the establishment of an ABI used to generate binaries. This means that the kernel needs to understand this ABI and, at the same time, all the binaries in the system need to be compiled with the same ABI.
When working with the GNU toolchain, a good source of gathering information and understanding the ways in which work is done with these tools is to consult the GNU coding standards. The coding standard's purposes are very simple: to make sure that the work with the GNU ecosystem is performed in a clean, easy, and consistent manner. This is a guideline that needs to be used by people interested in working with GNU tools to write reliable, solid, and portable software. The main focus of the GNU toolchain is represented by the C language, but the rules applied here are also very useful for any programming languages. The purpose of each rule is explained by making sure that the logic behind the given information is passed to the reader.
It is better to use the int
type, although you might consider defining a narrower data type. There are, of course, a number of special cases where this could be hard to use. One such example is the dev_t
system type, because it is shorter than int
on some machines and wider on others. The only way to offer support for non-standard C types involves checking the width of dev_t
using Autoconf
and, after this, choosing the argument type accordingly. However, it may not worth the trouble.
The POSIX.2 standard mentions that commands, such as du
and df
, should output sizes in units of 512 bytes. However, users want units of 1KB and this default behavior is implemented. If someone is interested in having the behavior requested by POSIX standard, they would need to set the POSIXLY_CORRECT
environment variable.
To make sure that you write robust code, a number of guidelines should be mentioned. The first one refers to the fact that limitations should not be used for any data structure, including files, file names, lines, and symbols, and especially arbitrary limitations. All data structures should be dynamically allocated. One of the reasons for this is represented by the fact that most Unix utilities silently truncate long lines; GNU utilities do not do these kind of things.
To decode arguments, the getopt_long
option can be used.
For error checks that identify impossible situations, just abort the program, since there is no need to print any messages. These type of checks bear witness to the existence of bugs. To fix these bugs, a developer will have to inspect the available source code and even start a debugger. The best approach to solve this problem would be to describe the bugs and problems using comments inside the source code. The relevant information could be found inside variables after examining them accordingly with a debugger.
This can also be done using the mkstemps
function, which is made available by Gnulib
.
After the introduction of the packages that comprise a toolchain, this section will introduce the steps needed to obtain a custom toolchain. The toolchain that will be generated will contain the same sources as the ones available inside the Poky dizzy branch. Here, I am referring to the gcc
version 4.9, binutils
version 2.24, and glibc
version 2.20. For Ubuntu systems, there are also shortcuts available. A generic toolchain can be installed using the available package manager, and there are also alternatives, such as downloading custom toolchains available inside Board Support Packages, or even from third parties, including CodeSourcery and Linaro. More information on toolchains can be found at http://elinux.org/Toolchains. The architecture that will be used as a demo is an ARM architecture.
The toolchain build process has eight steps. I will only outline the activities required for each one of them, but I must mention that they are all automatized inside the Yocto Project recipes. Inside the Yocto Project section, the toolchain is generated without notice. For interaction with the generated toolchain, the simplest task would be to call meta-ide-support, but this will be presented in the appropriate section as follows:
- The setup: This represents the step in which top-level build directories and source subdirectories are created. In this step, variables such as
TARGET
,SYSROOT
,ARCH
,COMPILER
,PATH
, and others are defined. - Geting the sources: This represents the step in which packages, such as
binutils
,gcc
,glibc
,linux kernel
headers, and various patches are made available for use in later steps. - GNU Binutils setup - This represents the steps in which the interaction with the
binutils
package is done, as shown here:- Unzip the sources available from the corresponding release
- Patch the sources accordingly, if this applies
- Configure, the package accordingly
- Compile the sources
- Install the sources in the corresponding location
- Linux kernel headers setup: This represents the steps in which the interaction with the Linux kernel sources is presented, as shown here:
- Unzip the kernel sources.
- Patch the kernel sources, if this applies.
- Configure the kernel for the selected architecture. In this step, the corresponding kernel config file is generated. More information about Linux kernel will be presented in Chapter 4, Linux Kernel.
- Compile the Linux kernel headers and copy them in the corresponding location.
- Install the headers in the corresponding locations.
- Glibc headers setup: This represents the steps used to setting the
glibc
build area and installation headers, as shown here:- Unzip the glibc archive and headers files
- Patch the sources, if this applies
- Configure the sources accordingly enabling the
-with-headers
variable to link the libraries to the corresponding Linux kernel headers - Compile the glibc headers files
- Install the headers accordingly
- GCC first stage setup: This represents the step in which the C runtime files, such as
crti.o
andcrtn.o
, are generated:- Unzip the gcc archive
- Patch the gcc sources if necessary
- Configure the sources enabling the needed features
- Compile the C runtime components
- Install the sources accordingly
- Build the glibc sources: This represents the step in which the
glibc
sources are built and the necessary ABI setup is done, as shown here:- Configure the
glibc
library by setting themabi
andmarch
variables accordingly - Compile the sources
- Install the
glibc
accordingly
- Configure the
- GCC second stage setup: This represents the final setup phase in which the toolchain configuration is finished, as shown here:
- Configure the
gcc
sources - Compile the sources
- Install the binaries in the corresponding location
- Configure the
After these steps are performed, a toolchain will be available for the developer to use. The same strategy and build procedure steps is followed inside the Yocto Project.
To decode arguments, the getopt_long
option can be used.
For error checks that identify impossible situations, just abort the program, since there is no need to print any messages. These type of checks bear witness to the existence of bugs. To fix these bugs, a developer will have to inspect the available source code and even start a debugger. The best approach to solve this problem would be to describe the bugs and problems using comments inside the source code. The relevant information could be found inside variables after examining them accordingly with a debugger.
This can also be done using the mkstemps
function, which is made available by Gnulib
.
After the introduction of the packages that comprise a toolchain, this section will introduce the steps needed to obtain a custom toolchain. The toolchain that will be generated will contain the same sources as the ones available inside the Poky dizzy branch. Here, I am referring to the gcc
version 4.9, binutils
version 2.24, and glibc
version 2.20. For Ubuntu systems, there are also shortcuts available. A generic toolchain can be installed using the available package manager, and there are also alternatives, such as downloading custom toolchains available inside Board Support Packages, or even from third parties, including CodeSourcery and Linaro. More information on toolchains can be found at http://elinux.org/Toolchains. The architecture that will be used as a demo is an ARM architecture.
The toolchain build process has eight steps. I will only outline the activities required for each one of them, but I must mention that they are all automatized inside the Yocto Project recipes. Inside the Yocto Project section, the toolchain is generated without notice. For interaction with the generated toolchain, the simplest task would be to call meta-ide-support, but this will be presented in the appropriate section as follows:
- The setup: This represents the step in which top-level build directories and source subdirectories are created. In this step, variables such as
TARGET
,SYSROOT
,ARCH
,COMPILER
,PATH
, and others are defined. - Geting the sources: This represents the step in which packages, such as
binutils
,gcc
,glibc
,linux kernel
headers, and various patches are made available for use in later steps. - GNU Binutils setup - This represents the steps in which the interaction with the
binutils
package is done, as shown here:- Unzip the sources available from the corresponding release
- Patch the sources accordingly, if this applies
- Configure, the package accordingly
- Compile the sources
- Install the sources in the corresponding location
- Linux kernel headers setup: This represents the steps in which the interaction with the Linux kernel sources is presented, as shown here:
- Unzip the kernel sources.
- Patch the kernel sources, if this applies.
- Configure the kernel for the selected architecture. In this step, the corresponding kernel config file is generated. More information about Linux kernel will be presented in Chapter 4, Linux Kernel.
- Compile the Linux kernel headers and copy them in the corresponding location.
- Install the headers in the corresponding locations.
- Glibc headers setup: This represents the steps used to setting the
glibc
build area and installation headers, as shown here:- Unzip the glibc archive and headers files
- Patch the sources, if this applies
- Configure the sources accordingly enabling the
-with-headers
variable to link the libraries to the corresponding Linux kernel headers - Compile the glibc headers files
- Install the headers accordingly
- GCC first stage setup: This represents the step in which the C runtime files, such as
crti.o
andcrtn.o
, are generated:- Unzip the gcc archive
- Patch the gcc sources if necessary
- Configure the sources enabling the needed features
- Compile the C runtime components
- Install the sources accordingly
- Build the glibc sources: This represents the step in which the
glibc
sources are built and the necessary ABI setup is done, as shown here:- Configure the
glibc
library by setting themabi
andmarch
variables accordingly - Compile the sources
- Install the
glibc
accordingly
- Configure the
- GCC second stage setup: This represents the final setup phase in which the toolchain configuration is finished, as shown here:
- Configure the
gcc
sources - Compile the sources
- Install the binaries in the corresponding location
- Configure the
After these steps are performed, a toolchain will be available for the developer to use. The same strategy and build procedure steps is followed inside the Yocto Project.
steps needed to obtain a custom toolchain. The toolchain that will be generated will contain the same sources as the ones available inside the Poky dizzy branch. Here, I am referring to the gcc
version 4.9, binutils
version 2.24, and glibc
version 2.20. For Ubuntu systems, there are also shortcuts available. A generic toolchain can be installed using the available package manager, and there are also alternatives, such as downloading custom toolchains available inside Board Support Packages, or even from third parties, including CodeSourcery and Linaro. More information on toolchains can be found at http://elinux.org/Toolchains. The architecture that will be used as a demo is an ARM architecture.
The toolchain build process has eight steps. I will only outline the activities required for each one of them, but I must mention that they are all automatized inside the Yocto Project recipes. Inside the Yocto Project section, the toolchain is generated without notice. For interaction with the generated toolchain, the simplest task would be to call meta-ide-support, but this will be presented in the appropriate section as follows:
- The setup: This represents the step in which top-level build directories and source subdirectories are created. In this step, variables such as
TARGET
,SYSROOT
,ARCH
,COMPILER
,PATH
, and others are defined. - Geting the sources: This represents the step in which packages, such as
binutils
,gcc
,glibc
,linux kernel
headers, and various patches are made available for use in later steps. - GNU Binutils setup - This represents the steps in which the interaction with the
binutils
package is done, as shown here:- Unzip the sources available from the corresponding release
- Patch the sources accordingly, if this applies
- Configure, the package accordingly
- Compile the sources
- Install the sources in the corresponding location
- Linux kernel headers setup: This represents the steps in which the interaction with the Linux kernel sources is presented, as shown here:
- Unzip the kernel sources.
- Patch the kernel sources, if this applies.
- Configure the kernel for the selected architecture. In this step, the corresponding kernel config file is generated. More information about Linux kernel will be presented in Chapter 4, Linux Kernel.
- Compile the Linux kernel headers and copy them in the corresponding location.
- Install the headers in the corresponding locations.
- Glibc headers setup: This represents the steps used to setting the
glibc
build area and installation headers, as shown here:- Unzip the glibc archive and headers files
- Patch the sources, if this applies
- Configure the sources accordingly enabling the
-with-headers
variable to link the libraries to the corresponding Linux kernel headers - Compile the glibc headers files
- Install the headers accordingly
- GCC first stage setup: This represents the step in which the C runtime files, such as
crti.o
andcrtn.o
, are generated:- Unzip the gcc archive
- Patch the gcc sources if necessary
- Configure the sources enabling the needed features
- Compile the C runtime components
- Install the sources accordingly
- Build the glibc sources: This represents the step in which the
glibc
sources are built and the necessary ABI setup is done, as shown here:- Configure the
glibc
library by setting themabi
andmarch
variables accordingly - Compile the sources
- Install the
glibc
accordingly
- Configure the
- GCC second stage setup: This represents the final setup phase in which the toolchain configuration is finished, as shown here:
- Configure the
gcc
sources - Compile the sources
- Install the binaries in the corresponding location
- Configure the
After these steps are performed, a toolchain will be available for the developer to use. The same strategy and build procedure steps is followed inside the Yocto Project.
As I have mentioned, the major advantage and available feature of the Yocto Project environment is represented by the fact that a Yocto Project build does not use the host available packages, but builds and uses its own packages. This is done to make sure that a change in the host environment does not influence its available packages and that builds are made to generate a custom Linux system. A toolchain is one of the components because almost all packages that are constituents of a Linux distribution need the usage of toolchain components.
The GNU CC and GCC C compiler package, which consists of all the preceding packages, is split into multiple fractions, each one with its purpose. This is mainly because each one has its purpose and is used with different scopes, such as sdk
components. However, as I mentioned in the introduction of this chapter, there are multiple toolchain build procedures that need to be assured and automated with the same source code. The available support inside Yocto is for gcc 4.8 and 4.9 versions. A quick look at the gcc
available recipes shows the available information:
The GNU Binutils package represents the binary tools collection, such as GNU Linker, GNU Assembler, addr2line
, ar
, nm
, objcopy
, objdump
, and other tools and related libraries. The Yocto Project offers support for the Binutils version 2.24, and is also dependent on the available toolchain build procedures, as it can be viewed from the inspection of the source code:
The uClibc is used as an alternative to glibc
C library because it generates smaller executable footprints. At the same time, uClibc
is the only package from the ones presented in the preceding list that has a bbappend
applied to it, since it extends the support for two machines, genericx86-64
and genericx86
. The change between glibc
and uClibc
can be done by changing the TCLIBC
variable to the corresponding variable in this way: TCLIBC = "uclibc"
.
Set the MACHINE
variable to the value qemuarm
accordingly inside the conf/local.conf
file:
The default C library used for the generation of the toolchain is glibc
, but it can be changed according to the developer's need. As seen from the presentation in the previous section, the toolchain generation process inside the Yocto Project is very simple and straightforward. It also avoids all the trouble and problems involved in the manual toolchain generation process, making it very easy to reconfigure also.
In this chapter, you will be presented with one of the most important components necessary for using a Linux system in an embedded environment. Here, I am referring to the bootloader, a piece of software that offers the possibility of initializing a platform and making it ready to boot a Linux operating system. In this chapter, the benefits and roles of bootloaders will be presented. This chapter mainly focuses on the U-Boot bootloaders, but readers are encouraged to have a look at others, such as Barebox, RedBoot, and so on. All these bootloaders have their respective features and there isn't one in particular that suits every need; therefore, experimentation and curiosity are welcome when this chapter. You have already been introduced to the the Yocto Project reference in the last chapter; hence, you will now be able to understand how this development environment works with various bootloaders, and especially the ones available inside a Board Support Package (BSP).
The bootloader represents the piece of software that is first executed during system initialization. It is used to load, decompress, and execute one or more binary applications, such as a Linux kernel or a root filesystem. Its role involves adding the system in a state where it can execute its primary functions. This is done after loading and starting the correct binary applications that it receives or has already saved on the internal memory. Upon initializing, the hardware bootloader may need to initialize the phase-locked loop (PLL), set the clocks, or enable access to the RAM memory and other peripherals. However, these initializations are done on a basic level; the rest are done by kernels drivers and other applications.
The first time that electricity runs into a development board processor, a great number of hardware components need to be prepared before running a program. For each architecture, hardware manufacturer, and even processor, this initialization process is different. In most cases, it involves a set of configurations and actions are different for a variety of processors and ends up fetching the bootstrap code from a storage device available in the proximity of the processor. This storage device is usually a flash memory and the bootstrap code is the first stage of the bootloader, and the one that initializes the processor and relevant hardware peripherals.
A bootloader does not have the infrastructure that a normal application has. It does not have the possibility to only be called by its name and start executing. After being switched on when it gains control, it creates its own context by initializing the processor and necessary hardware, such as DRAM, moves itself in the DRAM for faster execution, if necessary and finally, starts the actual execution of code.
For the Atmel SAMA5D3-Xplained board and others similar to it, the booting starts from an integrated boot code available in the ROM memory called BootROM on AT91 CPUs, which loads the first stage bootloader called AT91Bootstrap on SRAM and starts it. The first stage bootloader initializes the DRAM memory and starts the second stage bootloader, which is U-Boot in this case. More information on boot sequence possibilities can be found in the boot sequence header available, which you'll read about shortly.
- U-Boot: This is also called the Universal Bootloader and is available mostly for PowerPC and ARM architectures for embedded Linux systems
- Barebox: This was initially known as U-Boot v2 and was started in 2007 with the scope to solve the limitations of U-Boot; it changed its name over time because the design goals and community changed
- RedBoot: This is a RedHat bootloader derived from eCos, an open-source real-time operating system that is portable and devised for embedded systems
- rrload: This is a bootloader for ARM and is based on embedded Linux systems
- PPCBOOT: This is a bootloader for PowerPC and is based on embedded Linux systems
- CLR/OHH: This represents a flash bootloader for embedded Linux systems based on an ARM architecture
- Alios: This is a bootloader that is written mostly in assembler, does ROM and RAM initializations, and tries to completely remove the need for firmware on embedded systems
As mentioned previously, the bootloader is the component that is first run after initializing the system, and prepares the entire ecosystem for the operating system boot process. This process differs from one architecture to the other. For example, for the x86 architecture, the processor has access to BIOS, a piece of software available in a nonvolatile memory, which is usually a ROM. Its role starts out after resetting the system when it is executed and initializes the hardware components that will later be used by the first stage bootloader. It also executes the first stage of the bootloader.
For x86 processors, there are more bootloader solutions that are available:
- GRUB: The Grand Unified Bootloader is the most used and powerful bootloader available for Linux systems from desktop PC platforms. It is a component of the GNU Project and is one of the most potent bootloaders available for x86 architecture systems. This is because it is able to understand a large variety of filesystems and kernel images formats. It is able to change the the boot configuration during boot time. GRUB also has support for a network boot and command-line interface. It has a configuration file that is processed at boot time and can be modified. More information about it can be found at http://www.gnu.org/software/grub/.
- Lilo: The Linux Loader a bootloader mostly used in commercial Linux distributions. Similar to the previous point, it is available for desktop PC platforms. It has more than one component, the first component for historical reasons is available on the first sector of a disk drive; it is the bootstrap component. Due to the same historical reasons, it is limited to the 512 bytes dimension and it loads and offers control to the second stage bootloader that does most of the bootloader's work. Lilo has a configuration utility that is mainly used as a source of information for the Linux kernel booting process. More information about it can be found at http://www.tldp.org/HOWTO/LILO.html.
- Syslinux: It is used for removable media or network booting. Syslinux is a Linux operating system bootloader that runs on MS-DOS or Windows FAT filesystems and is mainly used for rescue and first time installations of Linux. More information on it can be found at http://www.kernel.org/pub/linux/utils/boot/syslinux/.
A NOR memory is preferred over the NAND one because it allows random address access. It is the place where the first stage bootloader is programmed to start the execution, and this doesn't make it the most practical mechanism of booting.
There are many open source bootloaders available today. Almost all of them have features to load and execute a program, which usually involves the operating system, and its features are used for serial interface communication. However, not all of them have the possibility to communicate over Ethernet or update themselves. Another important factor is represented by the widespread use of the bootloader. It is very common for organizations and companies to choose only one bootloader for the diversity of boards, processors, and architectures that they support. A similar thing happened with the Yocto Project when a bootloader was chosen to represent the official supported bootloader. They, and other similar companies, chose U-Boot bootloader, which is quite well known in the Linux community.
The U-Boot bootloader, or Das U-Boot as its official name, is developed and maintained by Wolfgang Denx with the support of the community behind it. It is licensed under GPLv2, its source code is freely available inside a git
repository, as shown in the first chapter, and it has a two month intervals between releases. The release version name is shown as U-boot vYYYY.MM
. The information about U-Boot loader is available at http://www.denx.de/wiki/U-Boot/ReleaseCycle.
The U-Boot source code has a very well defined directory structure. This can be easily seen with this console command:
tree -d -L 1 . ├── api ├── arch ├── board ├── common ├── configs ├── disk ├── doc ├── drivers ├── dts ├── examples ├── fs ├── include ├── lib ├── Licenses ├── net ├── post ├── scripts ├── test └── tools 19 directories
The arch
directory contains architecture-specific files and directories-specific to each architecture, CPU or development board. An api
contains external applications that are independent of a machine or architecture type. A board
contains inside boards with specific names of directories for all board-specific files. A common is a place where misc
functions are located. A disk
contains disk drive handling functions, and documentation is available inside the doc
directory. Drivers are available in the
drivers
directory. The filesystem-specific functionality is available inside the fs
directory. There are still some directories that would need mentioning here, such as the include
directory, which contains the header files; the lib
directory contains generic libraries with support for various utilities, such as the flatten device tree, various decompressions, a post
(Power On Self-Test) and others, but I will let them be discovered by the reader's curiosity, one small hint would be to inspect the README
file in the Directory Hierachy
section.
Moving through the U-Boot sources, which were downloaded in the previous chapter inside the ./include/configs
file, configuration files can be found for each supported board. These configuration file is an .h
file that contains a number of CONFIG_
files and defines information on memory mapping, peripherals and their setup, command line output, such as the boot default addresses used for booting a Linux system, and so on. More information on the configuration files could be found inside the README
file in the Configuration Options, section or in a board specific configuration file. For Atmel SAMA5D3-Xplained, the configuration file is include/configs/sama5d3_xplained.h
. Also, there are two configurations available for this board in the configs
directory, which are as follows:
These configurations are used to define the board Secondary Program Loader (SPL) initialization method. SPL represents a small binary built from the U-Boot source code that is placed on the SRAM memory and is used to load the U-Boot into the RAM memory. Usually, it has less than 4 KB of memory, and this is how the booting sequence looks:
Before actually starting the build for the U-Boot source code for a specific board, the board configuration must be specified. For the Atmel SAMA5_Xplained development board, as presented in the preceding image, there are two available configurations that could be done. The configuration is done with the make ARCH=arm CROSS_COMPILE=${CC} sama5d3_xplained_nandflash_defconfig
command. Behind this command, the include/config.h
file is created. This header include definitions that are specific for the chosen board, architecture, CPU, and also board-specific header includes. The defined CONFIG_*
variable read from the include/config.h
file includes determining the compilation process. After the configuration is completed, the build can be started for the U-Boot.
Another example that can be very useful when inspected relates to the other scenario of booting an embedded system, one that requires the use of a NOR memory. In this situation, we can take a look at a particular example. This is also well described inside the Embedded Linux Primer by Christopher Hallinan, where a processor of the AMCC PowerPC 405GP is discussed. The hardcoded address for this processor is 0xFFFFFFFC and is visible using .resetvec
, the reset vector placement. There also specifies the fact that the rest of this section is completed with only the value 1
until the end of the 0xFFFFFFFF stack; this implies that an empty flash memory array is completed only with values of 1
. The information about this section is available in resetvec.S
file, which is located at arch/powerpc/cpu/ppc4xx/resetvec.S
. The contents of resetvec.S
file is as follows:
The configuration for the U-Boot is done through two types of configuration variables. The first one is CONFIG_*
, and it makes references to configuration options that can be configured by a user to enable various operational features. The other option is called CFG_*
and this is used for configuration settings and to make references to hardware-specific details. The CFG_*
variable usually requires good knowledge of a hardware platform, peripherals and processors in general. The configure file for the SAMA5D3 Xplained hardware platform is available inside the include/config.h
header file, as follows:
The configuration variables available here represent the corresponding configurations for the SAMA5D3 Xplained board. A part of these configurations refer to a number of standard commands available for user interactions with the bootloader. These commands can be added or removed for the purpose of extending or subtracting commands from the available command line interface.
More information on the U-Boot configurable command interface can be found at http://www.denx.de/wiki/view/DULG/UBootCommandLineInterface.
In an industrial environment, interaction with the U-Boot is mainly done through the Ethernet interface. Not only does an Ethernet interface enable the faster transfer of operating system images, but it is also less prone to errors than a serial connection.
One of the most important features available inside a bootloader is related to the support for Dynamic Host Control Protocol (DHCP), Trivial File Transfer Protocol (TFTP), and even Bootstrap Protocol (BOOTP). BOOTP and DHCP enable an Ethernet connection to configure itself and acquire an IP address from a specialized server. TFTP enables the download of files through a TFTP server. The messages passed between a target device and the DHCP/BOOTP servers are represented in the following image in a more generic manner. Initially, the hardware platform sends a broadcast message that arrives at all the DHCP/BOOTP servers available. Each server sends back its offer, which also contains an IP address, and the client accepts the one that suits its purposes the best and declines the other ones.
After the target device has finished communication with DHCP/BOOTP, it remains with a configuration that is specific to the target and contains information, such as the hostname, target IP and hardware Ethernet address (MAC address), netmask, tftp server IP address and even a TFTP filename. This information is bound to the Ethernet port and is used later in the booting process.
I've mentioned previously that U-Boot is one of the most used and known bootloaders available. This is also due to the fact that its architecture enables the porting of new development platforms and processors in a very easy manner. At the same time, there are a huge number of development platforms available that could be used as references. The first thing that any developer who is interested in porting a new platform should do is to inspect the board
and arch
directories to establish their baselines, and, at the same time, also identify their similarities with other CPUs and available boards.
The board.cfg
file is the starting point to register a new platform. Here, the following information should be added as a table line:
To port a machine similar to SAMA5D3 Xplained, one of the directories that could be consulted is the
arch
directory. It contains files, such as board.c
, with information related to the initialization process for boards and SOCs. The most notable processes are board_init_r()
, which does the setup and probing for board and peripherals after its relocation in the RAM, board_init_f()
, which identifies the stack size and reserved address before its relocation in the RAM, and init_sequence[]
, which is called inside the board_init_f
for the setup of peripherals. Other important files inside the same locations are the bootm.c
and interrupts.c
files. The former has the main responsibility of the boot from memory of the operating system, and the latter is responsible for implementation of generic interrupts.
The board
directory also has some interesting files and functions that need to be mentioned here, such as the board/atmel/sama5d3_xplained/sama5d3_xplained.c
file. It contains functions, such as board_init(), dram_init()
, board_eth_init()
, board_mmc_init
, spl_board_ init()
, and mem_init()
that are used for initialization, and some of them called by the arch/arm/lib/board.c
file.
Here are some other relevant directories:
common
: This holds information about user commands, middleware, APIs that perform the interfacing between the middleware and user commands, and other functions and functionalities used by all available boards.drivers
: This contains drivers for various device drivers and middleware APIs, such asdrivers/mmc/mmc.c, drivers/pci/pci.c
,drivers/watchdog/at91sam9_wdt.c
and so on.fs
: Various supported filesystems, such as USB, SD Card, Ext2 FAT, and so on are available here.include
: This represents the location where all the headers necessary for most of the boards are present. SOCs and other software is also available. Inside include/configs, board-specific configurations are available, and include the headers imported from Linux; these could be used for various device drivers, porting, or other byte operations.tools
: This is the place where tools, such ascheckpatch.pl
, a patch examination tool used as a coding style check, are used before sending it to the mailing list or themkimage.c
tool. This is also used for the U-Boot generic header generation that makes Linux binaries, and assures that they are able to be booted using U-Boot.
The first thing that a developer should do is to track the upstream branch that corresponds to a local branch. Another piece of advice would be to forget about git
merge
and instead use git
rebase
. Keeping in contact with the upstream repository can be done using the git fetch
command. To work with patches, some general rules need to be followed, and patches need to have only one logical change, which can be any one of these:
- Changes should not contain unrelated or different modifications; only one patch is available and acceptable per changeset
- Commits should make the use of
git-bisect
where possible while detecting bugs in sources, when necessary - If multiple files are impacted by a set of modifications, all of them should be submitted in the same patch
- Patches need to have review, and a very thorough one at that
Let's take a look at following diagram, which illustrates the git rebase operation:
The git merge
operation, on the other hand, is a new commit that has two parents: the branch from which it was ported, and the new branch on which it was merged. In fact, it gathers a series of commits into one branch with a different commit ID, which is why they are difficult to manage.
More information related to git
interactions can be found at http://git-scm.com/documentation or http://www.denx.de/wiki/U-Boot/Patches.
Almost always when porting a new feature in U-Boot, debugging is involved. For a U-Boot debugger, there are two different situations that can occur:
- The first situation is when
lowlevel_init
was not executed - The second situation is when the
lowlevel_init
was executed; this is the most well known scenario
In the next few lines, the second situation will be considered: the baseline enabling a debugging session for U-Boot. To make sure that debugging is possible, the elf
file needs to be executed. Also, it cannot be manipulated directly because the linking address will be relocated. For this, a few tricks should be used:
- The first step is to make sure that the environment is clean and that old objects are not available any more:
make clean
- The next step would be to make sure the dependencies are cleaned:
find ./ | grep depend | xargs rm
- After the cleaning is finished, the target build can start and the output can be redirected inside a log file:
make sama5d3_xplained 2>&1 > make.log
- The generated output should be renamed to avoid debugging problems for multiple boards:
mv u-boot.bin u-boot_sama5d3_xplained.bin
- It is important to enable DEBUG in the board configuration file; inside
include/configs/ sama5d3_xplained.h
, add the#define
DEBUG line
An early development platform can be set up after relocation takes place and the proper breakpoint should be set after the relocation has ended. A symbol needs to be reloaded for U-Boot because the relocation will move the linking address. For all of these tasks, a gdb
script is indicated as gdb gdb-script.sh
:
One of the most important features available inside a bootloader is related to the support for Dynamic Host Control Protocol (DHCP), Trivial File Transfer Protocol (TFTP), and even Bootstrap Protocol (BOOTP). BOOTP and DHCP enable an Ethernet connection to configure itself and acquire an IP address from a specialized server. TFTP enables the download of files through a TFTP server. The messages passed between a target device and the DHCP/BOOTP servers are represented in the following image in a more generic manner. Initially, the hardware platform sends a broadcast message that arrives at all the DHCP/BOOTP servers available. Each server sends back its offer, which also contains an IP address, and the client accepts the one that suits its purposes the best and declines the other ones.
After the target device has finished communication with DHCP/BOOTP, it remains with a configuration that is specific to the target and contains information, such as the hostname, target IP and hardware Ethernet address (MAC address), netmask, tftp server IP address and even a TFTP filename. This information is bound to the Ethernet port and is used later in the booting process.
I've mentioned previously that U-Boot is one of the most used and known bootloaders available. This is also due to the fact that its architecture enables the porting of new development platforms and processors in a very easy manner. At the same time, there are a huge number of development platforms available that could be used as references. The first thing that any developer who is interested in porting a new platform should do is to inspect the board
and arch
directories to establish their baselines, and, at the same time, also identify their similarities with other CPUs and available boards.
The board.cfg
file is the starting point to register a new platform. Here, the following information should be added as a table line:
To port a machine similar to SAMA5D3 Xplained, one of the directories that could be consulted is the
arch
directory. It contains files, such as board.c
, with information related to the initialization process for boards and SOCs. The most notable processes are board_init_r()
, which does the setup and probing for board and peripherals after its relocation in the RAM, board_init_f()
, which identifies the stack size and reserved address before its relocation in the RAM, and init_sequence[]
, which is called inside the board_init_f
for the setup of peripherals. Other important files inside the same locations are the bootm.c
and interrupts.c
files. The former has the main responsibility of the boot from memory of the operating system, and the latter is responsible for implementation of generic interrupts.
The board
directory also has some interesting files and functions that need to be mentioned here, such as the board/atmel/sama5d3_xplained/sama5d3_xplained.c
file. It contains functions, such as board_init(), dram_init()
, board_eth_init()
, board_mmc_init
, spl_board_ init()
, and mem_init()
that are used for initialization, and some of them called by the arch/arm/lib/board.c
file.
Here are some other relevant directories:
common
: This holds information about user commands, middleware, APIs that perform the interfacing between the middleware and user commands, and other functions and functionalities used by all available boards.drivers
: This contains drivers for various device drivers and middleware APIs, such asdrivers/mmc/mmc.c, drivers/pci/pci.c
,drivers/watchdog/at91sam9_wdt.c
and so on.fs
: Various supported filesystems, such as USB, SD Card, Ext2 FAT, and so on are available here.include
: This represents the location where all the headers necessary for most of the boards are present. SOCs and other software is also available. Inside include/configs, board-specific configurations are available, and include the headers imported from Linux; these could be used for various device drivers, porting, or other byte operations.tools
: This is the place where tools, such ascheckpatch.pl
, a patch examination tool used as a coding style check, are used before sending it to the mailing list or themkimage.c
tool. This is also used for the U-Boot generic header generation that makes Linux binaries, and assures that they are able to be booted using U-Boot.
The first thing that a developer should do is to track the upstream branch that corresponds to a local branch. Another piece of advice would be to forget about git
merge
and instead use git
rebase
. Keeping in contact with the upstream repository can be done using the git fetch
command. To work with patches, some general rules need to be followed, and patches need to have only one logical change, which can be any one of these:
- Changes should not contain unrelated or different modifications; only one patch is available and acceptable per changeset
- Commits should make the use of
git-bisect
where possible while detecting bugs in sources, when necessary - If multiple files are impacted by a set of modifications, all of them should be submitted in the same patch
- Patches need to have review, and a very thorough one at that
Let's take a look at following diagram, which illustrates the git rebase operation:
The git merge
operation, on the other hand, is a new commit that has two parents: the branch from which it was ported, and the new branch on which it was merged. In fact, it gathers a series of commits into one branch with a different commit ID, which is why they are difficult to manage.
More information related to git
interactions can be found at http://git-scm.com/documentation or http://www.denx.de/wiki/U-Boot/Patches.
Almost always when porting a new feature in U-Boot, debugging is involved. For a U-Boot debugger, there are two different situations that can occur:
- The first situation is when
lowlevel_init
was not executed - The second situation is when the
lowlevel_init
was executed; this is the most well known scenario
In the next few lines, the second situation will be considered: the baseline enabling a debugging session for U-Boot. To make sure that debugging is possible, the elf
file needs to be executed. Also, it cannot be manipulated directly because the linking address will be relocated. For this, a few tricks should be used:
- The first step is to make sure that the environment is clean and that old objects are not available any more:
make clean
- The next step would be to make sure the dependencies are cleaned:
find ./ | grep depend | xargs rm
- After the cleaning is finished, the target build can start and the output can be redirected inside a log file:
make sama5d3_xplained 2>&1 > make.log
- The generated output should be renamed to avoid debugging problems for multiple boards:
mv u-boot.bin u-boot_sama5d3_xplained.bin
- It is important to enable DEBUG in the board configuration file; inside
include/configs/ sama5d3_xplained.h
, add the#define
DEBUG line
An early development platform can be set up after relocation takes place and the proper breakpoint should be set after the relocation has ended. A symbol needs to be reloaded for U-Boot because the relocation will move the linking address. For all of these tasks, a gdb
script is indicated as gdb gdb-script.sh
:
The board.cfg
file is the starting point to register a new platform. Here, the following information should be added as a table line:
To port a machine similar to SAMA5D3 Xplained, one of the directories that could be consulted is the
arch
directory. It contains files, such as board.c
, with information related to the initialization process for boards and SOCs. The most notable processes are board_init_r()
, which does the setup and probing for board and peripherals after its relocation in the RAM, board_init_f()
, which identifies the stack size and reserved address before its relocation in the RAM, and init_sequence[]
, which is called inside the board_init_f
for the setup of peripherals. Other important files inside the same locations are the bootm.c
and interrupts.c
files. The former has the main responsibility of the boot from memory of the operating system, and the latter is responsible for implementation of generic interrupts.
The board
directory also has some interesting files and functions that need to be mentioned here, such as the board/atmel/sama5d3_xplained/sama5d3_xplained.c
file. It contains functions, such as board_init(), dram_init()
, board_eth_init()
, board_mmc_init
, spl_board_ init()
, and mem_init()
that are used for initialization, and some of them called by the arch/arm/lib/board.c
file.
Here are some other relevant directories:
common
: This holds information about user commands, middleware, APIs that perform the interfacing between the middleware and user commands, and other functions and functionalities used by all available boards.drivers
: This contains drivers for various device drivers and middleware APIs, such asdrivers/mmc/mmc.c, drivers/pci/pci.c
,drivers/watchdog/at91sam9_wdt.c
and so on.fs
: Various supported filesystems, such as USB, SD Card, Ext2 FAT, and so on are available here.include
: This represents the location where all the headers necessary for most of the boards are present. SOCs and other software is also available. Inside include/configs, board-specific configurations are available, and include the headers imported from Linux; these could be used for various device drivers, porting, or other byte operations.tools
: This is the place where tools, such ascheckpatch.pl
, a patch examination tool used as a coding style check, are used before sending it to the mailing list or themkimage.c
tool. This is also used for the U-Boot generic header generation that makes Linux binaries, and assures that they are able to be booted using U-Boot.
The first thing that a developer should do is to track the upstream branch that corresponds to a local branch. Another piece of advice would be to forget about git
merge
and instead use git
rebase
. Keeping in contact with the upstream repository can be done using the git fetch
command. To work with patches, some general rules need to be followed, and patches need to have only one logical change, which can be any one of these:
- Changes should not contain unrelated or different modifications; only one patch is available and acceptable per changeset
- Commits should make the use of
git-bisect
where possible while detecting bugs in sources, when necessary - If multiple files are impacted by a set of modifications, all of them should be submitted in the same patch
- Patches need to have review, and a very thorough one at that
Let's take a look at following diagram, which illustrates the git rebase operation:
The git merge
operation, on the other hand, is a new commit that has two parents: the branch from which it was ported, and the new branch on which it was merged. In fact, it gathers a series of commits into one branch with a different commit ID, which is why they are difficult to manage.
More information related to git
interactions can be found at http://git-scm.com/documentation or http://www.denx.de/wiki/U-Boot/Patches.
Almost always when porting a new feature in U-Boot, debugging is involved. For a U-Boot debugger, there are two different situations that can occur:
- The first situation is when
lowlevel_init
was not executed - The second situation is when the
lowlevel_init
was executed; this is the most well known scenario
In the next few lines, the second situation will be considered: the baseline enabling a debugging session for U-Boot. To make sure that debugging is possible, the elf
file needs to be executed. Also, it cannot be manipulated directly because the linking address will be relocated. For this, a few tricks should be used:
- The first step is to make sure that the environment is clean and that old objects are not available any more:
make clean
- The next step would be to make sure the dependencies are cleaned:
find ./ | grep depend | xargs rm
- After the cleaning is finished, the target build can start and the output can be redirected inside a log file:
make sama5d3_xplained 2>&1 > make.log
- The generated output should be renamed to avoid debugging problems for multiple boards:
mv u-boot.bin u-boot_sama5d3_xplained.bin
- It is important to enable DEBUG in the board configuration file; inside
include/configs/ sama5d3_xplained.h
, add the#define
DEBUG line
An early development platform can be set up after relocation takes place and the proper breakpoint should be set after the relocation has ended. A symbol needs to be reloaded for U-Boot because the relocation will move the linking address. For all of these tasks, a gdb
script is indicated as gdb gdb-script.sh
:
The Yocto Project uses various recipes to define interactions to each of the supported bootloaders. Since there are multiple stages of booting, there are also multiple recipes and packages required inside the BSP. The recipes available for various bootloaders are not different from any other recipes available in the Yocto world. However, they have some details that make them unique.
This is one example of the configurations available for the sama5d3_xplained
development board:
In this chapter, you will not only learn about the Linux kernel in general, but also specific things about it. The chapter will start with a quick presentation of the history of Linux and its role and will then continue with an explanation of its various features. The steps used to interact with the sources of the Linux kernel will not be omitted. You will only be presented with the steps necessary to obtain a Linux kernel image from a source code, but also information about what porting for an new ARM machine implies, and some of the methods used to debug various problems that could appear when working with the Linux kernel sources in general. In the end, the context will be switched to the Yocto Project to show how the Linux kernel can be built for a given machine, and also how an external module can be integrated and used later from a root filesystem image.
This chapter will give you an idea of the Linux kernel and Linux operating system. This presentation would not have been possible without the historical component. Linux and UNIX are usually placed in the same historical context, but although the Linux kernel appeared in 1991 and the Linux operating system quickly became an alternative to the UNIX operating system, these two operating systems are members of the same family. Taking this into consideration, the history of UNIX operating system could not have started from another place. This means that we need to go back in time to more than 40 years ago, to be more precise, about 45 years ago to 1969 when Dennis Ritchie and Ken Thompson started the development of UNIX.
The predecessor of UNIX was Multiplexed Information and Computing Service (Multics), a multiuser operating system project that was not on its best shape at the time. Since the Multics had become a nonviable solution for Bell Laboratories Computer Sciences Research Center in the summer of 1969, a filesystem design was born and it later became what is known today as UNIX. Over time, it was ported on multiple machines due to its design and the fact that the source code was distributed alongside it. The most prolific contributor to the UNIX was the University of California, Berkeley. They also developed their own UNIX version called Berkeley Software Distribution (BSD), that was first released in 1977. Until the 1990s, multiple companies developed and offered their own distributions of UNIX, their main inspirations being Berkeley or AT&T. All of them helped UNIX become a stable, robust, and powerful operating system. Among the features that made UNIX strong as an operating system, the following can be mentioned:
- UNIX is simple. The number of system calls that it uses are reduced to only a couple of hundred and their design is basic
- Everything is regarded as a file in UNIX, making the manipulation of data and devices simpler, and it minimizes system calls used for interaction.
- Faster process creation time and the
fork()
system call. - The UNIX kernel and utilities written in C language as well as a property that makes it easily portable and accessible.
- Simple and robust interprocess communication (IPC) primitives helps in the creation of fast and simple programs that accomplish only one thing in the best available manner.
Linux is as an alternative solution to a UNIX variant called Minix, an operating system that was created for teaching purposes, but it lacked easy interaction with the system source code. Any changes made to the source code were not easily integrated and distributed because of Minix's license. Linus Torvalds first started working at a terminal emulator to connect to other UNIX systems from his university. Within the same academic year, emulator evolved in a full-fledged UNIX. He released it to be used by everyone in 1991.
One of the most attractive features of Linux is that it is an open source operating system whose source code is available under the GNU GPL license. When writing the Linux kernel, Linus Torvalds used the best design choices and features from the UNIX available in variations of the operating system kernel as a source of inspiration. Its license is what has propelled it into becoming the powerhouse it is today. It has engaged a large number of developers that helped with code enhancements, bug fixing, and much more.
Linux has become a truly collaborative project developed by a huge community over the internet. Although a great number of changes were made inside this project, Linus has remained its creator and maintainer. Change is a constant factor in everything around us and this applies to Linux and its maintainer, who is now called Greg Kroah-Hartman, and has already been its kernel maintainer for two years now. It may seem that in the period that Linus was around, the Linux kernel was a loose-knit community of developers. This may be because of Linus' harsh comments that are known worldwide. Since Greg has been appointed the kernel maintainer, this image started fading gradually. I am looking forward to the years to come.
With an impressive numbers of code lines, the Linux kernel is one of the most prominent open source projects and at the same time, the largest available one. The Linux kernel constitutes a piece of software that helps with the interfacing of hardware, being the lowest-level code available that runs in everyone's Linux operating system. It is used as an interface for other user space applications, as described in the following diagram:
The main roles of the Linux kernel are as follows:
- It provides a set of portable hardware and architecture APIs that offer user space applications the possibility to use necessary hardware resources
- It helps with the management of hardware resources, such as a CPU, input/output peripherals, and memory
- It is used for the management of concurrent accesses and the usage of necessary hardware resources by different applications.
To make sure that the preceding roles are well understood, an example will be very useful. Let's consider that in a given Linux operating system, a number of applications need access to the same resource, a network interface, or a device. For these elements, the kernel needs to multiplex a resource in order to make sure that all applications have access to it.
This section will introduce a number of features available inside the Linux kernel. It will also cover information about each of them, how they are used, what they represent, and any other relevant information regarding each specific functionality. The presentation of each feature familiarizes you with the main role of some of the features available inside the Linux kernel, as well as the Linux kernel and its source code in general.
On a more general note, some of the most valuable features that the Linux kernel has are as follows:
The preceding features does not constitute actual functionalities, but have helped the project along its development process and are still helping it today. Having said this, there are a lot of features that are implemented, such as fast user space mutex (futex), netfileters, Simplified Mandatory Access Control Kernel (smack), and so on. A complete list of these can be accessed and studied at http://en.wikipedia.org/wiki/Category:Linux_kernel_features.
When discussing the memory in Linux, we can refer to it as the physical and virtual memory. Compartments of the RAM memory are used for the containment of the Linux kernel variables and data structures, the rest of the memory being used for dynamic allocations, as described here:
_count
: This represents the page counter. When it reaches the0
value, the page is added to the free pages list.virtual
: This represents the virtual address associated to a physical page. The ZONE_DMA and ZONE_NORMAL pages are always mapped, while the ZONE_HIGHMEN are not always mapped.flags
: This represents a set of flags that describe the attributes of the page.
The zones of the physical memory have been previously. The physical memory is split up into multiple nodes that have a common physical address space and a fast local memory access. The smallest of them is ZONE_DMA between 0 to 16Mb. The next is ZONE_NORMAL, which is the LowMem area between 16Mb to 896Mb, and the largest one is ZONE_HIGHMEM, which is between 900Mb to 4GB/64Gb. This information can be visible both in the preceding and following images:
The virtual memory is used both in the user space and the kernel space. The allocation for a memory zone implies the allocation of a physical page as well as the allocation of an address space area; this is done both in the page table and in the internal structures available inside the operating system. The usage of the page table differs from one architecture type to another. For the Complex instruction set computing (CISC) architecture, the page table is used by the processor, but on a Reduced instruction set computing (RISC) architecture, the page table is used by the core for a page lookup and translation lookaside buffer (TLB) add operations. Each zone descriptor is used for zone mapping. It specifies whether the zone is mapped for usage by a file if the zone is read-only, copy-on-write, and so on. The address space descriptor is used by the operating system to maintain high-level information.
The methods used by the kernel for memory handling is the first subject that will be discussed here. This is done to make sure that you understand the methods used by the kernel to obtain memory. Although the smallest addressable unit of a processor is a byte, the Memory Management Unit (MMU), the unit responsible for virtual to physical translation the smallest addressable unit is the page. A page's size varies from one architecture to another. It is responsible for maintaining the system's page tables. Most of 32-bit architectures use 4KB pages, whereas the 64-bit ones usually have 8KB pages. For the Atmel SAMA5D3-Xplained board, the definition of the struct page
structure is as follows:
This is one of the most important fields of the page structure. The flags
field, for example, represents the status of the page; this holds information, such as whether the page is dirty or not, locked, or in another valid state. The values that are associated with this flag are defined inside the include/linux/page-flags-layout.h
header file. The virtual
field represents the virtual address associated with the page, count
represents the count value for the page that is usually accessible indirectly through the page_count()
function. All the other fields can be accessed inside the include/linux/mm_types.h
header file.
There are allocations that require interaction with more than one zone. One such example is a normal allocation that is able to use either ZONE_DMA
or ZONE_NORMAL
. ZONE_NORMAL
is preferred because it does not interfere with direct memory accesses, though when the memory is at full usage, the kernel might use other available zones besides the ones that it uses in normal scenarios. The kernel that is available is a struct zone structure that defines each zone's relevant information. For the Atmel SAMA5D3-Xplained board, this structure is as shown here:
As you can see, the zone that defines the structure is an impressive one. Some of the most interesting fields are represented by the watermark
variable, which contain the high, medium, and low watermarks for the defined zone. The present_pages
attribute represents the available pages within the zone. The name
field represents the name of the zone, and others, such as the lock
field, a spin lock that shields the zone structure for simultaneous access. All the other fields that can be identified inside the corresponding include/linux/mmzone.h
header file for the Atmel SAMA5D3 Xplained board.
This function is used to get the logical address for a corresponding memory page:
The preceding function does what the name suggests. It returns the page full of zero
values. The difference between this function and the __get_free_page()
function is that after being released, the page is filled with zero
values:
The preceding functions are used for freeing the given allocated pages. The passing of the pages should be done with care because the kernel is not able to check the information it is provided.
Usually the disk is slower than the physical memory, so this is one of the reasons that memory is preferred over disk storage. The same applies for processor's cache levels: the closer it resides to the processor the faster it is for the I/O access. The process that moves data from the disk into the physical memory is called page caching. The inverse process is defined as page writeback. These two notions will be presented in this subsection, but is it mainly about the kernel context.
The first time the kernel calls the read()
system call, the data is verified if it is present in the page cache. The process by which the page is found inside the RAM is called cache hit. If it is not available there, then data needs to be read from the disk and this process is called
cache miss.
When the kernel issues the write() system call, there are multiple possibilities for cache interaction with regard to this system call. The easiest one is to not cache the write system calls operations and only keep the data in the disk. This scenario is called no-write cache. When the write operation updates the physical memory and the disk data at the same time, the operation is called write-through cache. The third option is represented by write-back cache where the page is marked as dirty. It is added to the dirty list and over time, it is put on the disk and marked as not dirty. The best synonym for the dirty keyword is represented by the synchronized key word.
Besides its own physical memory, the kernel is also responsible for user space process and memory management. The memory allocated for each user space process is called process address space and it contains the virtual memory addressable by a given process. It also contains the related addresses used by the process in its interaction with the virtual memory.
Usually a process receives a flat 32 or 64-bit address space, its size being dependent on the architecture type. However, there are operating systems that allocate a segmented address space. The possibility of sharing the address space between the operating systems is offered to threads. Although a process can access a large memory space, it usually has permission to access only an interval of memory. This is called a memory area and it means that a process can only access a memory address situated inside a viable memory area. If it somehow tries to administrate a memory address outside of its valid memory area, the kernel will kill the process with the Segmentation fault notification.
A memory area contains the following:
- The
text
section maps source code - The
data
section maps initialized global variables - The
bss
section maps uninitialized global variables - The
zero page
section is used to process user space stack - The
shared libraries text
,bss
and data-specific sections - Mapped files
- Anonymous memory mapping is usually linked with functions, such as
malloc()
- Shared memory segments
A process address space is defined inside the Linux kernel source through a memory descriptor. This structure is called struct mm_struct
, which is defined inside the include/linux/mm_types.h
header file and contains information relevant for a process address space, such as the number of processes that use the address space, a list of memory areas, the last memory area that was used, the number of memory areas available, start and finish addresses for the code, data, heap and stack sections.
A process, as presented previously, is a fundamental unit in a Linux operating system and at the same time, is a form of abstraction. It is, in fact, a program in execution, but a program by itself is not a process. It needs to be in an active state and have associated resources. A process is able to become a parent by using the fork()
function, which spawns a child process. Both parent and child processes reside in separate address spaces, but both of them have the same content. The exec()
family of function is the one that is able to execute a different program, create an address space, and load it inside that address space.
- Calls the
dup_task_struct()
function to create a new kernel stack. Thetask_struct
andthread_info
structures are created for a new process. - Checks that the child does not go beyond the limits of the memory area.
- The child process distinguishes itself from its parent.
- It is set as
TASK_UNINTERRUPTIBLE
to make sure it does not run. - Flags are updated.
PID
is associated with the child process.- The flags that are already set are inspected and proper action is performed with respect to their values.
- The clean process is performed at the end when the child process pointer is obtained.
At the end of the execution, the process need to be terminated so that the resources can be freed, and the parent of the executing process needs to be notified about this. The method that is most used to terminate a process is done by calling the exit()
system call. A number of steps are needed for this process:
- The
PF_EXITING
flag is set. - The
del_timer_sync()
function is called to remove the kernel timers. - The
acct_update_integrals()
function is called when writing accounting and logging information. - The
exit_mm()
is called to release themm_struct
structure for the process. - The
exit_sem()
is called to dequeue the process from the IPC semaphore. - The
exit_files()
andexit_fs()
function are called to remove the links to various files descriptors. - The task exit code should be set.
- Call
exit_notify()
to notify the parent and set the task exit state toEXIT_ZOMBIE
. - Call
schedule()
to switch to a new process.
The process scheduler decides which resources are allocated for a runnable process. It is a piece of software that is responsible for multitasking, resource allocation to various processes, and decides how to best set the resources and processor time. it also decides which processes should run next.
The first design of the Linux scheduler was very simplistic. It was not able to scale properly when the number of processes increased, so from the 2.5 kernel version, a new scheduler was developed. It is called O(1) scheduler and offers a constant time algorithm for time slice calculation and a run queue that is defined on a per-processor basis. Although it is perfect for large servers, it is not the best solution for a normal desktop system. From the 2.6 kernel version, improvements have been made to the O(1) scheduler, such as the fair scheduling concept that later materialized from the kernel version 2.6.23 into the Completely Fair Scheduler (CFS), which became the defacto scheduler.
The CFC has a simple idea behind. It behaves as if we have a perfect multitasking processor where each process gets 1/n
slice of the processor's time and this time slice is an incredibly small. The n
value represents the number of running processes. Con Kolivas is the Australian programmer that contributed to the fair scheduling implementation, also known as Rotating Staircase Deadline Scheduler (RSDL). Its implementation required a red-black tree for the priorities of self-balancing and also a time slice that is calculated at the nanosecond level. Similarly to the O(1) scheduler, CFS applies the notion of weight, which implies that some processes wait more than others. This is based on the weighed fair queuing algorithm.
For processes to interact with a system, an interface should be provided to give the user space application the possibility of interacting with hardware and other processes.System
calls. These are used as an interface between the hardware and the user space. They are also used to ensure stability, security, and abstraction, in general. These are common layers that constitute an entry point into the kernel alongside traps and exceptions, as described here:
The interaction with most of the system calls that are available inside the Linux system is done using the C library. They are able to define a number of arguments and return a value that reveals whether they were successful or not. A value of zero
usually means that the execution ended with success, and in case errors appear, an error code will be available inside the errno
variable. When a system call is done, the following steps are followed:
- The switch into kernel mode is made.
- Any restrictions to the kernel space access are eliminated.
- The stack from the user space is passed into the kernel space.
- Any arguments from the user space are checked and copied into the kernel space.
- The associated routine for the system call is identified and run.
- The switch to the user space is made and the execution of the application continues.
The Linux operating system is able to support a large variety of filesystem options. This is done due to the existence of Virtual File System (VFS), which is able to provide a common interface for a large number of filesystem types and handle the systems calls relevant to them.
The filesystem types supported by the VFS can be put in these three categories:
- Disk-based filesystems: These manage the memory on a local disk or devices that are used for disk emulation. Some of the most well known ones are:
- Linux filesystems, such as Second Extended Filesystem (Ext2), Third Extended Filesystem (Ext3), and Forth Extended Filesystem (Ext4)
- UNIX filesystems, such as sysv filesystem, UFS, Minix filesystem, and so on
- Microsoft filesystems, such as MS-DOS, NTFS (available since Windows NT), and VFAT (available since Windows 95)
- ISO966 CD-ROM filesystem and disk format DVD filesystem
- Proprietary filesystems, such as the ones from Apple, IBM, and other companies
- Network filesystems: They are allowed to access various filesystem types over a network on other computers. One of the most well known ones is NFS. Of course, there are others but they are not as well known. These include Andrew filesystem (AFS), Novel's NetWare Core Protocol (NCP), Constant Data Availability (Coda), and so on.
- Special filesystems: The
/proc
filesystem is the perfect example for this category of filesystems. This category of filesystems enables an easier access for system applications to interrogate data structures of kernels and implement various features.
The virtual filesystem system call implementation is very well summarized in this image:
In the preceding image, it can be seen how easily the copy is handled from one filesystem type to another. It only uses the basic open()
, close()
, read()
, write()
functions available for all the other filesystem interaction. However, all of them implement the specific functionality underneath for the chosen filesystem. For example, the open()
system calls sys_open()
and it takes the same arguments as open()
and returns the same result. The difference between sys_open()
and open()
is that sys_open()
is a more permissive function.
discussing the memory in Linux, we can refer to it as the physical and virtual memory. Compartments of the RAM memory are used for the containment of the Linux kernel variables and data structures, the rest of the memory being used for dynamic allocations, as described here:
_count
: This represents the page counter. When it reaches the0
value, the page is added to the free pages list.virtual
: This represents the virtual address associated to a physical page. The ZONE_DMA and ZONE_NORMAL pages are always mapped, while the ZONE_HIGHMEN are not always mapped.flags
: This represents a set of flags that describe the attributes of the page.
The zones of the physical memory have been previously. The physical memory is split up into multiple nodes that have a common physical address space and a fast local memory access. The smallest of them is ZONE_DMA between 0 to 16Mb. The next is ZONE_NORMAL, which is the LowMem area between 16Mb to 896Mb, and the largest one is ZONE_HIGHMEM, which is between 900Mb to 4GB/64Gb. This information can be visible both in the preceding and following images:
The virtual memory is used both in the user space and the kernel space. The allocation for a memory zone implies the allocation of a physical page as well as the allocation of an address space area; this is done both in the page table and in the internal structures available inside the operating system. The usage of the page table differs from one architecture type to another. For the Complex instruction set computing (CISC) architecture, the page table is used by the processor, but on a Reduced instruction set computing (RISC) architecture, the page table is used by the core for a page lookup and translation lookaside buffer (TLB) add operations. Each zone descriptor is used for zone mapping. It specifies whether the zone is mapped for usage by a file if the zone is read-only, copy-on-write, and so on. The address space descriptor is used by the operating system to maintain high-level information.
The methods used by the kernel for memory handling is the first subject that will be discussed here. This is done to make sure that you understand the methods used by the kernel to obtain memory. Although the smallest addressable unit of a processor is a byte, the Memory Management Unit (MMU), the unit responsible for virtual to physical translation the smallest addressable unit is the page. A page's size varies from one architecture to another. It is responsible for maintaining the system's page tables. Most of 32-bit architectures use 4KB pages, whereas the 64-bit ones usually have 8KB pages. For the Atmel SAMA5D3-Xplained board, the definition of the struct page
structure is as follows:
This is one of the most important fields of the page structure. The flags
field, for example, represents the status of the page; this holds information, such as whether the page is dirty or not, locked, or in another valid state. The values that are associated with this flag are defined inside the include/linux/page-flags-layout.h
header file. The virtual
field represents the virtual address associated with the page, count
represents the count value for the page that is usually accessible indirectly through the page_count()
function. All the other fields can be accessed inside the include/linux/mm_types.h
header file.
There are allocations that require interaction with more than one zone. One such example is a normal allocation that is able to use either ZONE_DMA
or ZONE_NORMAL
. ZONE_NORMAL
is preferred because it does not interfere with direct memory accesses, though when the memory is at full usage, the kernel might use other available zones besides the ones that it uses in normal scenarios. The kernel that is available is a struct zone structure that defines each zone's relevant information. For the Atmel SAMA5D3-Xplained board, this structure is as shown here:
As you can see, the zone that defines the structure is an impressive one. Some of the most interesting fields are represented by the watermark
variable, which contain the high, medium, and low watermarks for the defined zone. The present_pages
attribute represents the available pages within the zone. The name
field represents the name of the zone, and others, such as the lock
field, a spin lock that shields the zone structure for simultaneous access. All the other fields that can be identified inside the corresponding include/linux/mmzone.h
header file for the Atmel SAMA5D3 Xplained board.
This function is used to get the logical address for a corresponding memory page:
The preceding function does what the name suggests. It returns the page full of zero
values. The difference between this function and the __get_free_page()
function is that after being released, the page is filled with zero
values:
The preceding functions are used for freeing the given allocated pages. The passing of the pages should be done with care because the kernel is not able to check the information it is provided.
Usually the disk is slower than the physical memory, so this is one of the reasons that memory is preferred over disk storage. The same applies for processor's cache levels: the closer it resides to the processor the faster it is for the I/O access. The process that moves data from the disk into the physical memory is called page caching. The inverse process is defined as page writeback. These two notions will be presented in this subsection, but is it mainly about the kernel context.
The first time the kernel calls the read()
system call, the data is verified if it is present in the page cache. The process by which the page is found inside the RAM is called cache hit. If it is not available there, then data needs to be read from the disk and this process is called
cache miss.
When the kernel issues the write() system call, there are multiple possibilities for cache interaction with regard to this system call. The easiest one is to not cache the write system calls operations and only keep the data in the disk. This scenario is called no-write cache. When the write operation updates the physical memory and the disk data at the same time, the operation is called write-through cache. The third option is represented by write-back cache where the page is marked as dirty. It is added to the dirty list and over time, it is put on the disk and marked as not dirty. The best synonym for the dirty keyword is represented by the synchronized key word.
Besides its own physical memory, the kernel is also responsible for user space process and memory management. The memory allocated for each user space process is called process address space and it contains the virtual memory addressable by a given process. It also contains the related addresses used by the process in its interaction with the virtual memory.
Usually a process receives a flat 32 or 64-bit address space, its size being dependent on the architecture type. However, there are operating systems that allocate a segmented address space. The possibility of sharing the address space between the operating systems is offered to threads. Although a process can access a large memory space, it usually has permission to access only an interval of memory. This is called a memory area and it means that a process can only access a memory address situated inside a viable memory area. If it somehow tries to administrate a memory address outside of its valid memory area, the kernel will kill the process with the Segmentation fault notification.
A memory area contains the following:
- The
text
section maps source code - The
data
section maps initialized global variables - The
bss
section maps uninitialized global variables - The
zero page
section is used to process user space stack - The
shared libraries text
,bss
and data-specific sections - Mapped files
- Anonymous memory mapping is usually linked with functions, such as
malloc()
- Shared memory segments
A process address space is defined inside the Linux kernel source through a memory descriptor. This structure is called struct mm_struct
, which is defined inside the include/linux/mm_types.h
header file and contains information relevant for a process address space, such as the number of processes that use the address space, a list of memory areas, the last memory area that was used, the number of memory areas available, start and finish addresses for the code, data, heap and stack sections.
A process, as presented previously, is a fundamental unit in a Linux operating system and at the same time, is a form of abstraction. It is, in fact, a program in execution, but a program by itself is not a process. It needs to be in an active state and have associated resources. A process is able to become a parent by using the fork()
function, which spawns a child process. Both parent and child processes reside in separate address spaces, but both of them have the same content. The exec()
family of function is the one that is able to execute a different program, create an address space, and load it inside that address space.
- Calls the
dup_task_struct()
function to create a new kernel stack. Thetask_struct
andthread_info
structures are created for a new process. - Checks that the child does not go beyond the limits of the memory area.
- The child process distinguishes itself from its parent.
- It is set as
TASK_UNINTERRUPTIBLE
to make sure it does not run. - Flags are updated.
PID
is associated with the child process.- The flags that are already set are inspected and proper action is performed with respect to their values.
- The clean process is performed at the end when the child process pointer is obtained.
At the end of the execution, the process need to be terminated so that the resources can be freed, and the parent of the executing process needs to be notified about this. The method that is most used to terminate a process is done by calling the exit()
system call. A number of steps are needed for this process:
- The
PF_EXITING
flag is set. - The
del_timer_sync()
function is called to remove the kernel timers. - The
acct_update_integrals()
function is called when writing accounting and logging information. - The
exit_mm()
is called to release themm_struct
structure for the process. - The
exit_sem()
is called to dequeue the process from the IPC semaphore. - The
exit_files()
andexit_fs()
function are called to remove the links to various files descriptors. - The task exit code should be set.
- Call
exit_notify()
to notify the parent and set the task exit state toEXIT_ZOMBIE
. - Call
schedule()
to switch to a new process.
The process scheduler decides which resources are allocated for a runnable process. It is a piece of software that is responsible for multitasking, resource allocation to various processes, and decides how to best set the resources and processor time. it also decides which processes should run next.
The first design of the Linux scheduler was very simplistic. It was not able to scale properly when the number of processes increased, so from the 2.5 kernel version, a new scheduler was developed. It is called O(1) scheduler and offers a constant time algorithm for time slice calculation and a run queue that is defined on a per-processor basis. Although it is perfect for large servers, it is not the best solution for a normal desktop system. From the 2.6 kernel version, improvements have been made to the O(1) scheduler, such as the fair scheduling concept that later materialized from the kernel version 2.6.23 into the Completely Fair Scheduler (CFS), which became the defacto scheduler.
The CFC has a simple idea behind. It behaves as if we have a perfect multitasking processor where each process gets 1/n
slice of the processor's time and this time slice is an incredibly small. The n
value represents the number of running processes. Con Kolivas is the Australian programmer that contributed to the fair scheduling implementation, also known as Rotating Staircase Deadline Scheduler (RSDL). Its implementation required a red-black tree for the priorities of self-balancing and also a time slice that is calculated at the nanosecond level. Similarly to the O(1) scheduler, CFS applies the notion of weight, which implies that some processes wait more than others. This is based on the weighed fair queuing algorithm.
For processes to interact with a system, an interface should be provided to give the user space application the possibility of interacting with hardware and other processes.System
calls. These are used as an interface between the hardware and the user space. They are also used to ensure stability, security, and abstraction, in general. These are common layers that constitute an entry point into the kernel alongside traps and exceptions, as described here:
The interaction with most of the system calls that are available inside the Linux system is done using the C library. They are able to define a number of arguments and return a value that reveals whether they were successful or not. A value of zero
usually means that the execution ended with success, and in case errors appear, an error code will be available inside the errno
variable. When a system call is done, the following steps are followed:
- The switch into kernel mode is made.
- Any restrictions to the kernel space access are eliminated.
- The stack from the user space is passed into the kernel space.
- Any arguments from the user space are checked and copied into the kernel space.
- The associated routine for the system call is identified and run.
- The switch to the user space is made and the execution of the application continues.
The Linux operating system is able to support a large variety of filesystem options. This is done due to the existence of Virtual File System (VFS), which is able to provide a common interface for a large number of filesystem types and handle the systems calls relevant to them.
The filesystem types supported by the VFS can be put in these three categories:
- Disk-based filesystems: These manage the memory on a local disk or devices that are used for disk emulation. Some of the most well known ones are:
- Linux filesystems, such as Second Extended Filesystem (Ext2), Third Extended Filesystem (Ext3), and Forth Extended Filesystem (Ext4)
- UNIX filesystems, such as sysv filesystem, UFS, Minix filesystem, and so on
- Microsoft filesystems, such as MS-DOS, NTFS (available since Windows NT), and VFAT (available since Windows 95)
- ISO966 CD-ROM filesystem and disk format DVD filesystem
- Proprietary filesystems, such as the ones from Apple, IBM, and other companies
- Network filesystems: They are allowed to access various filesystem types over a network on other computers. One of the most well known ones is NFS. Of course, there are others but they are not as well known. These include Andrew filesystem (AFS), Novel's NetWare Core Protocol (NCP), Constant Data Availability (Coda), and so on.
- Special filesystems: The
/proc
filesystem is the perfect example for this category of filesystems. This category of filesystems enables an easier access for system applications to interrogate data structures of kernels and implement various features.
The virtual filesystem system call implementation is very well summarized in this image:
In the preceding image, it can be seen how easily the copy is handled from one filesystem type to another. It only uses the basic open()
, close()
, read()
, write()
functions available for all the other filesystem interaction. However, all of them implement the specific functionality underneath for the chosen filesystem. For example, the open()
system calls sys_open()
and it takes the same arguments as open()
and returns the same result. The difference between sys_open()
and open()
is that sys_open()
is a more permissive function.
process is defined as page writeback. These two notions will be presented in this subsection, but is it mainly about the kernel context.
The first time the kernel calls the read()
system call, the data is verified if it is present in the page cache. The process by which the page is found inside the RAM is called cache hit. If it is not available there, then data needs to be read from the disk and this process is called
cache miss.
When the kernel issues the write() system call, there are multiple possibilities for cache interaction with regard to this system call. The easiest one is to not cache the write system calls operations and only keep the data in the disk. This scenario is called no-write cache. When the write operation updates the physical memory and the disk data at the same time, the operation is called write-through cache. The third option is represented by write-back cache where the page is marked as dirty. It is added to the dirty list and over time, it is put on the disk and marked as not dirty. The best synonym for the dirty keyword is represented by the synchronized key word.
Besides its own physical memory, the kernel is also responsible for user space process and memory management. The memory allocated for each user space process is called process address space and it contains the virtual memory addressable by a given process. It also contains the related addresses used by the process in its interaction with the virtual memory.
Usually a process receives a flat 32 or 64-bit address space, its size being dependent on the architecture type. However, there are operating systems that allocate a segmented address space. The possibility of sharing the address space between the operating systems is offered to threads. Although a process can access a large memory space, it usually has permission to access only an interval of memory. This is called a memory area and it means that a process can only access a memory address situated inside a viable memory area. If it somehow tries to administrate a memory address outside of its valid memory area, the kernel will kill the process with the Segmentation fault notification.
A memory area contains the following:
- The
text
section maps source code - The
data
section maps initialized global variables - The
bss
section maps uninitialized global variables - The
zero page
section is used to process user space stack - The
shared libraries text
,bss
and data-specific sections - Mapped files
- Anonymous memory mapping is usually linked with functions, such as
malloc()
- Shared memory segments
A process address space is defined inside the Linux kernel source through a memory descriptor. This structure is called struct mm_struct
, which is defined inside the include/linux/mm_types.h
header file and contains information relevant for a process address space, such as the number of processes that use the address space, a list of memory areas, the last memory area that was used, the number of memory areas available, start and finish addresses for the code, data, heap and stack sections.
A process, as presented previously, is a fundamental unit in a Linux operating system and at the same time, is a form of abstraction. It is, in fact, a program in execution, but a program by itself is not a process. It needs to be in an active state and have associated resources. A process is able to become a parent by using the fork()
function, which spawns a child process. Both parent and child processes reside in separate address spaces, but both of them have the same content. The exec()
family of function is the one that is able to execute a different program, create an address space, and load it inside that address space.
- Calls the
dup_task_struct()
function to create a new kernel stack. Thetask_struct
andthread_info
structures are created for a new process. - Checks that the child does not go beyond the limits of the memory area.
- The child process distinguishes itself from its parent.
- It is set as
TASK_UNINTERRUPTIBLE
to make sure it does not run. - Flags are updated.
PID
is associated with the child process.- The flags that are already set are inspected and proper action is performed with respect to their values.
- The clean process is performed at the end when the child process pointer is obtained.
At the end of the execution, the process need to be terminated so that the resources can be freed, and the parent of the executing process needs to be notified about this. The method that is most used to terminate a process is done by calling the exit()
system call. A number of steps are needed for this process:
- The
PF_EXITING
flag is set. - The
del_timer_sync()
function is called to remove the kernel timers. - The
acct_update_integrals()
function is called when writing accounting and logging information. - The
exit_mm()
is called to release themm_struct
structure for the process. - The
exit_sem()
is called to dequeue the process from the IPC semaphore. - The
exit_files()
andexit_fs()
function are called to remove the links to various files descriptors. - The task exit code should be set.
- Call
exit_notify()
to notify the parent and set the task exit state toEXIT_ZOMBIE
. - Call
schedule()
to switch to a new process.
The process scheduler decides which resources are allocated for a runnable process. It is a piece of software that is responsible for multitasking, resource allocation to various processes, and decides how to best set the resources and processor time. it also decides which processes should run next.
The first design of the Linux scheduler was very simplistic. It was not able to scale properly when the number of processes increased, so from the 2.5 kernel version, a new scheduler was developed. It is called O(1) scheduler and offers a constant time algorithm for time slice calculation and a run queue that is defined on a per-processor basis. Although it is perfect for large servers, it is not the best solution for a normal desktop system. From the 2.6 kernel version, improvements have been made to the O(1) scheduler, such as the fair scheduling concept that later materialized from the kernel version 2.6.23 into the Completely Fair Scheduler (CFS), which became the defacto scheduler.
The CFC has a simple idea behind. It behaves as if we have a perfect multitasking processor where each process gets 1/n
slice of the processor's time and this time slice is an incredibly small. The n
value represents the number of running processes. Con Kolivas is the Australian programmer that contributed to the fair scheduling implementation, also known as Rotating Staircase Deadline Scheduler (RSDL). Its implementation required a red-black tree for the priorities of self-balancing and also a time slice that is calculated at the nanosecond level. Similarly to the O(1) scheduler, CFS applies the notion of weight, which implies that some processes wait more than others. This is based on the weighed fair queuing algorithm.
For processes to interact with a system, an interface should be provided to give the user space application the possibility of interacting with hardware and other processes.System
calls. These are used as an interface between the hardware and the user space. They are also used to ensure stability, security, and abstraction, in general. These are common layers that constitute an entry point into the kernel alongside traps and exceptions, as described here:
The interaction with most of the system calls that are available inside the Linux system is done using the C library. They are able to define a number of arguments and return a value that reveals whether they were successful or not. A value of zero
usually means that the execution ended with success, and in case errors appear, an error code will be available inside the errno
variable. When a system call is done, the following steps are followed:
- The switch into kernel mode is made.
- Any restrictions to the kernel space access are eliminated.
- The stack from the user space is passed into the kernel space.
- Any arguments from the user space are checked and copied into the kernel space.
- The associated routine for the system call is identified and run.
- The switch to the user space is made and the execution of the application continues.
The Linux operating system is able to support a large variety of filesystem options. This is done due to the existence of Virtual File System (VFS), which is able to provide a common interface for a large number of filesystem types and handle the systems calls relevant to them.
The filesystem types supported by the VFS can be put in these three categories:
- Disk-based filesystems: These manage the memory on a local disk or devices that are used for disk emulation. Some of the most well known ones are:
- Linux filesystems, such as Second Extended Filesystem (Ext2), Third Extended Filesystem (Ext3), and Forth Extended Filesystem (Ext4)
- UNIX filesystems, such as sysv filesystem, UFS, Minix filesystem, and so on
- Microsoft filesystems, such as MS-DOS, NTFS (available since Windows NT), and VFAT (available since Windows 95)
- ISO966 CD-ROM filesystem and disk format DVD filesystem
- Proprietary filesystems, such as the ones from Apple, IBM, and other companies
- Network filesystems: They are allowed to access various filesystem types over a network on other computers. One of the most well known ones is NFS. Of course, there are others but they are not as well known. These include Andrew filesystem (AFS), Novel's NetWare Core Protocol (NCP), Constant Data Availability (Coda), and so on.
- Special filesystems: The
/proc
filesystem is the perfect example for this category of filesystems. This category of filesystems enables an easier access for system applications to interrogate data structures of kernels and implement various features.
The virtual filesystem system call implementation is very well summarized in this image:
In the preceding image, it can be seen how easily the copy is handled from one filesystem type to another. It only uses the basic open()
, close()
, read()
, write()
functions available for all the other filesystem interaction. However, all of them implement the specific functionality underneath for the chosen filesystem. For example, the open()
system calls sys_open()
and it takes the same arguments as open()
and returns the same result. The difference between sys_open()
and open()
is that sys_open()
is a more permissive function.
own physical memory, the kernel is also responsible for user space process and memory management. The memory allocated for each user space process is called process address space and it contains the virtual memory addressable by a given process. It also contains the related addresses used by the process in its interaction with the virtual memory.
Usually a process receives a flat 32 or 64-bit address space, its size being dependent on the architecture type. However, there are operating systems that allocate a segmented address space. The possibility of sharing the address space between the operating systems is offered to threads. Although a process can access a large memory space, it usually has permission to access only an interval of memory. This is called a memory area and it means that a process can only access a memory address situated inside a viable memory area. If it somehow tries to administrate a memory address outside of its valid memory area, the kernel will kill the process with the Segmentation fault notification.
A memory area contains the following:
- The
text
section maps source code - The
data
section maps initialized global variables - The
bss
section maps uninitialized global variables - The
zero page
section is used to process user space stack - The
shared libraries text
,bss
and data-specific sections - Mapped files
- Anonymous memory mapping is usually linked with functions, such as
malloc()
- Shared memory segments
A process address space is defined inside the Linux kernel source through a memory descriptor. This structure is called struct mm_struct
, which is defined inside the include/linux/mm_types.h
header file and contains information relevant for a process address space, such as the number of processes that use the address space, a list of memory areas, the last memory area that was used, the number of memory areas available, start and finish addresses for the code, data, heap and stack sections.
A process, as presented previously, is a fundamental unit in a Linux operating system and at the same time, is a form of abstraction. It is, in fact, a program in execution, but a program by itself is not a process. It needs to be in an active state and have associated resources. A process is able to become a parent by using the fork()
function, which spawns a child process. Both parent and child processes reside in separate address spaces, but both of them have the same content. The exec()
family of function is the one that is able to execute a different program, create an address space, and load it inside that address space.
- Calls the
dup_task_struct()
function to create a new kernel stack. Thetask_struct
andthread_info
structures are created for a new process. - Checks that the child does not go beyond the limits of the memory area.
- The child process distinguishes itself from its parent.
- It is set as
TASK_UNINTERRUPTIBLE
to make sure it does not run. - Flags are updated.
PID
is associated with the child process.- The flags that are already set are inspected and proper action is performed with respect to their values.
- The clean process is performed at the end when the child process pointer is obtained.
At the end of the execution, the process need to be terminated so that the resources can be freed, and the parent of the executing process needs to be notified about this. The method that is most used to terminate a process is done by calling the exit()
system call. A number of steps are needed for this process:
- The
PF_EXITING
flag is set. - The
del_timer_sync()
function is called to remove the kernel timers. - The
acct_update_integrals()
function is called when writing accounting and logging information. - The
exit_mm()
is called to release themm_struct
structure for the process. - The
exit_sem()
is called to dequeue the process from the IPC semaphore. - The
exit_files()
andexit_fs()
function are called to remove the links to various files descriptors. - The task exit code should be set.
- Call
exit_notify()
to notify the parent and set the task exit state toEXIT_ZOMBIE
. - Call
schedule()
to switch to a new process.
The process scheduler decides which resources are allocated for a runnable process. It is a piece of software that is responsible for multitasking, resource allocation to various processes, and decides how to best set the resources and processor time. it also decides which processes should run next.
The first design of the Linux scheduler was very simplistic. It was not able to scale properly when the number of processes increased, so from the 2.5 kernel version, a new scheduler was developed. It is called O(1) scheduler and offers a constant time algorithm for time slice calculation and a run queue that is defined on a per-processor basis. Although it is perfect for large servers, it is not the best solution for a normal desktop system. From the 2.6 kernel version, improvements have been made to the O(1) scheduler, such as the fair scheduling concept that later materialized from the kernel version 2.6.23 into the Completely Fair Scheduler (CFS), which became the defacto scheduler.
The CFC has a simple idea behind. It behaves as if we have a perfect multitasking processor where each process gets 1/n
slice of the processor's time and this time slice is an incredibly small. The n
value represents the number of running processes. Con Kolivas is the Australian programmer that contributed to the fair scheduling implementation, also known as Rotating Staircase Deadline Scheduler (RSDL). Its implementation required a red-black tree for the priorities of self-balancing and also a time slice that is calculated at the nanosecond level. Similarly to the O(1) scheduler, CFS applies the notion of weight, which implies that some processes wait more than others. This is based on the weighed fair queuing algorithm.
For processes to interact with a system, an interface should be provided to give the user space application the possibility of interacting with hardware and other processes.System
calls. These are used as an interface between the hardware and the user space. They are also used to ensure stability, security, and abstraction, in general. These are common layers that constitute an entry point into the kernel alongside traps and exceptions, as described here:
The interaction with most of the system calls that are available inside the Linux system is done using the C library. They are able to define a number of arguments and return a value that reveals whether they were successful or not. A value of zero
usually means that the execution ended with success, and in case errors appear, an error code will be available inside the errno
variable. When a system call is done, the following steps are followed:
- The switch into kernel mode is made.
- Any restrictions to the kernel space access are eliminated.
- The stack from the user space is passed into the kernel space.
- Any arguments from the user space are checked and copied into the kernel space.
- The associated routine for the system call is identified and run.
- The switch to the user space is made and the execution of the application continues.
The Linux operating system is able to support a large variety of filesystem options. This is done due to the existence of Virtual File System (VFS), which is able to provide a common interface for a large number of filesystem types and handle the systems calls relevant to them.
The filesystem types supported by the VFS can be put in these three categories:
- Disk-based filesystems: These manage the memory on a local disk or devices that are used for disk emulation. Some of the most well known ones are:
- Linux filesystems, such as Second Extended Filesystem (Ext2), Third Extended Filesystem (Ext3), and Forth Extended Filesystem (Ext4)
- UNIX filesystems, such as sysv filesystem, UFS, Minix filesystem, and so on
- Microsoft filesystems, such as MS-DOS, NTFS (available since Windows NT), and VFAT (available since Windows 95)
- ISO966 CD-ROM filesystem and disk format DVD filesystem
- Proprietary filesystems, such as the ones from Apple, IBM, and other companies
- Network filesystems: They are allowed to access various filesystem types over a network on other computers. One of the most well known ones is NFS. Of course, there are others but they are not as well known. These include Andrew filesystem (AFS), Novel's NetWare Core Protocol (NCP), Constant Data Availability (Coda), and so on.
- Special filesystems: The
/proc
filesystem is the perfect example for this category of filesystems. This category of filesystems enables an easier access for system applications to interrogate data structures of kernels and implement various features.
The virtual filesystem system call implementation is very well summarized in this image:
In the preceding image, it can be seen how easily the copy is handled from one filesystem type to another. It only uses the basic open()
, close()
, read()
, write()
functions available for all the other filesystem interaction. However, all of them implement the specific functionality underneath for the chosen filesystem. For example, the open()
system calls sys_open()
and it takes the same arguments as open()
and returns the same result. The difference between sys_open()
and open()
is that sys_open()
is a more permissive function.
presented previously, is a fundamental unit in a Linux operating system and at the same time, is a form of abstraction. It is, in fact, a program in execution, but a program by itself is not a process. It needs to be in an active state and have associated resources. A process is able to become a parent by using the fork()
function, which spawns a child process. Both parent and child processes reside in separate address spaces, but both of them have the same content. The exec()
family of function is the one that is able to execute a different program, create an address space, and load it inside that address space.
- Calls the
dup_task_struct()
function to create a new kernel stack. Thetask_struct
andthread_info
structures are created for a new process. - Checks that the child does not go beyond the limits of the memory area.
- The child process distinguishes itself from its parent.
- It is set as
TASK_UNINTERRUPTIBLE
to make sure it does not run. - Flags are updated.
PID
is associated with the child process.- The flags that are already set are inspected and proper action is performed with respect to their values.
- The clean process is performed at the end when the child process pointer is obtained.
At the end of the execution, the process need to be terminated so that the resources can be freed, and the parent of the executing process needs to be notified about this. The method that is most used to terminate a process is done by calling the exit()
system call. A number of steps are needed for this process:
- The
PF_EXITING
flag is set. - The
del_timer_sync()
function is called to remove the kernel timers. - The
acct_update_integrals()
function is called when writing accounting and logging information. - The
exit_mm()
is called to release themm_struct
structure for the process. - The
exit_sem()
is called to dequeue the process from the IPC semaphore. - The
exit_files()
andexit_fs()
function are called to remove the links to various files descriptors. - The task exit code should be set.
- Call
exit_notify()
to notify the parent and set the task exit state toEXIT_ZOMBIE
. - Call
schedule()
to switch to a new process.
The process scheduler decides which resources are allocated for a runnable process. It is a piece of software that is responsible for multitasking, resource allocation to various processes, and decides how to best set the resources and processor time. it also decides which processes should run next.
The first design of the Linux scheduler was very simplistic. It was not able to scale properly when the number of processes increased, so from the 2.5 kernel version, a new scheduler was developed. It is called O(1) scheduler and offers a constant time algorithm for time slice calculation and a run queue that is defined on a per-processor basis. Although it is perfect for large servers, it is not the best solution for a normal desktop system. From the 2.6 kernel version, improvements have been made to the O(1) scheduler, such as the fair scheduling concept that later materialized from the kernel version 2.6.23 into the Completely Fair Scheduler (CFS), which became the defacto scheduler.
The CFC has a simple idea behind. It behaves as if we have a perfect multitasking processor where each process gets 1/n
slice of the processor's time and this time slice is an incredibly small. The n
value represents the number of running processes. Con Kolivas is the Australian programmer that contributed to the fair scheduling implementation, also known as Rotating Staircase Deadline Scheduler (RSDL). Its implementation required a red-black tree for the priorities of self-balancing and also a time slice that is calculated at the nanosecond level. Similarly to the O(1) scheduler, CFS applies the notion of weight, which implies that some processes wait more than others. This is based on the weighed fair queuing algorithm.
For processes to interact with a system, an interface should be provided to give the user space application the possibility of interacting with hardware and other processes.System
calls. These are used as an interface between the hardware and the user space. They are also used to ensure stability, security, and abstraction, in general. These are common layers that constitute an entry point into the kernel alongside traps and exceptions, as described here:
The interaction with most of the system calls that are available inside the Linux system is done using the C library. They are able to define a number of arguments and return a value that reveals whether they were successful or not. A value of zero
usually means that the execution ended with success, and in case errors appear, an error code will be available inside the errno
variable. When a system call is done, the following steps are followed:
- The switch into kernel mode is made.
- Any restrictions to the kernel space access are eliminated.
- The stack from the user space is passed into the kernel space.
- Any arguments from the user space are checked and copied into the kernel space.
- The associated routine for the system call is identified and run.
- The switch to the user space is made and the execution of the application continues.
The Linux operating system is able to support a large variety of filesystem options. This is done due to the existence of Virtual File System (VFS), which is able to provide a common interface for a large number of filesystem types and handle the systems calls relevant to them.
The filesystem types supported by the VFS can be put in these three categories:
- Disk-based filesystems: These manage the memory on a local disk or devices that are used for disk emulation. Some of the most well known ones are:
- Linux filesystems, such as Second Extended Filesystem (Ext2), Third Extended Filesystem (Ext3), and Forth Extended Filesystem (Ext4)
- UNIX filesystems, such as sysv filesystem, UFS, Minix filesystem, and so on
- Microsoft filesystems, such as MS-DOS, NTFS (available since Windows NT), and VFAT (available since Windows 95)
- ISO966 CD-ROM filesystem and disk format DVD filesystem
- Proprietary filesystems, such as the ones from Apple, IBM, and other companies
- Network filesystems: They are allowed to access various filesystem types over a network on other computers. One of the most well known ones is NFS. Of course, there are others but they are not as well known. These include Andrew filesystem (AFS), Novel's NetWare Core Protocol (NCP), Constant Data Availability (Coda), and so on.
- Special filesystems: The
/proc
filesystem is the perfect example for this category of filesystems. This category of filesystems enables an easier access for system applications to interrogate data structures of kernels and implement various features.
The virtual filesystem system call implementation is very well summarized in this image:
In the preceding image, it can be seen how easily the copy is handled from one filesystem type to another. It only uses the basic open()
, close()
, read()
, write()
functions available for all the other filesystem interaction. However, all of them implement the specific functionality underneath for the chosen filesystem. For example, the open()
system calls sys_open()
and it takes the same arguments as open()
and returns the same result. The difference between sys_open()
and open()
is that sys_open()
is a more permissive function.
decides which resources are allocated for a runnable process. It is a piece of software that is responsible for multitasking, resource allocation to various processes, and decides how to best set the resources and processor time. it also decides which processes should run next.
The first design of the Linux scheduler was very simplistic. It was not able to scale properly when the number of processes increased, so from the 2.5 kernel version, a new scheduler was developed. It is called O(1) scheduler and offers a constant time algorithm for time slice calculation and a run queue that is defined on a per-processor basis. Although it is perfect for large servers, it is not the best solution for a normal desktop system. From the 2.6 kernel version, improvements have been made to the O(1) scheduler, such as the fair scheduling concept that later materialized from the kernel version 2.6.23 into the Completely Fair Scheduler (CFS), which became the defacto scheduler.
The CFC has a simple idea behind. It behaves as if we have a perfect multitasking processor where each process gets 1/n
slice of the processor's time and this time slice is an incredibly small. The n
value represents the number of running processes. Con Kolivas is the Australian programmer that contributed to the fair scheduling implementation, also known as Rotating Staircase Deadline Scheduler (RSDL). Its implementation required a red-black tree for the priorities of self-balancing and also a time slice that is calculated at the nanosecond level. Similarly to the O(1) scheduler, CFS applies the notion of weight, which implies that some processes wait more than others. This is based on the weighed fair queuing algorithm.
For processes to interact with a system, an interface should be provided to give the user space application the possibility of interacting with hardware and other processes.System
calls. These are used as an interface between the hardware and the user space. They are also used to ensure stability, security, and abstraction, in general. These are common layers that constitute an entry point into the kernel alongside traps and exceptions, as described here:
The interaction with most of the system calls that are available inside the Linux system is done using the C library. They are able to define a number of arguments and return a value that reveals whether they were successful or not. A value of zero
usually means that the execution ended with success, and in case errors appear, an error code will be available inside the errno
variable. When a system call is done, the following steps are followed:
- The switch into kernel mode is made.
- Any restrictions to the kernel space access are eliminated.
- The stack from the user space is passed into the kernel space.
- Any arguments from the user space are checked and copied into the kernel space.
- The associated routine for the system call is identified and run.
- The switch to the user space is made and the execution of the application continues.
The Linux operating system is able to support a large variety of filesystem options. This is done due to the existence of Virtual File System (VFS), which is able to provide a common interface for a large number of filesystem types and handle the systems calls relevant to them.
The filesystem types supported by the VFS can be put in these three categories:
- Disk-based filesystems: These manage the memory on a local disk or devices that are used for disk emulation. Some of the most well known ones are:
- Linux filesystems, such as Second Extended Filesystem (Ext2), Third Extended Filesystem (Ext3), and Forth Extended Filesystem (Ext4)
- UNIX filesystems, such as sysv filesystem, UFS, Minix filesystem, and so on
- Microsoft filesystems, such as MS-DOS, NTFS (available since Windows NT), and VFAT (available since Windows 95)
- ISO966 CD-ROM filesystem and disk format DVD filesystem
- Proprietary filesystems, such as the ones from Apple, IBM, and other companies
- Network filesystems: They are allowed to access various filesystem types over a network on other computers. One of the most well known ones is NFS. Of course, there are others but they are not as well known. These include Andrew filesystem (AFS), Novel's NetWare Core Protocol (NCP), Constant Data Availability (Coda), and so on.
- Special filesystems: The
/proc
filesystem is the perfect example for this category of filesystems. This category of filesystems enables an easier access for system applications to interrogate data structures of kernels and implement various features.
The virtual filesystem system call implementation is very well summarized in this image:
In the preceding image, it can be seen how easily the copy is handled from one filesystem type to another. It only uses the basic open()
, close()
, read()
, write()
functions available for all the other filesystem interaction. However, all of them implement the specific functionality underneath for the chosen filesystem. For example, the open()
system calls sys_open()
and it takes the same arguments as open()
and returns the same result. The difference between sys_open()
and open()
is that sys_open()
is a more permissive function.
to interact with a system, an interface should be provided to give the user space application the possibility of interacting with hardware and other processes.System
calls. These are used as an interface between the hardware and the user space. They are also used to ensure stability, security, and abstraction, in general. These are common layers that constitute an entry point into the kernel alongside traps and exceptions, as described here:
The interaction with most of the system calls that are available inside the Linux system is done using the C library. They are able to define a number of arguments and return a value that reveals whether they were successful or not. A value of zero
usually means that the execution ended with success, and in case errors appear, an error code will be available inside the errno
variable. When a system call is done, the following steps are followed:
- The switch into kernel mode is made.
- Any restrictions to the kernel space access are eliminated.
- The stack from the user space is passed into the kernel space.
- Any arguments from the user space are checked and copied into the kernel space.
- The associated routine for the system call is identified and run.
- The switch to the user space is made and the execution of the application continues.
The Linux operating system is able to support a large variety of filesystem options. This is done due to the existence of Virtual File System (VFS), which is able to provide a common interface for a large number of filesystem types and handle the systems calls relevant to them.
The filesystem types supported by the VFS can be put in these three categories:
- Disk-based filesystems: These manage the memory on a local disk or devices that are used for disk emulation. Some of the most well known ones are:
- Linux filesystems, such as Second Extended Filesystem (Ext2), Third Extended Filesystem (Ext3), and Forth Extended Filesystem (Ext4)
- UNIX filesystems, such as sysv filesystem, UFS, Minix filesystem, and so on
- Microsoft filesystems, such as MS-DOS, NTFS (available since Windows NT), and VFAT (available since Windows 95)
- ISO966 CD-ROM filesystem and disk format DVD filesystem
- Proprietary filesystems, such as the ones from Apple, IBM, and other companies
- Network filesystems: They are allowed to access various filesystem types over a network on other computers. One of the most well known ones is NFS. Of course, there are others but they are not as well known. These include Andrew filesystem (AFS), Novel's NetWare Core Protocol (NCP), Constant Data Availability (Coda), and so on.
- Special filesystems: The
/proc
filesystem is the perfect example for this category of filesystems. This category of filesystems enables an easier access for system applications to interrogate data structures of kernels and implement various features.
The virtual filesystem system call implementation is very well summarized in this image:
In the preceding image, it can be seen how easily the copy is handled from one filesystem type to another. It only uses the basic open()
, close()
, read()
, write()
functions available for all the other filesystem interaction. However, all of them implement the specific functionality underneath for the chosen filesystem. For example, the open()
system calls sys_open()
and it takes the same arguments as open()
and returns the same result. The difference between sys_open()
and open()
is that sys_open()
is a more permissive function.
existence of Virtual File System (VFS), which is able to provide a common interface for a large number of filesystem types and handle the systems calls relevant to them.
The filesystem types supported by the VFS can be put in these three categories:
- Disk-based filesystems: These manage the memory on a local disk or devices that are used for disk emulation. Some of the most well known ones are:
- Linux filesystems, such as Second Extended Filesystem (Ext2), Third Extended Filesystem (Ext3), and Forth Extended Filesystem (Ext4)
- UNIX filesystems, such as sysv filesystem, UFS, Minix filesystem, and so on
- Microsoft filesystems, such as MS-DOS, NTFS (available since Windows NT), and VFAT (available since Windows 95)
- ISO966 CD-ROM filesystem and disk format DVD filesystem
- Proprietary filesystems, such as the ones from Apple, IBM, and other companies
- Network filesystems: They are allowed to access various filesystem types over a network on other computers. One of the most well known ones is NFS. Of course, there are others but they are not as well known. These include Andrew filesystem (AFS), Novel's NetWare Core Protocol (NCP), Constant Data Availability (Coda), and so on.
- Special filesystems: The
/proc
filesystem is the perfect example for this category of filesystems. This category of filesystems enables an easier access for system applications to interrogate data structures of kernels and implement various features.
The virtual filesystem system call implementation is very well summarized in this image:
In the preceding image, it can be seen how easily the copy is handled from one filesystem type to another. It only uses the basic open()
, close()
, read()
, write()
functions available for all the other filesystem interaction. However, all of them implement the specific functionality underneath for the chosen filesystem. For example, the open()
system calls sys_open()
and it takes the same arguments as open()
and returns the same result. The difference between sys_open()
and open()
is that sys_open()
is a more permissive function.
An interrupt is a representation of an event that changes the succession of instructions performed by the processor. Interrupts imply an electric signal generated by the hardware to signal an event that has happened, such as a key press, reset, and so on. Interrupts are divided into more categories depending on their reference system, as follows:.
The difference between them is that all the available interrupts are permitted to act in the bottom half context. This helps the top half respond to another interrupt while the bottom half is working, which means that it is able to save its data in a specific buffer and it permits the bottom half to operate in a safe environment.
For the bottom half processing, there are four defined mechanisms available:
The available mechanisms are well presented here:
For the top half component of the interrupt, there are three levels of abstraction in the interrupt source code. The first one is the high-level driver API that has functions, such as request_irq()
, free_irq
, disable_irq()
, enable_irq()
, and so on. The second one is represented by the high-level IRQ flow handlers, which is a generic layer with predefined or architecture-specific interrupt flow handlers assigned to respond to various interrupts during device initialization or boot time. It defines a number of predefined functions, such as handle_level_irq()
, handle_simple_irq()
, handle_percpu_irq()
, and so on. The third is represented by chip-level hardware encapsulation. It defines the struct irq_chip
structure that holds chip-relevant functions used in the IRQ flow implementation. Some of the functions are irq_ack()
, irq_mask()
, and irq_unmask()
.
SA_SAMPLE_RANDOM
: This indicates that the interrupt can contribute to the entropy pool, that is, a pool with bits that possess a strong random property, by sampling unpredictable events, such as mouse movement, inter-key press time, disk interrupts, and so onSA_SHIRQ
: This shows that the interrupt is sharable between devices.SA_INTERRUPT
: This indicates a fast interrupt handler, so interrupts are disabled on the current processor-it does not represent a situation that is very desirable
The first mechanism that will be discussed regarding bottom half interrupt handling is represented by softirqs
. They are rarely used but can be found on the Linux kernel source code inside the kernel/softirq.c
file. When it comes to implementation, they are statically allocated at the compile step. They are created when an entry is added in the include/linux/interrupt.h
header file and the system information they provide is available inside the /proc/softirqs
file. Although not used too often, they can be executed after exceptions, interrupts, system calls, and when the ksoftirkd
daemon is run by the scheduler.
The last and the newest addition to the bottom half mechanism options is represented by the kernel threads that are operated entirely in the kernel mode since they are created/destroyed by the kernel. They appeared during the 2.6.30 kernel release, and also have the same advantages as the work queues, along with some extra features, such as the possibility of having their own context. It is expected that eventually the kernel threads will replace the work queues and tasklets, since they are similar to the user space threads. A driver might want to request a threaded interrupt handler. All it needs to do in this case is to use request_threaded_irq()
in a similar way to request_irq()
. The request_threaded_irq()
function offers the possibility of passing a handler and thread_fn
to split the interrupt handling code into two parts. In addition to this, quick_check_handler
is called to check if the interrupt was called from a device; if that is the case, it will need to call IRQ_WAKE_THREAD
to wake up the handler thread and execute thread_fn
.
The number of requests with which a kernel is dealing is likened to the number of requests a server has to receive. This situation can deal with race conditions, so a good synchronization method would be required. A number of policies are available for the way the kernel behaves by defining a kernel control path. Here is an example of a kernel control path:
A number of synchronization primitives have been born:
- Per-CPU variables: This is one of the most simple and efficient synchronization methods. It multiplies a data structure so that each one is available for each CPU.
- Atomic operations: This refers to atomic read-modify-write instructions.
- Memory barrier: This safeguards the fact that the operations done before the barrier are all finished before starting the operations after it.
- Spin lock: This represents a type of lock that implements bust waiting.
- Semaphore: This is a form of locking that implements sleep or blocking waiting.
- Seqlocks: This is similar to spin locks, but is based on an access counter.
- Local interrupt disabling: This forbids the use of functions that can be postponed on a single CPU.
- Read-copy-update(RCU): This is a method designed to protect the most used data structures used for reading. It offers a lock-free access to shared data structures using pointers.
With the preceding methods, race condition situations try to be fixed. It is the job of the developer to identify and solve all the eventual synchronization problems that might appear.
The last and the newest addition to the bottom half mechanism options is represented by the kernel threads that are operated entirely in the kernel mode since they are created/destroyed by the kernel. They appeared during the 2.6.30 kernel release, and also have the same advantages as the work queues, along with some extra features, such as the possibility of having their own context. It is expected that eventually the kernel threads will replace the work queues and tasklets, since they are similar to the user space threads. A driver might want to request a threaded interrupt handler. All it needs to do in this case is to use request_threaded_irq()
in a similar way to request_irq()
. The request_threaded_irq()
function offers the possibility of passing a handler and thread_fn
to split the interrupt handling code into two parts. In addition to this, quick_check_handler
is called to check if the interrupt was called from a device; if that is the case, it will need to call IRQ_WAKE_THREAD
to wake up the handler thread and execute thread_fn
.
The number of requests with which a kernel is dealing is likened to the number of requests a server has to receive. This situation can deal with race conditions, so a good synchronization method would be required. A number of policies are available for the way the kernel behaves by defining a kernel control path. Here is an example of a kernel control path:
A number of synchronization primitives have been born:
- Per-CPU variables: This is one of the most simple and efficient synchronization methods. It multiplies a data structure so that each one is available for each CPU.
- Atomic operations: This refers to atomic read-modify-write instructions.
- Memory barrier: This safeguards the fact that the operations done before the barrier are all finished before starting the operations after it.
- Spin lock: This represents a type of lock that implements bust waiting.
- Semaphore: This is a form of locking that implements sleep or blocking waiting.
- Seqlocks: This is similar to spin locks, but is based on an access counter.
- Local interrupt disabling: This forbids the use of functions that can be postponed on a single CPU.
- Read-copy-update(RCU): This is a method designed to protect the most used data structures used for reading. It offers a lock-free access to shared data structures using pointers.
With the preceding methods, race condition situations try to be fixed. It is the job of the developer to identify and solve all the eventual synchronization problems that might appear.
a server has to receive. This situation can deal with race conditions, so a good synchronization method would be required. A number of policies are available for the way the kernel behaves by defining a kernel control path. Here is an example of a kernel control path:
A number of synchronization primitives have been born:
- Per-CPU variables: This is one of the most simple and efficient synchronization methods. It multiplies a data structure so that each one is available for each CPU.
- Atomic operations: This refers to atomic read-modify-write instructions.
- Memory barrier: This safeguards the fact that the operations done before the barrier are all finished before starting the operations after it.
- Spin lock: This represents a type of lock that implements bust waiting.
- Semaphore: This is a form of locking that implements sleep or blocking waiting.
- Seqlocks: This is similar to spin locks, but is based on an access counter.
- Local interrupt disabling: This forbids the use of functions that can be postponed on a single CPU.
- Read-copy-update(RCU): This is a method designed to protect the most used data structures used for reading. It offers a lock-free access to shared data structures using pointers.
With the preceding methods, race condition situations try to be fixed. It is the job of the developer to identify and solve all the eventual synchronization problems that might appear.
Around the Linux kernel, there are a great number of functions that are influenced by time. From the scheduler to the system uptime, they all require a time reference, which includes both absolute and relative time. For example, an event that needs to be scheduled for the future, represents a relative time, which, in fact, implies that there is a method used to count time.
The timer implementation can vary depending on the type of the event. The periodical implementations are defined by the system timer, which issues an interrupt at a fixed period of time. The system timer is a hardware component that issues a timer interrupt at a given frequency to update the system time and execute the necessary tasks. Another one that can be used is the real-time clock, which is a chip with a battery attached that keeps counting time long after the system was shut down. Besides the system time, there are dynamic timers available that are managed by the kernel dynamically to plan events that run after a particular time has passed.
The timer interrupt has an occurrence window and for ARM, it is 100 times per second. This is called the system timer frequency or tick rate and its unit of measurement is hertz (Hz). The tick rate differs from one architecture to another. If for the most of them, we have the value of 100 Hz, there are others that have values of 1024 Hz, such as the Alpha and Itanium (IA-64) architectures, for example. The default value, of course, can be changed and increased, but this action has its advantages and disadvantages.
Some of the advantages of higher frequency are:
The total number of ticks done on a Linux operation system from the time it started booting is stored in a variable called jiffies inside the include/linux/jiffies.h
header file. At boot time, this variable is initialized to zero and one is added to its value each time an interrupt happens. So, the actual value of the system uptime can be calculated in the form of jiffies/Hz.
Until now, you were introduced to some of features of the Linux kernel. Now, it is time to present more information about the development process, versioning scheme, community contributions, and and interaction with the Linux kernel.
Linux kernel is a well known open source project. To make sure that developers know how to interact with it, information about how the git
interaction is done with this project, and at the same time, some information about its development and release procedures will be presented. The project has evolved and its development processes and release procedures have evolved with it.
Before presenting the actual development process, a bit of history will be necessary. Until the 2.6 version of the Linux kernel project, one release was made every two or three years, and each of them was identified by even middle numbers, such as 1.0.x, 2.0.x, and 2.6.x. The development branches were instead defined using even numbers, such as 1.1.x, 2.1.x, and 2.5.x, and they were used to integrate various features and functionalities until a major release was prepared and ready to be shipped. All the minor releases had names, such as 2.6.32 and 2.2.23, and they were released between major release cycles.
- All the new minor release versions, such as 2.6.x, contain a two week merge window in which a number of features could be introduced in the next release
- This merge window will be closed with a release test version called 2.6.(x+1)-rc1
- Then a 6-8 weeks bug fixing period follows when all the bugs introduced by the added features should be fixed
- In the bug fixing interval, tests were run on the release candidate and the 2.6.(x+1)-rcY test versions were released
- After the final test were done and the last release candidate is considered sufficiently stable, a new release will be made with a name, such as 2.6.(x+1), and this process will be continued once again
This process worked great but the only problem was that the bug fixes were only released for the latest stable versions of the Linux kernel. People needed long term support versions and security updates for their older versions, general information about these versions that were long time supported, and so on.
Since a great number of patches and features are included in the Linux kernel everyday, it becomes difficult to keep track of all the changes, and the bigger picture in general. This changed over time because sites, such as http://kernelnewbies.org/LinuxChanges and http://lwn.net/, appeared to help developers keep in touch with the world of Linux kernel.
Besides these links, the git
versioning control system can offer much needed information. Of course, this requires the existence of Linux kernel source clones to be available on the workstation. Some of the commands that offer a great deal of information are:
git log
: This lists all the commits with the latest situated on top of the listgit log –p
: This lists all the commits and with their correspondingdiffs
git tag –l
: This lists the available tagsgit checkout <tagname>
: This checks out a branch or tag from a working repositorygit log v2.6.32..master
: This lists all the changes between the given tag and the latest versiongit log –p V2.6.32..master MAINTAINERS
: This lists all the differences between the two given branches in theMAINTAINERS
file
Of course, this is just a small list with helpful commands. All the other commands are available at http://git-scm.com/docs/.
The Linux kernel offers support for a large variety of CPU architectures. Each architecture and individual board have their own maintainers, and this information is available inside the MAINTAINERS
file. Also, the difference between board porting is mostly given by the architecture, PowerPC being very different from ARM or x86. Since the development board that this book focuses on is an Atmel with an ARM Cortex-A5 core, this section will try to focus on ARM architecture.
The increase in popularity of the ARM architecture came with the refactoring of the work and the introduction of the device tree that dramatically reduced the amount of code available inside the mach-*
directories. If the SoC is supported by the Linux kernel, then adding support for a board is as simple as defining a device tree in the /arch/arm/boot/dts
directory with an appropriate name. For example, for <soc-name>-<board-name>.d
, include the relevant dtsi
files if necessary. Make sure that you build the device tree blob (DTB) by including the device tree into arch/arm/boot/dts/Makefile and add the missing device drivers for board.
- Generic code files: These usually have a single word name, such as
clock.c
,led.c
, and so on - CPU specific code: This is for the machine ID and usually has the
<machine-ID>*.c
form - for example,at91sam9263.c
,at91sam9263_devices.c
,sama5d3.c
, and so on - Board specific code: This usually is defined as board-*.c, such as
board-carmeva.c
,board-pcontrol-g20.c
,board-pcontrol-g20.c
, and so on
For a given board, the proper configuration should be made first inside the arch/arm/mach-*/Kconfig
file; for this, the machine ID should be identified for the board CPU. After the configuration is done, the compilation can begin, so for this, arch/arm/mach-*/Makefile
should also be updated with the required files to ensure board support. Another step is represented by the machine structure that defines the board and the machine type number that needs to be defined in the board-<machine>.c
file.
When the boot process starts in the first case, only the dtb
is necessary to pass to the boot loader and loaded to initialize the Linux kernel, while in the second case, the machine type number needs to be loaded in the R1
register. In the early boot process, __lookup_machine_type
looks for the machine_desc
structure and loads it for the initialization of the board.
After this information has been presented to you, and if you are eager to contribute to the Linux kernel, then this section should be read next. If you want to really contribute to the Linux kernel project, then a few steps should be performed before starting this work. This is mostly related to documentation and investigation of the subject. No one wants to send a duplicate patch or replicate the work of someone else in vain, so a search on the Internet on the topic of your interest could save a lot of time. Other useful advice is that after you've familiarized yourself with the subject, avoid sending a workaround. Try to reach the problem and offer a solution. If not, report the problem and describe it thoroughly. If the solution is found, then make both the problem and solution available in the patch.
One of the most valuable things in the open source community is the help you can get from others. Share your question and issues, but do not forget to mention the solution also. Ask the questions in appropriate mailing lists and try to avoid the maintainers, if possible. They are usually very busy and have hundreds and thousands of e-mails to read and reply. Before asking for help, try to research the question you want to raise, it will help both when formulating it but also it could offer an answer. Use IRC, if available, for smaller questions and lastly, but most importantly, try to not overdo it.
Before presenting the actual development process, a bit of history will be necessary. Until the 2.6 version of the Linux kernel project, one release was made every two or three years, and each of them was identified by even middle numbers, such as 1.0.x, 2.0.x, and 2.6.x. The development branches were instead defined using even numbers, such as 1.1.x, 2.1.x, and 2.5.x, and they were used to integrate various features and functionalities until a major release was prepared and ready to be shipped. All the minor releases had names, such as 2.6.32 and 2.2.23, and they were released between major release cycles.
- All the new minor release versions, such as 2.6.x, contain a two week merge window in which a number of features could be introduced in the next release
- This merge window will be closed with a release test version called 2.6.(x+1)-rc1
- Then a 6-8 weeks bug fixing period follows when all the bugs introduced by the added features should be fixed
- In the bug fixing interval, tests were run on the release candidate and the 2.6.(x+1)-rcY test versions were released
- After the final test were done and the last release candidate is considered sufficiently stable, a new release will be made with a name, such as 2.6.(x+1), and this process will be continued once again
This process worked great but the only problem was that the bug fixes were only released for the latest stable versions of the Linux kernel. People needed long term support versions and security updates for their older versions, general information about these versions that were long time supported, and so on.
Since a great number of patches and features are included in the Linux kernel everyday, it becomes difficult to keep track of all the changes, and the bigger picture in general. This changed over time because sites, such as http://kernelnewbies.org/LinuxChanges and http://lwn.net/, appeared to help developers keep in touch with the world of Linux kernel.
Besides these links, the git
versioning control system can offer much needed information. Of course, this requires the existence of Linux kernel source clones to be available on the workstation. Some of the commands that offer a great deal of information are:
git log
: This lists all the commits with the latest situated on top of the listgit log –p
: This lists all the commits and with their correspondingdiffs
git tag –l
: This lists the available tagsgit checkout <tagname>
: This checks out a branch or tag from a working repositorygit log v2.6.32..master
: This lists all the changes between the given tag and the latest versiongit log –p V2.6.32..master MAINTAINERS
: This lists all the differences between the two given branches in theMAINTAINERS
file
Of course, this is just a small list with helpful commands. All the other commands are available at http://git-scm.com/docs/.
The Linux kernel offers support for a large variety of CPU architectures. Each architecture and individual board have their own maintainers, and this information is available inside the MAINTAINERS
file. Also, the difference between board porting is mostly given by the architecture, PowerPC being very different from ARM or x86. Since the development board that this book focuses on is an Atmel with an ARM Cortex-A5 core, this section will try to focus on ARM architecture.
The increase in popularity of the ARM architecture came with the refactoring of the work and the introduction of the device tree that dramatically reduced the amount of code available inside the mach-*
directories. If the SoC is supported by the Linux kernel, then adding support for a board is as simple as defining a device tree in the /arch/arm/boot/dts
directory with an appropriate name. For example, for <soc-name>-<board-name>.d
, include the relevant dtsi
files if necessary. Make sure that you build the device tree blob (DTB) by including the device tree into arch/arm/boot/dts/Makefile and add the missing device drivers for board.
- Generic code files: These usually have a single word name, such as
clock.c
,led.c
, and so on - CPU specific code: This is for the machine ID and usually has the
<machine-ID>*.c
form - for example,at91sam9263.c
,at91sam9263_devices.c
,sama5d3.c
, and so on - Board specific code: This usually is defined as board-*.c, such as
board-carmeva.c
,board-pcontrol-g20.c
,board-pcontrol-g20.c
, and so on
For a given board, the proper configuration should be made first inside the arch/arm/mach-*/Kconfig
file; for this, the machine ID should be identified for the board CPU. After the configuration is done, the compilation can begin, so for this, arch/arm/mach-*/Makefile
should also be updated with the required files to ensure board support. Another step is represented by the machine structure that defines the board and the machine type number that needs to be defined in the board-<machine>.c
file.
When the boot process starts in the first case, only the dtb
is necessary to pass to the boot loader and loaded to initialize the Linux kernel, while in the second case, the machine type number needs to be loaded in the R1
register. In the early boot process, __lookup_machine_type
looks for the machine_desc
structure and loads it for the initialization of the board.
After this information has been presented to you, and if you are eager to contribute to the Linux kernel, then this section should be read next. If you want to really contribute to the Linux kernel project, then a few steps should be performed before starting this work. This is mostly related to documentation and investigation of the subject. No one wants to send a duplicate patch or replicate the work of someone else in vain, so a search on the Internet on the topic of your interest could save a lot of time. Other useful advice is that after you've familiarized yourself with the subject, avoid sending a workaround. Try to reach the problem and offer a solution. If not, report the problem and describe it thoroughly. If the solution is found, then make both the problem and solution available in the patch.
One of the most valuable things in the open source community is the help you can get from others. Share your question and issues, but do not forget to mention the solution also. Ask the questions in appropriate mailing lists and try to avoid the maintainers, if possible. They are usually very busy and have hundreds and thousands of e-mails to read and reply. Before asking for help, try to research the question you want to raise, it will help both when formulating it but also it could offer an answer. Use IRC, if available, for smaller questions and lastly, but most importantly, try to not overdo it.
MAINTAINERS
file. Also, the difference between board porting is mostly given
by the architecture, PowerPC being very different from ARM or x86. Since the development board that this book focuses on is an Atmel with an ARM Cortex-A5 core, this section will try to focus on ARM architecture.
The increase in popularity of the ARM architecture came with the refactoring of the work and the introduction of the device tree that dramatically reduced the amount of code available inside the mach-*
directories. If the SoC is supported by the Linux kernel, then adding support for a board is as simple as defining a device tree in the /arch/arm/boot/dts
directory with an appropriate name. For example, for <soc-name>-<board-name>.d
, include the relevant dtsi
files if necessary. Make sure that you build the device tree blob (DTB) by including the device tree into arch/arm/boot/dts/Makefile and add the missing device drivers for board.
- Generic code files: These usually have a single word name, such as
clock.c
,led.c
, and so on - CPU specific code: This is for the machine ID and usually has the
<machine-ID>*.c
form - for example,at91sam9263.c
,at91sam9263_devices.c
,sama5d3.c
, and so on - Board specific code: This usually is defined as board-*.c, such as
board-carmeva.c
,board-pcontrol-g20.c
,board-pcontrol-g20.c
, and so on
For a given board, the proper configuration should be made first inside the arch/arm/mach-*/Kconfig
file; for this, the machine ID should be identified for the board CPU. After the configuration is done, the compilation can begin, so for this, arch/arm/mach-*/Makefile
should also be updated with the required files to ensure board support. Another step is represented by the machine structure that defines the board and the machine type number that needs to be defined in the board-<machine>.c
file.
When the boot process starts in the first case, only the dtb
is necessary to pass to the boot loader and loaded to initialize the Linux kernel, while in the second case, the machine type number needs to be loaded in the R1
register. In the early boot process, __lookup_machine_type
looks for the machine_desc
structure and loads it for the initialization of the board.
After this information has been presented to you, and if you are eager to contribute to the Linux kernel, then this section should be read next. If you want to really contribute to the Linux kernel project, then a few steps should be performed before starting this work. This is mostly related to documentation and investigation of the subject. No one wants to send a duplicate patch or replicate the work of someone else in vain, so a search on the Internet on the topic of your interest could save a lot of time. Other useful advice is that after you've familiarized yourself with the subject, avoid sending a workaround. Try to reach the problem and offer a solution. If not, report the problem and describe it thoroughly. If the solution is found, then make both the problem and solution available in the patch.
One of the most valuable things in the open source community is the help you can get from others. Share your question and issues, but do not forget to mention the solution also. Ask the questions in appropriate mailing lists and try to avoid the maintainers, if possible. They are usually very busy and have hundreds and thousands of e-mails to read and reply. Before asking for help, try to research the question you want to raise, it will help both when formulating it but also it could offer an answer. Use IRC, if available, for smaller questions and lastly, but most importantly, try to not overdo it.
One of the most valuable things in the open source community is the help you can get from others. Share your question and issues, but do not forget to mention the solution also. Ask the questions in appropriate mailing lists and try to avoid the maintainers, if possible. They are usually very busy and have hundreds and thousands of e-mails to read and reply. Before asking for help, try to research the question you want to raise, it will help both when formulating it but also it could offer an answer. Use IRC, if available, for smaller questions and lastly, but most importantly, try to not overdo it.
The official location for the Linux kernel is available at http://www.kernel.org, but there a lot of smaller communities that contribute to the Linux kernel with their features or even maintain their own versions.
Although the Linux core contains the scheduler, memory management, and other features, it is quite small in size. The extremely large number of device drivers, architectures and boards support together with filesystems, network protocols and all the other components were the ones that made the size of the Linux kernel really big. This can be seen by taking a look at the size of the directories of the Linux.
The Linux source code structure contains the following directories:
arch
: This contains architecture-dependent codeblock
: This contains the block layer corecrypto
: This contains cryptographic librariesdrivers
: This gathers all the implementation of the device drivers with the exception of the sound onesfs
: This gathers all the available implementations of filesysteminclude
: This contains the kernel headersinit
: This has the Linux initialization codeipc
: This holds the interprocess communication implementation codekernel
: This is the core of the Linux kernellib
: This contains various libraries, such aszlibc
,crc
, and so onmm
: This contains the source code for memory managementnet
: This offers access to all the network protocol implementations supported inside Linuxsamples
: This presents a number of sample implementations, such askfifo
,kobject
, and so onscripts
: This is used both internally and externallysecurity
: This has a bunch of security implementation, such asapparmor
,selinux
,smack
, and so onsound
: This contains sound drivers and support codeusr
: This is theinitramfs cpio
archive that generates sourcesvirt
: This holds the source code for the virtualization supportCOPYING
: This represents the Linux license and the definition copying conditionsCREDITS
: This represents the collection of Linux's main contributorsDocumentation
: This contains corresponding documentation of kernel sourcesKbuild
: This represents the top-level kernel build systemKconfig
: This is the top-level descriptor for configuration parametersMAINTAINERS
: This a list with the maintainers of each kernel componentMakefile
: This represents the top-level makefileREADME
: This file describes what Linux is, it is the starting point for understanding the projectREPORTING-BUGS
: This offers information regarding the bug report procedure
As it can be seen, the source code of the Linux kernel is quite large, so a browsing tool would be required. There are a number of tools that can be used, such as Cscope, Kscope, or the web browser, Linux Cross Reference (LXR). Cscope is a huge project that can be also available with extensions for vim
and emacs
.
Before building a Linux kernel image, the proper configuration needs to be done. This is hard, taking into consideration that we have access to hundreds and thousands of components, such as drivers, filesystems, and other items. A selection process is done inside the configuration stage, and this is possible with the help of dependency definitions. The user has the chance to use and define a number of options that are enabled in order to define the components that will be used to build a Linux kernel image for a specific board.
Here are a number of variable options available for a configuration key:
bool
: These are the options can have true or false valuestristate
: This, besides the true and false options, also appears as a module optionint
: These values, are not that spread but they usually have a well-established value rangestring
: These values, are also not the most spread ones but usually contain some pretty basic information
Besides the manual configuration of the .config
file, configuration is the worst option for a developer, mostly because it can miss dependencies between some of the configurations. I would like to suggest to developers to use the make menuconfig
command that will launch a text console tool for the configuration of a kernel image.
After the configuration is done, the compilation process can be started. A piece of advice I would like to give is to use as many threads as possible if the host machine offers this possibility because it would help with the build process. An example of the build process start command is make –j 8
.
In an embedded development, the compilation process implies cross-compilation, the most visible difference with the native compilation process being the fact that it has a prefix with the target architecture available in the naming. The prefix setup can be done using the ARCH
variable that defines the name of the architecture of the target board and the CROSS_COMPILE
variable that defines the prefix for the cross-compilation toolchain. Both of them are defined in the top-level Makefile
.
The best option would be to set these variables as environment variables to make sure that a make process is not run for the host machine. Although it only works in the current terminal, it will be the best solution in the situation that no automation tool is available for these tasks, such as the Yocto Project. It is not recommended though to update the .bashrc
shell variables if you are planning to use more than one toolchain on the host machine.
Here are a number of variable options available for a configuration key:
bool
: These are the options can have true or false valuestristate
: This, besides the true and false options, also appears as a module optionint
: These values, are not that spread but they usually have a well-established value rangestring
: These values, are also not the most spread ones but usually contain some pretty basic information
Besides the manual configuration of the .config
file, configuration is the worst option for a developer, mostly because it can miss dependencies between some of the configurations. I would like to suggest to developers to use the make menuconfig
command that will launch a text console tool for the configuration of a kernel image.
After the configuration is done, the compilation process can be started. A piece of advice I would like to give is to use as many threads as possible if the host machine offers this possibility because it would help with the build process. An example of the build process start command is make –j 8
.
In an embedded development, the compilation process implies cross-compilation, the most visible difference with the native compilation process being the fact that it has a prefix with the target architecture available in the naming. The prefix setup can be done using the ARCH
variable that defines the name of the architecture of the target board and the CROSS_COMPILE
variable that defines the prefix for the cross-compilation toolchain. Both of them are defined in the top-level Makefile
.
The best option would be to set these variables as environment variables to make sure that a make process is not run for the host machine. Although it only works in the current terminal, it will be the best solution in the situation that no automation tool is available for these tasks, such as the Yocto Project. It is not recommended though to update the .bashrc
shell variables if you are planning to use more than one toolchain on the host machine.
configuration is done, the compilation process can be started. A piece of advice I would like to give is to use as many threads as possible if the host machine offers this possibility because it would help with the build process. An example of the build process start command is make –j 8
.
In an embedded development, the compilation process implies cross-compilation, the most visible difference with the native compilation process being the fact that it has a prefix with the target architecture available in the naming. The prefix setup can be done using the ARCH
variable that defines the name of the architecture of the target board and the CROSS_COMPILE
variable that defines the prefix for the cross-compilation toolchain. Both of them are defined in the top-level Makefile
.
The best option would be to set these variables as environment variables to make sure that a make process is not run for the host machine. Although it only works in the current terminal, it will be the best solution in the situation that no automation tool is available for these tasks, such as the Yocto Project. It is not recommended though to update the .bashrc
shell variables if you are planning to use more than one toolchain on the host machine.
The best option would be to set these variables as environment variables to make sure that a make process is not run for the host machine. Although it only works in the current terminal, it will be the best solution in the situation that no automation tool is available for these tasks, such as the Yocto Project. It is not recommended though to update the .bashrc
shell variables if you are planning to use more than one toolchain on the host machine.
As I mentioned previously, the Linux kernel has a lot of kernel modules and drivers that are already implemented and available inside the source code of the Linux kernel. A number of them, being so many, are also available outside the Linux kernel source code. Having them outside not only reduces the boot time by not initializing them at boot time, but is done instead at the request and needs of users. The only difference is that the loading and unloading of the modules requires root access.
For module interaction, multiple utilities used for multiple operations are available, such as modinfo
, which is used for information gathering about modules; insmod
is able for loading a module when the fill path to the kernel module is given. Similar utilities for a module are available. One of them is called modprobe
and the difference in modprobe
is that the full path is not necessary, as it is responsible for loading dependent modules of the chosen kernel object before loading itself. Another functionality that modprobe
offers is the –r
option. It is the remove functionality which offers support for removing the module and all its dependencies. An alternative to this is the rmmod
utility, which removes modules not used anymore. The last utility available is lsmod
, which lists the loaded modules.
The simplest kernel module example that can be written looks something similar to this:
Since the Linux kernel version 2.2, there is a possibility of using the _init and __exit
macros in this way:
The preceding macros are removed, the first one after the initialization, and the second one when the module is built-in within the Linux kernel sources.
As mentioned previously, a kernel module is not only available inside a Linux kernel, but also outside of the Linux kernel tree. For a built-in kernel module, the compile process is similar to the one of other available kernel modules and a developer can inspire its work from one of them. The kernel module available outside of the Linux kernel drivers and the build process requires access to the sources of the Linux kernel or the kernel headers.
For a kernel module available outside of the Linux kernel sources, a Makefile
example is available, as follows:
KDIR := <path/to/linux/kernel/sources> PWD := $(shell pwd) obj-m := hello_world.o all: $(MAKE) ARCH=arm CROSS_COMPILE=<arm-cross-compiler-prefix> -C $(KDIR) M=$(PWD)
For a module that is implemented inside a Linux kernel, a configuration for the module needs to be made available inside the corresponding Kconfig
file with the correct configuration. Also, the Makefile
near the Kconfig
file needs to be updated to let the Makefile
system know when the configuration for the module is updated and the sources need to be built. We will see an example of this kind for a kernel device driver here.
An example of the Kconfig
file is as follows:
An example of the Makefile
is as follows:
A driver is usually used as an interface with a framework that exposes a number of hardware features, or with a bus interface used to detect and communicate with the hardware. The best example is shown here:
An inheritance mechanism is used to create specialized structures from more generic ones, such as struct device_driver
and struct device
for every bus subsystem. The bus driver is the one responsible for representing each type of bus and matching the corresponding device driver with the detected devices, detection being done through an adapter driver. For nondiscoverable devices, a description is made inside the device tree or the source code of the Linux kernel. They are handled by the platform bus that supports platform drivers and in return, handles platform devices.
Having to debug the Linux kernel is not the most easy task, but it needs to be accomplished to make sure that the development process moves forward. Understanding the Linux kernel is, of course, one of the prerequisites. Some of the available bugs are very hard to solve and may be available inside the Linux kernel for a long period of time.
Although Linus Torvalds and the Linux community do not believe that the existence of a kernel debugger will do much good to a project, a better understanding of the code is the best approach for any project. There are still some debugger solutions that are available to be used. GNU debugger (gdb
) is the first one and it can be used in the same way as for any other process. Another one is the kgdb
a patch over gdb
that permits debugging of serial connections.
Moving on to the Yocto Project, we have recipes available for every kernel version available inside the BSP support for each supported board, and recipes for kernel modules that are built outside the Linux kernel source tree.
The recipe firstly defines repository-related information. It is defined through variables, such as SRC_URI
and SRCREV
. It also indicates the branch of the repository through the KBRANCH
variable, and also the place from where defconfig
needs to be put into the source code to define the .config
file. As seen in the recipe, there is an update made to the do_deploy
task for the kernel recipe to add the device driver to the tmp/deploy/image/sama5d3-xplained
directory alongside the kernel image and other binaries.
After the kernel is built by running the bitbake virtual/kernel
command, the kernel image will be available inside the tmp/deploy/image/sama5d3-xplained
directory under the zImage-sama5d3-xplained.bin
name, which is a symbolic link to the full name file and has a larger name identifier. The kernel image was deployed here from the place where the Linux kernel tasks were executed. The simplest method to discover that place would be to run bitbake –c devshell virtual/kernel
. A development shell will be available to the user for direct interaction with the Linux kernel source code and access to task scripts. This method is preferred because the developer has access to the same environment as bitbake
.
As mentioned in the example of the Linux kernel external module, the last two lines of each kernel module that is external or internal is packaged with the kernel-module-
prefix to make sure that when the IMAGE_INSTALL
variable is available, the value kernel-modules are added to all kernel modules available inside the /lib/modules/<kernel-version>
directory. The kernel module recipe is very similar to any available recipe, the major difference being in the form of the module inherited, as shown in the line inherit module.
A root filesystem consists of a directory and file hierarchy. In this file hierarchy, various filesystems can be mounted, revealing the content of a specific storage device. The mounting is done using the mount command, and after the operation is done, the mount point is populated with the content available on the storage device. The reverse operation is called umount
and is used to empty the mount point of its content.
A root filesytem is available in the root hierarchy, also known as /
. It is the first available filesystem and also the one on which the mount
command is not used, since it is mounted directly by the kernel through the root= argument
. The following are the multiple options to load the root filesystem:
Besides the options that require interaction with a board's internal memory or storage devices, one of the most used methods to load the root filesystem is represented by the NFS option, which implies that the root filesystem is available on your local machine and is exported over the network on your target. This option offers the following advantages:
- The size of the root filesystem will not be an issue due to the fact that the storage space on the development machine is much larger than the one available on the target
- The update process is much easier and can be done without rebooting
- Having access to an over the network storage is the best solution for devices with small even inexistent internal or external storage devices
For the client side available on the target, the Linux kernel needs to be configured accordingly to make sure that the NFS support is enabled, and also that an IP address will be available at boot time. This configurations are CONFIG_NFS_FS=y
, CONFIG_IP_PNP=y
, and CONFIG_ROOT_NFS=y
. The kernel also needs to be configured with the root=/dev/nfs
parameter, the IP address for the target, and the NFS server nfsroot=192.168.1.110:/nfs/rootfs
information. Here is an example of the communication between the two components:
There is also the possibility of having a root filesystem integrated inside the kernel image, that is, a minimal root filesytem whose purpose is to start the full featured root filesystem. This root filesystem is called initramfs
. This type of filesystem is very helpful for people interested in fast booting options of smaller root filesystems that only contain a number of useful features and need to be started earlier. It is useful for the fast loading of the system at boot time, but also as an intermediate step before starting the real root filesystem available on one of the available storage locations. The root filesystem is first started after the kernel booting process, so it makes sense for it to be available alongside the Linux kernel, as it resides near the kernel on the RAM memory. The following image explains this:
To create initramfs
, configurations need to be made available. This happens by defining either the path to the root filesystem directory, the path to a cpio
archive, or even a text file describing the content of the initramfs
inside the CONFIG_INITRAMFS_SOURCE
. When the kernel build starts, the content of CONFIG_INITRAMFS_SOURCE
will be read and the root filesystem will be integrated inside the kernel image.
The initial RAM disk or initrd
is another mechanism of mounting an early root filesystem. It also needs the support enabled inside the Linux kernel and is loaded as a component of the kernel. It contains a small set of executables and directories and represents a transient stage to the full featured root filesystem. It only represents the final stage for embedded devices that do not have a storage device capable of fitting a bigger root filesystem.
Using initrd
is not as simple as initramfs
. In this case, an archive needs to be copied in a similar manner to the one used for the kernel image, and the bootloader needs to pass its location and size to the kernel to make sure that it has started. Therefore, in this case, the bootloader also requires the support of initrd
. The central point of the initrd
is constituted by the linuxrc
file, which is the first script started and is usually used for the purpose of offering access to the final stage of the system boot, that is, the real root filesytem. After linuxrc
finishes the execution, the kernel unmounts it and continues with the real root filesystem.
No matter what their provenience is, most of the available root filesystems have the same organization of directories, as defined by the Filesystem Hierarchy Standard (FHS), as it is commonly called. This organization is of great help to both developers and users because it not only mentions a directory hierarchy, but also the purpose and content of the directories The most notable ones are:
/bin
: This refers to the location of most programs/sbin
: This refers to the location of system programs/boot
: This refers to the location for boot options, such as thekernel image
,kernel config
,initrd
,system maps
, and other information/home
: This refers to the user home directory/root
: This refers to the location of the root user's home location/usr
: This refers to user-specific programs and libraries, and mimics parts of the content of the root filesystem/lib
: This refers to the location of libraries/etc
: This refers to the system-wide configurations/dev
: This refers to the location of device files/media
: This refers to the location of mount points of removable devices/mnt
: This refers to the mount location point of static media/proc
: This refers to the mounting point of theproc
virtual filesystem/sys
: This refers to the mounting point of thesysfs
virtual filesystem/tmp
: This refers to the location temporary files/var
: This refers to data files, such as logging data, administrative information, or the location of transient data
The FHS changes over time, but not very much. Most of the previously mentioned directories remain the same for various reasons - the simplest one being the fact that they need to ensure backward compatibility.
The root filesystems are started by the kernel, and it is the last step done by the kernel before it ends the boot phase. Here is the exact code to do this:
/* * We try each of these until one succeeds. * * The Bourne shell can be used instead of init if we are * trying to recover a really broken machine. */ if (execute_command) { ret = run_init_process(execute_command); if (!ret) return 0; pr_err("Failed to execute %s (error %d). Attempting defaults...\n",execute_command, ret); } if (!try_to_run_init_process("/sbin/init") || !try_to_run_init_process("/etc/init") || !try_to_run_init_process("/bin/init") || !try_to_run_init_process("/bin/sh")) return 0; panic("No working init found. Try passing init= option to kernel." "See Linux Documentation/init.txt for guidance.");
In this code, it can easily be identified that there are a number of locations used for searching the init
process that needs to be started before exiting from the Linux kernel boot execution. The run_init_process()
function is a wrapper around the execve()
function that will not have a return value if no errors are encountered in the call procedure. The called program overwrites the memory space of the executing process, replacing the calling thread and inheriting its PID
.
Runlevel value |
Runlevel purpose |
---|---|
|
It refers to the shutdown and power down command for the whole system |
|
It is a single-user administrative mode with a standard login access |
|
It is multiuser without a TCP/IP connection |
|
It refers to a general purpose multiuser |
|
It is defined by the system's owner |
|
It refers to graphical interface and TCP/IP connection multiuser systems |
|
It refers to a system reboot |
|
It is a single user mode that offers access to a minimal root shell |
Each runlevel starts and kills a number of services. The services that are started begin with S
, and the ones that a killed begin with K
. Each service is, in fact, a shell script that defines the behaviour of the provides that it defines.
When the preceding inittab
file is parsed by the init
, the first script that is executed is the si::sysinit:/etc/init.d/rcS
line, identified through the sysinit
tag. Then, runlevel 5
is entered and the processing of instructions continues until the last level, until a shell is finally spawned using /sbin/getty symlink
. More information on either init
or inittab
can be found by running man init
or man inittab
in the console.
One of the most important challenges for the Linux system is the access allowed to applications to various hardware devices. Notions, such as virtual memory, kernel space, and user space, do not help in simplifying things, but add another layer of complexity to this information.
A device driver has the sole purpose of isolating hardware devices and kernel data structures from user space applications. A user does not need to know that to write data to a hard disk, he or she will be required to use sectors of various sizes. The user only opens a file to write inside it and close when finished. The device driver is the one that does all the underlying work, such as isolating complexities.
- Block devices: These are composed of fixed size blocks that are usually used when interacting with hard disks, SD cards, USB sticks, and so on
- Character devices: These are streams of characters that do not have a size, beginning, or end; they are mostly not in the form of block devices, such as terminals, serial ports, sound card and so on
Each device has a structure that offers information about it:
The mknod
utility that creates the device node uses a triplet of information, such as mknod /dev/testdev c 234 0
. After the command is executed, a new /dev/testdev
file appears. It should bind itself to a device driver that is already installed and has already defined its properties. If an open
command is issued, the kernel looks for the device driver that registered with the same major number as the device node. The minor number is used for handling multiple devices, or a family of devices, with the same device driver. It is passed to the device driver so that it can use it. There is no standard way to use the minor, but usually, it defines a specific device from a family of the devices that share the same major number.
devfs
: This refers to a device manager that is devised as a filesystem and is also accessible on a kernel space and user space.devtmpfs
: This refers to a virtual filesystem that has been available since the 2.6.32 kernel version release, and is an improvement todevfs
that is used for boot time optimizations. It only creates device nodes for hardware available on a local system.udev
: This refers to a daemon used on servers and desktop Linux systems. More information on this can be referred to by accesing https://www.kernel.org/pub/linux/utils/kernel/hotplug/udev/udev.html. The Yocto Project also uses it as the default device manager.mdev
: This offers a simpler solution thenudev
; it is, in fact, a derivation of udev.
Since system objects are also represented as files, it simplifies the method of interaction with them for applications. This would not been possible without the use of device nodes, that are actually files in which normal file interaction functions can be applied, such as open()
, read()
, write()
, and close()
.
The root filesystem can be deployed under a very broad form of the filesystem type, and each one does a particular task better than the rest. If some filesystems are optimized for performance, others are better at saving space or even recovering data. Some of the most commonly used and interesting ones will be presented here.
The logical division for a physical device, such as a hard disk or SD card, is called a partition. A physical device can have one or more partitions that cover its available storage space. It can be viewed as a logical disk that has a filesystem available for the user's purposes. The management of partitions in Linux is done using the fdisk
utility. It can be used to create
, list
, destroy
, and other general interactions, with more than 100 partition types. To be more precise, 128 partition types are available on my Ubuntu 14.04 development machine.
One of the most used and well known filesystem partition formats is ext2
. Also called second extended filesystem, it was introduced in 1993 by Rémy Card, a French software developer. It was used as the default filesystem for a large number of Linux distributions, such as Debian and Red Hat Linux, until it was replaced by its younger brothers, ext3
and ext4
. It continues to remain the choice of many embedded Linux distributions and flash storage devices.
The ext2
filesystem splits data into blocks, and the blocks are arranged into block groups. Each block group maintains a copy of a superblock and the descriptor table for that block group. Superblocks are to store configuration information, and hold the information required by the booting process, although there are available multiple copies of it; usually, the first copy that is situated in the first block of the file system is the one used. All the data for a file is usually kept in a single block so that searches can be made faster. Each block group, besides the data it contains, has information about the superblock, descriptor table for the block group, inode bitmap and table information, and the block bitmap. The superblock is the one that holds the information important for the booting process. Its first block is used for the booting process. The last notion presented is in the form of inodes
, or the index nodes, which represent files and directories by their permission, size, location on disk, and ownership.
There are multiple applications used for interaction with the ext2
filesystem format. One of them is
mke2fs
, which is used to create an ext2
filesystem on a mke2fs /deb/sdb1 –L
partition (ext2
label partition). The is the e2fsck
command, which is used to verify the integrity of the filesystem. If no errors are found, these tools give you information about the partition filesystem configuration, e2fsck /dev/sdb1
. This utility is also able to fix some of the errors that appear after improper utilization of the device, but cannot be used in all scenarios.
Ext3 is another powerful and well known filesystem. It replaced ext2
and became one of the most used filesystems on Linux distributions. It is in fact similar to ext2
; the difference being that it has the possibility to journalize the information available to it. The ext2
file format can be changed in an ext3
file format using the tune2fs –j /dev/sdb1
command. It is basically seen as an extension for the ext2
filesystem format, one that adds the journaling feature. This happens because it was engineered to be both forward and backward compatible.
Ext4 is the successor of ext3
, and was built with the idea of improving the performance and the storage limit in ext3
. It is also backward compatible with the ext3
and ext2
filesystems and also adds a number of features:
- Persistent preallocation: This defines the
fallocate()
system call that can be used to preallocate space, which is most likely in a contiguous form; it is very useful for databases and streaming of media - Delayed allocation: This is also called allocate-on-flush; it is used to delay the allocation blocks from the moment data from the disk is flushed, to reduce fragmentation and increase performance
- Multi block allocation: This is a side effect of delayed allocation because it allows for data buffering and, at the same time, the allocation of multiple blocks.
- Increase subdirectory limit: This the
ext3
has a limit of 32000 subdirectories, theext4
does not have this limitation, that is, the number of subdirectories are unlimited - Checksum for journal: This is used to improve reliability
Journalling Flash Filesystem version 2 (JFFS2) is a filesystem designed for the NAND and NOR flash memory. It was included in the Linux mainline kernel in 2001, the same year as the ext3
filesystem, although in different months. It was released in November for the Linux version 2.4.15, and the JFFS2 filesystem was released in September with the 2.4.10 kernel release. Since it's especially used to support flash devices, it takes into consideration certain things, such as the need to work with small files, and the fact that these devices have a wear level associated with them, which solves and reduces them by their design. Although JFFS2 is the standard for flash memory, there are also alternatives that try to replace it, such as LogFS, Yet Another Flash File System (YAFFS), and Unsorted Block Image File System (UBIFS).
Besides the previously mentioned filesystems, there are also some pseudo filesystems available, including proc
, sysfs
, and tmpfs
. In the next section, the first two of them will be described, leaving the last one for you to discover by yourself.
The proc
filesystem is a virtual filesystem available from the first version of Linux. It was defined to allow a kernel to offer information to the user about the processes that are run, but over time, it has evolved and is now able to not only offer statistics about processes that are run, but also offer the possibility to adjust various parameters regarding the management of memory, processes, interrupts, and so on.
The proc
filesystem has the following structure:
- For each running process, there is an available directory inside
/proc/<pid>
.It contains information about opened files, used memory, CPU usage, and other process-specific information. - Information on general devices is available inside
/proc/devices
,/proc/interrupts
,/proc/ioports
, and/proc/iomem
. - The kernel command line is available inside
/proc/cmdline
. - Files used to change kernel parameters are available inside
/proc/sys
. More information is also available insideDocumentation/sysctl
.
The sysfs
filesystem is used for the representation of physical devices. It is available since the introduction of the 2.6 Linux kernel versions, and offers the possibility of representing physical devices as kernel objects and associate device drivers with corresponding devices. It is very useful for tools, such as udev
and other device managers.
BusyBox was developed by Bruce Perens in 1999 with the purpose of integrating available Linux tools in a single executable. It has been used with great success as a replacement for a great number of Linux command line utilities. Due to this, and the fact that it is able to fit inside small embedded Linux distributions, it has gained a lot of popularity in the embedded environment. It provides utilities from file interactions, such as cp
, mkdir
, touch
, ls
, and cat
, as well as general utilities, such as dmesg
, kill
, fdisk
, mount
, umount
, and many others.
Not only is it very easy to configure and compile, but it is also very easy to use. The fact that it is very modular and offers a high degree of configuration makes it the perfect choice to use. It may not include all the commands available in a full-blown Linux distribution available on your host PC, but the ones that it does are more than enough. Also, these commands are just simpler versions of the full-blown ones used at implementation level, and are all integrated in one single executable available in /bin/busybox
as symbolic links of this executable.
The configuration of the BusyBox package also has a menuconfig
option available, similar to the one available for the kernel and U-Boot, that is, make
menuconfig
. It is used to show a text menu that can be used for faster configuration and configuration searches. For this menu to be available, first the ncurses
package needs to be available on the system that calls the make menuconfig
command.
It presents the list of the utilities enabled in the configuration stage. To invoke one of the preceding utilities, there are two options. The first option requires the use of the BusyBox binary and the number of utilities called, which are represented as ./busybox ls
, while the second option involves the use of the symbolic link already available in directories, such as /bin, /sbin, /usr/bin
, and so on.
Now that all the information relating to the root
filesystem has been presented to you, it would be good exercise to describe the must-have components of the minimal root
filesystem. This would not only help you to understand the rootfs
structure and its dependencies better, but also help with requirements needed for boot time and the size optimization of the root
filesystem.
From this information, the /lib
directory structure is defined. Its minimal form is:
The following symbolic links to ensure backward compatibility and version immunity for the libraries. The linux-gate.so.1
file in the preceding code is a virtual dynamically linked shared object (vDSO), exposed by the kernel at a well established location. The address where it can be found varies from one machine architecture to another.
Having mentioned all of this, the minimal root
filesystem seems to have only five directories and eight files. Its minimal size is below 2 MB and around 80 percent of its size is due to the C library package. It is also possible to minimize its size by using the Library Optimizer Tool. You can find more information on this at http://libraryopt.sourceforge.net/.
Moving to the Yocto Project, we can take a look at the core-image-minimal to identify its content and minimal requirements, as defined inside the Yocto Project. The core-image-minimal.bb
image is available inside the meta/recipes-core/images
directory, and this is how it looks:
Constructing a root
filesystem is not an easy task for anyone, but Yocto does it with a bit more success. It starts from the base-files recipe that is used to lay down the directory structure according to the Filesystem Hierarchy Standard (FHS), and, along with it, a number of other recipes are placed. This information is available inside the ./meta/recipes-core/packagegroups/packagegroup-core-boot.bb
recipe. As can be seen in the previous example, it also inherits a different kind of class, such as packagegroup.bbclass
, which is a requirement for all the package groups available. However, the most important factor is that it clearly defines the packages that constitute packagegroup
. In our case, the core boot package group contains packages, such as base-files
, base-passwd
(which contains the base system master password and group files), udev
, busybox
, and sysvinit
(a System V similar to init).
As can be seen in the previously shown file, the BusyBox package is a core component of the Yocto Project's generated distributions. Although information was available about the fact that BusyBox can offer an init alternative, the default Yocto generated distributions do not use this. Instead, they choose to move to the System V-like init, which is similar to the one available for Debian-based distributions. Nevertheless, a number of shell interaction tools are made available through the BusyBox recipe available inside the meta/recipes-core/busybox
location. For users interested in enhancing or removing some of features made available by the busybox
package, the same concepts that are available for the Linux kernel configuration are used. The busybox
package uses a defconfig
file on which a number of configuration fragments are applied. These fragments can add or remove features and, in the end, the final configuration file is obtained. This identifies the final features available inside the root
filesystem.
Inside this image, there is also another more generic image definition that is inherited. Here, I am referring to the atmel-demo-image.inc
file, and when opened, you can see that it contains the core of all the meta-atmel
layer images. Of course, if all the available packages are not enough, a developer could decide to add their own. There has two possibilities in front of a developer: to create a new image, or to add packages to an already available one. The end result is built using the bitbake atmel-xplained-demo-image
command. The output is available in various forms, and they are highly dependent on the requirements of the defined machine. At the end of the build procedure, the output will be used to boot the root filesystem on the actual board.
Poky represents the reference build system for the metadata and tools of the Yocto Project, which are used as starting points for anyone interested in interacting with the Yocto Project. It is platform-independent and provides the tools and mechanisms to build and customize the end result, which is in fact a Linux software stack. Poky is used as the central piece of interaction with the Yocto Project.
When working with the Yocto Project as a developer, it is very important to have information about mailing lists and an Internet Relay Chat (IRC) channel. Also, Project Bugzilla can be a source of inspiration in terms of a list of available bugs and features. All of these elements would need a short introduction, so the best starting point would be the Yocto Project Bugzilla. It represents a bug tracking application for the users of the Yocto Project and is the place where problems are reported. The next component is represented by the available channels of IRC. There are two available components on a freenode, one used for Poky and the other for discussions related to the Yocto Project, such as #poky and #yocto, respectively. The third element is represented by the Yocto Project mailing lists, which are used to subscribe to these mailing lists of the Yocto Project:
- http://lists.yoctoproject.org/listinfo/yocto: This refers to the mailing list where the Yocto Project discussions take place
- http://lists.yoctoproject.org/listinfo/poky: This refers to the mailing list where discussions regarding the Poky build of the Yocto Project system take place
- http://lists.yoctoproject.org/listinfo/yocto-announce: This refers to the mailing list where official announcements of the Yocto Project are made, as well as where milestones of the Yocto Project are presented
With the help of http://lists.yoctoproject.org/listinfo, more information can be gathered regarding general and project-specific mailing lists. It contains a list of all the mailing lists available at https://www.yoctoproject.org/tools-resources/community/mailing-lists.
In order to initiate development using the Yocto Project in general, and Poky in particular, you should not only use the previously mentioned components; some information regarding these tolls should also be made available. A very good explanation of the Yocto Project is available on their documentation page at https://www.yoctoproject.org/documentation. Those of you interested in reading a shorter introduction, it may be worth checking out the Embedded Linux Development with Yocto Project, Otavio Salvador and Daiane Angolini, by Packt Publishing.
To use the Yocto Project, a number of specific requirements are needed:
- A host system: Let's assume that this is a Linux-based host system. However, it is not just any host system; Yocto has certain requirements. The supported operating systems are available inside the
poky.conf
file, available inside directorymeta-yocto/conf/distro
. The supported operating systems are defined in theSANITY_TESTED_DISTROS
variable, and a few of these systems are as follows:- Ubuntu-12.04
- Ubuntu-13.10
- Ubuntu-14.04
- Fedora-19
- Fedora-20
- CentOS-6.4
- CentOS-6.5
- Debian-7.0
- Debian-7.1
- Debian-7.2
- Debian-7.3
- Debian-7.4
- Debian-7.5
- Debian-7.6
- SUSE-LINUX-12.2
- openSUSE-project-12.3
- openSUSE-project-13.1
- Required packages: This contains a list of the minimum requirements for the packages available on the host system, besides the ones already available. Of course, this is different from one host system to another and the systems vary according to their purposes. However, for the Ubuntu host, we need the following requirements:
- Essentials: This refers to
sudo apt-get install gawk wget git-core diffstat unzip texinfo gcc-multilib build-essential chrpath socat
- Graphical and Eclipse Plug-in extras: This refers to
sudo apt-get install libsdl1.2-dev xterm
- Documentation: This refers to
sudo apt-get install make xsltproc docbook-utils fop dblatex xmlto
- ADT Installer Extras: This refers to
sudo apt-get install autoconf automake libtool libglib2.0-dev
- Essentials: This refers to
- Yocto Project release: Before staring any work, one of the available Poky releases should be chosen. This book is based on the dizzy branch, which is the Poky 1.7 version, but a developer can chose whatever fits him or her best. Of course, since the interaction with the project is done using the
git
versioning system, the user will first need to clone the Poky repository, and any contributions to the project should be submitted as a patch to the open source community. There is also a possibility of getting a tar archive, but this method has some limitations due to the fact that any changes done on the source are harder to trace, and it also limits the interaction with the community involved in the project.
- Custom Yocto Project kernel interaction: If a developer decides that the kernel source Yocto Projects are maintained and are not suitable for their needs, they could get one of the local copies of the Yocto Project supported by kernel versions, available at http://git.yoctoproject.org/cgit.cgi under the Yocto Linux Kernel section, and modify it according to their needs. These changes, of course, along with the rest of the kernel sources, will need to reside in a separate repository, preferably
git
, and it will be introduced to the Yocto world through a kernel recipe. - The meta-yocto-kernel-extras git repository: Here the metadata needed is gathered when building and modifying kernel images. It contains a bunch of
bbappend
files that can be edited to indicate to the local that the source code has changed, which is a more efficient method to use when you are working on the development of features of the Linux kernel. It is available under the Yocto Metadata Layers section at http://git.yoctoproject.org/cgit.cgi. - Supported Board Support Packages (BSPs): There are a large number of BSP layers that are available and supported by the Yocto Project. The naming of each BSP layer is very simple,
meta-<bsp-name>
, and can be found at http://git.yoctoproject.org/cgit.cgi under the Yocto Metadata Layers section. Each BSP layer is, in fact, a collection of recipes that define the behavior and minimum requirements offered by the BSP provider. More information regarding the development of BSP can be found at http://www.yoctoproject.org/docs/1.7/dev-manual/dev-manual.html#developing-a-board-support-package-bsp. - Eclipse Yocto Plug-ins: For developers who are interested in writing applications, an Eclipse Integrated Development Environment (IDE) is available with Yocto-specific plug-ins. You can find more information on this at http://www.yoctoproject.org/docs/1.7/dev-manual/dev-manual.html#setting-up-the-eclipse-ide.
The development process inside the Yocto Project has many meanings. It can refer to the various bugs and features that are available inside the Yocto Project Bugzilla. The developer can assign one of them to his or her account and solve it. Various recipes can be upgraded, and this process also requires the developer's involvement; new features can also be added and various recipes need to be written by developers. All these tasks need to have a well defined process in place that also involves git
interaction.
To send changes added in the recipes back into the community, the available create-pull-request and send-pull request scripts can be used. These scripts are available inside the poky repository in the scripts directory. Also, in this section, there are also a bunch of other interesting scripts available, such as the create-recipe
script, and others that I will let you discover on your own. The other preferred method to send the changes upstream would be to use the manual method, which involves interaction with git
commands, such as git add
, git commit –s
, git format-patch
, git send-email
, and others.
- System development: This covers the development of the BSP, kernel development, and its configurations. Each of them has a section in the Yocto Project documentation describing respective development processes, as shown at http://www.yoctoproject.org/docs/1.7/bsp-guide/bsp-guide.html#creating-a-new-bsp-layer-using-the-yocto-bsp-script and http://www.yoctoproject.org/docs/1.7/kernel-dev/kernel-dev.html.
- User application development: This covers the development of applications for a targeted hardware device. The information regarding the necessary setup for the application development on the host system is available at http://www.yoctoproject.org/docs/1.7/adt-manual/adt-manual.html. This component will also be discussed in the Eclipse ADT Plug-ins section of this chapter.
- Temporary modification of source code: This covers the temporary modifications that appear in the development process. This involves the solution for various implementation problems that are available in a project's source code. After the problem is solved, the changes need to be available upstream and applied accordingly.
- Development of a Hob image: The Hob build system can be used for operating and customizing system images. It is a graphical interface developed in Python as a more efficient interface with the Bitbake build system.
- Devshell development: This is a method of development that uses the exact environment of the Bitbake build system's tasks. It is one of the most efficient methods used for debugging or package editing. It is also one of the quickest ways to set up the build environment when writing various components of a project.
For operating systems where the provided components are too old to satisfy the requirements of the Yocto Project, a buildtools toolchain is recommended for providing the required versions of the software. There are two methods used for installing a buildtools
tarball. The first method implies the use of an already available prebuilt tarball, and the second one involves building it using the Bitbake build system. More information about this option can be found in the subsections under the Required Git, tar, and Python Versions section of the Yocto documentation mega manual available at http://www.yoctoproject.org/docs/1.7/mega-manual/mega-manual.html#required-git-tar-and-python-versions.
The Application Development Toolkit, also called ADT, provides a cross-development platform suitable for custom build and user-targeted applications. It is comprised of the following elements:
- A cross-toolchain: It is associated with the
sysroot
, both of them being automatically generated using Bitbake, and the target-specific metadata is made available by the target hardware supplier. - The Quick Emulator environment (Qemu): It is used to simulate the target hardware.
- User-space tools: It improves the overall experience of development of an application
- Eclipse IDE: It contains Yocto Project-specific plug-ins
The toolchain can be generated using multiple methods. The most common one is to download the toolchain from http://downloads.yoctoproject.org/releases/yocto/yocto-1.7/toolchain/, and get the appropriate toolchain installer for your host and target. One such example is the poky-glibc-x86_64-core-image-sato-armv7a-vfp-neon-toolchain-1.7.sh
script, which when executed will install the toolchain in the default location of the /opt/poky/1.7/
directory. This location can be changed if proper arguments are offered in the script before starting the execution of the script.
Another method I prefer to use when generating a toolchain involves the use of the Bitbake build system. Here, I am referring to meta-ide-support
. When running bitbake meta-ide-support
, the cross-toolchain is generated and it populates the build directory. After this task is finished, the same result is obtained as in the previously mentioned solution, but in this case, a build directory that is already available is used. The only remaining task for both solutions would be to set up the environment using the script that contains the environment-setup
string and start using it.
The Qemu emulator offers the possibility to simulate one hardware device when this one is not available. There are multiple ways of making it available in the development process:
- Install the ADT using the adt-installer generated script. One of the steps available in this script offers the possibility to enable or disable the use of Qemu in the development process.
- A Yocto Project release is downloaded and in the development process, the environment is set up by default. Then, the Qemu is installed and available for use.
- A
git
clone of the Poky repository is created and the environment is set up. In this case, the Qemu is installed and available also. - The
cross-toolchain
tarball was downloaded, installed, and the environment was set up. This also, by default, enables the use of Qemu and installs it for later use.
The user-space tools are included into the distribution and are used during the development process. They are very common on a Linux platform and can include the following:
- Perf: It is a Linux performance counter that measures certain hardware and software events. More information about this is available at https://perf.wiki.kernel.org/, and also on the profiling and tracing manual of Yocto, where a whole section is devoted to this tool.
- PowerTop: It is a power measurement tool that is used to determine the amount of power a software consumes. More information about it is available at https://01.org/powertop/.
- LatencyTop: It is a similar tool to PowerTop, the difference being that this one focuses on the latency measurement from audio skips and stutters on the desktop to server overload; it has measurement for these kind of scenarios and answers for the latency problems. Although it seems that no commit has been done inside this project since 2009, it is still used today due to the fact that it is very useful.
- OProfile: It represents a system-wide profiler for the Linux ecosystem with a low overhead. More information about it is available at http://oprofile.sourceforge.net/about/. It also has a section available in the profiling and tracing manual of Yocto.
- SystemTap: It offers information on the infrastructure of a running Linux system, as well as the performance and functional problems of the system. It is not available though as an Eclipse extension, but only as a tool inside the Linux distribution. More information about it can be found at http://sourceware.org/systemtap. It also has a section defined in the profiling and tracing manual of Yocto.
- Lttng-ust: It is the user-space tracer for the
lttng
project and offers information related to user-space activities. More information is available at http://lttng.org/.
The last element of the ADT platform is represented by the Eclipse IDE. It is, in fact, the most popular development environment, and it offers full support for the development of the Yocto Project. With the installation of the Yocto Project Eclipse Plug-ins into the Eclipse IDE, the Yocto Project experience is complete. These plugins offer the possibility to cross-compile, develop, deploy, and execute the resultant binary in a Qemu emulated environment. Activities, such as cross-debugging, tracing, remote profiling, and power data collection, are also possible. More information about the activities that appear related to working with Eclipse Plug-ins for the Yocto Project can be found at http://www.yoctoproject.org/docs/1.7/mega-manual/mega-manual.html#adt-eclipse.
The application development process can also be done with other tools that are different from the ones already presented. However, all these options involve the use of a Yocto Project component, most notably the Poby reference system. Therefore, ADT is the suggested, tested, and recommended option by the open source community.
The project—Hob—represents a graphical user interface for the Bitbake build system. Its purpose was to simplify the interaction with the Yocto Project and create a leaner learning curve for the project, allowing users to perform daily tasks in a simpler manner. Its primary focus was the generation of a Linux operating system image. With time, it evolved and can now be considered a tool suitable for both experienced and nonexperienced users. Although I mostly prefer using the command line interaction, this statement does not hold true for all Yocto Project users.
It might seem, though, that Hob development stopped with the release of Daisy 1.6. The development activity somewhat moved to the new project—Toaster—, which will be explained shortly; the Hob project is still in use today and its functionalities should be mentioned. So, the current available version of Hob is able to do the following:
- Visibility for the executed and reused tasks during the build process
- Visibility for build components, such as recipes and packages of an image - this is done in a manner similar to Hob
- Offering information about recipes, such as dependencies, licenses, and so on
- Offering performance-related information, such as disk I/O , CPU usage, and so on
- Presenting errors, warnings, and trace reports for the purpose of debugging
Although it might not seem much, this project promises to offer the possibility to build and customize builds the same way that Hob did, along with many other goodies. You can find useful information about this tool at: https://wiki.yoctoproject.org/wiki/Toaster.
Autobuilder is a project that facilitates the build test automation and conducts quality assurance. Through this internal project, the Yocto community tries to set a path on which embedded developers are able to publish their QA tests and testing plans, develop new tools for automatic testing, continuous integration, and develop QA procedures to demonstrate and show them for the benefit of all involved parties.
These points are already achieved by a project that publishes its current status using this Autobuilder platform, which is available at http://autobuilder.yoctoproject.org/. This link is accessible to everyone and testing is performed on all the changes related to the Yocto Project, as well as nightly builds for all supported hardware platforms. Although started from the Buildbot project, from which it borrowed components for continuous integration, this project promises to move forward and offer the possibility of performing runtime testing and other must-have functionalities.
You can find some useful information about this project at: https://wiki.yoctoproject.org/wiki/AutoBuilder and https://wiki.yoctoproject.org/wiki/QA, which offers access to the QA procedures done for every release, as well as some extra information.
The Lava project is not an internal work of the Yocto Project, but is, in fact, a project developed by Linaro, which is an automated validation architecture aimed towards testing the deployments of Linux systems on devices. Although its primary focus is the ARM architecture, the fact that it is open source does not make it a disincentive. Its actual name is Linaro Automation and Validation Architecture (LAVA).
This is developed with the idea of a continuous evolving architecture that allows test performing along with automation and quality control. At the same time, it offers validation for gathered data. Tests can be anything from compiling a boot test to a change on the kernel scheduler that may or may not have reduced power consumption.
Wic is more of a feature then a project per se. It is the least documented, and if a search is conducted for it, you may find no results. I have decided to mention it here because in the development process, some special requirements could appear, such as generating a custom root
filesystem from available packages (such as .deb
, .rpm
, or .ipk
). This job is the one that is best suited for the wic tool.
A list with all the available projects developed around the Yocto Project can be found at https://www.yoctoproject.org/tools-resources/projects. Some of the projects available there were not discussed in the context of this chapter, but I will let you discover each one of them. There are also other external projects that did not make the list. I encourage you to find out and learn about them on your own.
In this chapter, you will be presented with a new perspective of the available tool in the Yocto Project. This chapter marks the beginning of the introduction to various tools available in the Yocto Project ecosystem, tools that are very useful and different from the Poky reference system. In this chapter, a short presentation to the Application Development Environment (ADE) is presented with emphasis on the Eclipse project and the Yocto Project's added plug-ins. A number of the plug-ins are shown along with their configurations and use cases.
A broader view of the Application Development Toolkit (ADT) will also be shown to you. This project's main objective is to offer a software stack that is able to develop, compile, run, debug, and profile software applications. It tries to do this without requiring extra learning from the developer's point of view. Its learning curve is very low, taking into consideration the fact that Eclipse is one of the most used Integrated Development Environment (IDEs), and over time, it has become very user-friendly, stable, and dependable. The ADT user experience is very similar to the one that any Eclipse or non-Eclipse user has when they use an Eclipse IDE. The available plug-ins try to make this experience as similar as possible so that development is similar to any Eclipse IDE. The only difference is between configuration steps, and this defines the difference between one Eclipse IDE version and another.
ADT is one of the components of the Yocto Project and provides a cross-development platform, which is perfect for user-specific application development. For the development process to take place in an orderly manner, some components are required:
- Eclipse IDE Yocto plug-ins
- QEMU emulator for specific hardware simulations
- Cross-toolchain alongside its specific
sysroot
, which are both architecture-specific and are generated using the metadata and the build system made available by the Yocto Project - Userspace tools to enhance a developer's experience with the application development process
The QEMU emulator is used to simulate various hardware. It can be obtained with these methods:
- Using the ADT installer script, which offers the possibility of installing it
- Cloning a Poky repository and sourcing the environment, access is granted to a QEMU environment
- Downloading a Yocto release and sourcing the environment offers for the same result
- Installing a cross-toolchain and sourcing the environment to make the QEMU environment available
Before explaining the ADT Project further, its Eclipse IDE plug-ins, other features, and functionalities of the setup would be required. To install the Eclipse IDE, the first step involves the setup of a host system. There are multiple methods to do this:
- Using an ADT install script: This is the recommended method to install the ADT, mostly because the installation process is completely automated. Users are in control of the features that they want available.
- Using the ADT tarball: This method involves a section of an appropriate tarball with the architecture-specific toolchain and setting it up using a script. The tarball can be both downloaded and manually built using Bitbake. This method also has limitations due to the fact that not all of its features are available after installation, apart from the cross-toolchain and QEMU emulator.
- Using a toolchain from the build directory: This method takes advantage of the fact that a build directory is already available, so the setup of the cross-toolchain is very easy. Also, in this case, it faces the same limitation as the one mentioned in the preceding point.
After the host system is prepared with all the required dependencies, the ADT tarball can be downloaded from http://downloads.yoctoproject.org/releases/yocto/yocto-1.7/adt-installer/. At this location, the adt_installer.tar.bz2
archive is available. It needs to be downloaded and its content extracted.
The archive is unpacked using the tar -xjf adt_installer.tar.bz2
command. It can be extracted in any directory, and after unpacking the adt-installer
directory, it is created and contains the ADT installer script called adt_installer
. It also has a configuration file called adt_installer.conf
, which is used to define the configurations before running the script. The configuration file defines information, such as the filesystem, kernel, QEMU support, and so on.
These are the variables that the configuration file contains:
YOCTOADT_REPO
: This defines the packages and root filesystem on which the installation is dependent. Its reference value is defined at http://adtrepo.yoctoproject.org//1.7. Here, the directory structure is defined and its structure is the same between releases.YOCTOADT_TARGETS
: This defines the target architecture for which the cross development environment is set up. There are default values defined that can be associated with this variable, such asarm
,ppc
,mips
,x86
, andx86_64
. Also, multiple values can be associated with it and the separation between them being is done using the space separator.YOCTOADT_QEMU
: This variable defines the use of the QEMU emulator. If it is set toY
, the emulator will be available after installation; otherwise the value is set toN
, and hence, the emulator won't be available.YOCTOADT_NFS_UTIL
: This defines if the NFS user-mode that will be installed. The available values are, as defined previously,Y
andN
. For the use of the Eclipse IDE plug-ins, it is necessary to define theY
value for bothYOCTOADT_QEMU
andYOCTOADT_NFS_UTIL
.YOCTOADT_ROOTFS_<arch>
: This specifies which architecture root filesystem to use from the repository that is defined in the first mentionedYOCTOADT_REPO
variable. For thearch
variable, the default values are the ones already mentioned in theYOCTOADT_TARGETS
variable. This variable's valid values are represented by the image files available, such asminimal
,sato
,minimal-dev
,sato-sdk
,lsb
,lsb-sdk
, and so on. For multiple arguments to the variable, the space separator can be used.YOCTOADT_TARGET_SYSROOT_IMAGE_<arch>
: This represents the root filesystem from which thesysroot
of the cross-development toolchain will be generated. The valid values for the 'arch' variable are the same as the one mentioned previously. Its value is dependent on what was previously defined as values for theYOCTOADT_ROOTFS_<arch>
variable. So, if only one variable is defines as the value for theYOCTOADT_ROOTFS_<arch>
variable, the same value will be available forYOCTOADT_TARGET_SYSROOT_IMAGE_<arch>
. Also, if multiple variables are defined in theYOCTOADT_ROOTFS_<arch>
variable, then one of them needs to define theYOCTOADT_TARGET_SYSROOT_IMAGE_<arch>
variable.YOCTOADT_TARGET_MACHINE_<arch>
: This defines the machine for which the image is downloaded, as there could be compilation option differences between machines of the same architecture. The valid values for this variable are can be mentioned as:qemuarm
,qemuppc
,ppc1022ds
,edgerouter
,beaglebone
, and so on.YOCTOADT_TARGET_SYSROOT_LOC_<arch>
: This defines the location where the targetsysroot
will be available after the installation process.
There are also some variables defined in the configuration files, such as YOCTOADT_BITBAKE
and YOCTOADT_METADATA
, which are defined for future work references. After all the variables are defined according to the needs of the developer, the installation process can start. This is done by running the adt_installer
script:
Here is an example of the adt_installer.conf
file:
After the installation has started, the user is asked the location of the cross-toolchain. If no alternative is offered, the default path is selected and the cross-toolchain is installed in the /opt/poky/<release>
directory. The installation process can be visualized both in a silent or interactive way. By using the I
option, the installation is done in an interactive mode, while the silent mode is enabled using the S
option.
There are two methods to obtain the toolchain installed for this second option. The first method involves the use of the toolchain installer available at http://downloads.yoctoproject.org/releases/yocto/yocto-1.7/toolchain/. Open the folder that matches your development host machine. In this folder, multiple install scripts are available. Each one matches a target architecture, so the right one should be selected for the target you have. One such example can be seen from http://downloads.yoctoproject.org/releases/yocto/yocto-1.7/toolchain/x86_64/poky-glibc-x86_64-core-image-sato-armv7a-vfp-neon-toolchain-1.7.sh, which is, in fact, the installer script for the armv7a
target and the x86_64
host machine.
If your target machine is not one of the ones that are made available by the Yocto community, or if you prefer an alternative to this method, then building the toolchain installer script is the method for you. In this case, you will require a build directory, and you will be presented with two alternatives, both of them are equally good:
- The first one involves the use of the
bitbake meta-toolchain
command, and the end result is an installer script that requires the installation and set up of the cross-toolchain in a separate location. - The second alternative involves the use of the
bitbake –c populate_sdk <image-name>
task, which offers the toolchain installer script and the matchingsysroot
for the target. The advantage here is that the binaries are linked with only one and the samelibc
, making the toolchain self-contained. There is, of course, a limitation that each architecture can create only one specific build. However, target-specific options are passed through thegcc
options. Using variables, such asCC
orLD
, makes the process easier to maintain and also saves some space in the build directory.
With the environment set up, writing of an application can start, but the developer would still need to complete some steps before finishing the activity, such as testing the application on the real root filesystem, debugging, and many others. For the kernel module and driver implementation, the kernel source code will be required, so the activity is just starting.
The plug-ins available for Eclipse from the Yocto Project include the functionalities for the ADT Project and toolchain. They allow developers to use a cross-compiler, debugger, and all the available tools generated with the Yocto Project, Poky, and additional meta layers. Not only can these components be used within the Eclipse IDE, but they also offer a familiar environment for application development.
The Yocto Project offers support for two versions of Eclipse, Kepler and Juno. The Kepler version is the one recommended with the latest Poky release. I also recommend the Kepler 4.3.2 version of Eclipse, the one downloaded from the official download site of Eclipse, http://www.eclipse.org/downloads.
From this site, the Eclipse Standard 4.3.2 version containing the Java Development Tools (JDT), the Eclipse Platform, and the Development Environment Plug-ins for the host machine should be downloaded. After the download is finished, the received archive content should be extracted using the tar command:
To initialize the Eclipse IDE perform the following steps:
- Select Workbench, and you will be moved into the empty workbench where the projects source code will be written.
- Now, navigate through the Help menu and select Install New Software.
Help menu
- A new window will open, and in the Work with: drop-down menu, select Kepler - http://download.eclipse.org/releases/kepler, as shown in the following screenshot:
Install window
- Expand the Linux Tools section and select LTTng – Linux Tracing Toolkit box, as shown in the following screenshot:
Install—LTTng – Linux Tracing Toolkit box
- Expand the Moble and Device Development section and select the following:
- C/C++ Remote Launch (Requires RSE Remote System Explorer)
- Remote System Explorer End-user Runtime
- Remote System Explorer User Actions
- Target Management Terminal
- TCF Remote System Explorer add-in
- TCF Target Explorer
- Expand the Programming Languages section and select the following:
- C/C++ Autotools Support
- C/C++ Development Tools
This is shown in the following screenshot:
Available software list window
- Finish the installation after taking a quick look at the Install Details menu and enabling the license agreement:
Install details window
After these steps, the Yocto Project Eclipse plug-ins can be installed into the IDE, but not before restarting the Eclipse IDE to make sure that the preceding changes take effect. The result after the configuration phase is visible here:
To install the Eclipse plug-ins for the Yocto Project, these steps are required:
- Start the Eclipse IDE as mentioned previously.
- As shown in the previous configuration, select the Install New Software option from the Help menu.
- Click on the Add button and insert
downloads.yoctoproject.org/releases/eclipse-plugin/1.7/kepler/
in the URL section. Give a proper name to the new Work with: site as indicated here:Edit site window
- After the OK button is pressed, and the Work with site is updated, new boxes appear. Select all of them, as shown in this image, and click on the Next button:
Install details window
- One final pick at the installed components and the installation is approaching its end.
Install details window
- If this warning message appears, press OK and move further. It only lets you know that the installed packages have unsigned content.
Security warning window
The installation finishes only after the Eclipse IDE is restarted for the changes to take effect.
After the installation, the Yocto plug-ins are available and ready to be configured. The configuration process involves the setup of the target-specific option and cross-compiler. For each specific target, the preceding configurations steps need to be performed accordingly.
The next elements that need to be configured are the toolchain location, sysroot
location, and the target architecture. The Toolchain Root Location is used to define the toolchain install location. For an installation done with the adt_installer
script, for example, the toolchain will be available in the /opt/poky/<release>
directory. The second argument, Sysroot Location, represents the location of the target device root filesystem. It can be found in the /opt/poky/<release>
directory, as seen the preceding example, or even inside the build directory if other method to generate it were used. The third and last option from this section is represented by the Target Architecture and it indicates the type of hardware used or emulated. As it can be seen on the window, it is a pull-down menu where the required option is selected, and a user will find all the supported architectures listed. In a situation where the necessary architecture is not available inside the pull-down menu, the corresponding image for the architecture will need to be built.
To define a project, these steps need to be taken:
- Select the Project… option from the File | New menu option, as shown here:
Eclipse IDE—Project
- Select C project from the C/C++ option. This will open a C Project window:
Eclipse IDE—New project window
- In the C Project window, there are multiple options available. Let's select Yocto Project ADT Autotools Project, and from there, the Hello World ANSI C Autotools Project option. Add a name for the new project, and we are ready to move to the next steps:
C project window
- In the C Project window we you be prompted to add information regarding the Author, Copyright notice, Hello world greetings, Source, and License fields accordingly:
C project—basic settings window
- After all the information is added, the Finish button can be clicked on. The user will be prompted in the new C/C++ perspective that is specific for the opened project, with the newly created project appearing on the left-hand side of the menu.
- After the project is created and the source code is written, to build the project, select the Build Project option from the Project… menu.
QEMU is used in the Yocto Project as a virtualization machine and emulator for various target architectures. It is very useful to run and test various Yocto generated applications and images, apart from fulfilling other purposes. Its primary use outside of the Yocto world is its selling point for the Yocto Project too, making it the default tool to be used to emulate hardware.
Interaction with the QEMU emulation is done within Eclipse, as shown previously. For this to happen, the proper configuration would be required, as instructed in the preceding section. Starting the QEMU emulation here is done using the External Tools option from the Run menu. A new window will be opened for the emulator, and after the corresponding login information is passed to the prompt, the shell will be available for user interaction. An application can be deployed and debugged on the emulator also.
Debugging an application can also be done using the QEMU emulator or the actual target hardware, if it exists. When the project was configured, a run/debug Eclipse configuration was generated as a C/C+ Remote Application instance, and it can be found on the basis of its name, which is according to the <project-name>_gdb_-<suffix>
syntax. For example, TestProject_gdb_armv5te-poky-linux-gnueabi
could be an example of this.
To connect to the Eclipse GDB interface and start the remote target debugging process, the user is required to perform a few steps:
- Select C/C++ Remote application from the Run | Debug configuration menu and choose the run/debug configuration from the C/C++ Remote Application available in the left panel.
- Select the suitable connection from the drop-down list.
- Select the binary application to deploy. If multiple executables are available in your project, by pushing the Search Project button, Eclipse will parse the project and provide a list with all the available binaries.
- Enter the absolute path in which the application will be deployed by setting the Remote Absolute File Path for C/C++ Application: field accordingly.
- Selecting the debugger option is available in the Debugger tab. To debug shared libraries, a few extra steps are necessary:
- Select the Add | Path Mapping option from the Source tab to make sure a path mapping is available for the debug configuration.
- Select Load shared libraries symbols automatically from the Debug/Shared Library tab and indicate the path of the shared libraries accordingly. This path is highly dependent on the architecture of the processor, so be very careful which library file you indicate. Usually, for the 32-bit architecture, the
lib
directory is selected, and for the 64-bit architecture, thelib64
directory is chosen. - On the Arguments tab, there is a possibility of passing various arguments to the application binary during the time of execution.
- Once all the debug configurations are finished, click on the Apply and Debug buttons. A new GDB session will be launched and Debug perspective will open. When the debugger is being initialized, Eclipse will open three consoles:
- After the setup of the debug configuration, the application can be rebuilt and executed again using the available Debug icon in the toolbar. If, in fact, you want only to run and deploy the application, the Run icon can be used.
Inside the Yocto Tools menu, you can see the supported tools that are used for the tracing and profiling of developed applications. These tools are used for enhancing various properties of the application and, in general, the development process and experience. The tools that will be presented are LTTng, Perf, LatencyTop, PerfTop, SystemTap, and KGDB.
- Start the tracing perspective by selecting Open Perspective from the Window menu.
- Create a new tracing project by selecting Project from the File | New menu.
- Select Control View from the Window | Show view | Other… | Lttng menu. This will enable you to access all these desired operations:
- Creating a new connection
- Creating a session
- Starting/stopping tracing
- Enabling events
Next, we'll introduce the user space performance analyzing tool called Perf. It offers statistical profiling of the application code and a simple CPU for multiple threads and kernel. To do this, it uses a number of performance counters, dynamic probes, or trace points. To use the Eclipse Plug-in, a remote connection to the target is required. It can be done by the Perf wizard or by using the Remote System Explorer | Connection option from the File | New | Other menu. After the remote connection is set up, interaction with the tool is the same as in the case of the command line support available for the tool.
LatencyTop
is an application that is used to identify the latencies available within the kernel and also their root cause. This tool is not available for ARM kernels that have Symmetric multiprocessing (SMP) support enabled due to the limitation of the ARM kernels. This application also requires a remote connection. After the remote connection is set up, the interaction is the same as in the case of the command line support available for the tool. This application is run from the Eclipse Plug-in using sudo
.
PowerTop is used to measure the consumption of electrical power. It analyzes the applications, kernel options, and device drivers that run on a Linux system and estimates their power consumption. It is very useful to identify components that use the most amount of power. This application requires a remote connection. After the remote connection is set up, the interaction with the application is the same as for the command line available support for the tool. This application is run from the Eclipse Plug-in using the –d option to display the output in the Eclipse window.
SystemTap
is a tool that enables the use of scripts to get results from a running Linux. SystemTap provides free software (GPL) infrastructure to simplify the gathering of information about the running Linux system via the tracing of all kernel calls. It's very similar to dtrace from Solaris, but it is still not suited for production systems, unlike dtrace. It uses a language similar to awk
and its scripts have the .stp
extension. The monitored data can be extracted and various filters and complex processing can be done on them. The Eclipse Plug-in uses the crosstap
script to translate the .stp
scripts to a C language to create a Makefile
, run a C compiler to create a kernel module for the target architecture that is inserted into the target kernel, and later, collect the tracing data from the kernel. To start the SystemTap plug-in in Eclipse, there are a number of steps to be followed:
- Select the systemtap option from the Yocto Project Tools menu.
- In the opened windows, the crosstap argument needs to be passed:
- Set the Metadata Location variable to the corresponding
poky
directory - Set Remote User ID by entering the root (the default option) because it has
ssh
access to the target-any other user that has the same privileges is also a good choice - Set in the Remote Host variable to the corresponding IP address for the target
- Use the Systemtap Scripts variable for the full path to the
.stp
scripts - Set additional cross options using the Systemtap Args field
- Set the Metadata Location variable to the corresponding
The output of the .stp
script should be available in the console view from Eclipse.
The last tool we'll take a look at is KGDB. This tool is used specifically for the debugging of Linux kernel, and is useful only if development on the Linux kernel source code is done inside the Eclipse IDE. To use this tool, a number of necessary configuration setups are required:
- Disable the C/C++ indexing:
- Select the C/C++ Indexer option from the Window | Preferences menu
- Unselect the Enable indexer checkbox
- Create a project where the kernel source code can be imported:
- Select the C/C++ | C Project option from the File | New menu
- Select the Makefile project | Empty project option and give a proper name to the project
- Unselect the Use default location option
- Click on the Browse button and identify the kernel source code local git repository location
- Press the Finish button and the project should be created
After the prerequisites are fulfilled, the actual configuration can start:
- Select the Debug Configuration option from the Run menu.
- Double-click on the GDB Hardware Debugging option to create a default configuration named <project name> Default.
- From the Main tab, browse to the location of the
vmlinux
built image, select the Disable auto build radio button, as well as the GDB (DFS) Hardware Debugging Launcher option. - For the C/C++ Application option available in the Debugger tab, browse for the location of the GDB binary available inside the toolchain (if ADT installer script is available, its default location should be
/opt/poky/1.7/sysroots/x86_64-pokysdk-linux/usr/bin/arm-poky-linux-gnueabi/arm-poky-linux-gnueabi-gdb
). Select Generic serial option from the JTAG Device menu. The Use remote target option is a requirement. - From the Startup tab, select the Load symbols option. Make sure that the Use Project binary option indicates the correct
vmlinux
image and that the Load image option is not selected. - Press the Apply button to make sure the previous configuration is enabled.
- Prepare the target for the serial communication debugging:
- Set the
echo ttyS0,115200
|/sys/module/kgdboc/parameters/kgdboc
option to make sure the appropriate device is used for debugging - Start KGDB on the
echo g
|/proc/sysrq-trigger
target - Close the terminal with the target but keep the serial connectivity
- Set the
- Select the Debug Configuration option from the Run menu
- Select the previously created configuration and click on the Debug button
The bitbake commander offers the possibility of editing recipes and creating a metadata project in a manner similar to the one available in the command line. The difference between the two is that the Eclipse IDE is used to do the metadata interaction.
To make sure that a user is able to do these sort of actions, a number of steps are required:
- Select the Project option from the File | New menu
- Select the Yocto Project BitBake Commander wizard from the opened window
- Select the New Yocto Project option and a new window will be opened tp define properties of the new project
- Using Project Location, identify the parent of the
poky
directory - Use the Project Name option to define the project name. Its default value is poky
- For the Remote service provider variable, select the Local choice and make use of the same choice for the Connection name drop-down list
- Make sure that the Clone checkbox is not selected for an installed
poky
source directory
By using the Eclipse IDE, its features are available to be used. One of the most useful features is the quick search option that could prove to be very useful for some developers. Other benefits include the possibility of creating recipes using templates, editing them with syntax highlighting, auto completion, error reports on the fly, and many more.
used in the Yocto Project as a virtualization machine and emulator for various target architectures. It is very useful to run and test various Yocto generated applications and images, apart from fulfilling other purposes. Its primary use outside of the Yocto world is its selling point for the Yocto Project too, making it the default tool to be used to emulate hardware.
Interaction with the QEMU emulation is done within Eclipse, as shown previously. For this to happen, the proper configuration would be required, as instructed in the preceding section. Starting the QEMU emulation here is done using the External Tools option from the Run menu. A new window will be opened for the emulator, and after the corresponding login information is passed to the prompt, the shell will be available for user interaction. An application can be deployed and debugged on the emulator also.
Debugging an application can also be done using the QEMU emulator or the actual target hardware, if it exists. When the project was configured, a run/debug Eclipse configuration was generated as a C/C+ Remote Application instance, and it can be found on the basis of its name, which is according to the <project-name>_gdb_-<suffix>
syntax. For example, TestProject_gdb_armv5te-poky-linux-gnueabi
could be an example of this.
To connect to the Eclipse GDB interface and start the remote target debugging process, the user is required to perform a few steps:
- Select C/C++ Remote application from the Run | Debug configuration menu and choose the run/debug configuration from the C/C++ Remote Application available in the left panel.
- Select the suitable connection from the drop-down list.
- Select the binary application to deploy. If multiple executables are available in your project, by pushing the Search Project button, Eclipse will parse the project and provide a list with all the available binaries.
- Enter the absolute path in which the application will be deployed by setting the Remote Absolute File Path for C/C++ Application: field accordingly.
- Selecting the debugger option is available in the Debugger tab. To debug shared libraries, a few extra steps are necessary:
- Select the Add | Path Mapping option from the Source tab to make sure a path mapping is available for the debug configuration.
- Select Load shared libraries symbols automatically from the Debug/Shared Library tab and indicate the path of the shared libraries accordingly. This path is highly dependent on the architecture of the processor, so be very careful which library file you indicate. Usually, for the 32-bit architecture, the
lib
directory is selected, and for the 64-bit architecture, thelib64
directory is chosen. - On the Arguments tab, there is a possibility of passing various arguments to the application binary during the time of execution.
- Once all the debug configurations are finished, click on the Apply and Debug buttons. A new GDB session will be launched and Debug perspective will open. When the debugger is being initialized, Eclipse will open three consoles:
- After the setup of the debug configuration, the application can be rebuilt and executed again using the available Debug icon in the toolbar. If, in fact, you want only to run and deploy the application, the Run icon can be used.
Inside the Yocto Tools menu, you can see the supported tools that are used for the tracing and profiling of developed applications. These tools are used for enhancing various properties of the application and, in general, the development process and experience. The tools that will be presented are LTTng, Perf, LatencyTop, PerfTop, SystemTap, and KGDB.
- Start the tracing perspective by selecting Open Perspective from the Window menu.
- Create a new tracing project by selecting Project from the File | New menu.
- Select Control View from the Window | Show view | Other… | Lttng menu. This will enable you to access all these desired operations:
- Creating a new connection
- Creating a session
- Starting/stopping tracing
- Enabling events
Next, we'll introduce the user space performance analyzing tool called Perf. It offers statistical profiling of the application code and a simple CPU for multiple threads and kernel. To do this, it uses a number of performance counters, dynamic probes, or trace points. To use the Eclipse Plug-in, a remote connection to the target is required. It can be done by the Perf wizard or by using the Remote System Explorer | Connection option from the File | New | Other menu. After the remote connection is set up, interaction with the tool is the same as in the case of the command line support available for the tool.
LatencyTop
is an application that is used to identify the latencies available within the kernel and also their root cause. This tool is not available for ARM kernels that have Symmetric multiprocessing (SMP) support enabled due to the limitation of the ARM kernels. This application also requires a remote connection. After the remote connection is set up, the interaction is the same as in the case of the command line support available for the tool. This application is run from the Eclipse Plug-in using sudo
.
PowerTop is used to measure the consumption of electrical power. It analyzes the applications, kernel options, and device drivers that run on a Linux system and estimates their power consumption. It is very useful to identify components that use the most amount of power. This application requires a remote connection. After the remote connection is set up, the interaction with the application is the same as for the command line available support for the tool. This application is run from the Eclipse Plug-in using the –d option to display the output in the Eclipse window.
SystemTap
is a tool that enables the use of scripts to get results from a running Linux. SystemTap provides free software (GPL) infrastructure to simplify the gathering of information about the running Linux system via the tracing of all kernel calls. It's very similar to dtrace from Solaris, but it is still not suited for production systems, unlike dtrace. It uses a language similar to awk
and its scripts have the .stp
extension. The monitored data can be extracted and various filters and complex processing can be done on them. The Eclipse Plug-in uses the crosstap
script to translate the .stp
scripts to a C language to create a Makefile
, run a C compiler to create a kernel module for the target architecture that is inserted into the target kernel, and later, collect the tracing data from the kernel. To start the SystemTap plug-in in Eclipse, there are a number of steps to be followed:
- Select the systemtap option from the Yocto Project Tools menu.
- In the opened windows, the crosstap argument needs to be passed:
- Set the Metadata Location variable to the corresponding
poky
directory - Set Remote User ID by entering the root (the default option) because it has
ssh
access to the target-any other user that has the same privileges is also a good choice - Set in the Remote Host variable to the corresponding IP address for the target
- Use the Systemtap Scripts variable for the full path to the
.stp
scripts - Set additional cross options using the Systemtap Args field
- Set the Metadata Location variable to the corresponding
The output of the .stp
script should be available in the console view from Eclipse.
The last tool we'll take a look at is KGDB. This tool is used specifically for the debugging of Linux kernel, and is useful only if development on the Linux kernel source code is done inside the Eclipse IDE. To use this tool, a number of necessary configuration setups are required:
- Disable the C/C++ indexing:
- Select the C/C++ Indexer option from the Window | Preferences menu
- Unselect the Enable indexer checkbox
- Create a project where the kernel source code can be imported:
- Select the C/C++ | C Project option from the File | New menu
- Select the Makefile project | Empty project option and give a proper name to the project
- Unselect the Use default location option
- Click on the Browse button and identify the kernel source code local git repository location
- Press the Finish button and the project should be created
After the prerequisites are fulfilled, the actual configuration can start:
- Select the Debug Configuration option from the Run menu.
- Double-click on the GDB Hardware Debugging option to create a default configuration named <project name> Default.
- From the Main tab, browse to the location of the
vmlinux
built image, select the Disable auto build radio button, as well as the GDB (DFS) Hardware Debugging Launcher option. - For the C/C++ Application option available in the Debugger tab, browse for the location of the GDB binary available inside the toolchain (if ADT installer script is available, its default location should be
/opt/poky/1.7/sysroots/x86_64-pokysdk-linux/usr/bin/arm-poky-linux-gnueabi/arm-poky-linux-gnueabi-gdb
). Select Generic serial option from the JTAG Device menu. The Use remote target option is a requirement. - From the Startup tab, select the Load symbols option. Make sure that the Use Project binary option indicates the correct
vmlinux
image and that the Load image option is not selected. - Press the Apply button to make sure the previous configuration is enabled.
- Prepare the target for the serial communication debugging:
- Set the
echo ttyS0,115200
|/sys/module/kgdboc/parameters/kgdboc
option to make sure the appropriate device is used for debugging - Start KGDB on the
echo g
|/proc/sysrq-trigger
target - Close the terminal with the target but keep the serial connectivity
- Set the
- Select the Debug Configuration option from the Run menu
- Select the previously created configuration and click on the Debug button
The bitbake commander offers the possibility of editing recipes and creating a metadata project in a manner similar to the one available in the command line. The difference between the two is that the Eclipse IDE is used to do the metadata interaction.
To make sure that a user is able to do these sort of actions, a number of steps are required:
- Select the Project option from the File | New menu
- Select the Yocto Project BitBake Commander wizard from the opened window
- Select the New Yocto Project option and a new window will be opened tp define properties of the new project
- Using Project Location, identify the parent of the
poky
directory - Use the Project Name option to define the project name. Its default value is poky
- For the Remote service provider variable, select the Local choice and make use of the same choice for the Connection name drop-down list
- Make sure that the Clone checkbox is not selected for an installed
poky
source directory
By using the Eclipse IDE, its features are available to be used. One of the most useful features is the quick search option that could prove to be very useful for some developers. Other benefits include the possibility of creating recipes using templates, editing them with syntax highlighting, auto completion, error reports on the fly, and many more.
To connect to the Eclipse GDB interface and start the remote target debugging process, the user is required to perform a few steps:
- Select C/C++ Remote application from the Run | Debug configuration menu and choose the run/debug configuration from the C/C++ Remote Application available in the left panel.
- Select the suitable connection from the drop-down list.
- Select the binary application to deploy. If multiple executables are available in your project, by pushing the Search Project button, Eclipse will parse the project and provide a list with all the available binaries.
- Enter the absolute path in which the application will be deployed by setting the Remote Absolute File Path for C/C++ Application: field accordingly.
- Selecting the debugger option is available in the Debugger tab. To debug shared libraries, a few extra steps are necessary:
- Select the Add | Path Mapping option from the Source tab to make sure a path mapping is available for the debug configuration.
- Select Load shared libraries symbols automatically from the Debug/Shared Library tab and indicate the path of the shared libraries accordingly. This path is highly dependent on the architecture of the processor, so be very careful which library file you indicate. Usually, for the 32-bit architecture, the
lib
directory is selected, and for the 64-bit architecture, thelib64
directory is chosen. - On the Arguments tab, there is a possibility of passing various arguments to the application binary during the time of execution.
- Once all the debug configurations are finished, click on the Apply and Debug buttons. A new GDB session will be launched and Debug perspective will open. When the debugger is being initialized, Eclipse will open three consoles:
- After the setup of the debug configuration, the application can be rebuilt and executed again using the available Debug icon in the toolbar. If, in fact, you want only to run and deploy the application, the Run icon can be used.
Inside the Yocto Tools menu, you can see the supported tools that are used for the tracing and profiling of developed applications. These tools are used for enhancing various properties of the application and, in general, the development process and experience. The tools that will be presented are LTTng, Perf, LatencyTop, PerfTop, SystemTap, and KGDB.
- Start the tracing perspective by selecting Open Perspective from the Window menu.
- Create a new tracing project by selecting Project from the File | New menu.
- Select Control View from the Window | Show view | Other… | Lttng menu. This will enable you to access all these desired operations:
- Creating a new connection
- Creating a session
- Starting/stopping tracing
- Enabling events
Next, we'll introduce the user space performance analyzing tool called Perf. It offers statistical profiling of the application code and a simple CPU for multiple threads and kernel. To do this, it uses a number of performance counters, dynamic probes, or trace points. To use the Eclipse Plug-in, a remote connection to the target is required. It can be done by the Perf wizard or by using the Remote System Explorer | Connection option from the File | New | Other menu. After the remote connection is set up, interaction with the tool is the same as in the case of the command line support available for the tool.
LatencyTop
is an application that is used to identify the latencies available within the kernel and also their root cause. This tool is not available for ARM kernels that have Symmetric multiprocessing (SMP) support enabled due to the limitation of the ARM kernels. This application also requires a remote connection. After the remote connection is set up, the interaction is the same as in the case of the command line support available for the tool. This application is run from the Eclipse Plug-in using sudo
.
PowerTop is used to measure the consumption of electrical power. It analyzes the applications, kernel options, and device drivers that run on a Linux system and estimates their power consumption. It is very useful to identify components that use the most amount of power. This application requires a remote connection. After the remote connection is set up, the interaction with the application is the same as for the command line available support for the tool. This application is run from the Eclipse Plug-in using the –d option to display the output in the Eclipse window.
SystemTap
is a tool that enables the use of scripts to get results from a running Linux. SystemTap provides free software (GPL) infrastructure to simplify the gathering of information about the running Linux system via the tracing of all kernel calls. It's very similar to dtrace from Solaris, but it is still not suited for production systems, unlike dtrace. It uses a language similar to awk
and its scripts have the .stp
extension. The monitored data can be extracted and various filters and complex processing can be done on them. The Eclipse Plug-in uses the crosstap
script to translate the .stp
scripts to a C language to create a Makefile
, run a C compiler to create a kernel module for the target architecture that is inserted into the target kernel, and later, collect the tracing data from the kernel. To start the SystemTap plug-in in Eclipse, there are a number of steps to be followed:
- Select the systemtap option from the Yocto Project Tools menu.
- In the opened windows, the crosstap argument needs to be passed:
- Set the Metadata Location variable to the corresponding
poky
directory - Set Remote User ID by entering the root (the default option) because it has
ssh
access to the target-any other user that has the same privileges is also a good choice - Set in the Remote Host variable to the corresponding IP address for the target
- Use the Systemtap Scripts variable for the full path to the
.stp
scripts - Set additional cross options using the Systemtap Args field
- Set the Metadata Location variable to the corresponding
The output of the .stp
script should be available in the console view from Eclipse.
The last tool we'll take a look at is KGDB. This tool is used specifically for the debugging of Linux kernel, and is useful only if development on the Linux kernel source code is done inside the Eclipse IDE. To use this tool, a number of necessary configuration setups are required:
- Disable the C/C++ indexing:
- Select the C/C++ Indexer option from the Window | Preferences menu
- Unselect the Enable indexer checkbox
- Create a project where the kernel source code can be imported:
- Select the C/C++ | C Project option from the File | New menu
- Select the Makefile project | Empty project option and give a proper name to the project
- Unselect the Use default location option
- Click on the Browse button and identify the kernel source code local git repository location
- Press the Finish button and the project should be created
After the prerequisites are fulfilled, the actual configuration can start:
- Select the Debug Configuration option from the Run menu.
- Double-click on the GDB Hardware Debugging option to create a default configuration named <project name> Default.
- From the Main tab, browse to the location of the
vmlinux
built image, select the Disable auto build radio button, as well as the GDB (DFS) Hardware Debugging Launcher option. - For the C/C++ Application option available in the Debugger tab, browse for the location of the GDB binary available inside the toolchain (if ADT installer script is available, its default location should be
/opt/poky/1.7/sysroots/x86_64-pokysdk-linux/usr/bin/arm-poky-linux-gnueabi/arm-poky-linux-gnueabi-gdb
). Select Generic serial option from the JTAG Device menu. The Use remote target option is a requirement. - From the Startup tab, select the Load symbols option. Make sure that the Use Project binary option indicates the correct
vmlinux
image and that the Load image option is not selected. - Press the Apply button to make sure the previous configuration is enabled.
- Prepare the target for the serial communication debugging:
- Set the
echo ttyS0,115200
|/sys/module/kgdboc/parameters/kgdboc
option to make sure the appropriate device is used for debugging - Start KGDB on the
echo g
|/proc/sysrq-trigger
target - Close the terminal with the target but keep the serial connectivity
- Set the
- Select the Debug Configuration option from the Run menu
- Select the previously created configuration and click on the Debug button
The bitbake commander offers the possibility of editing recipes and creating a metadata project in a manner similar to the one available in the command line. The difference between the two is that the Eclipse IDE is used to do the metadata interaction.
To make sure that a user is able to do these sort of actions, a number of steps are required:
- Select the Project option from the File | New menu
- Select the Yocto Project BitBake Commander wizard from the opened window
- Select the New Yocto Project option and a new window will be opened tp define properties of the new project
- Using Project Location, identify the parent of the
poky
directory - Use the Project Name option to define the project name. Its default value is poky
- For the Remote service provider variable, select the Local choice and make use of the same choice for the Connection name drop-down list
- Make sure that the Clone checkbox is not selected for an installed
poky
source directory
By using the Eclipse IDE, its features are available to be used. One of the most useful features is the quick search option that could prove to be very useful for some developers. Other benefits include the possibility of creating recipes using templates, editing them with syntax highlighting, auto completion, error reports on the fly, and many more.
of developed applications. These tools are used for enhancing various properties of the application and, in general, the development process and experience. The tools that will be presented are LTTng, Perf, LatencyTop, PerfTop, SystemTap, and KGDB.
- Start the tracing perspective by selecting Open Perspective from the Window menu.
- Create a new tracing project by selecting Project from the File | New menu.
- Select Control View from the Window | Show view | Other… | Lttng menu. This will enable you to access all these desired operations:
- Creating a new connection
- Creating a session
- Starting/stopping tracing
- Enabling events
Next, we'll introduce the user space performance analyzing tool called Perf. It offers statistical profiling of the application code and a simple CPU for multiple threads and kernel. To do this, it uses a number of performance counters, dynamic probes, or trace points. To use the Eclipse Plug-in, a remote connection to the target is required. It can be done by the Perf wizard or by using the Remote System Explorer | Connection option from the File | New | Other menu. After the remote connection is set up, interaction with the tool is the same as in the case of the command line support available for the tool.
LatencyTop
is an application that is used to identify the latencies available within the kernel and also their root cause. This tool is not available for ARM kernels that have Symmetric multiprocessing (SMP) support enabled due to the limitation of the ARM kernels. This application also requires a remote connection. After the remote connection is set up, the interaction is the same as in the case of the command line support available for the tool. This application is run from the Eclipse Plug-in using sudo
.
PowerTop is used to measure the consumption of electrical power. It analyzes the applications, kernel options, and device drivers that run on a Linux system and estimates their power consumption. It is very useful to identify components that use the most amount of power. This application requires a remote connection. After the remote connection is set up, the interaction with the application is the same as for the command line available support for the tool. This application is run from the Eclipse Plug-in using the –d option to display the output in the Eclipse window.
SystemTap
is a tool that enables the use of scripts to get results from a running Linux. SystemTap provides free software (GPL) infrastructure to simplify the gathering of information about the running Linux system via the tracing of all kernel calls. It's very similar to dtrace from Solaris, but it is still not suited for production systems, unlike dtrace. It uses a language similar to awk
and its scripts have the .stp
extension. The monitored data can be extracted and various filters and complex processing can be done on them. The Eclipse Plug-in uses the crosstap
script to translate the .stp
scripts to a C language to create a Makefile
, run a C compiler to create a kernel module for the target architecture that is inserted into the target kernel, and later, collect the tracing data from the kernel. To start the SystemTap plug-in in Eclipse, there are a number of steps to be followed:
- Select the systemtap option from the Yocto Project Tools menu.
- In the opened windows, the crosstap argument needs to be passed:
- Set the Metadata Location variable to the corresponding
poky
directory - Set Remote User ID by entering the root (the default option) because it has
ssh
access to the target-any other user that has the same privileges is also a good choice - Set in the Remote Host variable to the corresponding IP address for the target
- Use the Systemtap Scripts variable for the full path to the
.stp
scripts - Set additional cross options using the Systemtap Args field
- Set the Metadata Location variable to the corresponding
The output of the .stp
script should be available in the console view from Eclipse.
The last tool we'll take a look at is KGDB. This tool is used specifically for the debugging of Linux kernel, and is useful only if development on the Linux kernel source code is done inside the Eclipse IDE. To use this tool, a number of necessary configuration setups are required:
- Disable the C/C++ indexing:
- Select the C/C++ Indexer option from the Window | Preferences menu
- Unselect the Enable indexer checkbox
- Create a project where the kernel source code can be imported:
- Select the C/C++ | C Project option from the File | New menu
- Select the Makefile project | Empty project option and give a proper name to the project
- Unselect the Use default location option
- Click on the Browse button and identify the kernel source code local git repository location
- Press the Finish button and the project should be created
After the prerequisites are fulfilled, the actual configuration can start:
- Select the Debug Configuration option from the Run menu.
- Double-click on the GDB Hardware Debugging option to create a default configuration named <project name> Default.
- From the Main tab, browse to the location of the
vmlinux
built image, select the Disable auto build radio button, as well as the GDB (DFS) Hardware Debugging Launcher option. - For the C/C++ Application option available in the Debugger tab, browse for the location of the GDB binary available inside the toolchain (if ADT installer script is available, its default location should be
/opt/poky/1.7/sysroots/x86_64-pokysdk-linux/usr/bin/arm-poky-linux-gnueabi/arm-poky-linux-gnueabi-gdb
). Select Generic serial option from the JTAG Device menu. The Use remote target option is a requirement. - From the Startup tab, select the Load symbols option. Make sure that the Use Project binary option indicates the correct
vmlinux
image and that the Load image option is not selected. - Press the Apply button to make sure the previous configuration is enabled.
- Prepare the target for the serial communication debugging:
- Set the
echo ttyS0,115200
|/sys/module/kgdboc/parameters/kgdboc
option to make sure the appropriate device is used for debugging - Start KGDB on the
echo g
|/proc/sysrq-trigger
target - Close the terminal with the target but keep the serial connectivity
- Set the
- Select the Debug Configuration option from the Run menu
- Select the previously created configuration and click on the Debug button
The bitbake commander offers the possibility of editing recipes and creating a metadata project in a manner similar to the one available in the command line. The difference between the two is that the Eclipse IDE is used to do the metadata interaction.
To make sure that a user is able to do these sort of actions, a number of steps are required:
- Select the Project option from the File | New menu
- Select the Yocto Project BitBake Commander wizard from the opened window
- Select the New Yocto Project option and a new window will be opened tp define properties of the new project
- Using Project Location, identify the parent of the
poky
directory - Use the Project Name option to define the project name. Its default value is poky
- For the Remote service provider variable, select the Local choice and make use of the same choice for the Connection name drop-down list
- Make sure that the Clone checkbox is not selected for an installed
poky
source directory
By using the Eclipse IDE, its features are available to be used. One of the most useful features is the quick search option that could prove to be very useful for some developers. Other benefits include the possibility of creating recipes using templates, editing them with syntax highlighting, auto completion, error reports on the fly, and many more.
bitbake commander offers the possibility of editing recipes and creating a metadata project in a manner similar to the one available in the command line. The difference between the two is that the Eclipse IDE is used to do the metadata interaction.
To make sure that a user is able to do these sort of actions, a number of steps are required:
- Select the Project option from the File | New menu
- Select the Yocto Project BitBake Commander wizard from the opened window
- Select the New Yocto Project option and a new window will be opened tp define properties of the new project
- Using Project Location, identify the parent of the
poky
directory - Use the Project Name option to define the project name. Its default value is poky
- For the Remote service provider variable, select the Local choice and make use of the same choice for the Connection name drop-down list
- Make sure that the Clone checkbox is not selected for an installed
poky
source directory
By using the Eclipse IDE, its features are available to be used. One of the most useful features is the quick search option that could prove to be very useful for some developers. Other benefits include the possibility of creating recipes using templates, editing them with syntax highlighting, auto completion, error reports on the fly, and many more.
The Hob project represents a GUI alternative to the BitBake build system. Its purpose is to execute the most common tasks in an easier and faster manner, but it does not make command-line interactions go away. This is because most parts of recipes and configurations still need to be done manually. In the previous chapter, the BitBake Commander extension was introduced as an alternative solution for the editing of recipes, but in this project, it has its limitations.
Hob's primary purpose is to allow interaction with the build system made easier for users. Of course, there are users who do not prefer the graphical user interface alternatives to command-line options, and I kind of agree with them, but this is another discussion altogether. Hob can be an option for them also; it is an alternative not only for people who prefer having an interface in front of them, but also for those who are attached to their command-line interaction.
In this chapter, I will show you how to use the Hob project to build a Linux operating system image. To demonstrate this, I will use the Atmel SAMA5D3 Xplained machine, which is what I also used for other demonstrations in previous chapters.
To retrieve the graphical interface, the user needs perform the given steps required for the BitBake command-line interaction. Firstly, it needs to create a build directory and from this build directory, the user needs to start the Hob graphical interface, using the Hob commands, given as follows:
The next step is to establish the layers that are required for your build. You can do this by selecting them in the Layers window. The first thing to do for the meta-atmel
layer is to add it to the build. Although you may start work in an already existing build directory, Hob will not be able to retrieve the existing configurations and will create a new one over the bblayers.conf
and local.conf
configuration files. It will mark the added lines using the next #added by hob
message.
After the corresponding meta-atmel
layer is added to the build directory, all the supported machines are available in the Select a machine drop-down, including those that are added by the meta-atmel
layer. From the available options, the sama5d3-xplained machine needs to be selected:
When the Atmel sama5d3-xplained machine is selected, an error, shown in the following screenshot, appears:
Since all the available configuration files and recipes are parsed, the parsing process takes a while, and after this, you will see an error, as shown in the following screenshot:
After another quick inspection, you will see the following code:
The only explanation is the fact the meta-atmel
layer does not update its recipes but appends them. This can be overcome in two ways. The simplest one would be to update the recipe the .bbappend
file and make sure that the new available recipe is transformed into a patch for the upstream community. A patch with the required changes inside the meta-atmel
layer will be explained to you shortly, but first, I will present the available options and the necessary changes that are needed to resolve the problems existing in the build process.
After this problem is fixed, new options will be available to the user, as depicted in the following screenshot:
Now, the user has the chance to select the image that needs to be built, as well as the extra configurations that need to be added. These configurations include:
Some of these are depicted in the following screenshot:
I've chosen to change the distribution type from poky-tiny to poky, and the resulting root filesystem output format is visible in the following screenshot:
With the tweaks made, the recipes are reparsed, and when this process is finished, the resulting image can be selected so that the build process can start. The image that is selected for this demonstration is the atmel-xplained-demo-image image, which corresponds to the recipes with the same name. This information is also displayed in the following screenshot:
The build process is started by clicking on the Build image button. A while after the build starts, an error will show up, which tells us that the meta-atmel BSP layer requires more of the dependencies that need to be defined by us:
This information is gathered from the iperf
recipe, which is not available in the included layers; it is available inside the meta-openembedded/meta-oe
layer. After a more detailed search and update process, there have been a few revelations. There are more layer dependencies than required for the meta-atmel
BSP layer, which are given as follows:
- Replace
packagegroup-core-basic
withpackagegroup-core-full-cmdline
because the latest Poky has updated thepackagegroup
names. - Delete
python-setuptools
because it is not available in themeta-openembedded/meta-oe
layer anymore, as well as in the newmeta-openembedded/meta-python
layer, which is the new placeholder for all Python-related recipes. Thepython-setuptools
tool was removed because it had the ability to download, build, install, upgrade, and uninstall extra Python packages, and is not a mandatory requirement for Yocto. This is its general purpose. - The preceding change regarding the update to
qt4-embedded-4.8.6
forqt4-embedded-4.8.5
, as shown earlier, presented errors.
All the changes made to the meta-atmel
layer are available in following patch:
This patch has been given in the chapter as an example for Git interaction and is a necessity when creating a patch that needs to be upstream to the community. At the time of writing this chapter, this patch had not yet been released to the upstream community, so this could be a gift for anyone interested in adding a contribution to the meta-atmel community in particular and the Yocto community in general.
Toaster represents an alternative to Hob, which at a given point in time, will replace it completely. It is also a web-based interface for the BitBake command line. This tool is much more effective than Hob; it is not only able to do the most common tasks in a similar manner as Hob, but it also incorporates a build analysis component that collects data regarding the build process and the resultant outcome. These results are presented in a very easy-to-grasp manner, offering the chance to search, browse, and query the information.
From the collected information, we can mention the following:
- Structure of the image directory
- The available build configurations
- The outcome of a build along with the errors and registered warnings
- The packages present in an image recipe
- Recipes and packages that are built
- Tasks that are executed
- Performance data regarding executed tasks, such as CPU usage, time, and disk I/O usage
- Dependency and reverse dependencies for recipes
There are also some drawbacks to the Hob solution. Toaster does not yet offer the ability to configure and launch a build. However, there are initiatives taken to include these functionalities that Hob has inside Toaster, which will be implemented in the near future.
- Interactive mode: This is the mode available and released with the Yocto Project 1.6 release version. It is based on a
toasterui
build recording component and atoastergui
build inspection and statistics user interface. - Managed mode: In addition to the Yocto Project 1.6 release version, this is the mode that handles build configurations, scheduling, and executions that are triggered from the web interface.
- Remote managed mode: This is a hosted Toaster mode and is defined for production because it offers support for multiple users and customized installations.
- Local managed mode or _local_ is: This is the mode available after a Poky checkout and permits running builds using the local machine code and build directory. It is the also used by anyone who interacts with a Toaster project for the first time.
- For the interactive mode, building with tools, such as AutoBuilder, BuildBot, or Jenkins, a set up separated from the hardware on which the Yocto Project builds are running will be required. Behind a normal instance of Toaster, there are three things that happen:
- A BitBake server is started
- A Toaster UI is started and connected to the BitBake server as well as to an SQL database
- A web server is started for the purpose of reading information related to a database and displaying it on the web interface
To set up an SQL server on a Ubuntu machine, a package needs to be installed, using the following command:
With the set up done, the database synchronization can begin in the following way:
After this is done, the normal build process can be started and builds can begin while the build is running inside the web interface logs and data is available to be examined. One quick mention, though: do not forget to kill the BitBake server after you have finished working inside the build directory using the bitbake –m
command.
Run a build in the console, and it will be automatically updated in the web interface, as shown in the following screenshot:
After the build is finished, the web interface will be updated accordingly. I closed the header image and information to make sure that only the builds are visible in the web page.
For the failing build, a detailed fail report is available, as displayed in the following screenshot:
The build that finished successfully offers access to a lot of information. The following screenshot shows interesting features that a build should have. It shows, for the kernel build, all the BitBake variables used, their values, their location, and a short description. This information is very useful for all developers, not only because it offers all of this at a single location, but also because it offers a search option that reduces the search time spent looking for a troublesome variable to a minimum:
To stop Toaster, the source toaster stop
command can be used after the execution activities are finished.
bitbake-cookerdaemon.log
: This log file is necessary for the BitBake server.toastermain.pid
: This is the file that containspid
of the web server.toasterui.pid
: It contains the DSI data bridge,pid
toaster.sqlite
: This is the database filetoaster_web.log
: This is the web server log filetoaster_ui.log
: This is the log file used for components of the user interface
Autobuilder is the project responsible for QA, and a testing build is available inside the Yocto Project. It is based on the BuildBot project. Although this topic isn't dealt with in this book, for those of you interested in the BuildBot project, you can find more information about it in the following information box.
Note
The starting page of Buildbot can be accssed at http://trac.buildbot.net/. You can find a guide on quick starting BuildBot at http://docs.buildbot.net/0.8.5/tutorial/tour.html, and its concepts can be found at http://docs.buildbot.net/latest/manual/concepts.html.
We are now going to address a software area that is very poorly treated by developers in general. Here, I am referring to the testing and quality assurance of a development process. This is, in fact, an area that requires more attention from us, including me as well. The Yocto Project through the AutoBuilder initiative tries to bring more attention to this area. Also, in the past few years, there has been a shift toward QA and Continuous Integration (CI) of available open source projects, and this can primarily be seen in the Linux Foundation umbrella projects.
- Publishing the testing and QA plans using Bugzilla test cases and plans (https://bugzilla.yoctoproject.org).
- Demonstrating these plans and making them accessible for everyone to see. Of course, for this, you will need a corresponding account.
- Developing tools, tests, and QA procedures for everyone to use.
Having the preceding activities as a foundation, they offer access to a public AutoBuilder that shows the current status of the Poky master branch. Nightly builds and test sets are executed for all the supported targets and architectures and are all available for everyone at http://autobuilder.yoctoproject.org/.
To interact with the AutoBuilder Project, the setup is defined in the README-QUICKSTART
file as a four-step procedure:
At this point, the AutoBuilder should be running. If it is started inside a web interface, the result should look similar to the following screenshot:
As it can be visible from the header of the web page, there are multiple options available not only for the executed builds, but also for a different view and perspective of them. Here is one of the visualization perspectives:
Swabber is a project, which although is presented on Yocto Project's official page, is said to be a work in progress; no activity has been done on it since September 18, 2011. It does not have a maintainers file where you can find more information about its creators. However, the committers list should be enough for anyone interested in taking a deeper look at this project.
For interaction with Swabber, the repository needs to be cloned first. The following command can be used for this purpose:
As you can see, this project is not a major one, but consists of a number of tools made available by a passionate few. This includes two guys from Windriver: Alex deVries and David Borman. They worked on their own on the previously presented tools and made them available for the open source community to use. Swabber is written using the C language, which is a big shift from the usual Python/Bash tools and other projects that are offered by the Yocto Project community. Every tool has its own purpose, the similitude being that all the tools are built using the same Makefile. Of course, this isn't restricted only to the usage of binaries; there are also two bash scripts available for distribution detect and update.
Note
More information about the tool can be found from its creators. Their e-mail addresses, which are available in the commits for the project, are <
alex.devries@windriver.com>
and <david.borman@windriver.com>
. However, please note that these are the workplace e-mail IDs and the people that worked on Swabber may not have the same e-mail address at the moment.
The interaction with the Swabber tools is well described in the README
file. Here, information regarding the setup and running of Swabber is available, though, for your sake, this will also be presented in the next few lines, so that you can understand quicker and in an easier manner.
The first required step is the compilation of sources. This is done by invoking the make
command. After the source code is built and the executables are available, the host distribution can be profiled using the update_distro
command, followed by the location of the distribution directory. The name we've chosen for it is Ubuntu-distro-test
, and it is specific for the host distribution on which the tool is executed. This generation process can take some time at first, but after this, any changes to the host system will be detected and the process will take lesser time. At the end of the profiling process, this is how the content of the Ubuntu-distro-test
directory looks:
Ubuntu-distro-test/ ├── distro ├── distro.blob ├── md5 └── packages
After the host distribution is profiled, a Swabber report can be generated based on the profile created. Also, before creating the report, a profile log can be created for later use along with the reporting process. To generate the report, we will create a log file location with some specific log information. After the logs are available, the reports can be generated:
This information was required by the tool, as shown in its help information:
From the help information attached in the preceding code, the role of the arguments selected for the test command can be investigated. Also, an inspection of the tool's source code is recommended due to the fact that there are no more than 1550 lines in a C file, the biggest one being the swabber.c
file.
Wic is a command line tool that can be also seen as an extension of the BitBake build system. It was developed due to the need of having a partitioning mechanism and a description language. As it can be concluded easily, BitBake lacks in these areas and although initiatives were taken to make sure that such a functionality would be available inside the BitBake build system, this was only possible to an extent; for more complex tasks, Wic can be an alternative solution.
When an image is being built using BitBake, the work is done inside an image recipe that inherits image.bbclass
for a description of its functionality. Inside this class, the do_rootfs()
task is the one that the OS responsible for the creation of the root filesystem directory that will be later be included in the final package and includes all the sources necessary to boot a Linux image on various boards. With the do_rootf()
task finished, a number of commands are interrogated to generate an output for each one of the image defined types. The definition of the image type is done through the IMAGE_FSTYPE
variable and for each image output type, there is an IMAGE_CMD_type
variable defined as an extra type that is inherited from an external layer or a base type described in the image_types.bbclass
file.
The available mechanism implemented in the Yocto Project is visible inside the image_types.bbclass
file through the IMAGE_CMD_type
variable and has this form:
The preceding implementation implies that for each implemented filesystem, a series of commands are invoked. This is a good and simple method for a very simple filesystem format. However, for more complex ones, a language would be required to define the format, its state, and in general, the properties of the image format. Various other complex image format options, such as vmdk, live, and directdisk file types, are available inside Poky. They all define a multistage image formatting process.
Historically, the usage of directdisk
was hardcoded in its first versions. For every image recipe, there was a similar implementation that mirrored the basic one and hardcoded the heritage inside the recipe for the image.bbclass
functionality. In the case of the vmdk
image format, the inherit boot-directdisk
line is added.
The image_types_fsl.bbclass
file defines the IMAGE_CMD
commands, as follows:
There are multiple methods to create various filesystems and they are spread over a large number of existing Yocto layers with some documentation available for the general public. There are even a number of scripts used to create a suitable filesystem for a developer's needs. One such example is the scripts/contrib/mkefidisk.sh
script. It is used to create an EFI-bootable direct disk image from another image format, that is, a live.hddimg
one. However, a main idea remains: this kind of activity should be done without any middle image filesystem that is generated in intermediary phases and with something other than a partition language that is unable to handle complicated scenarios.
From this project, the most used and interesting components were clearpart
, part
, and bootloader
, and these are useful for our purposes as well. When you take a look at the Yocto Project's Wic tool, it is also available inside the configuration files. If the configuration file for Wic is defined as .wks
inside the Fedora kickstart project, the configuration file read uses the .yks
extension. One such configuration file is defined as follows:
def pre(): free-form python or named 'plugin' commands clearpart commands part commands bootloader commands named 'plugin' commands def post(): free-form python or named 'plugin' commands
The idea behind the preceding script is very simple: the clearpart
component is used to clear the disk of any partitions while the part
component is used for the reverse, that is, the components used for creating and installing the filesystem. The third too that is defined is the bootloader
component, which is used for installation of the bootloader, and also handles the corresponding information received from the part
component. It also makes sure that the boot process is done as described inside the configuration file. The functions defined as pre()
and post()
are used for pre and post calculus for creation of the image, stage image artefacts, or other complex tasks.
As shown in the preceding description, the interaction with the Fedora kickstarter project was very productive and interesting, but the source code is written using Python inside the Wic project. This is due to the fact that a Python implementation for a similar tool was searched for and it was found under the form of the pykickstarted
library. This is not all that the preceding library was used for by the Meego project inside its Meego Image Creator (MIC) tool. This tool was used for a Meego-specific image creation process. Later, this project was inherited by the Tizen project.
Wic, the tool that I promised to present in this section is derived from the MIC project and both of them use the kickstarter project, so all three are based on plugins that define the behavior of the process of creating various image formats. In the first implementation of Wic, it was mostly a functionality of the MIC project. Here, I am referring to the Python classes it defines that were almost entirely copied inside Poky. However, over time, the project started to have its own implementations, and also its own personality. From version 1.7 of the Poky repository, no direct reference to MIC Python defined classes remained, making Wic a standalone project that had its own defined plugins and implementations. Here is how you can inspect the various configuration of formats accessible inside Wic:
tree scripts/lib/image/canned-wks/ scripts/lib/image/canned-wks/ ├── directdisk.wks ├── mkefidisk.wks ├── mkgummidisk.wks └── sdimage-bootpart.wks
There are configurations defined inside Wic. However, considering the fact that the interest in this tool has grown in the last few years, we can only hope that the number of supported configurations will increase.
I mentioned previously that the MIC and Fedora kickstarter project dependencies were removed, but a quick search inside the Poky scripts/lib/wic
directory will reveal otherwise. This is because Wic and MIC are both have the same foundation, the pykickstarted
library. Though Wic is now heavily based on MIC and both have the same parent, the kickstarter project, their implementations, functionalities, and various configurations make them different entities, which although related have taken different paths of development.
LAVA (Linaro Automation and Validation Architecture) is a continuous integration system that concentrates on a physical target or virtual hardware deployment where a series of tests are executed. The executed tests are of a large variety from the simplest ones which only requires booting a target to some very complex scenarios that require external hardware interaction.
LAVA represents a collection of components that are used for automated validation. The main idea behind the LAVA stack is to create a quality controlled testing and automation environment that is suitable for projects of all sizes. For a closer look at a LAVA instance, the reader could inspect an already created one, the official production instance of which is hosted by Linaro in Cambridge. You can access it at https://validation.linaro.org/. I hope you enjoy working with it.
The LAVA framework offers support for the following functionalities:
- It supports scheduled automatic testing for multiple packages on various hardware packages
- It makes sure that after a device crashes, the system restarts automatically
- It conducts regression testing
- It conducts continuous integration testing
- It conducts platform enablement testing
- It provides support for both local and cloud solutions
- It provides support for result bundles
- It provides measurements for performance and power consumption
For testing with the LAVA framework, the first step would be to understand its architecture. Knowing this helps not only with test definitions, but also with extending them, as well as the development of the overall project. The major components of this project are as follows:
+-------------+ |web interface| +-------------+ | v +--------+ +---->|database| | +--------+ | +-----------+------[worker]-------------+ | | | | +----------------+ +----------+ | | |scheduler daemon|---→ |dispatcher| | | +----------------+ +----------+ | | | | +------------------------------+--------+ | V +-------------------+ | device under test | +-------------------+
The first component, the web interface, is responsible for user interaction. It is used to store data and submitted jobs using RDBMS, and is also responsible to display the results, device navigation, or as job submission receiver activities that are done through the XMLRPC API. Another important component is represented by the scheduler daemon, which is responsible for the allocation of jobs. Its activity is quite simple. It is responsible for pooling the data from a database and reserving devices for jobs that are offered to them by the dispatcher, another important component. The dispatcher is the component responsible for running actual jobs on the devices. It also manages the communication with a device, download images, and collects results.
To install LAVA or any LAVA-related packages on a 64-bit Ubuntu 14.04 machine, new package dependencies are required in addition to the enabled support for universal repositories deb http://people.linaro.org/~neil.williams/lava jessie main
, besides the installation process described previously for the Debian distribution. I must mention that when the lava-dev
package is installed, the user will be prompted to a menu that indicates nullmailer mailname
. I've chosen to let the default one remain, which is actually the host name of the computer running the nullmailer
service. I've also kept the same configuration defined by default for smarthost
and the installation process has continued. The following are the commands necessary to install LAVA on a Ubuntu 14.04 machine:
Note
Information about the LAVA installation process is available at https://validation.linaro.org/static/docs/installing_on_debian.html#. Here, you also find the installation processes for bot Debian and Ubuntu distributions.
When Linux is mentioned, usually General Purpose Operating System (GPOS) is related to it, but over time, the need to have the same benefits as Real-Time Operating System (RTOS) for Linux has become more stringent. The challenge for any real-time system is to meet the given timing constrains in spite of the number and type of random asynchronous events. This is no simple task and an extensive number of papers and researches were done on theory of the real-time systems. Another challenge for a real-time system would be to have an upper limit on latency, called a scheduling deadline. Depending on how systems meet this challenge, they can be split into hard, firm, and soft:
- Hard real-time system: This represents system for which a deadline miss will result in a complete system failure.
- Firm real-time system: This represents systems for which a deadline miss is acceptable but the system quality can be degraded. Also, after the deadline is missed, the result that is offered is not useful anymore.
- Soft real-time system: This represents systems for which missing of deadlines degrades the usefulness of the received result and consequently, of the quality of the system. In these kind of systems, the meeting of the deadline is seen as a goal than as a strict requirement.
There are multiple reasons for Linux not being suitable as a RTOS:
- Paging: The page swap process through virtual memory is without limits. There is no method in place to know the time that will pass until you can get a page from a disk, and this implies that there is no upper limit to the delay caused by the fault in a page.
- Coarsed-grained synchronization: Here, the definition of the Linux kernel is not preemptible. This means that once a process is inside the kernel context, it cannot be preempted until it exits the context. At an event occurrence, the new event needs to wait for scheduling until the already available one exits the kernel context.
- Batching: An operation can be batched for a more efficient use of resources. The simplest example of this is the page freeing process. Instead of freeing each separate page, Linux is able to pass multiple pages and clean as many as possible.
- Request reordering: The I/O requests can be reordered for processes, making the process of using hardware more efficient.
- Fairness in scheduling: This is a UNIX heritage and refers to the fact that a scheduler tries to be fair with all running processes. This property offers the possibility of lower priority processes that have been waiting for a long time to be scheduled before higher priority ones.
The first thing anyone can do to improve the latency of the standard Linux operating system would be to try and make a change to the scheduling policies. The default Linux time sharing scheduling policies are called SCHED_OTHER, and they use a fairness algorithm, giving all processes zero priority, the lowest one available. Other such scheduling policies are SCHED_BATCH for batch scheduling of the processes and the SCHED_IDLE, which is suitable for the scheduling of extremely low priority jobs. The alternatives to this scheduling policy are SCHED_FIFO and SCHED_RR. Both of them are intended as real-time policies and are time-critical applications that require precise control processes and their latencies.
The other available implementation is interrupt abstraction. This approach is based on the fact that not all systems require a hard real-time determinism and most of them only require a section of their task to be executed in a real-time context. The idea behind this approach is to run Linux with the priority of an idle task under a real-time kernel and non-real-time tasks to continue to execute them as they normally do. This implementation fakes the disabling of an interrupt for the real-time kernel, but in fact, it is passed to the real-time kernel. For this type of implementation, there are three available solutions:
- RTLinux: It represents the original implementation of the interrupt abstraction approach and was developed at the Institute of Mining and Technology, New Mexico. Although it still has an open source implementation, most of the development is now done through FSMLabs engineers, later required by the Wind River System on the commercial version of it. The commercial support for RTLinux ended in August 2011.
- RTAI: It is an enhancement made to the RTLinux solution developed in the department of Aerospace Engineering from the Politecnico di Milano. This project is a very active with a high number of developers and has current releases available.
- Xenomai: It represents the third implementation. It's history is a bit twisted: it appeared in August 2001, only to be merged with RTAI in 2013 to generate a real-time operating system that was fit for production. However, the fusion was dispersed in 2005 and it became an independent project again.
The following diagram presents a basic RTLinux architecture.
A similar architecture, as shown in the preceding diagram, applies to the two other solutions since both of them were born from the RTLinux implementation. The difference between them is at the implementation level and each offers various benefits.
The PREEMPT_RT patches are the first option for every developer when a real-time solution is required. For some developers, the PREEMPT_RT patches transform Linux into a real-time solution suitable for their needs. This solution could not replace a real-time operation system, but is, in fact, suitable for a large number of systems.
The PREEMPT_RT patch transforms Linux from a general purpose operating system into a preemptible one using the following tricks:
- Protecting critical sections with the preemptible
rwlock_t preemptible
andspinlock_t
. The use of the old solutions is still available usingraw_spinlock_t
, which shares the same API asspinlock_t
. - The kernel locking mechanisms is preempted by using
rtmutexes
. - A priority inversion and priority inheritance mechanism is implemented for
mutexes
,spinlocks
andrw_semaphores
. - Converting the available Linux timer API into o