How To Guide: Embedded Gentoo Linux

Author Note: This was originally written for Linux Journal in 2008. The content is likely out of date. I am replicating it here for posterity.

For many Linux users the prospect of building an embedded Linux system can seem overwhelming and a bit out of reach – I use to be one such user. Several months ago I wanted to get into embedded Linux but did not know where to start. After doing some research I came across the Embedded Gentoo project. Using several different on-line documents (see resources) and following the mailing list (see resources) I was able to build a usable embedded Linux system.

Getting Started

The Embedded Gentoo project was created to provide a cross compiled embedded Linux system. This means Embedded Gentoo uses a full Gentoo installation as a host for building the embedded system. Cross compile build systems, such as this one, allow the host and target machines to be different architectures. This article takes the path of least resistance, both the host and the target are x86 machines.

Building an Embedded Gentoo system requires a working Gentoo system. The system can be a fully installed version, or running from the Gentoo live CD. The live CD version will also require an empty partition on a disk to create the build environment.

Building the Development Environment

The first step to creating an Embedded Gentoo system is to build the development environment. The development environment is just a directory were the system will be built – it can be anywhere on the host Gentoo system. The partition housing the build environment will need roughly 5GB of free disk space. All of the commands in this article need to be ran as root.

# mkdir -p /opt/embedded_gentoo/usr/portage

Adding the -p option recursively makes the required directory tree.

Next download an embedded Gentoo snapshot (see resources). This article will use a stage 3 tarball (version 2005.1). However, a stage 1 or 2 tarball would work just fine. Why a stage 3? Stage 3 is the quickest to build, it contains pre-compiled bootstrap and installation packages; stage 1 builds everything from scratch and stage 2 only has a the compiled bootstrap packages. The bootstrap packages

Next, unpack the snapshot into the development environment. (Change the xxxx.x to the version of the snapshot that was downloaded.

# tar -xjpf stage3-x86-uclib-xxxx.x.tar.bz2 -C /opt/embedded_gentoo

Now copy over the DNS information from the host system; This allows name resolution to be preformed inside the development environment.

# cp /etc/resolv.conf /opt/embedded_gentoo/etc/resolv.conf

Binding the Development Environment to the Host System

After unpacking everything a few mount points need to created in the development environment. First is the proc directory; it is where all of the information about the system hardware is contained. The second is the portage directory; it contains information about all of the ebuild packages that are available on the system. (Portage is the Gentoo package manager; it can be compared to apt and yum; ebuilds are software packages, much like RPM and DEB packages except ebuilds usually contain source only.) Lastly is the dev directory; it contains pointers to the devices on the host machine.

# mount --bind /proc /opt/embedded_gentoo/proc
# mount --bind /usr/portage /opt/embedded_gentoo/usr/portage
# mount --bind /dev /opt/embedded_gentoo/dev

From here on out all of the work will be done inside the development environment. The chroot utility will change the root directory of the current session and allow access into the development environment.

# chroot /opt/embedded_gentoo /bin/bash --login

Once in the development environment the environment variables need to be updated.

# env-update
# source /etc/profile

env-update is a Gentoo utility that reads the files in /etc/env.d and automatically creates /etc/profile.env and /etc/ld.so.conf, then runs ldconfig to update and configure the dynamic linker run time bindings. The source utility applies the changes caused by env-update to be applied to the current session.

At this point the development environment is built and ready to use. The development environment can be exited at anytime. Repeat the steps in this section to re-enter the development environment.

Configuring the Development Environment

The next step is to modify /etc/make.conf to fit the target system. Gentoo uses this file to obtain information about what architecture and settings need to be used when compiling. The nano text editor must be used to modify the file, it is the only one available in the development environment. The target system that is being used, for the purposes of this article, has an i586 CPU and a VIA video card; these settings are denoted in the make.conf. For a complete breakdown of what the make.conf file is used for see the resources.

USE="minimal"
CHOST="i586-gentoo-linux-uclibc"
CFLAGS="-march=i586 -Os -pipe -fomit-frame-pointer -m3dnow"
CXXFLAGS="${CFLAGS}"
FEATURES="buildpkg"
VIDEO_CARDS="vesa via"
UCLIBC_CPU="586"
  • USE tells Portage the default options to set on every package that is installed. In this case the minimal flag is used to only install what is necessary for each package to compile and install properly.
  • CHOST lets Portage know what the target architecture is.
  • CFLAGS and CXXFLAGS are passed to compiler when packages are being built. These setting allow the compiled packages to be optimized a specific way when compiled.
  • FEATURES defines actions Portage takes by default. In this case buildpkg is set so that binary packages will be created for all packages that are built.
  • VIDEO_CARDS tells Portage to add support for the listed cards, if applicable, to any packages that are installed.
  • UCLIB_CPU configures uClib to use the listed setting as the system architecture, in this case 586.

Next, tell portage to use the 2.6 Linux Kernel; this is accomplished by creating a symbolic link from the downloaded tarball to the system’s make.profile file. Also note that the path may not be the same if a different version of Embedded Gentoo is being used.

# ln -snf /usr/portage/profiles/uclibc/x86 /etc/make.profile

Building the Embedded System

The development environment is now complete and the final embedded system is ready to be built. Start the process by creating a directory where the final system can be built.

# mkdir /final_system

The first thing to build into the final system is the baselayout package; this will put a proper file system in place. At the time of this writing baselayout-lite is masked in portage; it has to be unmasked by adding it to the package.keywords file. In Portage being masked means the package is not considered fully stable and has been blocked from being installed.

# echo "sys-apps/baselayout-lite -*" >> /etc/portage/package.keywords
# ROOT=/final_system emerge baselayout-lite

Next install uClibc and BusyBox. uClibc is a C library for developing embedded Linux systems; it takes the place of the usual GNU C Library. BusyBox provides all of the standard UNIX utilities that make the system work, in one small executable.

# ROOT=/final_system USE=make-symlinks emerge uclibc busybox

Configuring the Embedded System

There are several tasks to be done inside of the final system that require interaction with the shell; To preform these tasks the root file system needs to be changed again.

chroot /final_system /bin/sh

The first task is to run the passwd command in order to change the root password. This password will be the root password on the embedded system once it is deployed, so do not forget it.

Creating the fstab file is the next step. The fstab is where all of the systems mount points are configured. Since this is an embedded operating system and will most likely be running on a flash based disk the mount points need to be configured as read only. Flash based drives and disks have a finite amount of write cycles; once the limit is reached the device will fail to operate. The primary way to insure that writes do not occur, even if it causes a system failure, is to make the proper previsions in the fstab. The fstab file is located at /etc/fstab; use vi to edit the file (vi, is the only editor available while inside the final embedded system).

/dev/hda1 / ext2 noatime,ro 0 0
none /proc proc defaults 0 0
none /sys sysfs defaults 0 0
none /tmp tmpfs size=32m 0 0

The fstab above shows that the primary partition is a read only (the ro options makes this so) ext2 file system. Since it is read only a journalizing file system, such as ext3 or riserfs, cannot be used. The rest of the entries are fairly standard with the exception of the /tmp line. This line tells the system to mount the /tmp directory into a memory type file system; this allows the system to log events and such into memory instead of to a drive partition.

At this point the embedded system is configured; use the exit command to escape the chroot. Note that exiting this chroot does not exit the chroot of the development environment.

Install and Configure the Boot Loader

The next step is to install Grub, the boot loader; it is in portage so a simple emerge command will install the proper libraries.

# ROOT=/final_system emerge grub

After installing grub there are some symbolic links that need to be created. These links ensure that the boot stage files are in the right place inside the final system directory structure.

# ln -s /lib/grub/i386-gentoo/stage1 /final_system/boot/grub/
# ln -s /lib/grub/i386-gentoo/stage2 /final_system/boot/grub/
# ln -s /lib/grub/i386-gentoo/e2fs_state1_5 /final_system/boot/grub/

There is only one drive partition on this system, as displayed in the fstab above. That means the partition must be able to boot, and Grub is going to look for a “grub” directory on this partition when the system is booted.

# ln -s /boot/grub /final_system

Grub is now installed and must be configured. When grub starts it looks for a file called “menu.lst”. This file tells Grub where the root file system is and what kernel to use; there are a host of other settings that are useful from time to time. Using nano, open /final_system/boot/grub/menu.lst and add the following:

default 0
timeout 5
splashimage=(hd0,0)/boot/grub/splash.xpm.gz
title=Linux 2.6.x
root (hd0,0)
kernel /boot/vmlinuz-2.6.x root=/dev/hda1 vga=792

Install Additional Software

Building embedded systems is fun, or at least interesting, but at some point the system will need to do something other than run common Linux utilities. Fortunately, Embedded Gentoo has access to any piece of software that is in the normal Gentoo portage tree. Before installing anything from portage check the dependency list; often there are several dependencies that are not required and can be ignored to save space. These packages include portage configuration packages, audio/video codecs that are unwanted, font packages, and more.

# emerge -pv package

This command pretends to install the requested package (the -p option); it will list the package, configuration options, and dependencies. If everything looks alright remove the pretend option, and run the command again to install the package. If there are dependencies that can be avoided, use the --nodeps option and install all of the required packages one at a time. This can be tricky, make sure all of the real required dependencies get installed.

Build and Configure the Kernel

Portage has several pre-configured kernels to choose from. However, the most flexable is the vanilla kernel (this article was prepaired using version 2.6.17.6.).

# emerge vanilla-sources

There are hundreds of configuration settings in the kernel. To build a simple embedded system, such as this one, the default settings are fine. If the target system has special hardware or other needs, the kernel configuration tool can be used to set them.

# cd /usr/src/linux
# make menuconfig

The kernel and its modules are ready to be built. Keep in mind, depending on the kernel configuration there may not be any kernel modules.

# make
# make INSTALL_MOD_PATH=/final_system modules_install

Once the kernel and the modules are compiled they need to be moved to the proper place in the final system directory structure.

# cp /usr/src/linux/arch/i386/boot/bzImage /final_system/boot/vmlinuz-2.6.x

Cleanup

There are a few places in the final system where unnecessary files congregate; to save space, they should be removed. These files are related to portage and were created when packages were emerged. They will be created again the next time emerge is ran.

# rm -R /final_system/var/db/pkg/*
# rm -R /final_system/var/lib/portage

The chroot environment can be exited at this point.

Deploying the Embedded System

The final system is now ready to be deployed to the target hardware. The next few steps outline how to put the system on a CompactFlash card, attached via a USB card reader. The flash drive is displayed as /dev/sda. Of course there are several other options that can be used to deploy the system. Some possible options include mounting a device over a network (useful for DiskOnChip installations), using a CD-ROM, or installing to a secondary hard drive in the host system. If using an alternative deployment method some of the instructions will be different.

The final system should be about 30MB (use the command du -hs /opt/embedded_gentoo/final_system to see the actual size). Use fdisk to create one ext2 partition (see resources) that is larger than the size of the final system, the partition also needs to be made bootable.

Next, create the ext2 file system on the first partition of the device.

# mke2fs /dev/sda1

The newly minted file system can now be mounted and the embedded system files can be copied to the drive. If the final embedded system ever changes — additional software, configuration change, etc — this is the only step that has to be repeated to re-deploy the system.

# mount /dev/sda1 /media/usbdisk
# cp -adpR /opt/embedded_gentoo/final_system/* /media/usbdisk
# umount /media/usbdisk

The flash drive is mounted to /media/usbdisk, this step may be done automatically by the desktop when the device is recognized. Next the files are copied over; the options passed to the copy command ensure that the original structure is kept intact including symbolic links, permissions, and timestamps. Then the drives in unmounted; if the device was automatically mounted, make sure there is nothing on the desktop trying to access the drive this will cause the umount command to fail.

The last step is to install the master boot record on the flash drive using the Grub command shell.

# /sbin/grub
grub> device (hd0) /dev/sda
grub> root (hd0,0)
grub> setup (hd0)
grub> quit

The device command tells Grub which device/drive to use. The root command specifies the boot partition on the device. setup creates the master boot record. Finally quit exits the Grub shell.

That is all, the flash drive now contains a bootable embedded Linux system.

Conclusion

The world of embedded Linux is vast, and often difficult to navigate. Starting with a base, such as Gentoo, makes the process easier and builds understanding in how embedded Linux works.