Home IoT & Hardware Linux: Embedded Development

Linux: Embedded Development

By Alexandru Vaduva , Alex Gonzalez , Chris Simmonds
books-svg-icon Book
Subscription $15.99 $10 p/m for three months
$10 p/m for first 3 months. $15.99 p/m after that. Cancel Anytime!
What do you get with a Packt Subscription?
This book & 7000+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with a Packt Subscription?
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with eBook + Subscription?
Download this book in EPUB and PDF formats, plus a monthly download credit
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with a Packt Subscription?
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with eBook?
Download this book in EPUB and PDF formats
Access this title in our online reader
DRM FREE - Read whenever, wherever and however you want
Online reader with customised display settings for better reading experience
What do you get with video?
Download this video in MP4 format
Access this title in our online reader
DRM FREE - Watch whenever, wherever and however you want
Online reader with customised display settings for better learning experience
What do you get with video?
Stream this video
Access this title in our online reader
DRM FREE - Watch whenever, wherever and however you want
Online reader with customised display settings for better learning experience
What do you get with Audiobook?
Download a zip folder consisting of audio files (in MP3 Format) along with supplementary PDF
What do you get with Exam Trainer?
Flashcards, Mock exams, Exam Tips, Practice Questions
Access these resources with our interactive certification platform
Mobile compatible-Practice whenever, wherever, however you want
BUY NOW $10 p/m for first 3 months. $15.99 p/m after that. Cancel Anytime!
Subscription $15.99 $10 p/m for three months
What do you get with a Packt Subscription?
This book & 7000+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with a Packt Subscription?
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with eBook + Subscription?
Download this book in EPUB and PDF formats, plus a monthly download credit
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with a Packt Subscription?
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with eBook?
Download this book in EPUB and PDF formats
Access this title in our online reader
DRM FREE - Read whenever, wherever and however you want
Online reader with customised display settings for better reading experience
What do you get with video?
Download this video in MP4 format
Access this title in our online reader
DRM FREE - Watch whenever, wherever and however you want
Online reader with customised display settings for better learning experience
What do you get with video?
Stream this video
Access this title in our online reader
DRM FREE - Watch whenever, wherever and however you want
Online reader with customised display settings for better learning experience
What do you get with Audiobook?
Download a zip folder consisting of audio files (in MP3 Format) along with supplementary PDF
What do you get with Exam Trainer?
Flashcards, Mock exams, Exam Tips, Practice Questions
Access these resources with our interactive certification platform
Mobile compatible-Practice whenever, wherever, however you want
About this book
Embedded Linux is a complete Linux distribution employed to operate embedded devices such as smartphones, tablets, PDAs, set-top boxes, and many more. An example of an embedded Linux distribution is Android, developed by Google. This learning path starts with the module Learning Embedded Linux Using the Yocto Project. It introduces embedded Linux software and hardware architecture and presents information about the bootloader. You will go through Linux kernel features and source code and get an overview of the Yocto Project components available. The next module Embedded Linux Projects Using Yocto Project Cookbook takes you through the installation of a professional embedded Yocto setup, then advises you on best practices. Finally, it explains how to quickly get hands-on with the Freescale ARM ecosystem and community layer using the affordable and open source Wandboard embedded board. Moving ahead, the final module Mastering Embedded Linux Programming takes you through the product cycle and gives you an in-depth description of the components and options that are available at each stage. You will see how functions are split between processes and the usage of POSIX threads. By the end of this learning path, your capabilities will be enhanced to create robust and versatile embedded projects. This Learning Path combines some of the best that Packt has to offer in one complete, curated package. It includes content from the following Packt products: ? Learning Embedded Linux Using the Yocto Project by Alexandru Vaduva ? Embedded Linux Projects Using Yocto Project Cookbook by Alex González ? Mastering Embedded Linux Programming by Chris Simmonds
Publication date:
September 2016
Publisher
Packt
ISBN
9781787124202

   

In this chapter, you will be presented with the advantages of Linux and open source development. There will be examples of systems running embedded Linux, which a vast number of embedded hardware platforms support. After this, you will be introduced to the architecture and development environment of an embedded Linux system, and, in the end, the Yocto Project, where its Poky build system's properties and purposes are summarized.

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

Allowing access to others, having external help, modifications, debugging, and optimizations performed on your open source software implies a longer life for the product and better quality achieved over time. At the same time, the open source environment offers access to a variety of components that could easily be integrated in your product if there's a requirement for them. This permits a quick development process, lower costs, and also shifts a great deal of the maintenance and development work from your product. Also, it offers the possibility to support a particular component to make sure that it continues to suit your needs. However, in most instances, you would need to take some time and build this component for your product from zero.

This brings us to the next benefit of open source, which involves testing and quality assurance for our product. Besides the lesser amount of work that is needed for testing, it is also possible to choose from a number of options before deciding which components fits best for our product. Also, it is cheaper to use open source software, than buying and evaluating proprietary products. This takes and gives back process, visible in the open source community, is the one that generates products of a higher quality and more mature ones. This quality is even greater than that of other proprietary or closed source similar products. Of course, this is not a generally valid affirmation and only happens for mature and widely used products, but here appears the term community and foundation into play.

In general, open source software is developed with the help of communities of developers and users. This system offers access to a greater support on interaction with the tools directly from developers - the sort of thing that does not happen when working with closed source tools. Also, there is no restriction when you're looking for an answer to your questions, no matter whether you work for a company or not. Being part of the open source community means more than bug fixing, bug reporting, or feature development. It is about the contribution added by the developers, but, at the same time, it offers the possibility for engineers to get recognition outside their working environment, by facing new challenges and trying out new things. It can also be seen as a great motivational factor and a source of inspiration for everyone involved in the process.

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:

The preceding command generates this output:

Now, moving on to exercises, the first one requires you fetch the git repository sources for the U-Boot package:

After the sources are available on your machine, you can try to take a look inside the board directory; here, a number of development board manufacturers will be present. Let's take a look at board/atmel/sama5d3_xplained, board/faraday/a320evb, board/freescale/imx, and board/freescale/b4860qds. By observing each of these directories, a pattern can be visualized. Almost all of the boards contain a Kconfig file, inspired mainly from kernel sources because they present the configuration dependencies in a clearer manner. A maintainers file offers a list with the contributors to a particular board support. The base Makefile file takes from the higher-level makefiles the necessary object files, which are obtained after a board-specific support is built. The difference is with board/freescale/imx which only offers a list of configuration data that will be later used by the high-level makefiles.

At the kernel level, the hardware-dependent support is added inside the arch file. Here, for each specific architecture besides Makefile and Kconfig, various numbers of subdirectories could also be added. These offer support for different aspects of a kernel, such as the boot, kernel, memory management, or specific applications.

By cloning the kernel sources, the preceding information can be easily visualized by using this code:

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:

The preceding information can be verified using two methods: the first one involves opening the sysdeps/arm directory, for example, or by reading the ChangeLog.old-ports-arm library. Although it's old and has nonexistent links, such as ports directory, which disappeared from the newer versions of the repository, the latter can still be used as a reference point.

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.

The configurations and options for the selection of one package version in favor of another is mainly added inside the machine configuration. One such example is the Freescale MPC8315E-rdb low-power model supported by Yocto 1.6, and its machine configuration is available inside the meta-yocto-bsp/conf/machine/mpc8315e-rdb.conf file.

General description

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

Now, moving on to exercises, the first one requires you fetch the git repository sources for the U-Boot package:

After the sources are available on your machine, you can try to take a look inside the board directory; here, a number of development board manufacturers will be present. Let's take a look at board/atmel/sama5d3_xplained, board/faraday/a320evb, board/freescale/imx, and board/freescale/b4860qds. By observing each of these directories, a pattern can be visualized. Almost all of the boards contain a Kconfig file, inspired mainly from kernel sources because they present the configuration dependencies in a clearer manner. A maintainers file offers a list with the contributors to a particular board support. The base Makefile file takes from the higher-level makefiles the necessary object files, which are obtained after a board-specific support is built. The difference is with board/freescale/imx which only offers a list of configuration data that will be later used by the high-level makefiles.

At the kernel level, the hardware-dependent support is added inside the arch file. Here, for each specific architecture besides Makefile and Kconfig, various numbers of subdirectories could also be added. These offer support for different aspects of a kernel, such as the boot, kernel, memory management, or specific applications.

By cloning the kernel sources, the preceding information can be easily visualized by using this code:

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:

The preceding information can be verified using two methods: the first one involves opening the sysdeps/arm directory, for example, or by reading the ChangeLog.old-ports-arm library. Although it's old and has nonexistent links, such as ports directory, which disappeared from the newer versions of the repository, the latter can still be used as a reference point.

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.

The configurations and options for the selection of one package version in favor of another is mainly added inside the machine configuration. One such example is the Freescale MPC8315E-rdb low-power model supported by Yocto 1.6, and its machine configuration is available inside the meta-yocto-bsp/conf/machine/mpc8315e-rdb.conf file.

Examples

Now, moving

on to exercises, the first one requires you fetch the git repository sources for the U-Boot package:

After the sources are available on your machine, you can try to take a look inside the board directory; here, a number of development board manufacturers will be present. Let's take a look at board/atmel/sama5d3_xplained, board/faraday/a320evb, board/freescale/imx, and board/freescale/b4860qds. By observing each of these directories, a pattern can be visualized. Almost all of the boards contain a Kconfig file, inspired mainly from kernel sources because they present the configuration dependencies in a clearer manner. A maintainers file offers a list with the contributors to a particular board support. The base Makefile file takes from the higher-level makefiles the necessary object files, which are obtained after a board-specific support is built. The difference is with board/freescale/imx which only offers a list of configuration data that will be later used by the high-level makefiles.

At the kernel level, the hardware-dependent support is added inside the arch file. Here, for each specific architecture besides Makefile and Kconfig, various numbers of subdirectories could also be added. These offer support for different aspects of a kernel, such as the boot, kernel, memory management, or specific applications.

By cloning the kernel sources, the preceding information can be easily visualized by using this code:

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:

The preceding information can be verified using two methods: the first one involves opening the sysdeps/arm directory, for example, or by reading the ChangeLog.old-ports-arm library. Although it's old and has nonexistent links, such as ports directory, which disappeared from the newer versions of the repository, the latter can still be used as a reference point.

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.

The configurations and options for the selection of one package version in favor of another is mainly added inside the machine configuration. One such example is the Freescale MPC8315E-rdb low-power model supported by Yocto 1.6, and its machine configuration is available inside the meta-yocto-bsp/conf/machine/mpc8315e-rdb.conf file.

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.

GNU/Linux consists of the Linux kernel and has a collection of user space applications that are put on top of GNU C Library; this acts as a computer operating system. It may be considered as one of the most prolific instances of open source and free software available, which is still in development. Its history started in 1983 when Richard Stallman founded the GNU Project with the goal of developing a complete Unix-like operating system, which could be put together only from free software. By the beginning of the 1990s, GNU already offered a collection of libraries, Unix-like shells, compilers, and text editors. However, it lacked a kernel. They started developing their own kernel, the Hurd, in 1990. The kernel was based on a Mach micro-kernel design, but it proved to be difficult to work with and had a slow development process.

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:

Introducing GNU/Linux

Although not originally envisioned to run on anything else then x86 PCs, today, the Linux operating system is the most widespread and portable operating system. It can be found on both embedded devices or supercomputers because it offers freedom to its users and developers. Having tools to generate customizable Linux systems is another huge step forward in the development of this tool. It offers access to the GNU/Linux ecosystem to new categories of people who, by using a tool, such as BitBake, end up learning more about Linux, its architecture differences, root filesystem construction and configuration, toolchains, and many other things present in the Linux world.

Linux is not designed to work on microcontrollers. It will not work properly if it has less then 32 MB of RAM, and it will need to have at least 4 MB of storage space. However, if you take a look at this requirement, you will notice that it is very permissive. Adding to this is the fact that it also offers support for a variety of communication peripherals and hardware platforms, which gives you a clear image of why it is so widely adopted.

Working with a Linux architecture in an embedded environment requires certain standards. This is an image that represents graphically an environment which was made available on one of free-electrons Linux courses:

Introducing GNU/Linux

The preceding image presents the two main components that are involved in the development process when working with Linux in the embedded devices world:

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.

Using the Yocto Project, the whole process gets a bit more automatic, mostly because the workflow permitted this. Doing things manually requires a number of steps to be taken by developers:

All these steps tend to become more complex with the increase in the number of software packages that need to be introduced in the final deployable state. Taking this into consideration, it can clearly be stated that manual work is suitable only for a small number of components; automation tools are usually preferred for large and complex systems.

In the last ten years, a number of automation tools could be used to generate an embedded Linux distribution. All of them were based on the same strategy as the one described previously, but they also needed some extra information to solve dependency related problems. These tools are all built around an engine for the execution of tasks and contain metadata that describes actions, dependencies, exceptions, and rules.

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:

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/.

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.

Poky is build system that is developed with the idea of keeping simplicity in mind. By default, the configuration for a test build requires very little interaction from the user. Based on the clone made in one of the previous exercises, we can do a new exercise to emphasize this idea:

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:

OpenEmbedded

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:

OpenEmbedded

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.

Since the development tools on the host machine are prone to change, Yocto usually compiles the necessary tools for the development process of packages and images, and only after their build process is finished, the Bitbake build engine starts building the requested packages. This isolation from the developer's machine helps the development process by guaranteeing the fact that updates from the host machine do not influence or affect the processes of generating the embedded Linux distribution.

Another critical point that was elegantly solved by the Yocto Project is represented by the way that the toolchain handles the inclusion of headers and libraries; because this could bring later on not only compilation but also execution errors that are very hard to predict. Yocto resolves these problems by moving all the headers and libraries inside the corresponding sysroots directory, and by using the sysroot option, the build process makes sure that no contamination is done with the native components. An example will emphasize this information better:

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:

There are also other things that bother developers, such as ptest integration and SDK sysroot's lack of extensibility, but a part of them are solved by the big community behind the project, and until the project shows its limitations, a new one will still need to wait to take its place. Until this happens, Yocto is the framework to use to develop custom embedded Linux distribution or products based in Linux.

Buildroot

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:

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/.

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.

Poky is build system that is developed with the idea of keeping simplicity in mind. By default, the configuration for a test build requires very little interaction from the user. Based on the clone made in one of the previous exercises, we can do a new exercise to emphasize this idea:

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:

OpenEmbedded

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:

OpenEmbedded

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.

Since the development tools on the host machine are prone to change, Yocto usually compiles the necessary tools for the development process of packages and images, and only after their build process is finished, the Bitbake build engine starts building the requested packages. This isolation from the developer's machine helps the development process by guaranteeing the fact that updates from the host machine do not influence or affect the processes of generating the embedded Linux distribution.

Another critical point that was elegantly solved by the Yocto Project is represented by the way that the toolchain handles the inclusion of headers and libraries; because this could bring later on not only compilation but also execution errors that are very hard to predict. Yocto resolves these problems by moving all the headers and libraries inside the corresponding sysroots directory, and by using the sysroot option, the build process makes sure that no contamination is done with the native components. An example will emphasize this information better:

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:

There are also other things that bother developers, such as ptest integration and SDK sysroot's lack of extensibility, but a part of them are solved by the big community behind the project, and until the project shows its limitations, a new one will still need to wait to take its place. Until this happens, Yocto is the framework to use to develop custom embedded Linux distribution or products based in Linux.

OpenEmbedded

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.

Poky is build system that is developed with the idea of keeping simplicity in mind. By default, the configuration for a test build requires very little interaction from the user. Based on the clone made in one of the previous exercises, we can do a new exercise to emphasize this idea:

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:

OpenEmbedded

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:

OpenEmbedded

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.

Since the development tools on the host machine are prone to change, Yocto usually compiles the necessary tools for the development process of packages and images, and only after their build process is finished, the Bitbake build engine starts building the requested packages. This isolation from the developer's machine helps the development process by guaranteeing the fact that updates from the host machine do not influence or affect the processes of generating the embedded Linux distribution.

Another critical point that was elegantly solved by the Yocto Project is represented by the way that the toolchain handles the inclusion of headers and libraries; because this could bring later on not only compilation but also execution errors that are very hard to predict. Yocto resolves these problems by moving all the headers and libraries inside the corresponding sysroots directory, and by using the sysroot option, the build process makes sure that no contamination is done with the native components. An example will emphasize this information better:

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:

There are also other things that bother developers, such as ptest integration and SDK sysroot's lack of extensibility, but a part of them are solved by the big community behind the project, and until the project shows its limitations, a new one will still need to wait to take its place. Until this happens, Yocto is the framework to use to develop custom embedded Linux distribution or products based in Linux.

 

In this chapter, you will learn about toolchains, how to use and customize them, and how code standards apply to them. A toolchain contains a myriad of tools, such as compilers, linkers, assemblers, debuggers, and a variety of miscellaneous utilities that help to manipulate the resulting application binaries. In this chapter, you will learn how to use the GNU toolchain and become familiar with its features. You will be presented with examples that will involve manual configurations, and at the same time, these examples will be moved to the Yocto Project environment. At the end of the chapter, an analysis will be made to identify the similarities and differences between manual and automatic deployment of a toolchain, and the various usage scenarios available for it.

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.

Usually a toolchain, which is available on a traditional desktop or server, executes on these machines and produces executables and libraries that are available and can run on the same system. A toolchain that is normally used for an embedded development environment is called is a cross toolchain. In this case, programs, such as gcc, run on the host system for a specific target architecture, for which it produces a binary code. This whole process is referred to as cross-compilation, and it is the most common way to build sources for embedded development.

Introducing toolchains

In a toolchain environment, three different machines are available:

These three machine are used to generate four different toolchain build procedures:

The three machines that generate four different toolchain build procedures is described in the following diagram:

Introducing toolchains

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).

Toolchain configuration is no easy process. Before starting the search for a prebuilt toolchain, or even building one yourself, the best solution would be to check for a target specific BSP; each development platform usually offers one.

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:

The projects included in the toolchain is described in the following diagram:

Components of toolchains

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.

In this book, however, we will cover only the toolchain building components. These include the following:

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 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).

An application binary interface (ABI) represents the interface between two modules. It gives information on how functions are called and the kind of information that should be passed between components or to the operating system. Referring to a book, such as The Linux Kernel Primer, will do you good, and in my opinion, is a complete guide for what the ABI offers. I will try to reproduce this definition for you.

An ABI can be seen as a set of rules similar to a protocol or an agreement that offers the possibility for a linker to put together compiled modules into one component without the need of recompilation. At the same time, an ABI describes the binary interface between these components. Having this sort of convention and conforming to an ABI offers the benefits of linking object files that could have been compiled with different compilers.

It can be easily seen from both of these definitions that an ABI is dependent on the type of platform, which can include physical hardware, some kind of virtual machine, and so on. It may also be dependent on the programming language that is used and the compiler, but most of it depends on the platform.

The ABI presents how the generated codes operate. The code generation process must also be aware of the ABI, but when coding in a high-level language, attention given to the ABI is rarely a problem. This information could be considered as necessary knowledge to specify some ABI related options.

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 kernel in reference to a user space ABI is backward compatible, and it makes sure that binaries are generated using older kernel header versions, rather than the ones available on the running kernel, will work best. The disadvantages of this are represented by the fact that new system calls, data structures, and binaries generated with a toolchain that use newer kernel headers, might not work for newer features. The need for the latest kernel headers can be justified by the need to have access to the latest kernel features.

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).

It was originally written as the compiler for the GNU operating system and developed as a 100 percent free software. It is distributed under the GNU GPL. This helped it extend to its functionalities across a wide variety of architectures, and it played an important role in the growth of open source software.

The development of GCC started with the effort put in by Richard Stallman to bootstrap the GNU operating system. This quest led Stallman to write his own compiler from scratch. It was released in 1987, with Stallman as the author and other as contributors to it. By 1991, it had already reached a stable phase, but it was unable to include improvements due to its architectural limitations. This meant that the starting point for work on GCC version 2 had begun, but it did not take long until the need for development of new language interfaces started to appear in it as well, and developers started doing their own forks of the compiler source code. This fork initiative proved to be very inefficient, and because of the difficulty of accepting the code procedure, working on it became really frustrating.

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.

GCC was initially written almost entirely in C language, although the Ada frontend is written mostly in Ada language. However, in 2012, the GCC committee announced the use of C++ as an implementation language. The GCC library could not be considered finished as an implementation language, even though its main activities include adding new languages support, optimizations, improved runtime libraries, and increased speed for debugging applications.

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.

Until recently, the syntax tree abstraction of a program was not independent of a target processor, because the meaning of the tree was different from one language frontend to the other, and each provided its own tree syntax. All this changed with the introduction of GENERIC and GIMPLE architecture-independent representations, which were introduced with the GCC 4.0 version.

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 GIMPLE transformation represents complex expressions that are split into a three address code using temporary variables. The GIMPLE representation was inspired by the SIMPLE representation used on the McCAT compiler for simplifying the analysis and optimization of programs.

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 GCC backend is mainly represented by preprocessor macros and specific target architecture functions, such as endianness definitions, calling conventions, or word sizes. The initial stage of the backend uses these representations to generate the RTL; this suggests that although GCC's RTL representation is nominally processor-independent, the initial processing of abstract instructions is adapted for each specific target.

A machine-specific description file contains RTL patterns, also code snippets, or operand constraints that form a final assembly. In the process of RTL generation, the constraints of the target architecture are verified. To generate an RTL snippet, it must match one or a number RTL patterns from the machine description file, and at the same time also satisfy the limitations for these patterns. If this is not done, the process of conversion for the final RTL into machine code would be impossible. Toward the end of compilation, the RTL representation becomes a strict form. Its representation contains a real machine register correspondence and a template from the target's machine description file for each instruction reference.

As a result, the machine code is obtained by calling small snippets of code, which are associated with corresponding patterns. In this way, instructions are generated from target instruction sets. This process involves the usage of registers, offsets, and addresses from the reload phase.

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.

The next C library that I'm going to discuss here is the one that resides as the main C library used by the Yocto Project until version 1.7. Here, I'm referring to the eglibc library. This is a version of glibc optimized for the usage of embedded devices and is, at the same time, able to preserve the compatibility standards.

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.

The newlib system calls describe the usage of the C library across multiple operation systems, and also on embedded systems that do not require an operating system. It is included in commercial GCC distributions, such as Red Hat, CodeSourcery, Attolic, KPIT and others. It also supported by architecture vendors that include ARM, Renesas, or Unix-like environments, such as Cygwin, and even proprietary operating systems of the Amiga personal computer.

By 2007, it also got support from the toolchain maintainers of Nintendo DS, PlayStation, portable SDK Game Boy Advance systems, Wii, and GameCube development platforms. Another addition was made to this list in 2013 when Google Native Client SDK included newlib as their primary C library.

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:

It also has a list of restrictions compared to glibc, as follows:

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:

The uClibc library also has another quality that makes it quite useful. It introduces a new ideology and, because of this, the C library does not try to support as many standards as possible. However, it focuses on embedded Linux and consists of the features necessary for developers who face the limitation of available space. Due to this reason, this library was written from scratch, and even though it has its fair share of limitations, uClibc is an important alternative to glibc. If we take into consideration the fact that most of the features used from C libraries are present in it, the final size is four times smaller, and WindRiver, MontaVista, and TimeSys are active maintainers of it.

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.

The klibc library is licensed under GNU GPL since it uses some components from the Linux kernel, so, as a whole, it is visible as a GPL licensed software, limiting its applicability in commercial embedded software. However, most of the source code of libraries is written under the BSD license.

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.

The main language that we will be focusing on will also be the C programming language. With regard to the GNU coding standard compatibility regarding libraries for GNU, exceptions or utilities and their compatibility should be very good when compared with standards, such as the ones from Berkeley Unix, Standard C, or POSIX. In case of conflicts in compatibility, it is very useful to have compatibility modes for that programming language.

Standards, such as POSIX and C, have a number of limitations regarding the support for extensions - however, these extensions could still be used by including a —posix, —ansi, or —compatible option to disable them. In case the extension offers a high probability of breaking a program or script by being incompatible, a redesign of its interface should be made to ensure compatibility.

A large number of GNU programs suppress the extensions that are known to cause conflict with POSIX if the POSIXLY_CORRECT environment variable is defined. The usage of user defined features offers the possibility for interchanging GNU features with other ones totally different, better, or even use a compatible feature. Additional useful features are always welcomed.

If we take a quick look at the GNU Standard documentation, some useful information can be learned from it:

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.

For the GNU Project, the implementation of an organization's standard specifications is optional, and this can be done only if it helps the system by making it better overall. In most situations, following published standards fits well within a users needs because their programs or scripts could be considered more portable. One such example is represented by the GCC, which implements almost all the features of Standard C, as the standard requires. This offers a great advantage for the developers of the C program. This also applies to GNU utilities that follow POSIX.2 specifications.

There are also specific points in the specifications that are not followed, but this happens with the sole reason of making the GNU system better for users. One such example would be the fact that the Standard C program does not permit extensions to C, but, GCC implements many of them, some being later embraced by the standard. For developers interested in outputting an error message as required by the standard, the --pedantic argument can be used. It is implemented with a view to making sure that GCC fully implements the standard.

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.

Another such example is represented by the GNU utilities, which don't always respect the POSIX.2 standard specifications when referring to support for long named command-line options or intermingling of options with arguments. This incompatibility with the POSIX standard is very useful in practice for developers. The main idea here is not to reject any new feature or remove an older one, although a certain standard mentions it as deprecated or forbidden.

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.

Utilities that are used to read files should avoid dropping null characters or nonprinting characters. The exception here is when these utilities, that are intended for interfacing with certain types of printers or terminals, are unable to handle the previously mentioned characters. The advice that I'd give in this case would be to try and make programs work with a UTF-8 character set, or other sequences of bytes used to represent multibyte characters.

Make sure that you check system calls for error return values; the exception here is when a developer wishes to ignore the errors. It would be a good idea to include the system error text from strerror, perror, or equivalent error handling functions, in error messages that result from a crashed on system call, adding the name of the source code file, and also the name of the utility. This is done to make sure that the error message is easy to read and understand by anyone involved in the interaction with the source code or the program.

Check the return value for malloc or realloc to verify if they've returned zero. In case realloc is used in order to make a block smaller in systems that approximate block dimensions to powers of 2, realloc may have a different behavior and get a different block. In Unix, when realloc has a bug, it destroys the storage block for a zero return value. For GNU, this bug does not occur, and when it fails, the original block remains unchanged. If you want to run the same program on Unix and do not want to lose data, you could check if the bug was resolved on the Unix system or use the malloc GNU.

The content of the block that was freed is not accessible to alter or for any other interactions from the user. This can be done before calling free.

When a malloc command fails in a noninteractive program, we face a fatal error. In case the same situation is repeated, but, this time, an interactive program is involved, it would be better to abort the command and return to the read loop. This offers the possibility to free up virtual memory, kill other processes, and retry the command.

To decode arguments, the getopt_long option can be used.

When writing static storage during program execution, use C code for its initialization. However, for data that will not be changed, reserve C initialized declarations.

Try to keep away from low-level interfaces to unknown Unix data structures - this could happen when the data structure do not work in a compatible fashion. For example, to find all the files inside a directory, a developer could use the readdir function, or any high-level interface available function, since these do not have compatibility problems.

For signal handling, use the BSD variant of signal and the POSIX sigaction function. The USG signal interface is not the best alternative in this case. Using POSIX signal functions is nowadays considered the easiest way to develop a portable program. However, the use of one function over another is completely up to the developer.

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.

Do not use a count of the encountered errors in a program as an exit status. This practice is not the best, mostly because the values for an exit status are limited to 8 bits only, and an execution of the executable might have more than 255 errors. For example, if you try to return exit status 256 for a process, the parent process will see a status of zero and consider that the program finished successfully.

If temporary files are created, checking that the TMPDIR environment variable would be a good idea. If the variable is defined, it would be wise to use the /tmp directory instead. The use of temporary files should be done with caution because there is the possibility of security breaches occurring when creating them in world-writable directories. For C language, this can be avoided by creating temporary files in the following manner:

This can also be done using the mkstemps function, which is made available by Gnulib.

For a bash environment, use the noclobber environment variable, or the set -C short version, to avoid the previously mentioned problem. Furthermore, the mktemp available utility is altogether a better solution for making a temporary file a shell environment; this utility is available in the GNU Coreutils package.

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:

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.

Advice on robust programming

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.

Utilities that are used to read files should avoid dropping null characters or nonprinting characters. The exception here is when these utilities, that are intended for interfacing with certain types of printers or terminals, are unable to handle the previously mentioned characters. The advice that I'd give in this case would be to try and make programs work with a UTF-8 character set, or other sequences of bytes used to represent multibyte characters.

Make sure that you check system calls for error return values; the exception here is when a developer wishes to ignore the errors. It would be a good idea to include the system error text from strerror, perror, or equivalent error handling functions, in error messages that result from a crashed on system call, adding the name of the source code file, and also the name of the utility. This is done to make sure that the error message is easy to read and understand by anyone involved in the interaction with the source code or the program.

Check the return value for malloc or realloc to verify if they've returned zero. In case realloc is used in order to make a block smaller in systems that approximate block dimensions to powers of 2, realloc may have a different behavior and get a different block. In Unix, when realloc has a bug, it destroys the storage block for a zero return value. For GNU, this bug does not occur, and when it fails, the original block remains unchanged. If you want to run the same program on Unix and do not want to lose data, you could check if the bug was resolved on the Unix system or use the malloc GNU.

The content of the block that was freed is not accessible to alter or for any other interactions from the user. This can be done before calling free.

When a malloc command fails in a noninteractive program, we face a fatal error. In case the same situation is repeated, but, this time, an interactive program is involved, it would be better to abort the command and return to the read loop. This offers the possibility to free up virtual memory, kill other processes, and retry the command.

To decode arguments, the getopt_long option can be used.

When writing static storage during program execution, use C code for its initialization. However, for data that will not be changed, reserve C initialized declarations.

Try to keep away from low-level interfaces to unknown Unix data structures - this could happen when the data structure do not work in a compatible fashion. For example, to find all the files inside a directory, a developer could use the readdir function, or any high-level interface available function, since these do not have compatibility problems.

For signal handling, use the BSD variant of signal and the POSIX sigaction function. The USG signal interface is not the best alternative in this case. Using POSIX signal functions is nowadays considered the easiest way to develop a portable program. However, the use of one function over another is completely up to the developer.

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.

Do not use a count of the encountered errors in a program as an exit status. This practice is not the best, mostly because the values for an exit status are limited to 8 bits only, and an execution of the executable might have more than 255 errors. For example, if you try to return exit status 256 for a process, the parent process will see a status of zero and consider that the program finished successfully.

If temporary files are created, checking that the TMPDIR environment variable would be a good idea. If the variable is defined, it would be wise to use the /tmp directory instead. The use of temporary files should be done with caution because there is the possibility of security breaches occurring when creating them in world-writable directories. For C language, this can be avoided by creating temporary files in the following manner:

This can also be done using the mkstemps function, which is made available by Gnulib.

For a bash environment, use the noclobber environment variable, or the set -C short version, to avoid the previously mentioned problem. Furthermore, the mktemp available utility is altogether a better solution for making a temporary file a shell environment; this utility is available in the GNU Coreutils package.

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:

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.

Generating the toolchain

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:

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 first step for the Yocto Project is to identify the exact sources and packages that will be combined to generate the toolchain that will be used by later built packages, such as U-Boot bootloader, kernel, BusyBox and others. In this book, the sources that will be discussed are the ones available inside the dizzy branch, the latest poky 12.0 version, and the Yocto Project version 1.7. The sources can be gathered using the following command:

Gathering the sources and investigating the source code, we identified a part of the packages mentioned and presented in the preceding headings, as shown here:

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 last components is represented by C libraries that are present as components inside the Poky dizzy branch. There are two C libraries available that can be used by developers. The first one is represented by the GNU C library, also known as glibc, which is the most used C library in Linux systems. The sources for glibc package can be viewed here:

From these sources, the same location also includes tools, such as ldconfig, a standalone native dynamic linker for runtime dependencies and a binding and cross locale generation tool. In the other C library, called uClibc, as previously mentioned, a library designed for embedded systems has fewer recipes, as it can be viewed from the Poky 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".

As mentioned previously, the toolchain generation process for the Yocto Project is simpler. It is the first task that is executed before any recipe is built using the Yocto Project. To generate the cross-toolchain inside using Bitbake, first, the bitbake meta-ide-support task is executed. The task can be executed for the qemuarm architecture, for example, but it can, of course, be generated in a similar method for any given hardware architecture. After the task finishes the execution process, the toolchain is generated and it populates the build directory. It can be used after this by sourcing the environment-setup script available in the tmp directory:

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 main purpose of this chapter is to present the main properties of embedded bootloaders and firmware, their booting mechanisms, and the problems that appear when firmware is updated or modified. We will also discuss the problems related to safety, installation, or fault tolerance. With regard to bootloader and firmware notions, we have multiple definitions available and a number of them refer to traditional desktop systems, which we are not interested in.

A firmware usually represents a fixed and small program that is used on a system to control hardware. It performs low-level operations and is usually stored on flash, ROM, EPROM, and so on. It is not changed very often. Since there have been situations where this term has confused people and was sometimes used only to define hardware devices or represent data and its instructions, it was avoided altogether. It represents a combination of the two: computer data and information, along with the hardware device combined in a read-only piece of software available on the device.

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.

Today, a number of bootloaders are available. Due to limited space available for this topic, and also the fact that their number is high, we will only discuss the most popular ones. U-Boot is one of the most popular bootloaders available for architectures, such as PowerPC, ARM, MIPS, and others. It will constitute the primary focus of this chapter.

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.

The majority of the available processors when power is applied to them go to a default address location, and after finding the first bytes of binary data, start executing them. Based on this information, the hardware designers define the layout for the flash memory and the address ranges that could later be used to load and boot the Linux operating system from predictable addresses.

In the first stage of initialization, the board init is done, usually in the assembler language specific to the processor and after this is finished, the entire ecosystem is prepared for the operating system booting process. The bootloader is responsible for this; it is the component that offers the possibility to load, locate, and execute primary components of the operating system. Additionally, it can contain other advanced features, such as the capability to upgrade the OS image, validate an OS image, choose between several OS images, and even the possibility to upgrade itself. The difference between the traditional PC BIOS and an embedded bootloader is the fact that in an embedded environment, the bootloader is overwritten after the Linux kernel starts execution. It, in fact, ceases to exist after it offers control to the OS image.

Bootloaders need to carefully initialize peripherals, such as flash or DRAM, before they are used. This is not an easy task to do. For example, the DRAM chips cannot be read or written in a direct method - each chip has a controller that needs to be enabled for read and write operations. At the same time, the DRAM needs to be continually refreshed because the data will be lost otherwise. The refresh operation, in fact, represents the reading of each DRAM location within the time frame mentioned by the hardware manufacturer. All these operations are the DRAM controller's responsibility, and it can generate a lot of frustration for the embedded developer because it requires specific knowledge about the architecture design and DRAM chip.

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.

The first element that poses as a complexity is the compatibility of the start up code with the processor's boot sequence. The first executable instructions need to be at a predefined location in the flash memory, which is dependent of the processor and even hardware architecture. There is also the possibility for a number of processors to seek for those first executable instructions in several locations based on the hardware signals that are received.

Another possibility is to have the same structure on many of the newly available development boards, such as the Atmel SAMA5D3-Xplained:

The role of the bootloader

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.

The lack of an execution context represents another complexity. Having to write even a simple "Hello World" in a system without a memory and, therefore, without a stack on which to allocate information, would look very different from the well-known "Hello World" example. This is the reason why the bootloader initializes the RAM memory to have a stack available and is able to run higher-level programs or languages, such as C.

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.

The first stage bootloader is very small in terms of dimensions - in general, it is only 512 bytes and resides on a volatile memory. It performs the initialization for the full bootloader during the second stage. The second stage bootloaders usually reside next to the first stage ones, they contain the most number of features and do most of the work. They also know how to interpret various filesystem formats, mostly because the kernel is loaded from a filesystem.

For x86 processors, there are more bootloader solutions that are available:

For most embedded systems, this booting process does not apply, although there are some that replicate this behavior. There are two types of situations that will be presented next. The first one is a situation where the code execution starts from a fixed address location, and the second one refers to a situation where the CPU has a code available in the ROM memory that is called.

Delving into the bootloader cycle

The right-hand side of the image is presented as the previously mentioned booting mechanism. In this case, the hardware requires a NOR flash memory chip, available at the start address to assure the start of the code execution.

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.

Although it is not the most practical method used for the bootloader boot process, it is still available. However, it somehow becomes usable only on boards that are not suitable for more potent booting options.

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:

The U-Boot bootloader

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:

On inspection of this file's source code, it can be seen that only an instruction is defined in this section independently of the available configuration options.

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.

Booting the U-Boot options

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.

To boot images, U-Boot offers a number of capabilities that refer to the support of storage subsystems. These options include the RAM boot, MMC boot, NAND boot, NFS boot and so on. The support for these options is not always easy and could imply both hardware and software complexity.

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:

More information about the SAMA5D3 Xplained board can be found by inspecting the corresponding doc directory and README files, such as README.at91, README.at91-soc, README.atmel_mci, README.atmel_pmecc, README.ARM-memory-map, and so on.

For people interested in committing to the changes they made while porting a new development board, CPU, or SOC to U-Boot, a few rules should be followed. All of these are related to the git interaction and help you to ensure the proper maintenance of your branches.

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:

Let's take a look at following diagram, which illustrates the git rebase operation:

Porting U-Boot

As shown in both the preceding and following diagram, the git rebase operation has recreated the work from one branch onto another. Every commit from one branch is made available on the succeeding one, just after the last commit from it.

Porting U-Boot

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.

Porting U-Boot

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:

Booting the U-Boot options

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.

Booting the U-Boot options

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.

To boot images, U-Boot offers a number of capabilities that refer to the support of storage subsystems. These options include the RAM boot, MMC boot, NAND boot, NFS boot and so on. The support for these options is not always easy and could imply both hardware and software complexity.

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:

More information about the SAMA5D3 Xplained board can be found by inspecting the corresponding doc directory and README files, such as README.at91, README.at91-soc, README.atmel_mci, README.atmel_pmecc, README.ARM-memory-map, and so on.

For people interested in committing to the changes they made while porting a new development board, CPU, or SOC to U-Boot, a few rules should be followed. All of these are related to the git interaction and help you to ensure the proper maintenance of your branches.

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:

Let's take a look at following diagram, which illustrates the git rebase operation:

Porting U-Boot

As shown in both the preceding and following diagram, the git rebase operation has recreated the work from one branch onto another. Every commit from one branch is made available on the succeeding one, just after the last commit from it.

Porting U-Boot

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.

Porting U-Boot

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:

Porting U-Boot

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:

More information about the SAMA5D3 Xplained board can be found by inspecting the corresponding doc directory and README files, such as README.at91, README.at91-soc, README.atmel_mci, README.atmel_pmecc, README.ARM-memory-map, and so on.

For people interested in committing to the changes they made while porting a new development board, CPU, or SOC to U-Boot, a few rules should be followed. All of these are related to the git interaction and help you to ensure the proper maintenance of your branches.

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:

Let's take a look at following diagram, which illustrates the git rebase operation:

Porting U-Boot

As shown in both the preceding and following diagram, the git rebase operation has recreated the work from one branch onto another. Every commit from one branch is made available on the succeeding one, just after the last commit from it.

Porting U-Boot

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.

Porting U-Boot

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.

The board that we will focus on here is the sama5d3_xplained development board, and it is available inside the meta-atmel layer. Inside this layer, the corresponding recipes for the first and second stage bootloaders can be found inside the recipes-bsp directory. Here, I am referring to the at91bootstrap and u-boot recipes. There are some misconceptions about first stage and second stage bootloaders. They might be referred to as second level and third level bootloaders, because the boot ROM code may or may not be taken into account during a discussion. In this book, we prefer to call them as first stage and second stage bootloaders.

The AT91bootstrap package represents the first-stage bootloader from Atmel available for their SOCs. It manages hardware initialization and also executes the second stage bootloader download from a boot media inside the memory; it starts it at the end. In the meta-atmel layer, the second stage bootloader is u-boot, and it is later used for the Linux operating system boot.

Usually, inside a BSP layer, the support for multiple development boards is offered, and this means that multiple versions and bootloader packages are offered as well. The distinction between them, however, is on the basis of machine configurations. For the SAMA5D3 Xplained development board, the machine configuration is available inside the conf/machine/sama5d3_xplained file. In this file, the preferred bootloader versions, providers, and configurations are defined. If these configurations are not MACHINE specific, they could very well be performed inside the package recipe.

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:

Nowadays, UNIX is a mature operating system with support for features, such as virtual memory, TCP/IP networking, demand paging preemptive multiprocessing, and multithreading. The features spread is wide and varies from small embedded devices to systems with hundreds of processors. Its development has moved past the idea that UNIX is a research project, and it has become an operating system that is general-purpose and practically fits any needs. All this has happened due to its elegant design and proven simplicity. It was able to evolve without losing its capability to remain simple.

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.

Today, Linux is an experienced operating system that is able to run on a multitude of architectures. It is able to run on devices that are even smaller than a wristwatch or on clusters of supercomputer. It's the new sensation of our days and is being adopted by companies and developers around the world in an increasingly diversified manner. The interest in the Linux operating system is very strong and this implies not only diversity, but also offers a great number of benefits, ranging from security, new features, embedded solutions to server solution options, and many 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.

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:

Memory mapping and management

The physical memory defines algorithms and data structures that are able to maintain the memory, and it is done at the page level relatively independently by the virtual memory. Here, each physical page has a struct page descriptor associated with it that is used to incorporate information about the physical page. Each page has a struct page descriptor defined. Some of the fields of this structure are as follows:

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:

Memory mapping and management

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 memory allocation is different between the user space and kernel space context because the kernel space memory allocation is not able to allocate memory in an easy manner. This difference is mostly due to the fact that error management in the kernel context is not easily done, or at least not in the same key as the user space context. This is one of the problems that will be presented in this section along with the solutions because it helps readers understand how memory management is done in the context of the Linux kernel.

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.

The kernel divides the hardware into various zone of memory, mostly because there are pages in the physical memory that are not accessible for a number of the tasks. For example, there are hardware devices that can perform DMA. These actions are done by interacting with only a zone of the physical memory, simply called ZONE_DMA. It is accessible between 0-16 Mb for x86 architectures.

There are four main memory zones available and other two less notable ones that are defined inside the kernel sources in the include/linux/mmzone.h header file. The zone mapping is also architecture-dependent for the Atmel SAMA5D3-Xplained board. We have the following zones defined:

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.

With this information available, we can move ahead and find out how the kernel implements memory allocation. All the available functions that are necessary for memory allocation and memory interaction in general, are inside the linux/gfp.h header file. Some of these functions are:

This function is used to allocate physical pages in a continuous location. At the end, the return value is represented by the pointer of the first page structure if the allocation is successful, or NULL if errors occur:

This function is used to get the logical address for a corresponding memory page:

This one is similar to the alloc_pages() function, but the difference is that the return variable is offered in the struct page * alloc_page(gfp_t gfp_mask) return argument:

The preceding two functions are wrappers over similar ones, the difference is that this function returns only one page information. The order for this function has the zero value:

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.

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:

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.

For a kernel thread, no process address space associated with it; for kernel, the process descriptor structure is defined as NULL. In this way, the kernel mentions that a kernel thread does not have a user context. A kernel thread only has access to the same memory as all the other processes. A kernel thread does not have any pages in a user space or access to the user space memory.

Since the processors work only with physical addresses, the translation between physical and virtual memory needs to be made. These operations are done by the page tables that split the virtual addresses into smaller components with associated indexes that are used for pointing purposes. In the majority of available boards and architectures in general, the page table lookup is handled by the hardware; the kernel is responsible for setting it up.

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.

When fork() is used, the resources that the parent process has are reproduced for the child. This function is implemented in a very interesting manner; it uses the clone() system call that, at it's base, contains the copy_process() function. This functions does the following:

Threads in Linux are very similar to processes. They are viewed as processes that share various resources, such as memory address space, open files, and so on. The creation of threads is similar to a normal task, the exception being the clone() function, which passes flags that mention shared resources. For example, the clone function calls for a thread, which is clone(CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND, 0), while for the normal fork looks similar to clone(SIGCHLD, 0).

The notion of kernel threads appeared as a solution to problems involving tasks running in the background of the kernel context. The kernel thread does not have an address space and is only available inside the kernel context. It has the same properties as a normal process, but is only used for special tasks, such as ksoftirqd, flush, and so on.

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:

After the preceding steps are performed, the object associated with this task is freed and it becomes unrunnable. Its memory exists solely as information for its parent. After its parent announces that this information is of no use to it, this memory is freed for the system to use.

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.

A process scheduler constitutes one of the most important components of the Linux kernel because it defines the user interaction with the operating system in general. The Linux kernel CFS is the scheduler that appeals to developers and users because it offers scalability and performance with the most reasonable approach.

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:

System calls

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:

A system call has a syscall number associated with it, which is a unique number used as a reference for the system call that cannot be changed (there is no possibility of implementing a system call). A symbolic constant for each system call number is available in the <sys/syscall.h> header file. To check the existence of a system call, sys_ni_syscall() is used, which returns the ENOSYS error for an invalid system call.

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:

The virtual filesystem system call implementation is very well summarized in this image:

The virtual file system

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.

All the other three system calls have corresponding sys_read(), sys_write(), and sys_close() functions that are called internally.

Memory mapping and management

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:

Memory mapping and management

The physical memory defines algorithms and data structures that are able to maintain the memory, and it is done at the page level relatively independently by the virtual memory. Here, each physical page has a struct page descriptor associated with it that is used to incorporate information about the physical page. Each page has a struct page descriptor defined. Some of the fields of this structure are as follows:

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:

Memory mapping and management

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 memory allocation is different between the user space and kernel space context because the kernel space memory allocation is not able to allocate memory in an easy manner. This difference is mostly due to the fact that error management in the kernel context is not easily done, or at least not in the same key as the user space context. This is one of the problems that will be presented in this section along with the solutions because it helps readers understand how memory management is done in the context of the Linux kernel.

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.

The kernel divides the hardware into various zone of memory, mostly because there are pages in the physical memory that are not accessible for a number of the tasks. For example, there are hardware devices that can perform DMA. These actions are done by interacting with only a zone of the physical memory, simply called ZONE_DMA. It is accessible between 0-16 Mb for x86 architectures.

There are four main memory zones available and other two less notable ones that are defined inside the kernel sources in the include/linux/mmzone.h header file. The zone mapping is also architecture-dependent for the Atmel SAMA5D3-Xplained board. We have the following zones defined:

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.

With this information available, we can move ahead and find out how the kernel implements memory allocation. All the available functions that are necessary for memory allocation and memory interaction in general, are inside the linux/gfp.h header file. Some of these functions are:

This function is used to allocate physical pages in a continuous location. At the end, the return value is represented by the pointer of the first page structure if the allocation is successful, or NULL if errors occur:

This function is used to get the logical address for a corresponding memory page:

This one is similar to the alloc_pages() function, but the difference is that the return variable is offered in the struct page * alloc_page(gfp_t gfp_mask) return argument:

The preceding two functions are wrappers over similar ones, the difference is that this function returns only one page information. The order for this function has the zero value:

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.

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:

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.

For a kernel thread, no process address space associated with it; for kernel, the process descriptor structure is defined as NULL. In this way, the kernel mentions that a kernel thread does not have a user context. A kernel thread only has access to the same memory as all the other processes. A kernel thread does not have any pages in a user space or access to the user space memory.

Since the processors work only with physical addresses, the translation between physical and virtual memory needs to be made. These operations are done by the page tables that split the virtual addresses into smaller components with associated indexes that are used for pointing purposes. In the majority of available boards and architectures in general, the page table lookup is handled by the hardware; the kernel is responsible for setting it up.

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.

When fork() is used, the resources that the parent process has are reproduced for the child. This function is implemented in a very interesting manner; it uses the clone() system call that, at it's base, contains the copy_process() function. This functions does the following:

Threads in Linux are very similar to processes. They are viewed as processes that share various resources, such as memory address space, open files, and so on. The creation of threads is similar to a normal task, the exception being the clone() function, which passes flags that mention shared resources. For example, the clone function calls for a thread, which is clone(CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND, 0), while for the normal fork looks similar to clone(SIGCHLD, 0).

The notion of kernel threads appeared as a solution to problems involving tasks running in the background of the kernel context. The kernel thread does not have an address space and is only available inside the kernel context. It has the same properties as a normal process, but is only used for special tasks, such as ksoftirqd, flush, and so on.

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:

After the preceding steps are performed, the object associated with this task is freed and it becomes unrunnable. Its memory exists solely as information for its parent. After its parent announces that this information is of no use to it, this memory is freed for the system to use.

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.

A process scheduler constitutes one of the most important components of the Linux kernel because it defines the user interaction with the operating system in general. The Linux kernel CFS is the scheduler that appeals to developers and users because it offers scalability and performance with the most reasonable approach.

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:

System calls

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:

A system call has a syscall number associated with it, which is a unique number used as a reference for the system call that cannot be changed (there is no possibility of implementing a system call). A symbolic constant for each system call number is available in the <sys/syscall.h> header file. To check the existence of a system call, sys_ni_syscall() is used, which returns the ENOSYS error for an invalid system call.

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:

The virtual filesystem system call implementation is very well summarized in this image:

The virtual file system

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.

All the other three system calls have corresponding sys_read(), sys_write(), and sys_close() functions that are called internally.

Page cache and page writeback

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

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:

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.

For a kernel thread, no process address space associated with it; for kernel, the process descriptor structure is defined as NULL. In this way, the kernel mentions that a kernel thread does not have a user context. A kernel thread only has access to the same memory as all the other processes. A kernel thread does not have any pages in a user space or access to the user space memory.

Since the processors work only with physical addresses, the translation between physical and virtual memory needs to be made. These operations are done by the page tables that split the virtual addresses into smaller components with associated indexes that are used for pointing purposes. In the majority of available boards and architectures in general, the page table lookup is handled by the hardware; the kernel is responsible for setting it up.

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.

When fork() is used, the resources that the parent process has are reproduced for the child. This function is implemented in a very interesting manner; it uses the clone() system call that, at it's base, contains the copy_process() function. This functions does the following:

Threads in Linux are very similar to processes. They are viewed as processes that share various resources, such as memory address space, open files, and so on. The creation of threads is similar to a normal task, the exception being the clone() function, which passes flags that mention shared resources. For example, the clone function calls for a thread, which is clone(CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND, 0), while for the normal fork looks similar to clone(SIGCHLD, 0).

The notion of kernel threads appeared as a solution to problems involving tasks running in the background of the kernel context. The kernel thread does not have an address space and is only available inside the kernel context. It has the same properties as a normal process, but is only used for special tasks, such as ksoftirqd, flush, and so on.

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:

After the preceding steps are performed, the object associated with this task is freed and it becomes unrunnable. Its memory exists solely as information for its parent. After its parent announces that this information is of no use to it, this memory is freed for the system to use.

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.

A process scheduler constitutes one of the most important components of the Linux kernel because it defines the user interaction with the operating system in general. The Linux kernel CFS is the scheduler that appeals to developers and users because it offers scalability and performance with the most reasonable approach.

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:

System calls

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:

A system call has a syscall number associated with it, which is a unique number used as a reference for the system call that cannot be changed (there is no possibility of implementing a system call). A symbolic constant for each system call number is available in the <sys/syscall.h> header file. To check the existence of a system call, sys_ni_syscall() is used, which returns the ENOSYS error for an invalid system call.

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:

The virtual filesystem system call implementation is very well summarized in this image:

The virtual file system

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.

All the other three system calls have corresponding sys_read(), sys_write(), and sys_close() functions that are called internally.

The process address space

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:

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.

For a kernel thread, no process address space associated with it; for kernel, the process descriptor structure is defined as NULL. In this way, the kernel mentions that a kernel thread does not have a user context. A kernel thread only has access to the same memory as all the other processes. A kernel thread does not have any pages in a user space or access to the user space memory.

Since the processors work only with physical addresses, the translation between physical and virtual memory needs to be made. These operations are done by the page tables that split the virtual addresses into smaller components with associated indexes that are used for pointing purposes. In the majority of available boards and architectures in general, the page table lookup is handled by the hardware; the kernel is responsible for setting it up.

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.

When fork() is used, the resources that the parent process has are reproduced for the child. This function is implemented in a very interesting manner; it uses the clone() system call that, at it's base, contains the copy_process() function. This functions does the following:

Threads in Linux are very similar to processes. They are viewed as processes that share various resources, such as memory address space, open files, and so on. The creation of threads is similar to a normal task, the exception being the clone() function, which passes flags that mention shared resources. For example, the clone function calls for a thread, which is clone(CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND, 0), while for the normal fork looks similar to clone(SIGCHLD, 0).

The notion of kernel threads appeared as a solution to problems involving tasks running in the background of the kernel context. The kernel thread does not have an address space and is only available inside the kernel context. It has the same properties as a normal process, but is only used for special tasks, such as ksoftirqd, flush, and so on.

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:

After the preceding steps are performed, the object associated with this task is freed and it becomes unrunnable. Its memory exists solely as information for its parent. After its parent announces that this information is of no use to it, this memory is freed for the system to use.

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.

A process scheduler constitutes one of the most important components of the Linux kernel because it defines the user interaction with the operating system in general. The Linux kernel CFS is the scheduler that appeals to developers and users because it offers scalability and performance with the most reasonable approach.

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:

System calls

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:

A system call has a syscall number associated with it, which is a unique number used as a reference for the system call that cannot be changed (there is no possibility of implementing a system call). A symbolic constant for each system call number is available in the <sys/syscall.h> header file. To check the existence of a system call, sys_ni_syscall() is used, which returns the ENOSYS error for an invalid system call.

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:

The virtual filesystem system call implementation is very well summarized in this image:

The virtual file system

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.

All the other three system calls have corresponding sys_read(), sys_write(), and sys_close() functions that are called internally.

Process management

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.

When fork() is used, the resources that the parent process has are reproduced for the child. This function is implemented in a very interesting manner; it uses the clone() system call that, at it's base, contains the copy_process() function. This functions does the following:

Threads in Linux are very similar to processes. They are viewed as processes that share various resources, such as memory address space, open files, and so on. The creation of threads is similar to a normal task, the exception being the clone() function, which passes flags that mention shared resources. For example, the clone function calls for a thread, which is clone(CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND, 0), while for the normal fork looks similar to clone(SIGCHLD, 0).

The notion of kernel threads appeared as a solution to problems involving tasks running in the background of the kernel context. The kernel thread does not have an address space and is only available inside the kernel context. It has the same properties as a normal process, but is only used for special tasks, such as ksoftirqd, flush, and so on.

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:

After the preceding steps are performed, the object associated with this task is freed and it becomes unrunnable. Its memory exists solely as information for its parent. After its parent announces that this information is of no use to it, this memory is freed for the system to use.

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.

A process scheduler constitutes one of the most important components of the Linux kernel because it defines the user interaction with the operating system in general. The Linux kernel CFS is the scheduler that appeals to developers and users because it offers scalability and performance with the most reasonable approach.

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:

System calls

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:

A system call has a syscall number associated with it, which is a unique number used as a reference for the system call that cannot be changed (there is no possibility of implementing a system call). A symbolic constant for each system call number is available in the <sys/syscall.h> header file. To check the existence of a system call, sys_ni_syscall() is used, which returns the ENOSYS error for an invalid system call.

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:

The virtual filesystem system call implementation is very well summarized in this image:

The virtual file system

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.

All the other three system calls have corresponding sys_read(), sys_write(), and sys_close() functions that are called internally.

Process scheduling

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.

A process scheduler constitutes one of the most important components of the Linux kernel because it defines the user interaction with the operating system in general. The Linux kernel CFS is the scheduler that appeals to developers and users because it offers scalability and performance with the most reasonable approach.

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:

System calls

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:

A system call has a syscall number associated with it, which is a unique number used as a reference for the system call that cannot be changed (there is no possibility of implementing a system call). A symbolic constant for each system call number is available in the <sys/syscall.h> header file. To check the existence of a system call, sys_ni_syscall() is used, which returns the ENOSYS error for an invalid system call.

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:

The virtual filesystem system call implementation is very well summarized in this image:

The virtual file system

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.

All the other three system calls have corresponding sys_read(), sys_write(), and sys_close() functions that are called internally.

System calls

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:

System calls

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:

A system call has a syscall number associated with it, which is a unique number used as a reference for the system call that cannot be changed (there is no possibility of implementing a system call). A symbolic constant for each system call number is available in the <sys/syscall.h> header file. To check the existence of a system call, sys_ni_syscall() is used, which returns the ENOSYS error for an invalid system call.

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:

The virtual filesystem system call implementation is very well summarized in this image:

The virtual file system

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.

All the other three system calls have corresponding sys_read(), sys_write(), and sys_close() functions that are called internally.

The virtual file system

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:

The virtual filesystem system call implementation is very well summarized in this image:

The virtual file system

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.

All the other three system calls have corresponding sys_read(), sys_write(), and sys_close() functions that are called internally.

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 Linux interrupt handling layer offers an abstraction of interrupt handling for various device drivers through comprehensive API functions. It is used to request, enable, disable, and free interrupts, making sure that portability is guaranteed on multiple platforms. It handles all available interrupt controller hardware.

The generic interrupt handling uses the __do_IRQ() handler, which is able to deal with all the available types of the interrupt logic. The handling layers are divided in two components:

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:

Interrupts

Although the model for the top and bottom half interrupt mechanism looks simple, it has a very complicated function calling mechanism model. This example shows this fact for the ARM architecture:

Interrupts

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().

A module is required to register an interrupt channel and release it afterwards. The total number of supported requests is counted from the 0 value to the number of IRQs-1. This information is available inside the <asm/irq.h> header file. When the registration is done, a handler flag is passed to the request_irq() function to specify the interrupt handler's type, as follows:

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.

Next on the list are tasklets. Although they are built on top of softirqs, they are more commonly used for bottom half interrupt handling. Here are some of the reasons why this is done:

Tasklets have a struct tasklet_struct structure available. These are also available inside the include/linux/interrupt.h header file, and unlike softirqs, tasklets are non-reentrant.

Third on the list are work queues that represent a different form of doing the work allotted in comparison to previously presented mechanisms. The main differences are as follows:

Although they might have a latency that is slightly bigger the tasklets, the preceding qualities are really useful. The tasklets are built around the struct workqueue_struct structure, available inside the kernel/workqueue.c file.

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:

Methods to perform kernel synchronization

The preceding image offers a clear picture as to why synchronization is necessary. For example, a race condition can appear when more than one kernel control path is interlinked. To protect these critical regions, a number of measures should be taken. Also, it should be taken into consideration that an interrupt handler cannot be interrupted and softirqs should not be interleaved.

A number of synchronization primitives have been born:

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.

Bottom halves

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.

Next on the list are tasklets. Although they are built on top of softirqs, they are more commonly used for bottom half interrupt handling. Here are some of the reasons why this is done:

Tasklets have a struct tasklet_struct structure available. These are also available inside the include/linux/interrupt.h header file, and unlike softirqs, tasklets are non-reentrant.

Third on the list are work queues that represent a different form of doing the work allotted in comparison to previously presented mechanisms. The main differences are as follows:

Although they might have a latency that is slightly bigger the tasklets, the preceding qualities are really useful. The tasklets are built around the struct workqueue_struct structure, available inside the kernel/workqueue.c file.

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:

Methods to perform kernel synchronization

The preceding image offers a clear picture as to why synchronization is necessary. For example, a race condition can appear when more than one kernel control path is interlinked. To protect these critical regions, a number of measures should be taken. Also, it should be taken into consideration that an interrupt handler cannot be interrupted and softirqs should not be interleaved.

A number of synchronization primitives have been born:

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.

Methods to perform kernel synchronization

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:

Methods to perform kernel synchronization

The preceding image offers a clear picture as to why synchronization is necessary. For example, a race condition can appear when more than one kernel control path is interlinked. To protect these critical regions, a number of measures should be taken. Also, it should be taken into consideration that an interrupt handler cannot be interrupted and softirqs should not be interleaved.

A number of synchronization primitives have been born:

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 disadvantages of higher frequency on the other hand, implies a higher overhead. The processors spend more time in a timer interrupt context; also, an increase in power consumption will take place because more computing is done.

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.

The development process

This way of working was kept up until the 2.6.0 version when a large number of features were added inside the kernel during every minor release, and all of them were very well put together as to not cause the need for the branching out of a new development branch. This implied a faster pace of release with more features available. So, the following changes have appeared since the release of the 2.6.14 kernel:

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.

This process changed in time and in July 2011, the 3.0 Linux kernel version appeared. It appeared with a couple of small changes designed to change the way the interaction was to be done to solve the previously mentioned requests. The changes were made to the numbering scheme, as follows:

Although it only removed one digit from the numbering scheme, this change was necessary because it marked the 20th anniversary of the Linux kernel.

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:

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 main focus in our case is the arch/arm directory, which contains sub directories such as, boot, common, configs, crypto, firmware, kernel, kvm, lib, mm, net, nwfpe, oprofile, tools, vfp, and xen. It also contains an important number of directories that are specific for different CPU families, such as the mach-* directories or the plat-* directories. The first mach-* category contains support for the CPU and several boards that use that CPU, and the second plat-* category contains platform-specific code. One example is plat-omap, which contains common code for both mach-omap1 and mach-omap2.

The development for the ARM architecture has suffered a great change since 2011. If until then ARM did not use a device tree, it was because it needed to keep a large portion of the code inside the mach-* specific directory, and for each board that had support inside the Linux kernel, a unique machine ID was associated and a machine structure was associates with each board that contained specific information and a set of callbacks. The boot loader passed this machine ID to a specific ARM registry and in this way, the kernel knew the board.

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.

In the eventuality that the board does not have support inside the Linux kernel, the appropriate additions would be required inside the mach-* directory. Inside each mach-* directory, there are three types of files available:

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.

The machine structure uses two macros: MACHINE_START and MACHINE_END. Both are defined inside arch/arm/include/asm/march/arch.h and are used to define the machine_desc structure. The machine type number is available inside the arch/arm/tools/mach_types file. This file is used to generate the include/asm-arm/mach-types.h file for the board.

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.

When you are preparing for a patch, make sure that it is done on the corresponding branch, and also that you read the Documentation/BUG-HUNTING file first. Identify bug reports, if any, and make sure you link your patch to them. Do not hesitate to read the Documentation/SubmittingPatches guidelines before sending. Also, do not send your changes before testing them properly. Always sign your patches and make the first description line as suggestive as possible. When sending the patches, find appropriate mailing lists and maintainers and wait for the replies. Solve comments and resubmit them if this is needed, until the patch is considered acceptable.

The development process

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.

The development process

This way of working was kept up until the 2.6.0 version when a large number of features were added inside the kernel during every minor release, and all of them were very well put together as to not cause the need for the branching out of a new development branch. This implied a faster pace of release with more features available. So, the following changes have appeared since the release of the 2.6.14 kernel:

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.

This process changed in time and in July 2011, the 3.0 Linux kernel version appeared. It appeared with a couple of small changes designed to change the way the interaction was to be done to solve the previously mentioned requests. The changes were made to the numbering scheme, as follows:

Although it only removed one digit from the numbering scheme, this change was necessary because it marked the 20th anniversary of the Linux kernel.

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:

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 main focus in our case is the arch/arm directory, which contains sub directories such as, boot, common, configs, crypto, firmware, kernel, kvm, lib, mm, net, nwfpe, oprofile, tools, vfp, and xen. It also contains an important number of directories that are specific for different CPU families, such as the mach-* directories or the plat-* directories. The first mach-* category contains support for the CPU and several boards that use that CPU, and the second plat-* category contains platform-specific code. One example is plat-omap, which contains common code for both mach-omap1 and mach-omap2.

The development for the ARM architecture has suffered a great change since 2011. If until then ARM did not use a device tree, it was because it needed to keep a large portion of the code inside the mach-* specific directory, and for each board that had support inside the Linux kernel, a unique machine ID was associated and a machine structure was associates with each board that contained specific information and a set of callbacks. The boot loader passed this machine ID to a specific ARM registry and in this way, the kernel knew the board.

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.

In the eventuality that the board does not have support inside the Linux kernel, the appropriate additions would be required inside the mach-* directory. Inside each mach-* directory, there are three types of files available:

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.

The machine structure uses two macros: MACHINE_START and MACHINE_END. Both are defined inside arch/arm/include/asm/march/arch.h and are used to define the machine_desc structure. The machine type number is available inside the arch/arm/tools/mach_types file. This file is used to generate the include/asm-arm/mach-types.h file for the board.

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.

When you are preparing for a patch, make sure that it is done on the corresponding branch, and also that you read the Documentation/BUG-HUNTING file first. Identify bug reports, if any, and make sure you link your patch to them. Do not hesitate to read the Documentation/SubmittingPatches guidelines before sending. Also, do not send your changes before testing them properly. Always sign your patches and make the first description line as suggestive as possible. When sending the patches, find appropriate mailing lists and maintainers and wait for the replies. Solve comments and resubmit them if this is needed, until the patch is considered acceptable.

Kernel porting

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 main focus in our case is the arch/arm directory, which contains sub directories such as, boot, common, configs, crypto, firmware, kernel, kvm, lib, mm, net, nwfpe, oprofile, tools, vfp, and xen. It also contains an important number of directories that are specific for different CPU families, such as the mach-* directories or the plat-* directories. The first mach-* category contains support for the CPU and several boards that use that CPU, and the second plat-* category contains platform-specific code. One example is plat-omap, which contains common code for both mach-omap1 and mach-omap2.

The development for the ARM architecture has suffered a great change since 2011. If until then ARM did not use a device tree, it was because it needed to keep a large portion of the code inside the mach-* specific directory, and for each board that had support inside the Linux kernel, a unique machine ID was associated and a machine structure was associates with each board that contained specific information and a set of callbacks. The boot loader passed this machine ID to a specific ARM registry and in this way, the kernel knew the board.

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.

In the eventuality that the board does not have support inside the Linux kernel, the appropriate additions would be required inside the mach-* directory. Inside each mach-* directory, there are three types of files available:

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.

The machine structure uses two macros: MACHINE_START and MACHINE_END. Both are defined inside arch/arm/include/asm/march/arch.h and are used to define the machine_desc structure. The machine type number is available inside the arch/arm/tools/mach_types file. This file is used to generate the include/asm-arm/mach-types.h file for the board.

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.

When you are preparing for a patch, make sure that it is done on the corresponding branch, and also that you read the Documentation/BUG-HUNTING file first. Identify bug reports, if any, and make sure you link your patch to them. Do not hesitate to read the Documentation/SubmittingPatches guidelines before sending. Also, do not send your changes before testing them properly. Always sign your patches and make the first description line as suggestive as possible. When sending the patches, find appropriate mailing lists and maintainers and wait for the replies. Solve comments and resubmit them if this is needed, until the patch is considered acceptable.

Community interaction

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.

When you are preparing for a patch, make sure that it is done on the corresponding branch, and also that you read the Documentation/BUG-HUNTING file first. Identify bug reports, if any, and make sure you link your patch to them. Do not hesitate to read the Documentation/SubmittingPatches guidelines before sending. Also, do not send your changes before testing them properly. Always sign your patches and make the first description line as suggestive as possible. When sending the patches, find appropriate mailing lists and maintainers and wait for the replies. Solve comments and resubmit them if this is needed, until the patch is considered acceptable.

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:

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.

All the configurations specific for a supported board are located inside a configuration file, simply named .config, and it is situated on the same level as the previously presented files and directory locations. Their form is usually represented as configuration_key=value. There are, of course, dependencies between these configurations, so they are defined inside the Kconfig files.

Here are a number of variable options available for a configuration key:

With regard to the Kconfig files, there are two options available. The first one makes option A visible only when option B is enabled and is defined as depends on, and the second option offers the possibility of enabling option A. This is done when the option is enabled automatically and is defined as select.

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.

At the end of the build process, a vmlinux image is offered and also some architecture-dependent images are made available inside the architecture-specific files for the ARM architecture. The result of this is available inside arch/arm/boot/*Image. Also, the Atmel SAMA5D3-Xplained board will offer a specific device tree file that is available in arch/arm/boot/dts/*.dtb. If the vmlinux image file is an ELF file with debug information that cannot be used for booting except for debug purposes, the arch/arm/boot/*Image file is the solution for this purpose.

The installation is the next step when development is done for any other application. The same also takes place for the Linux kernel, but in an embedded environment, this step seems kind of unnecessary. For Yocto enthusiasts, this step is also available. However, in this step, proper configurations are done for the kernel source and headers are to be used by the dependencies that do the storing for the deploy step.

The kernel modules, as mentioned in the cross-compilation chapter, need to be later used for the compiler build. The install for the kernel modules could be done using the make modules_install command, and this offers the possibility to install the sources available inside the /lib/modules/<linux-kernel-version> directory with all the module dependencies, symbols, and aliases.

Configuring kernel

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.

All the configurations specific for a supported board are located inside a configuration file, simply named .config, and it is situated on the same level as the previously presented files and directory locations. Their form is usually represented as configuration_key=value. There are, of course, dependencies between these configurations, so they are defined inside the Kconfig files.

Here are a number of variable options available for a configuration key:

With regard to the Kconfig files, there are two options available. The first one makes option A visible only when option B is enabled and is defined as depends on, and the second option offers the possibility of enabling option A. This is done when the option is enabled automatically and is defined as select.

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.

At the end of the build process, a vmlinux image is offered and also some architecture-dependent images are made available inside the architecture-specific files for the ARM architecture. The result of this is available inside arch/arm/boot/*Image. Also, the Atmel SAMA5D3-Xplained board will offer a specific device tree file that is available in arch/arm/boot/dts/*.dtb. If the vmlinux image file is an ELF file with debug information that cannot be used for booting except for debug purposes, the arch/arm/boot/*Image file is the solution for this purpose.

The installation is the next step when development is done for any other application. The same also takes place for the Linux kernel, but in an embedded environment, this step seems kind of unnecessary. For Yocto enthusiasts, this step is also available. However, in this step, proper configurations are done for the kernel source and headers are to be used by the dependencies that do the storing for the deploy step.

The kernel modules, as mentioned in the cross-compilation chapter, need to be later used for the compiler build. The install for the kernel modules could be done using the make modules_install command, and this offers the possibility to install the sources available inside the /lib/modules/<linux-kernel-version> directory with all the module dependencies, symbols, and aliases.

Compiling and installing the kernel

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.

At the end of the build process, a vmlinux image is offered and also some architecture-dependent images are made available inside the architecture-specific files for the ARM architecture. The result of this is available inside arch/arm/boot/*Image. Also, the Atmel SAMA5D3-Xplained board will offer a specific device tree file that is available in arch/arm/boot/dts/*.dtb. If the vmlinux image file is an ELF file with debug information that cannot be used for booting except for debug purposes, the arch/arm/boot/*Image file is the solution for this purpose.

The installation is the next step when development is done for any other application. The same also takes place for the Linux kernel, but in an embedded environment, this step seems kind of unnecessary. For Yocto enthusiasts, this step is also available. However, in this step, proper configurations are done for the kernel source and headers are to be used by the dependencies that do the storing for the deploy step.

The kernel modules, as mentioned in the cross-compilation chapter, need to be later used for the compiler build. The install for the kernel modules could be done using the make modules_install command, and this offers the possibility to install the sources available inside the /lib/modules/<linux-kernel-version> directory with all the module dependencies, symbols, and aliases.

Cross-compiling the Linux kernel

In an

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.

Loading and interacting with the Linux kernel modules requires logging information to be made available. The same happens for any kernel module dependencies. The logging information is available through the dmesg command and the level of logging enables manual configuration using the loglevel parameter or it can be disabled with the quite parameter. Also for the kernel dependencies, information about them is available inside the /lib/modules/<kernel-version>/modules.dep file.

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:

This is a simple hello world kernel module. Useful information that can be gathered from the preceding example is that every kernel module needs a start function defined in the preceding example as hello_world_init(). It is called when the module is inserted, and a cleanup function called hello_world_exit() is called when the module is removed.

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:

In both these examples, the source code file is hello_world.c and the resulting kernel module if it is not built-in is called hello_world.ko.

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:

Devices and modules

Since there are multiple scenarios of using a device driver and three device mode structures are available:

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.

For most of the trivial ones, some of the following steps should be taken. First, identify the bug properly; it is not only useful when define the problem, but also helps with reproducing it. The second step involves finding the source of the problem. Here, I am referring to the first kernel version in which the bug was first reported. Good knowledge about the bug or the source code of the Linux kernel is always useful, so make sure that you understand the code before you start working on it.

The bugs inside the Linux kernel have a wide spread. They vary from a variable not being stored properly to race conditions or hardware management problems, they have widely variable manifestations and a chain of events. However, debugging them is not as difficult as it sounds. Besides some specific problems, such as race conditions and time constraints, debugging is very similar to the debugging of any large user space application.

The first, easiest, and most handy method to debug the kernel is the one that involves the use of the printk() function. It is very similar to the printf() C library function, and although old and not recommended by some, it does the trick. The new preferred method involves the usage of the pr_*() functions, such as pr_emerg(), pr_alert(), pr_crit(), pr_debug(), and so on. Another method involves the usage of the dev_*() functions, such as dev_emerg(), dev_alert(), dev_crit(), dev_dbg(), and so on. They correspond to each logging level and also have extra functions that are defined for debugging purposes and are compiled when CONFIG_DEBUG is enabled.

When a kernel oops crash appears, it signals that the kernel has made a mistake. Not being able to fix or kill itself, it offers access to a bunch of information, such as useful error messages, registers content, and back trace information.

The Magic SysRq key is another method used in debugging. It is enabled by CONFIG_MAGIC_SYSRQ config and can be used to debug and rescue kernel information, regardless of its activity. It offers a series of command-line options that can be used for various actions, ranging from changing the nice level to rebooting the system. Plus, it can be toggled on or off by changing the value in the /proc/sys/kernel/sysrq file. More information about the system request key can be found at Documentation/sysrq.txt.

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.

If none of the preceding methods fail to help solve the problem and you've tried everything but can't seem to arrive at a solution, then you can contact the open source community for help. There will always will be developers there who will lend you a hand.

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 Atmel SAMA5D3-Xplained board uses the linux-yocto-custom kernel. This is defined inside the conf/machine/sama5d3-xplained.conf machine configuration file using the PREFERRED_PROVIDER_virtual/kernel variable. No PREFERRED_VERSION is mentioned, so the latest version is preferred; in this case, we are talking about the linux-yocto-custom_3.10.bb recipe.

The linux-yocto-custom_3.10.bb recipe fetches the kernel sources available inside Linux Torvalds' git repository. After a quick look at the sources once the do_fetch task is finished, it can be observed that the Atmel repository was, in fact, fetched. The answer is available inside the linux-yocto-custom_3.10.bbappend file, which offers another SR_URI location. Other useful information you can gather from here is the one available in bbappend file, inside it is very well stated that the SAMA5D3 Xplained machine is a COMPATIBLE_MACHINE:

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.

The kernel recipe inherits the kernel.bbclass and kernel-yocto.bbclass files, which define most of its tasks actions. Since it also generates a device tree, it needs access to linux-dtb.inc, which is available inside the meta/recipes-kernel/linux directory. The information available in the linux-yocto-custom_3.10.bb recipe is rather generic and overwritten by the bbappend file, as can be seen here:

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.

A kernel module, on the other hand, has a different kind of behavior if it is not built-in inside the Linux kernel source tree. For the modules that are build outside of the source tree, a new recipe need to be written, that is, a recipe that inherits another bitbake class this time called module.bbclass. One example of an external Linux kernel module is available inside the meta-skeleton layer in the recipes-kernel/hello-mod directory:

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.

Inside the Yocto Project, there are multiple commands available to interact with the kernel and kernel module recipes. The simplest command is, of course, bitbake <recipe-name>, but for the Linux kernel, there are a number of commands available to make the interaction easier. The most used one is the bitbake -c menuconfig virtual/kernel operation, which offers access to the kernel configuration menu.

Besides already known tasks, such as configure, compile, and devshell, that are used mostly in the development process, there are other ones, such as diffconfig, which uses the diffconfig script available in the Linux kernel scripts directory. The difference between the implementation of the Yocto Project and the available script of the Linux kernel is the fact that the former adds the kernel config creation phase. These config fragments are used to add kernel configurations into the .config file as part of the automation process.

 

In this chapter, you will learn about the root filesystem and its structure. You will also be presented with information about the root filesystem's content, the various device drivers available, and its the communication with the Linux kernel. We will slowly make the transition to the Yocto Project and the method used to define the Linux root filesystem's content. The necessary information will be presented to make sure that a user will be also able to customize the rootfs filesystem according to its needs.

The special requirements of the root filesystem will be presented. You will be given information on its content, subdirectories, defined purposes, the various filesystem options available, the BusyBox alternative, and also a lot of interesting features.

When interacting with an embedded environment, a lot of developers would start from a minimal root filesystem made available by a distribution provider, such as Debian, and using a cross-toolchain will enhance it with various packages, tools, and utilities. If the number of packages to be added is big, it can be very troublesome work. Starting from scratch would be an even bigger nightmare. Inside the Yocto Project, this job is automatized and there is no need for manual work. The development is started from scratch, and it offers a large number of packages inside the root filesystem to make the work fun and interesting. So, let's move ahead and take a look at this chapter's content to understand more about root filesystems in general.

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.

The preceding commands are very useful for the interaction of applications with various files available, regardless of their location and format. For example, the standard form for the mount command is mount –t type device directory. This command asks the kernel to connect the filesystem from the device that has the type format mentioned in the command line, along with the directory mentioned in the same command. The umount command needs to be given before removing the device to make sure the kernel caches are written in the storage point.

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:

These options are chosen by hardware and system architects. To make use of these, the kernel and bootloader need to be configured accordingly.

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 downside of the over the network storage is the fact that a sever client architecture is needed. So, for NFS, an NFS server functionality will need to be available on the development machine. For a Ubuntu host, the required configuration involves installing the nfs-kernel–server package, sudo apt-get install nfs-kernel-server. After the package is installed, the exported directory location needs to be specified and configured. This is done using the /etc/exports file; here, configuration lines similar to /nfs/rootfs <client-IP-address> (rw,no_root_squash,no_subtree_check) appear, where each line defines a location for the over the network shared locations with the NFS client. After the configuration is finished, the NFS server needs to be restarted in this way: sudo /etc/init.d/nfs-kernel-server restart.

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:

Interacting with the root filesystem

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:

Interacting with the root filesystem

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.

On a traditional system, the initrd is created using the mkinitrd tool, which is, in fact, a shell script that automates the steps necessary for the creation of initrd. Here is an example of its functionality:

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:

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.

This initialization phase is so old that a similar structure inside the Linux 1.0 version is also available. This represents the user space processing start. If the kernel is not able to execute one of the four preceding functions in the predefined locations, then the kernel will halt and a panic message will be prompted onto the console to issue an alert that no init processes can be started. So, the user space processing will not start until the kernel space processing is finished.

For the majority of the available Linux systems, /sbin/init is the location where the kernel spawns the init process; the same affirmation is also true for the Yocto Project's generated root filesystems. It is the first application run in the user space context, but it isn't the only necessary feature of the root filesystem. There are a couple of dependencies that need to be resolved before running any process inside the root filesystem. There are dependencies used to solve dynamically linked dependencies references that were not solved earlier, and also dependencies that require external configurations. For the first category of dependencies, the ldd tool can be used to spot the dynamically linked dependencies, but for the second category, there is no universal solution. For example, for the init process, the configuration file is inittab, which is available inside the /etc directory.

For developers not interested in running another init process, this option is available and can be accessed using the kernel command line with the available init= parameter, where the path to the executed binary should be made available. This information is also available in the preceding code. The customization of the init process is not a method commonly used by developers, but this is because the init process is a very flexible one, which makes a number of start up scripts available.

Every process started after init uses the parent-child relationship, where init acts as the parent for all the processes run in the user space context, and is also the provider of environment parameters. Initially, the init process spawns processes according to the information available inside the /etc/inittab configuration file, which defines the runlevel notion. A runlevel represents the state of the system and defines the programs and services that have been started. There are eight runlevels available, numbered from 0 to 6, and a special one that is noted as S. Their purpose is described here:

Runlevel value

Runlevel purpose

0

It refers to the shutdown and power down command for the whole system

1

It is a single-user administrative mode with a standard login access

2

It is multiuser without a TCP/IP connection

3

It refers to a general purpose multiuser

4

It is defined by the system's owner

5

It refers to graphical interface and TCP/IP connection multiuser systems

6

It refers to a system reboot

s

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.

The /etc/inittab configuration script defines the runlevel and the instructions applied to all of them. For the Yocto Project, the /etc/inittab looks similar to this:

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.

The last stage of any Linux system is represented by the power off or shutdown command. It is very important, because if it's not done appropriately, it can affect the system by corrupting data. There are, of course, multiple options to implement the shutdown scheme, but the handiest ones remain in the form of utilities, such as shutdown, halt, or reboot. There is also the possibility to use init 0 to halt the system, but, in fact, what all of them have in common is the use of the SIGTERM and SIGKILL signals. SIGTERM is used initially to notify you about the decision to shut down the system, to offer the chance to the system to perform necessary actions. After this is done, the SIGKILL signal is sent to terminate all the processes.

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.

Inside the user space, all the device drivers have associated device nodes, which are, in fact, special files that represent a device. All the device files are located in the /dev directory and the interaction with them is done through the mknod utility. The device nodes are available under two abstractions:

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.

Using the mknod utility requires manual interaction and root privileges, and lets the developer do all the heavy lifting needed to identify the properties of the device node and its device driver correspondent. The latest Linux system offers the possibility to automate this process and to also complete these actions every time devices are detected or disappear. This is done as follows:

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.

Journaling is a method that is used to log all the changes made on a filesystem form by making the recovery functionality possible. There are also other features that ext3 adds besides the ones that are already mentioned; here, I am referring to the possibility of not checking for consistencies in the filesystem, mostly because journalizing the log can be reversed. Another important feature is that it can be mounted without checking whether the shutdown was performed correctly. This takes place because the system does not need to conduct a consistency check at power down.

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:

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.

With the passing of time, the proc virtual filesystem became a necessity for Linux system users since it gathered a very large number of user space functionalities. Commands, such as top, ps, and mount would not work without it. For example, the mount example given without a parameter will present proc mounted on the /proc in the form of proc on /proc type proc (rw,noexec,nosuid,nodev). This takes place since it is necessary to have proc mounted on the root filesystem on par with directories, such as /etc, /home, and others that are used as the destination of the /proc filesystem. To mount the proc filesystem, the mount –t proc nodev/proc mount command that is similar to the other filesystems available is used. More information on this can be found inside the kernel sources documentation at Documentation/filesystems/proc.txt.

The proc filesystem has the following structure:

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.

The sysfs directory structure has a subdirectory for every major system device class, and it also has a system buses subdirectory. There is also systool that can be used to browse the sysfs directory structure. Similar to the proc filesystem, systool also can also be visible if the sysfs on /sys type sysfs (rw,noexec,nosuid,nodev) mount command is offered on the console. It can be mounted using the mount -t sysfs nodev /sys command.

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.

A developer interaction with the BusyBox source code package is very simple: just configure, compile, and install it, and there you have it. Here are some detailed steps to explain the following:

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.

At the end of the process, the BusyBox executable is available. If it's called without arguments, it will present an output very similar to this:

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.

Besides the utilities that are already available, BusyBox also offers implementation alternatives for the init program. In this case, the init does not know about a runlevel, and all its configurations available inside the /etc/inittab file. Another factor that differentiates it from the standard /etc/inittab file is the fact that this one also has its special syntax. For more information, examples/inittab available inside BusyBox can be consulted. There are also other tools and utilities implemented inside the BusyBox package, such as a lightweight version for vi, but I will let you discover them for yourself.

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.

The starting point to describe the components is /sbin/init; here, by using the ldd command, the runtime dependencies can be found. For the Yocto Project, the ldd /sbin/init command returns:

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.

After this, init and its runlevel must be defined. The minimal form for this is available inside the BusyBox package, so it will also be available inside the /bin directory. Alongside it, a symbolic link for shell interaction is necessary, so this is how the minimal for the bin directory will look:

Next, the runlevel needs to be defined. Only one is used in the minimal root filesystem, not because it is a strict requirement, but due to the fact that it can suppress some BusyBox warnings. This is how the /etc directory will look:

At the end, the console device needs to be available to the user for input and output operations, so the last piece of the root filesystem is inside the /dev directory:

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:

You can see here that this is similar to any other recipe. The image defines the LICENSE field and inherits a bbclass file, which defines its tasks. A short summary is used to describe it, and it is very different from normal package recipes. It does not have LIC_FILES_CHKSUM to check for licenses or a SRC_URI field, mostly because it does not need them. In return, the file defines the exact packages that should be contained in the root filesystem, and a number of them are grouped inside packagegroup for easier handling. Also, the core-image bbclass file defines a number of other tasks, such as do_rootfs, which is only specific for image recipes.

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 the Yocto Project, it is possible to minimize the size of the root filesystem by using the poky-tiny.conf distribution policies, which are available inside the meta-yocto/conf/distro directory. When they're used, these policies reduce not only the boot size, but the boot time as well. The simplest example for this is available using the qemux86 machine. Here, changes are visible, but they are somewhat different from the ones already mentioned in the Minimal root filesystem section. The purpose of the minimization work done on qemux86 was done around the core-image-minimal image. Its goals is to reduce the size to under 4 MB of the resulting rootfs and the boot time to under 2 seconds.

Now, moving to the selected Atmel SAMA5D3 Xplained machine, another rootfs is generated and its content is quite big. Not only has it included the packagegroup-core-boot.bb package group, but other package groups and separate packages are also included. One such example is the atmel-xplained-demo-image.bb image available inside the meta-atmel layer in the recipes-core/images directory:

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.

 

In this chapter, you will be given a short introduction to a number of components from the ecosystem of the Yocto Project. This chapter is meant to introduce all of them so that in subsequent chapters they can be presented more elaborately. It also tries to direct readers toward extra readings. For each presented tool, feature, or interesting fact, links are offered to help interested readers search for their own answers to the questions in this book and those that this chapter does not cover.

This chapter is full of guidance and relevant examples for an embedded development process that involves specific Yocto Project tools. The selection of the tools was done in a purely subjective manner. Only the tools that are considered helpful in the development process have been selected. We also considered the fact that some of them could offer new insights into the embedded world and the development for embedded systems in general.

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:

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:

There are other extra optional requirements that should be taken care of if special requirements are needed, as follows:

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.

Before moving on to describe the other components presented in this chapter, a review of the existing Yocto Project development models will be made. This process involves these tools made available by the Yocto 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:

In this section, each of the preceding elements will be discussed, and we will start with the cross-development toolchain. It consists of a cross-linker, cross-debugger, and a cross-compiler that are used for the application development of a target. It also needs the associated target sysroot because the necessary headers and libraries are required when building an application that will run on the target device. The generated sysroot is obtained from the same configuration that generates the root filesystem; this refers to the image recipe.

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:

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:

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.

To better understand the workflow of the application development of the ADT toolkit platform and Eclipse, an overview of the whole process is available in the following image:

Eclipse ADT plug-ins

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:

The Hob project can be started in the same way that Bitbake is executed. After the environment sources and the build directory are created, the hob command can be called and the graphical interface will appear for the user. The disadvantage of this is that this tool does not substitute the command-line interaction. If new recipes need to be created, then this tool will not be able to provide any help with the task.

The next project is called Toaster. It is an application programming interface and also a web interface that the Yocto Project builds. In its current state, it is only able to gather and present information relevant to a build process through a web browser. These are some of its functionalities:

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.

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.

This tool tries to solve some special requirements from devices or bootloaders, such as special formatting or the partitioning of the root filesystem. It is a highly customized tool that offers the possibility of extending its features. It has been developed from another tool called oeic, which was used to create a certain proprietary formatted image for hardware and was imported into the Yocto Project to serve a broader purposes for developers who did not wanted to touch recipes and had already packaged sources, or required special formatting for their deliverable Linux image.

Unfortunately, there is no documentation available for this tool, but I can direct those who are interested to its location on the Yocto Project. It resides in the Poky repository in the scripts directory under the name of wic. Wic can be used as any script, and it provides a help interface where you can seek more information. Also, its functionalities will be presented in an extended manner in the coming chapters.

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.

The ADT offers the possibility of using a standalone cross-compiler, debugging tool profilers, emulators, and even development board interaction in a platform-independent manner. Although interaction with hardware is the best option for an embedded developer, in most cases, the real hardware is missing due to various reasons. For these scenarios, it is possible to use a QEMU emulator to simulate the necessary hardware.

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:

The Eclipse plug-ins are available when offering full support to the Yocto Project with the Eclipse IDE and maximizing the Yocto experience. The end result is an environment that is customized for the Yocto developer's needs, with a cross-toolchain, deployment on a real hardware, or QEMU emulation features, and also a number of tools that are available for collecting data, tracing, profiling, and performance reviews.

The QEMU emulator is used to simulate various hardware. It can be obtained with these methods:

The toolchain contains a cross-debugger, cross-compiler, and cross-linker, which are very well used in the process of application development. The toolchain also comes with a matching sysroot for the target device because it needs access to various headers and libraries necessary to run on the target architecture. The sysroot is generated from the root filesystem and uses the same metadata configuration.

The userspace tools include the tools already mentioned in the previous chapters, such as SystemTap, PowerTop, LatencyTop, perf, OProfile, and LTTng-UST. They are used for getting information about the system and developed application; information, such as power consumption, desktop stutters, counting of events, performance overviews, and diagnosing software, hardware, or functional problems, and even tracing software activities.

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:

The ADT install script is the preferred method to install the ADT. Of course, before moving on to the installation step, the necessary dependencies need to be available to make sure that the ADT install script runs smoothly.

These packages were already mentioned in the previous chapters, but they will once again, be explained here to make things easy for you. I advise you to go back to these chapters and refer to the information once again as a memory exercise. To refer to packages that might be of interest to you, take a look at the ADT Installer packages, such as autoconf automake libtool libglib2.0-dev, Eclipse Plug-ins, and graphical support offered by the libsdl1.2-dev xterm packages.

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.

This tarball can also be generated using the Bitbake build system inside a build directory, and the result will be available inside the tmp/deploy/sdk/adt_installer.tar.bz2 location. To generate it, the next command needs to be given into the build directory, which is bitbake adt-installer. The build directory also needs to be properly configured for the target device.

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 as arm, ppc, mips, x86, and x86_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 to Y, the emulator will be available after installation; otherwise the value is set to N, 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 and N. For the use of the Eclipse IDE plug-ins, it is necessary to define the Y value for both YOCTOADT_QEMU and YOCTOADT_NFS_UTIL.
  • YOCTOADT_ROOTFS_<arch>: This specifies which architecture root filesystem to use from the repository that is defined in the first mentioned YOCTOADT_REPO variable. For the arch variable, the default values are the ones already mentioned in the YOCTOADT_TARGETS variable. This variable's valid values are represented by the image files available, such as minimal, 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 the sysroot 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 the YOCTOADT_ROOTFS_<arch> variable. So, if only one variable is defines as the value for the YOCTOADT_ROOTFS_<arch> variable, the same value will be available for YOCTOADT_TARGET_SYSROOT_IMAGE_<arch>. Also, if multiple variables are defined in the YOCTOADT_ROOTFS_<arch> variable, then one of them needs to define the YOCTOADT_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 target sysroot 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:

# Yocto ADT Installer Configuration File
#
# Copyright 2010-2011 by Intel Corp.
#
# Permission is hereby granted, free of charge, to any person obtaining a copy 
# of this software and associated documentation files (the "Software"), to deal 
# in the Software without restriction, including without limitation the rights 
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 
# copies of the Software, and to permit persons to whom the Software is 
# furnished to do so, subject to the following conditions:

# The above copyright notice and this permission notice shall be included in 
# all copies or substantial portions of the Software.

# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 
# THE SOFTWARE.


# Your yocto distro repository, this should include IPKG based packages and root filesystem files where the installation is based on

YOCTOADT_REPO="http://adtrepo.yoctoproject.org//1.7"
YOCTOADT_TARGETS="arm x86"
YOCTOADT_QEMU="Y"
YOCTOADT_NFS_UTIL="Y"

#YOCTOADT_BITBAKE="Y"
#YOCTOADT_METADATA="Y"

YOCTOADT_ROOTFS_arm="minimal sato-sdk"
YOCTOADT_TARGET_SYSROOT_IMAGE_arm="sato-sdk"
YOCTOADT_TARGET_MACHINE_arm="qemuarm"
YOCTOADT_TARGET_SYSROOT_LOC_arm="$HOME/test-yocto/$YOCTOADT_TARGET_MACHINE_arm"

#Here's a template for setting up target arch of x86 
YOCTOADT_ROOTFS_x86="sato-sdk"
YOCTOADT_TARGET_SYSROOT_IMAGE_x86="sato-sdk"
YOCTOADT_TARGET_MACHINE_x86="qemux86"
YOCTOADT_TARGET_SYSROOT_LOC_x86="$HOME/test-yocto/$YOCTOADT_TARGET_MACHINE_x86"

#Here's some template of other arches, which you need to change the value in ""
YOCTOADT_ROOTFS_x86_64="sato-sdk"
YOCTOADT_TARGET_SYSROOT_IMAGE_x86_64="sato-sdk"
YOCTOADT_TARGET_MACHINE_x86_64="qemux86-64"
YOCTOADT_TARGET_SYSROOT_LOC_x86_64="$HOME/test-yocto/$YOCTOADT_TARGET_MACHINE_x86_64"

YOCTOADT_ROOTFS_ppc="sato-sdk"
YOCTOADT_TARGET_SYSROOT_IMAGE_ppc="sato-sdk"
YOCTOADT_TARGET_MACHINE_ppc="qemuppc"
YOCTOADT_TARGET_SYSROOT_LOC_ppc="$HOME/test-yocto/$YOCTOADT_TARGET_MACHINE_ppc"

YOCTOADT_ROOTFS_mips="sato-sdk"
YOCTOADT_TARGET_SYSROOT_IMAGE_mips="sato-sdk"
YOCTOADT_TARGET_MACHINE_mips="qemumips"
YOCTOADT_TARGET_SYSROOT_LOC_mips="$HOME/test-yocto/$YOCTOADT_TARGET_MACHINE_mips"

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.

At the end of the install procedure, the cross-toolchain will be found in its defined location. An environment setup script will be available for later usage, and the image tarball in the adt-installer directory, and the sysroot directory is defined in the location of the YOCTOADT_TARGET_SYSROOT_LOC_<arch> variable.

As shown previously, there is more than one method to prepare the ADT environment. The second method involves only the installation of the toolchain installer—although it offers the possibility of having a prebuilt cross-tooolchain, support files and scripts, such as the runqemu script to start something similar to a kernel or Linux image in an emulator—which does not offer the same possibilities as the first option. Also, this option has its limitations regarding the sysroot directory. Although it's been generated, the sysroot directory might still need to be extracted and installed in a separate location. This can happened for various reasons, such as the need to boot a root filesystem over NFS or develop the application using the root filesystem as the target sysroot.

The root filesystem can be extracted from an already generated cross-toolchain using the runqemu-extract-sdk script, which should be called only after the cross-development environment script was set up using source command.

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 matching sysroot for the target. The advantage here is that the binaries are linked with only one and the same libc, 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 the gcc options. Using variables, such as CC or LD, makes the process easier to maintain and also saves some space in the build directory.

After the installer is downloaded, make sure that the install script has set the execution correctly, and start the installation with the ./poky-glibc-x86_64-core-image-sato-armv7a-vfp-neon-toolchain-1.7.sh command.

Some of the information you require includes the place where the installation should be made, the default location being the /opt/poky/1.7 directory. To avoid this, the script can be called with the –d <install-location> argument and the installation can be made in the <install-location> location, as mentioned.

After the installation process is finished, the cross-toolchain will be available in the selected location, and the environment script will also be available for sourcing when needed.

The third option involves the use of the build directory and the execution of the bitbake meta-ide-support command. Inside the build directory, the proper environment needs to be set using one of the two available build environment setup scripts, which include the oe-init-build-env script or oe-init-build-env-memres. The local configuration from the local.conf file also needs to be set accordingly for the target architecture. After these steps are fulfilled by the developer, the bitbake meta-ide-support command could be used to start the generation of the cross-toolchain. At the end of the process, an environment setup script will be available inside the <build-dir-path>/tmp directory, but in this case, the toolchain is tightly linked into the build directory in which it was built.

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 Eclipse IDE is an alternative for developers who are not interested in interacting with editors, such as vim, although, in my opinion, vim can be used for all kinds of projects. Even if their dimensions or complexities are not a problem, the overhead for using vim might not suit all tastes. The Eclipse IDE is the best alternative available for all developers. It has a lot of useful features and functionalities that can make your life a little easier and it is pretty easy to grasp.

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:

The next step is represented by the configuration. With the content extracted, the Eclipse IDE needs to be configured before installing the Yocto Project-specific plug-ins. The configuration starts with initializing the Eclipse IDE:

The Eclipse IDE is started after executing the ./eclipse executable and setting the Workspace location. This is how the starting windows looks:

To initialize the Eclipse IDE perform the following steps:

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:

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 configuration process is done by selecting the Preferences option from the Window menu. A new window will open, and from there, the Yocto Project ADT option should be selected. More details are available, as shown in the following screenshot:

The next thing to do involves the configuration of the available options of the cross-compiler. The first option refers to the toolchain type, and there are two options available, Standalone prebuilt toolchain and Build system derived toolchain, which is the default selected option. The former refers to a toolchain specific for an architecture that already has an existing kernel and root filesystem, so the developed application will be made available in the image manually. However, this step is not a requirement since all the components are separated. The latter option refers to a toolchain built inside a Yocto Project build directory.

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.

The last remaining section is represented by the target specific option. This refers to the possibility of emulating an architecture using QEMU or running the image on the externally available hardware. For external hardware, use the External HW option that needs to be selected for the work to be finished, but for the QEMU emulation, there are still things to do besides selecting the QEMU option. In this scenario, the user will also need to indicate the Kernel and Custom Option. For the kernel selection, the process is simple. It is available in the prebuilt image location in case the Standalone pre-built toolchain option was selected or in the tmp/deploy/images/<machine-name> directory if the Build system derived toolchain option was selected. For the second option, the Custom Option argument, the process for adding it will not be as simple as the preceding options.

The Custom Option field needs to be filled with various options, such as kvm, nographic, publicvnc, or serial, which indicate major options for the emulated architecture or their parameters. These are kept inside angled brackets, and include parameters, such as the memory used (-m 256), networking support (-net), and full screen support (-full-screen). More information regarding the available options and parameters can be found using the man qemu command. All of the preceding configurations can be overwritten using the Change Yocto Project Settings option from the Project menu after a project is defined.

To define a project, these steps need to be taken:

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:

  1. 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.
  2. Select the suitable connection from the drop-down list.
  3. 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.
  4. Enter the absolute path in which the application will be deployed by setting the Remote Absolute File Path for C/C++ Application: field accordingly.
  5. 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, the lib64 directory is chosen.
    • On the Arguments tab, there is a possibility of passing various arguments to the application binary during the time of execution.
  6. 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:
  7. 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.

The first one we'll take a look at is the LTTng Eclipse Plug-in, which offers the possibility of tracing a target session and analyzing the results. To start working with the tool, a quick configuration is necessary first, as follows:

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:

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:

After the prerequisites are fulfilled, the actual configuration can start:

After the Debug button is pressed, the debug session should start and the target will be halted in the kgdb_breakpoint() function. From there, all the commands specific to GDB are available and ready to be used.

QEMU emulator

QEMU is

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:

  1. 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.
  2. Select the suitable connection from the drop-down list.
  3. 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.
  4. Enter the absolute path in which the application will be deployed by setting the Remote Absolute File Path for C/C++ Application: field accordingly.
  5. 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, the lib64 directory is chosen.
    • On the Arguments tab, there is a possibility of passing various arguments to the application binary during the time of execution.
  6. 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:
  7. 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.

The first one we'll take a look at is the LTTng Eclipse Plug-in, which offers the possibility of tracing a target session and analyzing the results. To start working with the tool, a quick configuration is necessary first, as follows:

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:

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:

After the prerequisites are fulfilled, the actual configuration can start:

After the Debug button is pressed, the debug session should start and the target will be halted in the kgdb_breakpoint() function. From there, all the commands specific to GDB are available and ready to be used.

Debugging

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:

  1. 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.
  2. Select the suitable connection from the drop-down list.
  3. 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.
  4. Enter the absolute path in which the application will be deployed by setting the Remote Absolute File Path for C/C++ Application: field accordingly.
  5. 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, the lib64 directory is chosen.
    • On the Arguments tab, there is a possibility of passing various arguments to the application binary during the time of execution.
  6. 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:
  7. 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.

The first one we'll take a look at is the LTTng Eclipse Plug-in, which offers the possibility of tracing a target session and analyzing the results. To start working with the tool, a quick configuration is necessary first, as follows:

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:

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:

After the prerequisites are fulfilled, the actual configuration can start:

After the Debug button is pressed, the debug session should start and the target will be halted in the kgdb_breakpoint() function. From there, all the commands specific to GDB are available and ready to be used.

Profiling and tracing

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.

The first one we'll take a look at is the LTTng Eclipse Plug-in, which offers the possibility of tracing a target session and analyzing the results. To start working with the tool, a quick configuration is necessary first, as follows:

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:

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:

After the prerequisites are fulfilled, the actual configuration can start:

After the Debug button is pressed, the debug session should start and the target will be halted in the kgdb_breakpoint() function. From there, all the commands specific to GDB are available and ready to be used.

The Yocto Project bitbake commander

The
 

In this chapter, you will be introduced to new tools and components used in the Yocto community. As the title suggests, this chapter is dedicated to another category of tools. I will start with Hob as a graphical interface, which is slowly dying, and in time, will be replaced by a new web interface called Toaster. A new point of discussion will also be introduced in this chapter. Here, I am referring to the QA and testing component that is, in most cases, absent or lacking from most of the projects. Yocto takes this problem very seriously and offers a solution for it. This solution will be presented in the last section of the chapter.

You will also be offered a more detailed presentation to components, such as Hob, Toaster, and Autobuilder. Each of these components will be assessed separately and their benefits and use cases are looked at in detail. For the first two components, (that is, Hob and Toaster) information regarding the build process is offered alongside the various setup scenarios. Hob is similar to BitBake and is tightly integrated with Poky and the Build Directory. Toaster, on the other hand, is a looser alternative that offers multiple configuration alternatives and setups, and a performance section that can be very useful for any developer interested in improving the build system's overall performance. The chapter ends with section on Autobuilder. This project is the cornerstone of the Yocto project that is dedicated to making embedded development and open source more user-friendly, in general, but also offers more secure and error-free projects. I hope that you enjoy this chapter; let's proceed to the first section.

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.

Hob may not be able to a lot of tasks apart from most common ones, such as building an image, modifying its existing recipes, running an image through a QEMU emulator, or even deploying it on a USB device for some live-booting operations on a target device. Having all these functionalities is not much, but is a lot of fun. Your experience with the tools in Yocto Project do not matter here. The previously mentioned tasks can be done very easily and in an intuitive manner, and this is the most interesting thing about Hob. It offers its users what they need in a very easy fashion. People who interact with it can learn from the lessons it has to offer, whether they're graphic interface enthusiasts or command-line savvy.

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.

First of all, let's see what Hob looks like when you start it for the first time. The result is shown in the following screenshot:

Hob

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.

Hob

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:

Hob

When the Atmel sama5d3-xplained machine is selected, an error, shown in the following screenshot, appears:

Hob

After adding the meta-qt5 layer to the layers section, this error disappears and the build process can continue. To retrieve the meta-qt5 layer, the following git command is necessary:

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:

Hob

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.

The other solution would be to include the required recipes that meta-atmel needs for the build process. The best place for it to be available would be also in meta-atmel. However, in this case, the .bbappend configuration file should be merged with the recipe, since having a recipe and its appended file in the same place does not make much sense.

After this problem is fixed, new options will be available to the user, as depicted in the following screenshot:

Hob

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:

Hob

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:

Hob

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:

Hob

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:

Hob

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:

The end result is available in the BBLAYERS variable that is be found in the bblayers.conf file, shown as follows:

There are some required changes in the meta-atmel layer that needs to be made before starting a complete build, given as follows:

All the changes made to the meta-atmel layer are available in following patch:

From 35ccf73396da33a641f307f85e6b92d5451dc255 Mon Sep 17 00:00:00 2001
From: "Alexandru.Vaduva" <vaduva.jan.alexandru@gmail.com>
Date: Sat, 31 Jan 2015 23:07:49 +0200
Subject: [meta-atmel][PATCH] Update suppport for atmel-xplained-demo-image
 image.

The latest poky contains updates regarding the qt4 version support
and also the packagegroup naming.
Removed packages which are no longer available.

Signed-off-by: Alexandru.Vaduva <vaduva.jan.alexandru@gmail.com>
---
 recipes-core/images/atmel-demo-image.inc           |  3 +--
 ...qt-embedded-linux-4.8.4-phonon-colors-fix.patch | 26 ----------------------
 ...qt-embedded-linux-4.8.4-phonon-colors-fix.patch | 26 ++++++++++++++++++++++
 recipes-qt/qt4/qt4-embedded_4.8.5.bbappend         |  2 --
 recipes-qt/qt4/qt4-embedded_4.8.6.bbappend         |  2 ++
 5 files changed, 29 insertions(+), 30 deletions(-)
 delete mode 100644 recipes-qt/qt4/qt4-embedded-4.8.5/qt-embedded-linux-4.8.4-phonon-colors-fix.patch
 create mode 100644 recipes-qt/qt4/qt4-embedded-4.8.6/qt-embedded-linux-4.8.4-phonon-colors-fix.patch
 delete mode 100644 recipes-qt/qt4/qt4-embedded_4.8.5.bbappend
 create mode 100644 recipes-qt/qt4/qt4-embedded_4.8.6.bbappend

diff --git a/recipes-core/images/atmel-demo-image.inc b/recipes-core/images/atmel-demo-image.inc
index fe13303..a019586 100644
--- a/recipes-core/images/atmel-demo-image.inc
+++ b/recipes-core/images/atmel-demo-image.inc
@@ -2,7 +2,7 @@ IMAGE_FEATURES += "ssh-server-openssh package-management"
 
 IMAGE_INSTALL = "\
     packagegroup-core-boot \
-    packagegroup-core-basic \
+    packagegroup-core-full-cmdline \
     packagegroup-base-wifi \
     packagegroup-base-bluetooth \
     packagegroup-base-usbgadget \
@@ -23,7 +23,6 @@ IMAGE_INSTALL = "\
     python-smbus \
     python-ctypes \
     python-pip \
-    python-setuptools \
     python-pycurl \
     gdbserver \
     usbutils \
diff --git a/recipes-qt/qt4/qt4-embedded-4.8.5/qt-embedded-linux-4.8.4-phonon-colors-fix.patch b/recipes-qt/qt4/qt4-embedded-4.8.5/qt-embedded-linux-4.8.4-phonon-colors-fix.patch
deleted file mode 100644
index 0624eef..0000000
--- a/recipes-qt/qt4/qt4-embedded-4.8.5/qt-embedded-linux-4.8.4-phonon-colors-fix.patch
+++ /dev/null
@@ -1,26 +0,0 @@
-diff --git a/src/3rdparty/phonon/gstreamer/qwidgetvideosink.cpp b/src/3rdparty/phonon/gstreamer/qwidgetvideosink.cpp
-index 89d5a9d..8508001 100644
---- a/src/3rdparty/phonon/gstreamer/qwidgetvideosink.cpp
-+++ b/src/3rdparty/phonon/gstreamer/qwidgetvideosink.cpp
-@@ -18,6 +18,7 @@
- #include <QApplication>
- #include "videowidget.h"
- #include "qwidgetvideosink.h"
-+#include <gst/video/video.h>
-
- QT_BEGIN_NAMESPACE
-
-@@ -106,11 +107,7 @@ static GstStaticPadTemplate template_factory_rgb =-     GST_STATIC_PAD_TEMPLATE("sink",- GST_PAD_SINK,
-                             GST_PAD_ALWAYS,
--                            GST_STATIC_CAPS("video/x-raw-rgb, "
--                                            "framerate = (fraction) [ 0, MAX ], "
--                                            "width = (int) [ 1, MAX ], "
--                                            "height = (int) [ 1, MAX ],"
--                                            "bpp = (int) 32"));
-+                            GST_STATIC_CAPS(GST_VIDEO_CAPS_xRGB_HOST_ENDIAN));
-
- template <VideoFormat FMT>
- struct template_factory;
-
diff --git a/recipes-qt/qt4/qt4-embedded-4.8.6/qt-embedded-linux-4.8.4-phonon-colors-fix.patch b/recipes-qt/qt4/qt4-embedded-4.8.6/qt-embedded-linux-4.8.4-phonon-colors-fix.patch
new file mode 100644
index 0000000..0624eef
--- /dev/null
+++ b/recipes-qt/qt4/qt4-embedded-4.8.6/qt-embedded-linux-4.8.4-phonon-colors-fix.patch
@@ -0,0 +1,26 @@
+diff --git a/src/3rdparty/phonon/gstreamer/qwidgetvideosink.cpp b/src/3rdparty/phonon/gstreamer/qwidgetvideosink.cpp
+index 89d5a9d..8508001 100644
+--- a/src/3rdparty/phonon/gstreamer/qwidgetvideosink.cpp
++++ b/src/3rdparty/phonon/gstreamer/qwidgetvideosink.cpp
+@@ -18,6 +18,7 @@
+ #include <QApplication>
+ #include "videowidget.h"
+ #include "qwidgetvideosink.h"
++#include <gst/video/video.h>
+
+ QT_BEGIN_NAMESPACE
+
+@@ -106,11 +107,7 @@ static GstStaticPadTemplate template_factory_rgb =+     GST_STATIC_PAD_TEMPLATE("sink",+ GST_PAD_SINK,+ GST_PAD_ALWAYS,+- GST_STATIC_CAPS("video/x-raw-rgb, "+-                                            "framerate = (fraction) [ 0, MAX ], "
+-                                            "width = (int) [ 1, MAX ], "
+-                                            "height = (int) [ 1, MAX ],"
+-                                            "bpp = (int) 32"));
++                            GST_STATIC_CAPS(GST_VIDEO_CAPS_xRGB_HOST_ENDIAN));
+
+ template <VideoFormat FMT>
+ struct template_factory;
+
diff --git a/recipes-qt/qt4/qt4-embedded_4.8.5.bbappend b/recipes-qt/qt4/qt4-embedded_4.8.5.bbappend
deleted file mode 100644
index bbb4d26..0000000
--- a/recipes-qt/qt4/qt4-embedded_4.8.5.bbappend
+++ /dev/null
@@ -1,2 +0,0 @@
-FILESEXTRAPATHS_prepend := "${THISDIR}/${PN}-${PV}:"
-SRC_URI += "file://qt-embedded-linux-4.8.4-phonon-colors-fix.patch"
diff --git a/recipes-qt/qt4/qt4-embedded_4.8.6.bbappend b/recipes-qt/qt4/qt4-embedded_4.8.6.bbappend
new file mode 100644
index 0000000..bbb4d26
--- /dev/null
+++ b/recipes-qt/qt4/qt4-embedded_4.8.6.bbappend
@@ -0,0 +1,2 @@
+FILESEXTRAPATHS_prepend := "${THISDIR}/${PN}-${PV}:"
+SRC_URI += "file://qt-embedded-linux-4.8.4-phonon-colors-fix.patch"
-- 
1.9.1

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.

The steps necessary to obtain this patch after the changes have been made, are described shortly. They define the steps needed to generate the patch, as shown in the following command, and is 0001-Update-suppport-for-atmel-xplained-demo-image-image.patch. It can be upstream to the community or directly to the maintainer of the meta-atmel layer using the information available in the README file and the git send-email command:

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:

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.

The current status of the Toaster Project permits the execution in various setups and running modes. Each of them will be presented and accordingly defined as follows:

There are scenarios when multiple Toaster instances are running on multiple remote machines, or when a single Toaster instance is shared among multiple users and build servers. All of them can be resolved by modifying the mode that the Toaster starts in and changing the SQL database and location of the web server accordingly. By having a common SQL database, a web server, and multiple BitBake servers with the Toaster user interface for each separate build directory, you can solve problems involved in the previously mentioned scenarios. So, each component in a Toaster instance can be run on a different machine, as long as communication is done appropriately and the components know about each other.

To set up an SQL server on a Ubuntu machine, a package needs to be installed, using the following command:

Having the necessary packages is not enough; setting them up is also required. Therefore, the proper username and password for the access web server is necessary, along with the proper administration rights for the MySQL account. Also, a clone of the Toaster master branch would be necessary for the web server, and after the sources are available, make sure that inside the bitbake/lib/toaster/toastermain/settings.py file, the DATABASES variable indicates the previous setup of the database. Make sure that you use the username and password defined for it.

With the set up done, the database synchronization can begin in the following way:

Now, the web server can be started using the python bitbake/lib/toaster/manage.py runserver command. For background execution, you can use the nohup python bitbake/lib/toaster/manage.py runserver 2>toaster_web.log >toaster_web.log & command.

This may be enough for starters, but as case logs are required for the builds, some extra setup is necessary. Inside the bitbake/lib/toaster/toastermain/settings.py file, the DATABASES variable indicates the SQL database for the logging server. Inside the build directory, call the source toaster start command and make sure that the conf/toaster.conf file is available. Inside this file, make sure that the Toaster and build history bbclasses are enabled to record information about the package:

After this set up is available, start the BitBake server and the logging interface with these commands:

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.

The local is very similar to the builds of the Yocto Project presented until now. This is the best mode for individual usage and learning to interact with the tool. Before starting the setup process, a few packages are required to be installed, using the following command lines:

After these packages are installed, make sure that you install the components required by Toaster; here, I am referring to the Django and South packages:

For interaction with the web server, the 8000 and 8200 ports are necessary, so make sure that they are not already reserved for other interactions. With this in mind, we can start the interaction with Toaster. Using the Poky build directory available from the downloads in the previous chapters, call the oe-init-build-env script to create a new build directory. This can be done on an already existing build directory, but having a new one will help identify the extra configuration files available for interaction with Toaster.

After the build directory is set according to your needs, the source toaster start command should be called, as mentioned previously, to start Toaster. At http://localhost:8000 , you will see the following screenshot if no build is executed:

Toaster

Run a build in the console, and it will be automatically updated in the web interface, as shown in the following screenshot:

Toaster

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.

Toaster

As seen in the preceding example, there are two builds that have finished in the preceding screenshot. Both of them are kernel builds. The first one finished with success, while the second has some errors and warnings. I did this as an example to present the user with alternative outputs for their build.

The build that failed took place due to lack of memory and space on the host machine, as seen in the following screenshot:

Toaster

For the failing build, a detailed fail report is available, as displayed in the following screenshot:

Toaster

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:

Toaster

To stop Toaster, the source toaster stop command can be used after the execution activities are finished.

Inside a build directory, Toaster creates a number of files; their naming and purpose are presented in the following lines:

With all of these factors mentioned, let's move to the next component, but not before offering a link to some interesting videos about Toaster.

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.

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.

The Yocto Project is actively involved in the following activities as part of the AutoBuilder project:

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:

The configuration files for this project are available inside the config directory. The autobuilder.conf file is used to define the parameters for the project, such as DL_DIR, SSTATE_DIR, and other build artifacts are very useful for a production setup, though not so useful for a local one. The next configuration file to inspect is yoctoABConfig.py, available in the yocto-controller directory where it defines the properties for the executed builds.

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:

Autobuilder

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:

Autobuilder

This project has more to offer to its users, but I will let the rest be discovered through trial and error and a reading of the README file. Keep in mind that this project was built with Buildbot, so the workflow is very similar to it.

 

In this chapter, you will be given a brief introduction to a number of tools that address various problems and solves them in ingenious ways. This chapter can be thought of as an appetizer for you. If any of the tools presented here seem to interest you, I encourage you to feed your curiosity and try to find more about that particular tool. Of course, this piece of advice applies to any information presented in this book. However, this bit of advice holds true particularly for this chapter because I've chosen a more general description for the tools I've presented. I've done this as I've assumed that some of you may not be interested in lengthy descriptions and would only want to focus your interest in the development process, rather than in other areas. For the rest of you who are interested in finding out more about other key areas, please feel free to go through the extensions of information available throughout the chapter.

In this chapter, a more detailed explanation of components, such as Swabber, Wic, and LAVA, will be offered. These tools are not the ones, which an embedded developer will encounter on everyday jobs, though interaction with such tools could make life a little easier. The first thing I should mention about these tools is that they have nothing in common with each other, and are very different from each other and address different requests. If Swabber, the first tool presented here, is used for access detection on a host development machine, the second tool represents a solution to the limitations that BitBake has with complex packaging options. Here, I am referring to the wic tool. The last element presented in this chapter is the automation testing framework called LAVA. It is an initiative from Linaro, a project that, in my opinion, is very interesting to watch. They are also combined with a continuous integration tool, like Jenkins, and this could make it a killer combination for every taste.

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.

This tool was selected for a short introduction in this chapter because it constitutes another point of view of the Yocto Project's ecosystem. Of course, a mechanism for access detection into the host system is not a bad idea and is very useful to detect accesses that could be problematic for your system, but it is not the first tool that comes to mind when developing software. When you have the possibility of redoing your build and inspecting your host ecosystem manually, you tend to lose sight of the fact that tools could be available for this task too, and that they could make your life easier.

For interaction with Swabber, the repository needs to be cloned first. The following command can be used for this purpose:

After the source code is available on the host, the content of the repository should look as follows:

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 and . 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.

The required.txt file contains the information about the packages used and also about the packages specific files. More information regarding configurations is also available inside the extra.txt file. Such information includes files and packages that can be accessed, various warnings and files that are not available in the host database, and various errors and files that are considered dangerous.

For the command on which the tracing is done, the output information is not much. It has only been offered as an example; I encourage you to try various scenarios and familiarize yourselves with the tool. It could prove helpful to you later.

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.

In the following lines, I will try to describe the problem associated with BitBake's lack of functionality and how Wic can solve this problem in an easy manner. I will also show you how this tool was born and what source of inspiration source was.

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 commands behind every one of these types are, in fact, a shell command-specific for a defined root filesystem format. The best example of this is the ext3 format. For this, the IMAGE_CMD_ext3 variable is defined and these commands are invoked, shown as follows:

After the commands are called, the output is in the form of a image-*.ext3 file. It is a newly created EXT3 filesystem according to the FSTYPES defined variable value, and it incorporates the root filesystem content. This example presents a very common and basic filesystem creation of commands. Of course, more complex options could be required in an industry environment, options that incorporate more than the root filesystem and add an extra kernel or even the bootloader alongside it, for instance. For these complex options, extensive mechanisms or tools are necessary.

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:

To use the newly defined image formats, the machine configuration needs to be updated accordingly, using the following commands:

By using the inherit ${IMAGE_CLASSES} command inside the image.bbclass file, the newly defined image_types_foo.bbclass file's functionality is visible and ready to be used and added to the IMAGE_FSTYPE variable.

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.

To use the vmdk image format, a vmdk value needs to be defined in the IMAGE_FSTYPE variable. However, for this image format to be generated and recognized, the image-vmdk.bbclass file's functionalities should be available and inherited. With the functionalities available, three things can happen:

This functionality offers the possibility of generating images that can be copied onto a hard disk. At the base of it, the syslinux configuration file can be generated, and two partitions are also required for the boot up process. The end result consists of an MBR and partition table section followed by a FAT16 partition containing the boot files, SYSLINUX and the Linux kernel, and an EXT3 partition for the root filesystem location. This image format is also responsible for moving the Linux kernel, the syslinux.cfg, and ldlinux.sys configurations on the first partition, and copying using the dd command the EXT3 image format onto the second partition. At the end of this process, space is reserved for the root with the tune2fs command.

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.

With regard to custom-defined image filesystem types, one such example can be found inside the meta-fsl-arm layer; this example is available inside the imx23evk.conf machine definition. This machine adds the next two image filesystem types: uboot.mxsboot-sdcard and sdcard.

The mxs-base.inc file included in the preceding lines is in return including the conf/machine/include/fsl-default-settings.inc file, which in return adds the IMAGE_CLASSES +="image_types_fsl" line as presented in the general case. Using the preceding lines offers the possibility for the IMAGE_CMD commands to be first executed for the commands available for the uboot.mxsboot-sdcard format, followed by the sdcard IMAGE_CMD commands-specific image format.

The image_types_fsl.bbclass file defines the IMAGE_CMD commands, as follows:

At the end of the execution process, the uboot.mxsboot-sdcard command is called using the mxsboot command. Following the execution of this command, the IMAGE_CMD_sdcard specific commands are called to calculate the SD card size and alignment, as well as to initialize the deploy space and set the appropriate partition type to the 0x53 value and copy the root filesystem onto it. At the end of the process, several partitions are available and they have corresponding twiddles that are used to package bootable images.

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.

Keeping this information in mind, it seems that in the preceding example, we should have used another script. Considering the fact that it is possible to build an image from within the build system and also outside of it, the search for a number of tools that fit our needs was started. This search ended at the Fedora kickstart project. Although it has a syntax that is also suitable for areas involving deployment efforts, it is often considered to be of most help to developers.

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:

LAVA is primarily written using Python, which is no different from what the Yocto Project offers us. As seen in the Toaster Project, LAVA also uses the Django framework for a web interface and the project is hosted using the Git versioning system. This is no surprise since we are talking about Linaro, a not-for-profit organization that works on free and open source projects. Therefore, the thumb rule applied to all the changes made to the project should return in the upstream project, making the project a little easier to maintain. However, it is also more robust and has better performance.

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.

There are scenarios when only the dispatcher can be used; these scenarios involve the usage of a local test or a testing feature development. There are also scenarios where all the components run on the same machine, such as a single deployment server. Of course, the desired scenario is to have components decoupled, the server on one machine, database on another one, and the scheduler daemon and dispatcher on a separate machine.

For the development process with LAVA, the recommended host machines are Debian and Ubuntu. The Linaro development team working with LAVA prefer the Debian distribution, but it can work well on an Ubuntu machine as well. There are a few things that need to be mentioned: for the Ubuntu machine, make sure that the universe repositories are available and visible by your package manager.

The first package that is necessary is lava-dev; it also has scripts that indicate the necessary package dependencies to assure the LAVA working environment. Here are the necessary commands required to do this:

Taking into consideration the location of the changes, various actions are required. For example, for a change in the templates directory's HTML content, refreshing the browser will suffice, but any changes made in the *_app directory's Python implementation will require a restart of the apache2ctl HTTP server. Also, any change made in the *_daemon directory's Python sources will require a restart of lava-server altogether.

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:

 

In this chapter, you will be presented with information on the real-time component of the Yocto Project. Also, in the same context, a short discussion regarding the general purpose of an operating system and a real-time operating system will be explained. We will then move toward the PREEMPT_RT patches that try to change normal Linux into a full powered real-time operating system; we will try to look at it from more angles and at the end, sum it up and draw a conclusion out of it. This is not all, any real-time operation needs its applications, so a short presentation on the do's and don'ts of application writing that is suitable in the context of a real-time operating system, will also be presented. Keeping all of this in mind, I believe it's time to proceed with this chapter content; I hope you enjoy it.

You will find a more detailed explanation of real-time components in this chapter. Also, the relation between Linux and real-time will be shown to you. As everyone knows already, the Linux operation system was designed as a general purpose OS very similar to the already available UNIX. It is very easy to see the fact that a multiuser system, such as Linux, and a real-time one are somewhat in conflict. The main reason for this is that for a general purpose, multiple user operating systems, such as Linux, are configured to obtain a maximal average throughput. This sacrifices latencies that offer exactly the opposite requirements for a real-time operating system.

The definition for real time is fairly easy to understand. The main idea behind it in computing is that a computer or any embedded device is able to offer feedback to its environment in time. This is very different from being fast; it is, in fact, fast enough in the context of a system and fast enough is different for the automobile industry or nuclear power plants. Also, this kind of a system will offer reliable responses to take decisions that don't not affect any exterior system. For example, in a nuclear power plant, it should detect and prevent any abnormal conditions to ensure that a catastrophe is avoided.

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:

There are multiple reasons for Linux not being suitable as a RTOS:

All the preceding characteristics constitute the reason why an upper boundary cannot be applied to the latency of a task or process, and also why Linux cannot become a hard real-time operating system. Let's take a look at the following diagram which illustrates the approaches of Linux OS to offer real-time characteristics:

Understanding GPOS and RTOS

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.

To offer more real-time characteristics to a Linux operating system, there are also two more approaches that can be presented. The first one refers to a more preemptive implementation of the Linux kernel. This approach can take advantage of the already available spinlock mechanism used for SMP support, making sure that multiple processes are prevented from executing simultaneously, though in the context of a single processor, the spinlocks are no ops. The interrupt handling also requires modifications this rescheduling to make possible if another higher priority process appears; in this situation, a new scheduler might also be required. This approach offers the advantage of not changing the interaction of a user space and the advantage of using APIs, such as POSIX or others. The drawback of this is that the kernel changes are very serious and every time a kernel version changes, these changes need to be adapted accordingly. If this work was not enough already, the end result is not fully real-time operating system, but one that reduces the latency of the operating system.

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: