mirror of
https://github.com/apache/nuttx.git
synced 2026-08-28 04:40:41 +00:00
FIOC_REFORMAT, FIOC_OPTIMIZE, FIOC_INTEGRITY and FIOC_DUMP act on a volume,
not on any one file, but the only route into a file system has been the
per-file ioctl method. A caller therefore has to open an unrelated file
just to name the volume it means.
For nxffs that is not merely awkward, it is a dead end. nxffs_ioctl()
refuses FIOC_REFORMAT while any file on the volume is open:
if (volume->ofiles)
{
ferr("ERROR: Open files\n");
ret = -EBUSY;
and every open file is on that list (nxffs_open.c). The descriptor used to
issue the command is itself such a file, so the check can never pass and
FIOC_REFORMAT is unreachable through the only interface that exposes it.
Add an optional ioctldir method to struct mountpt_operations, reached by
issuing the ioctl on a descriptor for the mountpoint directory:
fd = open("/mnt/nxffs", O_RDONLY | O_DIRECTORY);
ioctl(fd, FIOC_REFORMAT, 0);
It takes the same (mountpt, dir) pair as opendir/readdir/rewinddir, so it
reads as a member of the directory-operations family; the file system
recovers the volume from the mountpoint inode and may ignore dir. The
member is placed at the end of the structure rather than beside the other
directory operations on purpose: every file system initialises
mountpt_operations positionally, so a member inserted mid-structure would
force all of them to add a slot for a method they do not implement.
Appending keeps the change to one file system.
dir_ioctl() gives that method the first chance at every command when the
directory belongs to a mounted volume and the file system provides one, and
falls back to its own handling of FIOC_FILEPATH and BIOC_FLUSH when the file
system answers -ENOTTY. Trying the file system first is what lets a file
system override a command the VFS would otherwise answer generically; the
-ENOTTY fallback is what keeps the generic answers available to everyone
else. A file system that leaves the method NULL is unaffected: the VFS
answers exactly as before.
The existing per-file method could not simply be reused for this. It takes
a struct file, and every implementation that has an ioctl -- fat, romfs,
tmpfs, spiffs among them -- asserts on filep->f_priv and dereferences it,
so handing it a directory descriptor with no open file behind it would
fault. Making the entry point separate keeps that contract intact and
makes support explicit rather than assumed.
nxffs implements it, which is what makes its FIOC_REFORMAT reachable. The
per-file path is left in place and both share one implementation, so
nothing that works today stops working. spiffs, which has the same shape
of volume commands, can follow.
Measured on sim:nxffs, with one file written to the volume and then the
same sequence of ioctls issued on a file descriptor, on a descriptor for the
mountpoint directory, and on a descriptor for a pseudo file system directory.
Before:
FIOC_REFORMAT via file fd: ret=-1 errno=16 (EBUSY, as expected)
FIOC_REFORMAT via dir fd: ret=-1 errno=25
FIOC_FILEPATH via dir fd: ret=0 "/mnt/nxffs//"
BIOC_FLUSH via dir fd: ret=0
bogus cmd via dir fd: ret=-1 errno=25
FIOC_FILEPATH via /dev fd: ret=0 "/dev//"
bogus cmd via /dev fd: ret=-1 errno=25
name still in the raw MTD image afterwards: yes
After:
FIOC_REFORMAT via file fd: ret=-1 errno=16 (EBUSY, as expected)
FIOC_REFORMAT via dir fd: ret=0
FIOC_FILEPATH via dir fd: ret=0 "/mnt/nxffs//"
BIOC_FLUSH via dir fd: ret=0
bogus cmd via dir fd: ret=-1 errno=25
FIOC_FILEPATH via /dev fd: ret=0 "/dev//"
bogus cmd via /dev fd: ret=-1 errno=25
name still in the raw MTD image afterwards: no
Only the FIOC_REFORMAT line on the directory descriptor changes, and the raw
MTD image confirms the volume really was erased. FIOC_FILEPATH and
BIOC_FLUSH on a directory still answer even though nxffs is now consulted
ahead of them, an unrecognised command is still refused rather than
forwarded blindly, and a directory in the pseudo file system, which has no
ioctldir at all, is untouched.
Assisted-by: Claude Code:claude-opus-4-8
Assisted-by: Claude Code:claude-fable-5
Assisted-by: Claude Code:claude-opus-5
Signed-off-by: Marco Casaroli <marco.casaroli@gmail.com>
205 lines
No EOL
8.9 KiB
ReStructuredText
205 lines
No EOL
8.9 KiB
ReStructuredText
=====
|
|
NXFFS
|
|
=====
|
|
|
|
This page contains information about the implementation of the NuttX
|
|
wear-leveling FLASH file system, NXFFS.
|
|
|
|
General NXFFS organization
|
|
==========================
|
|
|
|
The following example assumes 4 logical blocks per FLASH erase block. The
|
|
actual relationship is determined by the FLASH geometry reported by the MTD
|
|
driver::
|
|
|
|
ERASE LOGICAL Inodes begin with a inode header. inode may
|
|
BLOCK BLOCK CONTENTS be marked as "deleted," pending re-packing.
|
|
n 4*n --+--------------+
|
|
|BBBBBBBBBBBBBB| Logic block header
|
|
|IIIIIIIIIIIIII| Inodes begin with a inode header
|
|
|DDDDDDDDDDDDDD| Data block containing inode data block
|
|
| (Inode Data) |
|
|
4*n+1 --+--------------+
|
|
|BBBBBBBBBBBBBB| Logic block header
|
|
|DDDDDDDDDDDDDD| Inodes may consist of multiple data blocks
|
|
| (Inode Data) |
|
|
|IIIIIIIIIIIIII| Next inode header
|
|
| | Possibly a few unused bytes at the end of a block
|
|
4*n+2 --+--------------+
|
|
|BBBBBBBBBBBBBB| Logic block header
|
|
|DDDDDDDDDDDDDD|
|
|
| (Inode Data) |
|
|
4*n+3 --+--------------+
|
|
|BBBBBBBBBBBBBB| Logic block header
|
|
|IIIIIIIIIIIIII| Next inode header
|
|
|DDDDDDDDDDDDDD|
|
|
| (Inode Data) |
|
|
n+1 4*(n+1) --+--------------+
|
|
|BBBBBBBBBBBBBB| Logic block header
|
|
| | All FLASH is unused after the end of the final
|
|
| | inode.
|
|
--+--------------+
|
|
|
|
General operation
|
|
=================
|
|
|
|
Inodes are written starting at the beginning of FLASH. As inodes are
|
|
deleted, they are marked as deleted but not removed. As new inodes are
|
|
written, allocations proceed to toward the end of the FLASH -- thus,
|
|
supporting wear leveling by using all FLASH blocks equally.
|
|
|
|
When the FLASH becomes full (no more space at the end of the FLASH), a
|
|
re-packing operation must be performed: All inodes marked deleted are
|
|
finally removed and the remaining inodes are packed at the beginning of
|
|
the FLASH. Allocations then continue at the freed FLASH memory at the
|
|
end of the FLASH.
|
|
|
|
Headers
|
|
=======
|
|
|
|
``BLOCK HEADER``
|
|
The block header is used to determine if the block has every been
|
|
formatted and also indicates bad blocks which should never be used.
|
|
|
|
``INODE HEADER``
|
|
Each inode begins with an inode header that contains, among other things,
|
|
the name of the inode, the offset to the first data block, and the
|
|
length of the inode data.
|
|
|
|
At present, the only kind of inode support is a file. So for now, the
|
|
term file and inode are interchangeable.
|
|
|
|
``INODE DATA HEADER``
|
|
Inode data is enclosed in a data header. For a given inode, there
|
|
is at most one inode data block per logical block. If the inode data
|
|
spans more than one logical block, then the inode data may be enclosed
|
|
in multiple data blocks, one per logical block.
|
|
|
|
NXFFS Limitations
|
|
=================
|
|
|
|
This implementation is very simple as, as a result, has several limitations
|
|
that you should be aware before opting to use NXFFS:
|
|
|
|
1. Since the files are contiguous in FLASH and since allocations always
|
|
proceed toward the end of the FLASH, there can only be one file opened
|
|
for writing at a time. Multiple files may be opened for reading.
|
|
|
|
2. Files may not be increased in size after they have been closed. The
|
|
O_APPEND open flag is not supported.
|
|
|
|
3. Files are always written sequential. Seeking within a file opened for
|
|
writing will not work.
|
|
|
|
4. There are no directories, however, '/' may be used within a file name
|
|
string providing some illusion of directories.
|
|
|
|
5. Files may be opened for reading or for writing, but not both: The O_RDWR
|
|
open flag is not supported.
|
|
|
|
6. The re-packing process occurs only during a write when the free FLASH
|
|
memory at the end of the FLASH is exhausted. Thus, occasionally, file
|
|
writing may take a long time.
|
|
|
|
7. Another limitation is that there can be only a single NXFFS volume
|
|
mounted at any time. This has to do with the fact that we bind to
|
|
an MTD driver (instead of a block driver) and bypass all of the normal
|
|
mount operations.
|
|
|
|
Multiple Writers
|
|
================
|
|
|
|
As mentioned in the limitations above, there can be only one file opened
|
|
for writing at a time. If one thread has a file opened for writing and
|
|
another thread attempts to open a file for writing, then that second
|
|
thread will be blocked and will have to wait for the first thread to
|
|
close the file.
|
|
|
|
Such behavior may or may not be a problem for your application, depending
|
|
(1) how long the first thread keeps the file open for writing and (2) how
|
|
critical the behavior of the second thread is. Note that writing to FLASH
|
|
can always trigger a major FLASH reorganization and, hence, there is no
|
|
way to guarantee the first condition: The first thread may have the file
|
|
open for a long time even if it only intends to write a small amount.
|
|
|
|
Also note that a deadlock condition would occur if the SAME thread
|
|
attempted to open two files for writing. The thread would would be
|
|
blocked waiting for itself to close the first file.
|
|
|
|
ioctls
|
|
======
|
|
|
|
The file system supports to ioctls:
|
|
|
|
``FIOC_REFORMAT``
|
|
Will force the flash to be erased and a fresh, empty NXFFS file system to
|
|
be written on it.
|
|
|
|
``FIOC_OPTIMIZE``
|
|
Will force immediate repacking of the file system. This will avoid the
|
|
delays to repack the file system in the emergency case when all of the
|
|
FLASH memory has been used. Instead, you can defer the garbage collection
|
|
to time when the system is not busy. Calling this function on a thrashing
|
|
file system will increase the amount of wear on the FLASH if you use this
|
|
frequently!
|
|
|
|
Both act on the volume rather than on any one file, so issue them on a
|
|
descriptor for the mountpoint directory::
|
|
|
|
int fd = open("/mnt/nxffs", O_RDONLY | O_DIRECTORY);
|
|
ioctl(fd, FIOC_REFORMAT, 0);
|
|
close(fd);
|
|
|
|
``FIOC_REFORMAT`` in particular is only reachable this way. It refuses to
|
|
run while any file on the volume is open, so issuing it on a descriptor for
|
|
a file *inside* the volume can never succeed -- that descriptor is itself
|
|
such a file, and the request returns ``-EBUSY``.
|
|
|
|
Things to Do
|
|
============
|
|
|
|
- The statfs() implementation is minimal. It should have some calculation
|
|
of the f_bfree, f_bavail, f_files, f_ffree return values.
|
|
- There are too many allocs and frees. More structures may need to be
|
|
pre-allocated.
|
|
- The file name is always extracted and held in allocated, variable-length
|
|
memory. The file name is not used during reading and eliminating the
|
|
file name in the entry structure would improve performance.
|
|
- There is a big inefficiency in reading. On each read, the logic searches
|
|
for the read position from the beginning of the file each time. This
|
|
may be necessary whenever an lseek() is done, but not in general. Read
|
|
performance could be improved by keeping FLASH offset and read positional
|
|
information in the read open file structure.
|
|
- Fault tolerance must be improved. We need to be absolutely certain that
|
|
any FLASH errors do not cause the file system to behavior incorrectly.
|
|
- Wear leveling might be improved (?). Files are re-packed at the front
|
|
of FLASH as part of the clean-up operation. However, that means the files
|
|
that are not modified often become fixed in place at the beginning of
|
|
FLASH. This reduces the size of the pool moving files at the end of the
|
|
FLASH. As the file system becomes more filled with fixed files at the
|
|
front of the device, the level of wear on the blocks at the end of the
|
|
FLASH increases.
|
|
- When the time comes to reorganization the FLASH, the system may be
|
|
unavailable for a long time. That is a bad behavior. What is needed,
|
|
I think, is a garbage collection task that runs periodically so that
|
|
when the big reorganization event occurs, most of the work is already
|
|
done. That garbage collection should search for valid blocks that no
|
|
longer contain valid data. It should pre-erase them, put them in
|
|
a good but empty state... all ready for file system re-organization.
|
|
NOTE: There is the FIOC_OPTIMIZE IOCTL command that can be used by an
|
|
application for force garbage collection when the system is not busy.
|
|
If used judiciously by the application, this can eliminate the problem.
|
|
- And worse, when NXFSS reorganization the FLASH a power cycle can
|
|
damage the file system content if it happens at the wrong time.
|
|
- The current design does not permit re-opening of files for write access
|
|
unless the file is truncated to zero length. This effectively prohibits
|
|
implementation of a proper truncate() method which should alter the
|
|
size of a previously written file. There is some fragmentary logic in
|
|
place but even this is conditioned out with __NO_TRUNCATE_SUPPORT__.
|
|
|
|
|
|
Further reference
|
|
=================
|
|
|
|
.. toctree::
|
|
how_nxffs_works.rst |