8.1 Selecting a Filesystem
Selecting a filesystem type for your root filesystem is a delicate process. The final decision is often a compromise between the filesystem's capabilities and the target's purpose. It is, for example, useless to choose a filesystem that provides persistent write storage, such as JFFS2, if the target never needs to permanently store any data. For such a target, a filesystem with no persistent storage, such as CRAMFS, is a much better choice.
Furthermore, you may want to consider using many filesystems for the same system. A system that needs read and write access to temporary files only, for instance, could have most of its root filesystem mounted on CRAMFS while having its /var/tmp directory mounted on TMPFS or a RAM disk, and its /tmp being a symbolic link to /var/tmp.
8.1.1 Characterizing Filesystems
To select the best filesystem or best combination of filesystems for a certain application, we need to have a minimum set of characteristics that can be used to compare filesystems. Table 8-1 summarizes the characteristics of the filesystems typically used in embedded Linux systems. For each filesystem type, these are the questions used to characterize it:
As I said above, a system needs a write-capable filesystem only if it needs to update data found on that filesystem. Similarly, a system requires persistent writes only if the updated data needs to be preserved upon reboots. A system that does not provide write capability does not require persistent storage or power down reliability, since none of the data it stores is ever modified.
Compression, on the other hand, is a desired characteristic of most filesystems, because it can lower the cost or increase the yield of storage in embedded systems. In some embedded systems, however, the increased cost, in CPU cycles, of compression and decompression may be undesirable.
While most filesystems are mounted directly from their storage device, filesystems mounted on RAM disks must first be extracted from their storage device into RAM before they can be mounted. Because the original filesystem image on the storage device is never accessed directly, most filesystem images created for use with RAM disks are usually compressed before being placed on a storage device. We will discuss the creation of such compressed filesystem images for use with RAM disks in Section 8.6.
Finally, no filesystem can be replaced while it is currently mounted. If a system's root filesystem is mounted from a JFFS2-formatted MTD partition, for example, the content of the MTD partition holding this filesystem cannot be overwritten with a new root filesystem. The only case where such a replacement is possible is when a filesystem is first copied into RAM before being mounted, as is the case of RAM disks. In that case, the actual media where the filesystem is stored is not accessed once the filesystem is mounted and can, therefore, be written over safely. As we shall see in Section 8.8, filesystems that are mounted in read and write mode from their original storage device can still be updated in various ways.
As you will have noticed, Linux supports a great deal many more filesystems than I cover in Table 8-1. But most of these filesystems are not well adapted for embedded Linux systems. In addition, although I mention JFFS in the table above, we will not discuss it below, since it has been largely superseded by JFFS2.
8.1.2 Guidelines for Filesystem Selection
Now that we have established a basic set of features for characterizing filesystems, let's review some general guidelines for selecting a filesystem configuration for your MTD-compatible storage device.
If your system has a very small amount of flash but a relatively generous amount of RAM, a RAM disk is probably your best choice, because the filesystem living on a RAM disk is compressed in its entirety on the storage device. The compression ratio on the storage device obtained by using a filesystem on a RAM disk is actually much higher than what can be achieved with a natively compressed filesystem, such as CRAMFS or JFFS2, because such filesystems must still keep their metadata, among other things, uncompressed. The RAM disk's edge in regards to on-storage-device compression are, however, offset by a higher RAM usage, since the entire filesystem lives uncompressed in RAM. Also, a RAM disk isn't appropriate if you need persistent data storage. Nevertheless, if your persistent data storage needs are limited, you can use a RAM disk for most of your root filesystem and mount only the data directories from a persistent filesystem such as JFFS2, as hinted to earlier. Also, using a RAM disk is often the easiest way to obtain a self-hosting target (which is a target that doesn't require a host to obtain its kernel or mount its root filesystem). x86 systems such as my DAQ module, for example, are the most likely to be shipped with RAM disks, since the prices for the components of such systems, including RAM, are low compared to other architectures. Note that although RAM can be cheap, it does consume more power than flash. Using large amounts of RAM for a RAM disk may, therefore, not be a viable option on some systems.
If your system has slightly more flash, or if you would rather save as much RAM as possible for the actual application running on your target and can spare a few extra CPU cycles for runtime decompression, CRAMFS is a very good candidate, granted the filesystem's limitations we discuss in Section 8.3 aren't a show stopper. Though CRAMFS's compression ratio is lower than a RAM disk, because of the reasons I outlined earlier, its capabilities are usually quite sufficient for most embedded applications that do not require persistent storage. As with RAM disks, nevertheless, you can mount the portion of the root filesystem that doesn't change at runtime on CRAMFS and the rest on a persistent filesystem such as JFFS2.
CRAMFS will not be a viable option, however, if your target must be able to be upgraded in the field. For example, the iPAQ Familiar distribution project switched from CRAMFS to JFFS2 precisely because users were unable to update their iPAQs without reprogramming their devices' flash. On the other hand, as another example, CRAMFS is a good candidate for my control module, because actual control procedures don't change very often in most industrial control applications.
If you need to be able to change any portion of your filesystem at any time, JFFS2 is the best candidate. Though JFFS2 doesn't achieve compression ratios as high as CRAMFS, since JFFS2 has to maintain space for garbage collection and metadata structures that allow filesystem writing, JFFS2 does provide power-down reliability and wear-leveling, which are very important characteristics for devices that rely on flash storage, as we discussed in Chapter 3. My user interface modules, for example, would be completely based on JFFS2 to ease updating and extend the lifetime of the devices' flash. At the time of this writing, however, JFFS2 is not a viable option if you are using a NAND flash device such as the DiskOnChip (DOC), as I explained in Chapter 3.
If you are using a DOC device and need to be able to change any portion of your filesystem at any time, using a disk filesystem over NFTL is your only available option at the time of this writing. Most embedded x86 devices that are equipped with DOC devices have to use this configuration. My DAQ module, for instance, can be configured to store some of its samples locally from time to time to a disk filesystem mounted over NFTL.
Whether you are using CRAMFS, JFFS2, or a disk filesystem over NFTL, you may want to consider mounting some directories on TMPFS. Though the content of this filesystem is not saved to persistent storage, it does allow you to use part of the RAM to store temporary files such as those typically found in your root filesystem's /tmp directory. If you are using a CRAMFS-based root filesystem, this allows you to have a directory, or a couple of directories, where you can both read and write files. If you are using either JFFS2 or a disk filesystem over NFTL, this allows you to avoid wearing out the storage device by manipulating data from RAM.
Obviously, these are guidelines only, and each system likely imposes additional limitations that you have to take into account. Nonetheless, these guidelines represent the typical design trade-offs when building embedded Linux systems and they should give you a basic idea of how to choose your final setup. In the rest of this chapter, I will discuss the actual setup of the filesystems we discussed earlier and further detail their use.
8.1.3 Filesystems for Disk Devices
If you are using a conventional disk as your main storage device for your system, such as one that connects to the system using an IDE or SCSI interface, I suggest you take a closer look at the various filesystems currently used in desktop and server Linux installations. In particular, you will find journalling filesystems such as ext3 and reiserfs to be quite well adapted to environments that need power down reliability such as embedded systems. Because the use of these filesystems is already amply covered elsewhere and embedded systems use them no differently from their workstation or server counterparts, I will not discuss their use any further. I refer you to classic texts on the use of Linux on servers and workstations for further details on such filesystems. IBM developerWorks' long series of articles by Daniel Robbins about Linux filesystems is of special interest here. In his series, Daniel provides in-depth discussion of the main journalling filesystems for Linux, including ext3, reiserfs, JFS, and XFS. See IBM's developerWorks site for Daniel's articles: http://www.ibm.com/developerworks/linux/. You may also be interested by Derek Vadala's Managing RAID on Linux (O'Reilly).