After choosing a filesystem philosophy and understanding discard, it’s time to get practical: how do you actually put ext4 onto a managed flash device, and how do you build an ext4 filesystem image offline for a board’s first flash during manufacturing? This lesson, part of our free linux kernel development course and free embedded systems course series, walks through both paths with original, board-agnostic examples.
What You Will Learn
- Why ext4 remains a strong default root filesystem choice for eMMC-backed embedded Linux devices
- How to format and mount ext4 at runtime on a target board
- How to build an ext4 filesystem image offline from a rootfs directory, for flashing during manufacturing
- Why the offline-image path needs a two-step upgrade through
tune2fs, and what that step actually does - How to verify image integrity before shipping it
Prerequisites
- This is the third lesson in a three-part storage sequence — read Flash Filesystem Selection Guide and Discard and TRIM Support first for full context
- A populated root filesystem directory tree (from earlier chapters on building a root filesystem) ready to convert into an image
e2fsprogsandgenext2fsinstalled on your build host
Why Ext4 Is Still A Strong Default
ext4 has been the default filesystem on Linux desktops and servers for well over a decade, and that same maturity carries real weight in embedded products: it is heavily tested, its recovery tools are reliable, and virtually every board support package and bootloader already knows how to boot from it. It provides a metadata journal, which — as covered in the discard lesson — is exactly what gives fast, safe recovery after the kind of unclean shutdown embedded products experience regularly. It’s also the filesystem Android has standardized on for eMMC-backed internal storage, which means the kernel code path is extremely well exercised in the wild.
Path A: Formatting Ext4 At Runtime
If your board already boots into some minimal environment — an initramfs, a recovery shell, or a previous OS install — and you just need to prepare a data or root partition, formatting at runtime is the simplest route:
# mkfs.ext4 /dev/mmcblk1p2
# mount -t ext4 -o discard /dev/mmcblk1p2 /mnt/rootfs
Notice the -o discard mount option from the previous lesson — it’s worth enabling here by default if your Flashbench measurement and discard_granularity check both confirmed support, since a freshly-formatted partition is exactly when stale-block bookkeeping starts accumulating.
Path B: Building An Ext4 Image Offline
For manufacturing, you usually don’t want to boot the target board just to format its storage — you want to build a complete filesystem image on your build host and flash it directly. The standard tool for turning a directory tree into a filesystem image without root privileges or a loopback mount is genext2fs.
Step 1 — Build the base image with genext2fs
Say we have a populated root filesystem tree at ./ep-rootfs from earlier bring-up work. We build an image sized in 1 KiB blocks, specifying the block size with -B and the total block count with -b:
$ genext2fs -B 1024 -b 65536 -d ep-rootfs ep-rootfs.img
That produces a 64 MiB image (65536 blocks × 1024 bytes). If you need specific file ownership and permissions baked into the image without running as root, genext2fs also accepts a device table via -D, letting you specify UID/GID and permission bits for each path in a simple text file — useful for setting the correct owner on, say, a setuid helper binary without needing root on the build host.
Step 2 — Why You Still Need tune2fs
Here’s the detail that trips people up the first time: despite the filename you gave it, genext2fs only ever writes an image in the older ext2 on-disk format. It has no knowledge of the journal, extents, or the other features that make ext4 what it is. To get a genuine ext4 image, you upgrade the freshly-built ext2 image in place with tune2fs:
$ tune2fs -j -J size=4 -O filetype,extents,uninit_bg,dir_index ep-rootfs.img
Breaking that command down:
| Flag | Effect |
|---|---|
-j | Adds an ext3-style journal to the filesystem |
-J size=4 | Sizes the journal at 4 MiB — scale this up for larger, write-heavy images |
-O filetype | Enables directory entries storing file type, avoiding a stat() per lookup |
-O extents | Switches block mapping to extent-based, ext4’s more efficient allocation scheme |
-O uninit_bg | Speeds up fsck by skipping unused block groups |
-O dir_index | Adds hashed directory indexing for faster lookups in large directories |
Step 3 — Verify Before Flashing
Always run a filesystem check on the image before it goes anywhere near production hardware — catching a corrupt image on the build host is a five-second fix; catching it after ten thousand units have shipped is not:
$ e2fsck -pDf ep-rootfs.img
Pass 1: Checking inodes, blocks, and sizes
Pass 2: Checking directory structure
Pass 3: Checking directory connectivity
Pass 4: Checking reference counts
Pass 5: Checking group summary information
ep-rootfs.img: 1842/16384 files (0.2% non-contiguous), 41102/65536 blocks
The -p flag auto-repairs safe issues without prompting, -D optimizes directories, and -f forces a check even if the filesystem appears clean — appropriate for a freshly-built image that’s never been mounted.
Flashing The Image To The Target
Once verified, the image can be written directly to the target partition using dd from a manufacturing jig, or embedded in a bootloader-driven flashing tool such as fastboot, depending on your platform:
> sudo dd if=ep-rootfs.img of=/dev/mmcblk1p2 bs=4M conv=fsync status=progress
Common Mistakes And Troubleshooting
- Forgetting the tune2fs step. Flashing a raw genext2fs output and mounting it with
-t ext4often still works, because the kernel’s ext4 driver can mount plain ext2 images — but you silently lose the journal and crash resilience that was the whole point of choosing ext4. - Undersizing the image. genext2fs doesn’t grow the image automatically as you add files; leave headroom for OTA updates, logs, and temp files, not just the exact size of the current rootfs tree.
- Skipping e2fsck before shipping. A subtly corrupt image can still mount successfully and pass casual smoke testing, only to fail once the journal is exercised in the field.
- Mixing block sizes between genext2fs and the target’s page size. Match the
-Bvalue to your Flashbench-measured page size where practical for cleaner alignment.
Best Practices
- Script the genext2fs → tune2fs → e2fsck sequence as a single reproducible build step, not a manual one-off.
- Version-control the device table used with
genext2fs -D, since it encodes security-relevant permission decisions. - Keep journal size proportional to expected write volume — a bigger journal absorbs more in-flight writes safely during a power loss, at the cost of a little image size.
Performance Considerations
Journal size is the main performance/safety trade-off unique to this workflow: a larger journal (-J size=N) tolerates more in-flight write activity before a crash without corruption, but consumes proportionally more flash space and takes marginally longer to replay on boot after an unclean shutdown.
Summary / Key Takeaways
- ext4 remains a mature, well-supported default for eMMC-backed embedded root filesystems.
- Runtime formatting with
mkfs.ext4suits boards that already boot into some minimal environment. - Offline image building goes through
genext2fs(ext2 base image) →tune2fs(adds journal and ext4 features) →e2fsck(verification) before flashing. - Always verify the image with
e2fsckbefore it reaches production hardware.
Conclusion
This closes out our three-lesson arc on managed flash: choosing a filesystem philosophy with Flashbench, keeping the flash controller informed with discard/TRIM, and now actually building and deploying an ext4 image, whether at runtime or as part of a manufacturing flash process. Together these give you a defensible, measured storage strategy rather than a guess copied from someone else’s board support package.
FAQ
Why does genext2fs only produce an ext2 image?
genext2fs was written before ext4 existed and has never been updated to natively write ext4 features like journaling or extents — it only generates the older ext2 layout.
Do I really need tune2fs, or can I just mount the genext2fs output as ext4?
You can mount it, since ext4 is backward compatible with ext2 images, but you’ll be running without a journal or extents until you run tune2fs to add those features.
How big should the journal be?
A few megabytes is enough for light workloads; scale it up for boards doing frequent writes, such as continuous logging or database-backed applications.
Is e2fsck necessary if the image mounted fine in testing?
Yes — a filesystem can mount and pass casual use while still containing latent corruption that only surfaces once the journal is exercised under real write load.
Can I use this same workflow for a data partition instead of the root filesystem?
Yes, the genext2fs/tune2fs/e2fsck sequence works identically for any ext4 partition image, not just the root filesystem.
Should I always enable discard when mounting the resulting ext4 image?
Only if you’ve confirmed the target device supports it via discard_granularity, as covered in the previous lesson on discard and TRIM.
Master The Full Storage Stack
This lesson wraps up our managed flash storage arc — keep going with the rest of this free embedded Linux course.
Browse the Free Course Next Chapter
2 Comments