Embedded Systems Using Linux



Overview

Linux is a very capable operating system that has one very big advantage over almost all other operating systems, which is its low cost. Since Linux is available for download free from the Internet, it has become very popular. It is very modular in nature, which also makes it suitable for use in embedded systems, since all features of the system that are not needed for a specific embedded system can be removed from the kernel. In addition, Linux has been ported successfully to a large number of processor architectures, which allows it to run on many different types of CPUs. There are a couple of things to consider when attempting to use Linux as the operating system for an embedded design.


First, the CPU must be supported by Linux. Currently, the following processors are supported by Linux: Intel 386 processors, ARM, CRIS (i.e. ETRAX 100LX from Axis Communications), Motorola 68000 series, Sparc (Sun Workstations), PowerPC, MIPS, s390 (mainframe) and several others.


Second, you must determine what features are needed in the kernel for the embedded system. For example, Linux generally boots from either an IDE or a SCSI hard disk. It can however be booted from either a CD-ROM drive, or from a ROM chip instead, if needed. Linux has support for several hundred different devices, such as video cards, network cards, sounds cards, etc. On an embedded system, you will want to only include support for the actual hardware devices that are available.


Lastly, you will also need to determine if the embedded system will require any real-time features. Remember, a real-time operating system is one that has a guarenteed response time to interrupts. Linux itself is not a real-time operating system, since interrupt response times are not absolutely measurable. It is possible to convert Linux into a real-time system however.


Linux Real-Time Extensions

In order to make Linux into a real-time operating system, the kernel itself is modified slightly so it can run as a task under a real-time scheduling process. This is the way the RTLinux project adds real-time features to Linux. In this case, a developer has a choice when creating software that handles interrupt. You can develop a standard Linux device driver, which will be installed and managed by Linux's normal tools, or you can write an RTLinux device driver. In the first case, interrupt response time is not guarenteed. If you develop an RTLinux device driver however, response time can be accurately predicted. It is generally measured in microseconds or maybe even less.



Building a Linux Kernel

Once you've made you choices on device support and whether you need a real-time or standard Linux kernel, the next step is to build the kernel itself. Of course, this requires that you first download the source code to the kernel itself. You may do so by visiting http://www.kernel.org, or via FTP at ftp.kernel.org. Once you have donwloaded the kernel source code, you must extract it (uncompress) so you can build it. The traditional place to place the kernel is in the /usr/src/linux folder. It is possible to install more than one version of the kernel and then create links from the folder to /usr/src/linux. Here is an example of the commands, assuming version 2.2.19 of the kernel is already in /usr/src/linux.


Commands to extract version 2.4.4 of the Linux kernel:

  cd /usr/src
  rm linux
  tar xzvf <path>/linux-2.2.4.tar.gz
  mv linux linux-2.4.4
  ln -s linux-2.4.4 linux

NOTES:

Example:

    rm /usr/src/linux
    ln -s /usr/src/linux-2.2.19 /usr/src/linux

Now that you have extracted the kernel, the next step is to select the options you would like included into the kernel. This can be accomplished using one of several scripts included in the kernel source code. To begin, make the source code directory your current directory, then run the 'make' command, followed by either 'config' (text-mode only), 'menuconfig' (text mode with menus), or xconfig (X-Windows configuration mode). Here is an example:

  cd /usr/src/linux
  make menuconfig

       or

  cd /usr/src/linux
  make xconfig

I don't recommend using the older 'config' option, since it is very slow to setup the kernel this way and if you make a mistake there is no way to correct it, except by starting over. Using the 'menuconfig' or 'xconfig' options is the best way to configure the kernel.


At this point, you should be presented with a bewildering array of options and settings that effect how the kernel will be built. The 'menuconfig' and 'xconfig' scripts have help screens for almost every option. You must now take the time to go through all the menu choices and enable or disable options and device drivers as needed. You may also want to read some or all of the text files in the /usr/src/linux/Documentation folder and sub-folders. There are many options and settings described there. Another choice is to visit http://www.kernel.org, where additional help files can be found.


Once you've made all the selections in the configuration scripts, exit and save your settings. These values are saved in a file named '.config' in the /usr/src/linux folder. It is possible to copy this file to a new name and later rename it back to '.config'. This is useful if you are working on several different systems, each with different hardware devices or processors.


A Word About Modules

Linux device drivers can be either built into the kernel itself, or can be built as modules. When built into the kernel, the device driver is always loaded and ready to be used. If built as a module, the device driver will have to be installed before attempting to use the device. This can be done automatically using scripts, manually from the command line, or the kernel can automatically load modules as needed. Each method has advantages and disadvantages.


Vendors like RedHat generally don't know what exact hardware will be available when the operating system gets installed, so they generally build almost all device drivers as modules. Then during the installation process, they will detect what hardware is available and modify Linux's startup script to install module drivers for all the hardware detected. This is great for desktop machines where large hard disks and gobs of RAM are available. For embedded systems however, I highly recommend putting device drivers into the kernel itself. Of course, you should only include those specific devices that you must support to get the smallest kernel available. For example, if your embedded design does not have a network cards, there is no reason to include any networking support at all in the kernel.


One thing to note is that if you are trying to build a kernel for a different processor than the one currently running Linux, you will have to install a cross-compiler and make a few manual changes to the build process. A cross-compiler generates the opcodes for the target processor, instead of the opcodes for the processor running on your computer. Cross-compilers for almost all processors supported by Linux are available from the GNU organization at http://www.gnu.org. Notes on building a kernel using a cross-compiler can be found in the /usr/src/linux/Documentation folder.


Building the Kernel

Now that you have configured your kernel, it's time to begin building it. The first step is to issue the command make dep. This will scan all the source code and header files, after reading your saved .config file and updates various interdependancies between kernel source code files and options. Once that has completed, you can then build the kernel itself by issuing the command: make zImage or make bzImage.


The make zImage command causes the kernel to be compressed using the standard gzip compression algorithm. The kernel will uncompress itself on the fly when attempting to boot. This is done by adding a small bit of 'uncompression' code to the beginning of the kernel itself. During boot time, the kernel will then extract the 'real' kernel from the file to memory and continue to boot. The make bzImage option using the newer bzip2 compression algorithm, which compresses the kernel better than the gzip version.


The actual building of the kernel will take some time, even on a fast machine. After it is completed, the kernel image file itself will be in the /usr/src/linux/arch/<arch-type>/boot directory. For Intel 386 systems, substitute i386 for <arch-type>. Other processor architectures will of course have different names. The kernel itself will be named zImage or bzImage, depending on which type of compression option you selected. This is the file you will need to put on floppy, CD-ROM or Flash ROM or other bootable device to start running Linux.


If you asked for any device drivers to be built as modules, then you must next issue the command make modules. Once this is completed, modules will be stored in various subdirectories under the /usr/src/linux directory. If you are building a new kernel for use on the host computer, you can install the modules with the command make modules_install. If you are cross-compiling to a new system, you will have to manually copy the modules by hand to the boot device.


Modules must be installed under the /lib/modules/<linux-version> folder for them to work properly. Linux cannot boot without some kind of file system. Luckily, this does not mean a hard disk is required, since Linux can use CD-ROM drives, network drives, or even Flash RAM cards as a kind of hard disk. It will obviously take a bit of planning to setup the file system correctly in many cases.


In addition to the kernel and modules, you will also need a few external utilities in order to boot Linux successfully. The modutils package of programs will be needed if you have modules. This consists of several programs with names like modprobe, insmod, rmmod and lsmod.


The Linux Boot Process

When the kernel is initially loaded, it will first perform a scan of system hardware, attempting to locate devices for which a driver was included into the kernel. You must ensure that the device driver needed to read at least one file system is included. This could be a CD-ROM file system, an IDE hard drive, or perhaps a RAM Disk or ROM file system image. Once all hardware is indentified, Linux will next move into the initialization phase.


In the initialization phase, the kernel will first mount the root file system, using the parameters passed to the kernel during bootup. The 'root' option is used to specify which device is used to mount the main Linux file system from. To boot from the first partition on the first IDE device, specify 'root=/dev/hda1', where 'hd' means IDE device, the 'a' means the first IDE device and the '1' means the first partition.


Once the root partition has been loaded, Linux will then do several additional steps to complete the boot process. Linux will look for a file named /etc/fstab, which specifies other file systems that should be mounted. Once this is completed, Linux will then run its first program, named 'init'. This program is responsible for completing the rest of the boot up sequence.


The 'init' program must be located in the /sbin directory, otherwise Linux will report a kernel panic and stop running. The 'init' program will read the file named '/etc/inittab'. This file consists mainly of several lines that can run additional programs. The 'inittab' file should first specify a runlevel setting. Once this is done, the 'init' program will then scan the rest of the file and run any programs that are listed as required at that runlevel.


This could include running software like a web server, X-Windows startup, various background daemons and other software. At a minimum, a few support files called libraries will also probably be needed. These library should be in the '/lib' directory, by default. It is your responsibility to ensure the root file system has all the necessary utilities, libraries and support files on it before attempting to boot Linux. This is not a trivial task, but it is not terribly difficult either.


This same boot process is run by Linux, no matter what kind of system it is running on. After the kernel is loaded and hardware is detected, the root file system is mounted and finally the 'init' program is run. Luckily the root file system does not have to be very large. Depending on features and additional software needed, a root file system can be as small as about 1 MB. It is possible to make a read-only root filesystem and store it on CD-ROM or a ROM chip. You must have some RAM available however, since Linux itself cannot normally run off the ROM chip. In addition, Linux requires a couple of directories to be 'writable'. At a minimum, the /tmp and /var directories should be setup so Linux can create temp files and store various configuration information needed at run time. Probably the best way to do this is to setup a small RAM disk for the /tmp and /var directories. This can be done via kernel bootup parameters, similar to the 'root=/dev/hda1' statement we discussed earlier.


Real-Time Linux

If you wish to make Linux into a real-time operating system, there are several choices available. There are a number of companies that will be glad to sell you a pre-configured version of Linux, with real-time capabilities already included. Here are some popular embedded, and/or real-time, versions of Linux that you may wish to investigate: Blue Cat Linux at http://www.lynuxworks.com, Hard Hat Linux at http://www.mvista.com, RT-Linux at http://www.rtlinux.com, ThinLinux at http://www.thinlinux.org, White Dwarf Linux at http://www.emjembedded.com/linux/dimmpc.html, and Linux Embedded at http://linux-embedded.com. There are other distributions available and more are release almost daily. Visit http://www.linux.org for more details and a long list of Linux distributions.


Most of the real-time enhancements made to Linux work by installing a new real-time kernel first, then booting Linux as a task under the real-time kernel. This is the way RT-Linux works. The process of creating a kernel and boot file system for a real-time version on Linux is very similar to the steps outline earlier. The biggest change is that the real-time kernel gets booted first, then Linux boots normally as a task under the real-time kernel. Consult the documentation for the real-time version of Linux for additional details.