Making Linux Work in a Tiny Flash

There are two areas that you can modify to reduce the size of your Linux distribution: your root filesystem (RFS) and your kernel. This document discusses how to select the various components of a Linux system, and describes ways to reduce its size while retaining needed functionality. Various aspects of configuring your RFS include:

Selecting a Starting Shell, Utilities, and Library

There’s no magic tool! Each system requires thought and experimentation, but the following tools can quickly get you started down the right path.

BusyBox

http://www.busybox.net/ – BusyBox is a multi-call binary providing a reduced-functionality implementation of most GNU command-line tools. BusyBox combines tiny versions of many common UNIX utilities into a single small executable, and is extremely modular, so you can easily include or exclude \commands (or features) at compile time. This is an excellent alternative to the full Bash and core utilities provided with most Linux systems.

Alternative C Libraries

Because glibc is unsuitable for many embedded development projects, several alternative C libraries are commonly used in embedded development projects, described in Alternative C Libraries for Embedded Development. TimeSys currently provides pre-built glibc and uClibc toolchains.

Selecting a Starting RFS

Most space-efficient filesystems currently in use implement compression schemes that result in an even smaller filesystem image. The following comparison lists alternatives to ext3 and jffs2.

Benefits Limitations

cramfs

Sourceforge:
cramfs

Compresses files to save space

Metadata not compressed, but stored efficiently

Performance hit when reading data, as it must be decompressed

Files < 16MB

Maximum filesystem size 256 MB

Does not store timestamps

Read-only

romfs

Sourceforge:
Linux romfs

Simple and fast

Driver does not need overhead of compression library

Fast access times

No last access, UID/GID information stored

Read-only

No compression

initramfs

Debian package:
initramfs-tools

Well supported for 2.6

Low kernel overhead (layer over page cache)

Records all metadata

Space efficient (allocates only what’s needed)

Easy-to-create filesystem

You need to create device nodes after mounting

UID/GID stored only as numbers

Sporadic support outside of 2.6

squashfs

Sourceforge:
squashfs

Wide architecture support

Can generate a filesystem with different endianess than the host system

Compresses all data, not just file data

Supported across many different kernel versions

Read-only

Slower access time due to decompression overhead

yaffs2
Home page:
yaffs2

Designed for NAND devices

Performs wear-leveling

Records all metadata

Quick mounting time

No Compression

Not as widely supported as JFFS2

Starting with a 2.1 MB root filesystem, TimeSys engineers compared these filesystems. They used the embedded development utilities from the LinuxLink Repository to create a filesystem image, added a kernel, and compared the final size.

As you would expect, there is a tradeoff between size and functionality. You will generally have to optimize for two of the following three desirable qualities: speed, small size, or low memory usage.

The squashfs filesystem produced the smallest RFS size, but has a little more overhead in the kernel. The smallest system was just under 2 MB in size:

  • Kernel (BUGboot image): 1,111 KB
  • Root Filesystem: 700 KB
  • Total: 1,800 KB

The RFS could have been made even smaller by reducing the features in BusyBox and uClibc; BUGboot seems to be a bit larger than other formats.

Strategies for Reducing RFS Size

Reducing the size of an RFS depends both on how small an RFS you need, and what RFS you start with. It is, however, easier and faster if you work up from zero rather than down from the default RFS size. This tactic forces you to think about every byte. The following table includes suggested approaches for different types of small filesystems:

Minimal

Small

Tiny

Minuscule

Ending Size 6 - 10 MB 2 - 2.5 MB 1.5 - 2 MB <1 MB
Starting Library glibc and BusyBox uClibc and BusyBox Pared-down uClibc and BusyBox No BusyBox (or very minimal), no shared libraries
Approach

Strip glibc libraries and remove locale data

Use BusyBox to supply user-land RFS programs

Use the uClibc as your C library and recompile applications

Use BusyBox

Manually create device nodes

Put effort into reducing what’s included in uClibc

Write your own system initialization scripts

init=<your program>
Notes This is surprisingly easy. Given the size of flash modules in some of today’s devices, it might be the most appropriate option. Most people using limited-resource systems find themselves in this category. This process is easier than it sounds, since most users save significant space just by writing a minimal initialization script, which eliminates the need for many support binaries in the RFS.

This approach produces minimal functionality, but if you are only running one application, it is a great solution.

 

Remove Unneeded Symbols

Running strip to remove unnecessary debugging and other symbols can greatly reduce the size of a library or an executable.

Eliminate Scripts

In order to run, shell scripts require a shell (which takes up space) and require boot time. To eliminate scripts, create files, directories, and device nodes in advance, if possible. Note that for almost all embedded systems, the devices remain fixed.

Create Device Nodes in Advance

In addition to removing the need for a shell script, creating device nodes ahead of time reduces the time required to boot the device and eliminates the need for other software, such as udev.

Use Static Linking

Even a small shared library has a lot of code that’s never called. Using static linking eliminates the need for a C library, and you save space by removing unused code. Many applications use only a fraction of the C library, and functionality that is not used is wasted space.

Change the Default init to Your Program

The default behavior for a Linux startup is to run the /sbin/init script, which typically calls other scripts and programs to boot the system. Most of this process is designed for flexibility, which is unnecessary and wastes space for most embedded systems. Change the kernel’s initialization script by adding the following to the command line:

init=<your program here>

If your system needs to start a few services, you can do that yourself. This might not be the right solution for more complex systems, but is a big space saver for smaller, single-purpose devices.

Adding to BusyBox Packages

BusyBox provides a great starting point for an embedded system, but there’s still a wealth of other packages for embedded systems. With LinuxLink, you can get many of these packages cross-compiled and ready to use for your processor. Some packages you might find useful are:

Strategies for Minimizing the Kernel

Once you have a root filesystem, you can remove support for functionality that you won’t need, and use a couple of methods to further reduce kernel size.

Compile Using the -Os Switch

This switch optimizes your code for size. The compiler might generate slower code, but it will be smaller. When you use -Os, the compiler forgoes optimizations (such as loop unrolling) that usually result in faster execution time, but produce more generated code. Like all optimizations, this will reduce the association between generated code and a particular line of source code, thus making debugging more difficult.

Use Linux-Tiny

Matt Mackall’s Linux-Tiny is a series of patches against the 2.6 mainline Linux kernel designed to reduce its memory and disk footprint, as well as to add optional features to help in working on small systems. Linux-Tiny patches have (mostly) been included in 2.6 distributions, so you will probably already have them included. If not, they are recommended.

Don’t Load Modules

If you compile modules directly into the kernel, you can remove support for them, plus you can save space in the RFS because insmod is not required (if that is the method used to load the module).

Consider Removing Other Features

The following features are often included in a kernel, but not generally useful in embedded development:

  • Video support
  • IDE support
  • NFS filesystem support
  • Debugging symbols
  • DHCP/BOOTP support – if you’re not using your network device frequently (or at all in production)
  • sysctl – if you don’t need to dynamically change your kernel configuration
  • Ext2/Ext3 support – if you’re not using this filesystem; most configurations include this by default

Other Strategies to Make Development Easier

Use RAM-based Filesystems for Temporary Data

Because most popular space-efficient filesystems are read-only, use RAM disks or tmpfs filesystems to store temporary data. By doing so, you won’t need to reserve parts of flash memory for such data. Usually, RAM is much less expensive and less scarce on a device, so it makes sense to use it instead of flash.

Use chroot to Test the Filesystem

This method allows you to test much faster. You can boot your board over NFS with a known good RFS, and chroot from that point to the filesystem you are testing. While not exact, you can get an idea whether the system is working before going through the time-consuming process of uploading and burning your image onto the board’s flash.


Also refer to the webinar Making Linux Work in a Tiny Flash. For this and other webinars, browse Embedded Development Webinars.