In this article, we explain how flash file systems achieve native support for raw flash devices. We compare flash file systems to a common alternative, which is the combination of a block device file system (e.g., FAT) with a flash translation layer (FTL), and we make the case that the native approach is a better overall solution. Finally, we show how TSFS enables seamless transition between raw flash and other common embedded storage technologies like SD card and eMMC.
Flash vs Block Device
To make sense of flash file systems, we first need to understand how flash memory behaves and how this behaviour differs from the block device abstraction that most non-flash file systems expect.
The block device abstraction is rather simple, so let’s start there. A block device is divided in fixed-size blocks that can be read or modified, following any arbitrary access pattern, with the only limitation that partial block access is not possible. An update smaller than a block, or that does not start or end on a block boundary, can still be achieved using a read-modify-write sequence (see Figure 1).
Hard disk drives, floppy disks, solid-state drives, eMMC, SD card and USB memory sticks, are all block devices. They rely on different technologies and have contrasting specifications, but they all provide the same access abstraction to the overlying file system.
Raw flash devices (NOR or NAND) are quite different, starting with their internal structure. Flash memory is divided in erase blocks which, in turn, are divided in pages. Only whole pages can be programmed (written to), akin to block devices. Before a page can be reprogrammed, though, the corresponding block must be erased (effectively setting all bits to ‘1’). This additional constraint might not look like much on the surface. One apparent workaround is some variation of the read-modify-write sequence used earlier (see Figure 2):
- load a whole erase block in RAM
- modify the content
- erase the block
- write the modified content back to its original location.
Unfortunately, this naive approach suffers numerous flaws:
- RAM usage: erase blocks are commonly larger than 64KiB — they can be as large as a few megabytes on higher density NAND — which is a lot of RAM for the average MCU. By contrast, the usual block device implementations have much smaller blocks (say between 512 and 4096 bytes). Some NOR flash devices support smaller block erase, but at the cost of degraded performances.
- Performance: erasing a block takes quite a lot of time (a second or so for NOR flash), so one block erase per update will yield extremely poor write performances.
- Wear: a block can only be erased a limited number of times before it ceases to function properly. One block erase per update will drastically shorten the device lifetime. Also, some blocks could be repeatedly erased while others remain untouched (depending on file-level access patterns) producing uneven wear across flash blocks, further shortening the device lifetime as over-erased blocks prematurely fail.
- Fail Safety: a power failure (or any other untimely interruption) occurring between the block erase and the final write will result in the loss of the entire block’s content.
This is essentially why a file system designed around the block device abstraction may not (and will likely not) work on a raw flash device.
The Flash File System
One possible approach to fulfilling flash access requirements is through a dedicated flash file system. From the application standpoint, a flash file system provides the same file and directory abstraction as any other file system. However, the flash file system provides native support for raw flash memory. This is usually based on some copy-on-write (COW) update algorithm, where a page is copied to an alternate location upon modification, rather than written back to its original location (see Figure 3).
Because it directly follows from fundamental flash characteristics, copy-on-write really is a defining component of flash file systems. By contrast, block device file systems usually rely on more straightforward in-place updates, although nothing prevents copy-on-write from being used on a block device, something that we will circle back to when we discuss TSFS.
Copy-on-write comes with its own share of complexity and implementation challenges. First, each page location must somehow be tracked as it changes across successive updates. Also, some form of garbage collection is needed in order to clear older blocks as more update blocks are needed (see Making Sense of Flash File System Performances). There are multiple ways to achieve these tasks and multiple implementations have successfully been used in the past. TSFS, YAFFS and LitteFS are all examples of flash file systems based on copy-on-write, although with very different specific implementations.
The copy-on-write approach solves all the previous issues with in-place updates:
- RAM usage: only the modified page must be loaded in RAM (as opposed to the much larger erase block).
- Performance: erasing is only needed when the current update block is filled (and not for every single update).
- Wear: Less erasing also means less wear and thus increased device life. Also out-of-place updates provide an opportunity for wear-levelling.
- Fail Safety: old data is not deleted before new data is written, providing an opportunity for fail-safe behaviour.
An Alternative: the Flash Translation Layer (FTL)
Another common way of coping with flash access requirements is through a Flash Translation Layer (FTL). The FTL is a software block device emulation layer inserted between the raw flash device and a regular block device file system like FAT (see Figure 4). It provides the requested block device abstraction to the overlying file system. Also, it usually implements some form of wear-levelling, and possibly other flash-specific services.
In our opinion, the FTL is truly useful in two narrow cases:
- for porting legacy applications to raw flash without changing the application code
- for USB storage devices based on the MSC class where the host file system (generally FAT) expects a block device.
Otherwise, a dedicated flash file system will likely offer more performance and lower RAM usage, mostly thanks to the possible deduplication of data structures and I/Os, across the otherwise distinct file system and FTL layers.
In practice, of course, various file systems will produce various performances, independent of the chosen architecture. A thoroughly implemented FTL will outperform a poorly implemented flash file system. But harnessing that natural performance advantage of the native solution over the emulation approach is something that you should expect from a high-performance flash file system.
TREEspan File System (TSFS)
Remember what we said earlier about using copy-on-write on a block device? Well, TSFS does exactly that to achieve both raw flash and block device support with a single codebase. Besides, using copy-on-write regardless of the underlying storage device provides the foundation of TSFS’s transactional behaviour (see Fail-Safe Storage with the TREEspan File System). There are other benefits to the specific copy-on-write algorithms at the core of TSFS, in particular when it comes to managed flash devices like SD card and eMMC, a topic that we will cover in a future article.
Conclusion
Flash file systems are a vast and complex topic, and we have admittedly only scratched the surface here. We do hope that you now have a better understanding of flash file systems and how they compare to possible alternatives. If you need expert advice on embedded file systems or file system integration, or if you want to know more about our embedded transactional flash file system, TSFS, please feel free to contact us.