Building UBIFS Filesystem Images
A free embedded Linux course lecture on UBIFS, the flash filesystem built on top of UBI volumes
Reading time
Level
Storage Strategy
In the previous lecture of this free linux kernel development course we built and inspected UBI volumes — the wear-leveled logical storage layer that sits on top of raw NAND. UBI on its own is not a filesystem; it just hands you block devices. This lecture covers UBIFS, the filesystem designed specifically to run on top of a UBI volume, and walks through the two-stage process of turning a directory tree into a bootable UBIFS image.
Keywords covered in this lecture
What You Will Learn
Prerequisites
This lecture assumes you have already completed the previous lecture on the UBI flash volume manager in this free linux kernel development course, and are comfortable with:
Why UBIFS Sits on UBI, Not on MTD Directly
JFFS2 and YAFFS2, covered earlier in this chapter, both talk directly to an MTD device and implement their own wear leveling internally. UBIFS takes a different approach: it assumes wear leveling and bad-block handling are already solved by the UBI layer underneath it, and instead focuses entirely on being a fast, robust filesystem on top of that. This division of labor — UBI handles the flash, UBIFS handles the files — is what lets UBIFS avoid duplicating logic that JFFS2 and YAFFS2 each implement separately, and it is why UBIFS has become the default choice for new raw-NAND embedded Linux products.
The practical consequence is that UBIFS is always mounted against a UBI volume device node — never directly against an MTD device. If you skip the UBI attach/volume-creation steps from the previous lecture, there is nothing for UBIFS to mount.
What Makes UBIFS Different: Index-on-Flash and the Journal
Unlike JFFS2 and YAFFS2, which rebuild their entire index in RAM by scanning the flash at mount time, UBIFS stores its index information on the flash itself. That means mounting is fast even on very large volumes, because UBIFS doesn’t need to replay the whole filesystem to reconstruct where every file lives — it just reads the on-flash index directly. The trade-off is that attaching the underlying UBI volume beforehand can still take noticeable time on large NAND chips (addressed by UBI fastmap, covered in the previous lecture), so the overall “time to first mount” depends on both layers.
UBIFS also supports write-back caching, much like a conventional disk filesystem: writes land in memory first and are flushed to flash later, which makes write-heavy workloads dramatically faster than JFFS2’s write-through behaviour. The cost is the usual write-back tradeoff — data that hasn’t been flushed yet is lost on a power failure. UBIFS mitigates this with a journal, a reserved area of flash (typically 4 MiB or more) used to recover the filesystem quickly after an unclean power-down, replaying only the recent journal entries instead of scanning the whole volume. Because the journal consumes a meaningful, fixed chunk of space, UBIFS is not a good fit for very small flash devices — on a 4 MB partition, the journal alone could be a large fraction of total capacity.
UBIFS Layered on Top of UBI and MTD
Stage 1: Building the UBIFS Image with mkfs.ubifs
Creating a UBIFS-backed rootfs is a two-stage process. The first stage packs an ordinary directory tree into a UBIFS image file using mkfs.ubifs. Three parameters matter most:
| Flag | Meaning |
|---|---|
-r | Source directory to pack into the image |
-m | Flash page size (minimum I/O unit) |
-e | LEB size — remember this is one page smaller than the PEB, as covered in the UBI lecture |
-c | Maximum number of LEBs the resulting volume may grow to |
-o | Output image filename |
For a target volume where the physical erase block is 128 KiB and the page size is 2048 bytes, the LEB size works out to 126 KiB (128 KiB minus one 2 KiB page), and a 32 MiB volume needs 256 such LEBs:
# Build a UBIFS image from the ./rootfs directory
mkfs.ubifs -r rootfs -m 2048 -e 126KiB -c 256 -o rootfs.ubi
Stage 2: Embedding the Image with ubinize
The output of mkfs.ubifs is a raw UBIFS image — it is not yet something you can flash directly, because it still needs to be embedded into a proper UBI volume structure (with the UBI headers UBI itself expects). That is the job of ubinize, which is driven by a small configuration file describing each volume to create. Here is an original example configuration, ep-ubinize.cfg, describing a single rootfs volume:
; ep-ubinize.cfg - describes one UBI volume for ubinize
[ep_rootfs_volume]
mode=ubi
image=rootfs.ubi
vol_id=0
vol_name=rootfs
vol_type=dynamic
vol_alignment=1
vol_flags=autoresize
Run ubinize against this configuration to produce a flashable UBI image:
# Embed rootfs.ubi into a proper UBI image ready to flash
ubinize -o ubi-image.img -m 2048 -p 128KiB -s 2048 ep-ubinize.cfg
The resulting ubi-image.img is what you actually write to the MTD partition (with ubiformat‘s image-loading mode, or via your bootloader’s flashing tool), rather than the intermediate rootfs.ubi file produced in stage one.
Mounting a UBIFS Volume
Once the UBI volumes exist — whether created fresh with ubimkvol and then formatted with UBIFS at runtime, or pre-built with mkfs.ubifs/ubinize and flashed at manufacturing time — mounting works like any other filesystem, using either the per-volume device node or the device-plus-name form:
# Mount using the UBI volume device node
mount -t ubifs /dev/ubi0_0 /mnt
# Equivalent: mount using device name + volume name
mount -t ubifs ubi0:rootfs /mnt
Using fsync and fdatasync Safely
Because UBIFS caches writes in memory before flushing them to flash, an application that needs a guarantee that specific data has actually survived a power loss must explicitly force that flush. Here is a small original example, ep_ubifs_sync_demo.c, that demonstrates writing a critical record and forcing it to flash before continuing:
/* ep_ubifs_sync_demo.c - force critical data to flash on UBIFS */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
if (argc != 2) {
fprintf(stderr, "usage: %s /mnt/config.dat\n", argv[0]);
return 1;
}
const char *record = "ep_config_version=7\n";
int fd = open(argv[1], O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (fd < 0) {
perror("open");
return 1;
}
if (write(fd, record, strlen(record)) < 0) {
perror("write");
close(fd);
return 1;
}
/* Force the write-back cache to flush this file's data to flash
* before we report success -- without this, a power loss right
* after write() could lose the record even though write() itself
* returned success. */
if (fdatasync(fd) < 0) {
perror("fdatasync");
close(fd);
return 1;
}
printf("ep_ubifs_sync_demo: record committed to flash\n");
close(fd);
return 0;
}
$ gcc -o ep_ubifs_sync_demo ep_ubifs_sync_demo.c
$ ./ep_ubifs_sync_demo /mnt/config.dat
ep_ubifs_sync_demo: record committed to flash
fdatasync(2) flushes file data (and only the metadata needed to retrieve it) without necessarily flushing every metadata attribute, which is usually enough and cheaper than a full fsync(2). Reserve fsync for cases where metadata like timestamps or file size also has to be crash-consistent.
JFFS2 vs YAFFS2 vs UBIFS at a Glance
| Feature | JFFS2 | YAFFS2 | UBIFS |
|---|---|---|---|
| Sits on top of | MTD directly | MTD directly | UBI volume |
| Wear leveling | Built-in, per-filesystem | Built-in, per-filesystem | Delegated to UBI, shared across volumes |
| Mount-time index rebuild | Full scan | Full scan | Index stored on flash — fast |
| Write caching | Write-through | Write-through | Write-back with journal |
| Good fit for very small flash | Yes | Yes | No — journal overhead |
Real-World Use Cases
Common Mistakes and Troubleshooting
| Symptom | Likely Cause | Fix |
|---|---|---|
| mkfs.ubifs fails with a size mismatch | -e value doesn’t account for the one-page LEB overhead | Recompute LEB size as PEB size minus one page, not the raw PEB size |
| Mount fails with “no UBI device” errors | Trying to mount UBIFS directly against an MTD device | Attach and create a UBI volume first; UBIFS never mounts on raw MTD |
| Data lost after an unexpected power cut | Relying on write() alone without fsync/fdatasync for critical data | Call fdatasync (or fsync where metadata matters) at crucial commit points |
| Filesystem fails to fit on a small flash chip | Journal overhead (4 MiB+) consuming too much of a small partition | Use JFFS2 instead on very small flash devices |
Best Practices
Performance consideration: write-back caching is UBIFS’s biggest performance win over JFFS2, but only for workloads that don’t need every write durable immediately — force syncs sparingly, only at genuine commit points. Security consideration: like UBI, UBIFS provides no encryption on its own; use dm-crypt or a similar layer beneath UBI if confidentiality is required.
Summary and Key Takeaways
Conclusion
UBIFS is the natural conclusion of the UBI story from the previous lecture: once wear leveling and bad-block handling are solved once, generically, UBIFS is free to focus purely on being a fast, journaled filesystem with on-flash indexing and write-back caching. Together, UBI and UBIFS form the storage stack behind the majority of modern raw-NAND embedded Linux products, and completing both lectures gives you everything needed to design, build, and troubleshoot that stack on real hardware — rounding out the storage strategy chapter of this free linux kernel development course.
Frequently Asked Questions
Can UBIFS be mounted directly on an MTD device?
No. UBIFS is designed to run on top of a UBI volume, which itself sits on top of MTD. There is no supported path to mount UBIFS directly against a raw MTD device.
Why does UBIFS mount faster than JFFS2 on large flash?
UBIFS stores its index information on the flash itself, so mounting reads that index directly instead of scanning the entire volume, unlike JFFS2 and YAFFS2 which rebuild their index by scanning at mount time.
What is the UBIFS journal for?
The journal is a reserved area of flash used for fast recovery after an unclean power-down. Instead of scanning the whole filesystem, UBIFS can replay just the recent journal entries to restore a consistent state.
Is UBIFS suitable for very small flash chips?
Generally no. The journal typically consumes 4 MiB or more, which can be a large fraction of a very small device’s total capacity — JFFS2 is usually a better fit there.
What is the difference between fsync and fdatasync?
fsync flushes both file data and all metadata to flash. fdatasync flushes the data and only the metadata strictly needed to retrieve it (like file size), which is often sufficient and cheaper.
Why does building a UBIFS image require two separate tools?
mkfs.ubifs packs a directory tree into a raw UBIFS image, but that image still needs to be wrapped with the UBI headers a UBI volume expects — that wrapping step is what ubinize does, producing a final image ready to flash.
Does write-back caching in UBIFS risk data loss?
Yes, for data that hasn’t been flushed yet at the moment of an unexpected power loss. Applications that need specific writes to be crash-durable should call fsync or fdatasync at the appropriate commit points.
Continue this free embedded Linux course
You’ve now covered the full raw-flash storage stack: MTD, JFFS2, YAFFS2, UBI, and UBIFS.
Explore More Free Lectures Back to Course Index
2 Comments