Added block_size search in lfs_mount, moved block_size/count into lfs_t

This adds two new configuration options: erase_size and erase_count,
allowing block_size and block_count to be loaded from the superblock
during mount. For backwards compability these default to block_size and
block_count if zero.

---

Unfortunately this is a bit easier said than done. littlefs keeps its
superblock in the metadata pair located at blocks {0,1}, which is also
where the root directory lives (keep in mind small littlefs images may
only have 2 blocks in total). If we mutate blocks {0,1}, we have to
erase before programming, which means it's possible to have the only
superblock in block 1. This presents a puzzle because how do you find
block 1 if you don't know the size of a block?

One solution presented here is to search for block 1 by trying different
sizes until we find a superblock. This isn't great but there are some
properties of this search that help:

1. If we do find a superblock, the search will never take longer than a
   mount with a known block_size. This is because we stop at block 1,
   searching at most O(block_size) bytes, and metadata fetch is already
   a O(block_size) operation.

   This means the concern is limited to how long it takes to fail when
   littlefs is not present on the disk.

2. We can assume the on-disk block_size is probably a factor of the
   total size of the disk. After a bit of digging into the math, this
   apparently reduces the runtime to the divisor function, d(n), which
   is sublinear.

   According to a blog post by Terence Tao this is bounded by the
   ridiculous O(e^O(log(n)/log(log(n)))):
   https://terrytao.wordpress.com/2008/09/23/the-divisor-bound

   This is apparently somewhere between O(sqrt(n)) and O(log(n)), but
   conveniently O(log(n)) on average and O(log(n)) for powers of 2.

   I've left it as O(d(n)) in the documentation, which might be a bit
   confusing, but I'm not sure how best to capture "mostly log(n)"
   correctly.

3. If we don't know the block_size, or don't know that block_size is
   aligned to the disk size, the best we can do is a O(n) search.

   In this case I've added a warning, so at least it's distinguishable
   from an infinite loop if debugging.
This commit is contained in:
Christopher Haster
2022-11-10 17:43:26 -06:00
parent c2147c45ee
commit 75f80aabd7
5 changed files with 373 additions and 156 deletions
+44 -9
View File
@@ -189,21 +189,52 @@ struct lfs_config {
int (*unlock)(const struct lfs_config *c);
#endif
// Minimum size of a block read in bytes. All read operations will be a
// multiple of this value.
// Minimum size of a read operation in bytes. All read operations
// will be a multiple of this value.
lfs_size_t read_size;
// Minimum size of a block program in bytes. All program operations will be
// a multiple of this value.
// Minimum size of a program operation in bytes. All program operations
// will be a multiple of this value.
lfs_size_t prog_size;
// Size of an erasable block in bytes. This does not impact ram consumption
// and may be larger than the physical erase size. However, non-inlined
// files take up at minimum one block. Must be a multiple of the read and
// program sizes.
// Minimum size of an erase operation in bytes. All erase operations
// will be a multiple of this value. This must be a multiple of the read
// and program sizes.
//
// If zero, the block_size is used as the erase_size. This is mostly for
// backwards compatibility.
lfs_size_t erase_size;
// Number of erase blocks on the device.
//
// If zero, the block_count is used as the erase_count. This is mostly for
// backwards compatibility.
lfs_size_t erase_count;
// Size of a logical block in bytes. This does not impact RAM consumption
// and may be a multiple of the physical erase_size.
//
// Note, non-inlined files take up at minimum one logical block, and
// directories take up at minimum two logical blocks.
//
// If zero, littlefs attempts to find the superblock and use the the
// block_size stored there. This requires searching different block_sizes.
// If a superblock is found this takes no longer than mounting with a known
// block_size, but it can take time to fail if a superblock is not found:
//
// - O(block_size) if a superblock is found
// - O(d(block_count)) if block_count is non-zero
// - O(log(block_count)) if block_count is a power of 2
// - O(erase_count) if block_count is zero
lfs_size_t block_size;
// Number of erasable blocks on the device.
// Number of logical blocks on the device.
//
// If zero, littlefs uses the block_count stored in the superblock.
//
// If non-zero and block_size is zero, littlefs will assume block_size
// is a factor of erase_size*block_count to speed up mount when no
// superblock is found.
lfs_size_t block_count;
// Number of erase cycles before littlefs evicts metadata logs and moves
@@ -408,6 +439,10 @@ typedef struct lfs {
} free;
const struct lfs_config *cfg;
lfs_size_t erase_size;
lfs_size_t erase_count;
lfs_size_t block_size;
lfs_size_t block_count;
lfs_size_t name_max;
lfs_size_t file_max;
lfs_size_t attr_max;