Creating the Root File System - Linux Embedded systems

Every Linux system has a root file system. On a desktop machine, the root file system is stored on a magnetic disk; but on an embedded system, the root file system may be stored on a flash device, packaged with the kernel, or, for some larger embedded systems, stored on a disk drive. During the bootup process, the kernel always looks for an initial RAM disk. The initial RAM disk comes from a file attached to the kernel during the build process that is uncompressed into RAM memory when the kernel starts. If a file to boot the system (initrd) is present, the kernel attempts to use the initial RAM disk.

Because the initial RAM disk is built during the kernel build process, it’s being built before the kernel. When the root file system is completed, the kernel build process points at the root file system created during this step.

Although a fully functional root file system can be just one executable file or a few device files, for this example the root file system consists of a complete set of command-line tools typically found on a desktop system compliments of the BusyBox project. BusyBox contains minimal implementations of the commands found on a desktop system, and all the utilities in BusyBox are compiled into one executable and the file system. The file system is then populated with symlinks to the BusyBox executable. In the main() of BusyBox, a switch uses the name of the file (which is the name of the symlink used to invoke the program) to figure out what code to execute.

Configuring the Environment
Before you build the root file system, create an installation folder for the executables, libraries, and other files that go into the root file system. From the perspective of the machine that eventually uses the root file system, the contents below this directory are the / of that machine. This is referred to as $RFS in the remainder of this section, because the directory can be in any arbitrary location:

Building and Installing BusyBox
Start by downloading and unpacking the source code

The method for configuring the tool should now be familiar. For example purposes, use the defconfig option that enables just about every BusyBox option except debugging:

In the menuconfig interface, do these two things:

  1. Set the Cross-Compiler Prefix under the build options to point to the newly created toolchain. This option is under BusyBox Settings” Build Options Menu. Set this value to the entire path and name of the cross-compiler. For example, using the cross-compiler built here, the value is /opt/arm/bin/arm-none-linux-gnueabi-. The trailing is important!
  2. Change the BusyBox installation to $RFS. Look for this option under Busy Box Settings Installation Options. This is where BusyBox will put its executable and symlinks after the build process.

After you’ve made these changes, the next step is starting a build:

The folder $RFS contains the start of a root file system:
linuxrc -> bin/busybox

This root file system isn’t yet complete. It’s missing some critical components such as the libraries, device files, and a few mount points. The next sections cover how to gather the rest of the components from the toolchain.

The Busy Box project was built with shared libraries the glibc shared libraries in this case. These libraries need to be in the root file system when the board runs; otherwise the program will refuse to load. In addition to the shared libraries, the system needs the program that loads the program and performs the dynamic linking of the program and the shared libraries, usually called or ldlinux. so. Just like everything else in Linux, this program doesn’t have a fixed name it can have any arbitrary name, so long as all parties agree on the label.

On a desktop Linux system, to find the name of the library loader, use the ldd command to show the libraries loaded by a program:

 $ ldd `which ls` => (0xb7f6c000) => /lib/tls/i686/cmov/ (0xb7f4f000) => /lib/ (0xb7f36000) => /lib/ (0xb7f2f000) => /lib/tls/i686/cmov/ (0xb7de0000) => /lib/tls/i686/cmov/ (0xb7dc8000) /lib/ (0xb7f6d000) => /lib/tls/i686/cmov/ (0xb7dc4000) => /lib/ (0xb7dbf000)  

Look at the list, and find the program that starts with an absolute path. In this system, the file is /lib/ When running a program linked with shared libraries, the operating system runs /lib/ and passes the name of the program to run (along with its parameters) to the loader.

To generate this output, ldd runs the program in question. This can’t be done with the crosscompiled BusyBox, because it contains binary code for an ARM processor, and the build was likely done on an Intel PC. The alternative to using ldd is the much cruder but effective solution of looking for strings in the executable that start with lib:

The files you’re interested are /lib/ (the loader),, and These files are symlinks stored under the $SYSROOT directory using during the toolchain build. To get them in the root file system, create the directory and copy the files:

To avoid the problem of shared libraries, BusyBox can be linked directly to the libc libraries (static linking) so that the .so files aren’t needed on the target. As an experiment, try static linking: use the search in BusyBox’s menuconfig to find the menu item, rebuild the root file system, and see how the size changes.

Creating Device Nodes and Directories
Linux requires that two devices be present in order to work correctly: console and null. A device node is a way for a user program to communicate with a kernel device driver. These are created on the file system so the kernel build can gather them into the RAM disk it uses to boot the file system:

This is one of the few commands during this process executed as root. Be careful and make sure the commands are executed in the right directory.

A root file system also needs a few extra directories: root, tmp, and proc. The proc directory isn’t strictly necessary, but many programs rely on the proc file system being mounted at /proc to operate correctly:

The system will start without these directories, but having them present prevents some error messages from appearing when the kernel loads. If you’re fretting over every byte in the root file system, you can leave these out to save a little space (even empty directories consume a few bytes).

Finishing Touches
The system requires a few additional files that are read by user programs: the user and group files and the inittab. You can create these files from the command line:

The first two lines create a group and user for the root user. The third line creates an inittab that starts a terminal login when the system starts. When a Linux system boots, it reads the init tab file to figure out what to run. In this case, the inittab instructs the system to run /sbin/getty at the start and to restart the program when it stops.

In a deployed Linux system, this file contains references to additional scripts that start programs necessary for the system, such as an HTTP server, or perform housekeeping tasks like configuring network adapters. The most important aspect of the inittab is that one program is configured for respawn: when init no longer has anything to run, it stops, and so does the kernel.

This system is built using an initial RAM disk. This is part of the kernel and is loaded into memory during boot time. The kernel attempts to run the file /linuxrc; if this file isn’t present or can’t be loaded, the kernel continues the standard booting process by mounting a root file system and as specified on the kernel command line. This is covered in the next section.

Building the Kernel
Now that the toolchain and root file system builds have been completed, the next step is to compile the kernel for the target machine. By now, the kernel configuration program should be familiar, because several of the projects have adopted it as their configuration interface. In addition, the process of configuring a project so that the source tree is in the correct state before building should also be familiar. To get the header files necessary to build the toolchain, you configured the kernel for an ARM processor running on one of ARM’s development boards using this command executed from the top-level kernel directory:
$ make ARCH=arm integrator_defconfig

This command results in the kernel finding the file integrator_defconfig under the ARM architecture directory and copying those settings into the current configuration, stored at .config. This file was contributed by ARM to make it easier to build a Linux kernel for this board and isn’t the only defconfig present. To see a list of all the defconfig files, do the following:
$make ARCH=arm help

This lists the make targets and the _defconfig files for the architecture. ARM has by far the largest count of defconfig files. This command doesn’t have any side effects, so feel free to view the support for other architectures by passing in other values for ARCH. You can find the population of acceptable values for ARCH by looking at the first level of subdirectories in the kernel sources under the arch directory. For example:
$find ./arch -type d maxdepth 1

Because the kernel has already been configured to build for the target device, only a little extra configuration is necessary: updating the kernel command line to boot up using some parameters specific to this build of the kernel and root file system. The kernel command line is like the command line on a utility run from a shell: the parameters get passed in to the kernel at bootup time and let you change how the kernel works without recompilation. In this case, make the kernel command line this value:

You set this value through the kernel configuration tool by running the following:
$ make ARCH=arm menuconfig

The following appears before the configuration menu, during compilation of the software that draws the configuration menu. This happens the first time the kernel menu configuration programs runs:

HOSTCC scripts/kconfig/lxdialog/inputbox.o
HOSTCC scripts/kconfig/lxdialog/menubox.o
HOSTCC scripts/kconfig/lxdialog/textbox.o
HOSTCC scripts/kconfig/lxdialog/util.o
HOSTCC scripts/kconfig/lxdialog/yesno.o



The settings for the kernel command line are at the following menu location: Boot Options Default Kernel Command String. To change the settings, highlight the item in the menu, and press Enter. The window shown in Figure below appears on screen.


The user interface for entering strings in the kernel configuration program is frustrating. You can’t use the cursor keys to navigate to some character and begin typing. Instead, you must make changes by backspacing and replacing text. The easiest way to make changes is to compose them in an editor and paste them into this control. After you update the boot parameters, select OK and Exit on the subsequent menus. The software asks to save changes, which you should affirm. The kernel is now ready to build.

When you run make from the command line for the kernel, you need to pass in the cross-compiler prefix and the architecture:

This command line requests that a zImage be built and prefixes the compiler name (GCC) with CROSS_COMPILE. How do you know the right target not xImage or ouiejsdf, for example? Table below lists the common build targets for Linux.


If you’ve built the kernel for an Intel x86 architecture, the target bzImage is familiar and is still the right value when you’re building for x86 embedded boards. The target in question in an ARM board, which expects a zImage. The documentation for a board’s boot loader specifies the format of binary it requires.

The kernel compilation takes about 30 minutes, give or take. The last few lines of the kernel build show what file was created:
Kernel: arch/arm/boot/Image is ready
Kernel: arch/arm/boot/zImage is ready

Congratulations! The kernel is completed, and the root file system has been built as part of kernel an initial RAM disk. When this kernel boots, it will start a command shell and wait for a login. The root file system has been built into the kernel, and the kernel has a copy of what was include in the ./usr
directory under the kernel root with the name initramfs_data.cpio.gz.

Trouble shooting Booting Problems
What if it didn’t work? The software built in this is remarkably resilient and is configured to do the right thing with minimal input. Still, after all this work, more often than not, you may have skipped a step, or some other problem may require debugging. In general, systems don’t boot for one of the following reasons:

  • The kernel isn’t properly configured for the board.
  • The root file system can’t be mounted.
  • The root file system init program doesn’t run.
  • It’s booting, but the serial connection parameters are wrong.

The easiest way to diagnose board-booting problems is to have a running Linux for the board.Because nearly every board vendor includes a Linux distribution of one sort of another, this is a less unreasonable statement than in years past. When Linux is up and running, you can use it as a test-bed for the code that’s not.

All rights reserved © 2020 Wisdom IT Services India Pvt. Ltd Protection Status

Linux Embedded systems Topics