preerase: Implemented the alloc-claim side of things (data blocks)
Note this is only half of preerased allocation.
And the easy half too.
The problem is littlefs's "restricted flash model" (as I'm now calling
it), which makes minimal assumptions about the behavior of the bd's
erase operation to support a wider range of devices. In particular,
littlefs doesn't assume the value of storage after an erase, which makes
detected failed progs (due to powerloss, etc) uniquely difficult.
For data blocks, we at least have the option of simply making sure the
relevant BMERASED range is deleted from the gbmap before use. This does
mean more progs during file writes, but in theory that is much cheaper
than erasing on-demand. And for storage where it's not, you should
probably consider not pre-erasing.
Fortunately, we don't actually need to commit to the gbmap to delete a
BMERASED range. If we consider BMERASED ranges outside the gbmap's known
window as invalid, we just need to decrement the known window to make
progress.
This is now implemented by lfs3_allocclaim, which forces an
lfs3_alloc_sync to ensure BMERASED blocks won't be reused even if power
is lost.
---
You may note this doesn't use the ecksums at all, except the check that
they're still valid. Unfortunately, we can't rely on ecksums for data
blocks because we don't control what gets progged. Worst-case, the data
being written matches the erased-state exactly, which is impossible for
littlefs to detect.
An alternative solution would be to add a header to every data block,
but this would come with several negatives:
- Headers would introduce some (albeit small) complexity into the write
path, have limited value outside of preerases (we would still need to
scan to allocate blocks), and raise questions around what headers
should contain.
- Files would no longer perform optimally around powers-of-two, which
may surprise users and risk unnecessarily poor performance.
- littlefs would lose its "universal migrator" status, as we would need
to inject headers into any existing data blocks.
The in-gbmap ecksums solve the different problem of allocating metadata
blocks, which we can't use lfs3_allocclaim for as it would introduce
recursion.
---
Adds a large (but necessary) chunk of code/stack to the gbmap mode, but
only when preerasing (and some non-gbmap noise?):
code stack ctx
before: 35116 2136 660
after: 35112 (-0.0%) 2136 (+0.0%) 660 (+0.0%)
code stack ctx
gbmap+np before: 38252 2144 776
gbmap+np after: 38252 (+0.0%) 2144 (+0.0%) 776 (+0.0%)
code stack ctx
gbmap+yp before: 38664 2144 796
gbmap+yp after: 38832 (+0.4%) 2168 (+1.1%) 796 (+0.0%)
This commit is contained in:
@@ -2169,10 +2169,10 @@ static lfs3_scmp_t lfs3_attr_cmp(lfs3_t *lfs3, const struct lfs3_attr *attr,
|
||||
// everything we need here
|
||||
|
||||
// block allocator flags
|
||||
#define LFS3_ALLOC_ERASE 0x000000001 // Please erase the block
|
||||
#define LFS3_alloc_ERASE 0x000000001 // Please erase the block
|
||||
|
||||
static inline bool lfs3_alloc_iserase(uint32_t flags) {
|
||||
return flags & LFS3_ALLOC_ERASE;
|
||||
return flags & LFS3_alloc_ERASE;
|
||||
}
|
||||
|
||||
// checkpoint the allocator
|
||||
@@ -2196,6 +2196,12 @@ static inline void lfs3_alloc_discard(lfs3_t *lfs3);
|
||||
static lfs3_sblock_t lfs3_alloc(lfs3_t *lfs3, uint32_t flags);
|
||||
#endif
|
||||
|
||||
// allocate a block and sync gbmap if necessary
|
||||
#ifndef LFS3_RDONLY
|
||||
static lfs3_sblock_t lfs3_allocclaim(lfs3_t *lfs3, lfs3_mdir_t *mdir,
|
||||
uint32_t flags);
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
/// Block pointer things ///
|
||||
@@ -2386,8 +2392,10 @@ static int lfs3_data_readbptr(lfs3_t *lfs3, lfs3_data_t *data,
|
||||
|
||||
// allocate a bptr
|
||||
#ifndef LFS3_RDONLY
|
||||
static int lfs3_bptr_alloc(lfs3_t *lfs3, lfs3_bptr_t *bptr) {
|
||||
lfs3_sblock_t block = lfs3_alloc(lfs3, LFS3_ALLOC_ERASE);
|
||||
static int lfs3_bptr_alloc(lfs3_t *lfs3, lfs3_mdir_t *mdir,
|
||||
lfs3_bptr_t *bptr) {
|
||||
lfs3_sblock_t block = lfs3_allocclaim(lfs3, mdir,
|
||||
LFS3_alloc_ERASE);
|
||||
if (block < 0) {
|
||||
return block;
|
||||
}
|
||||
@@ -2624,7 +2632,7 @@ static inline int lfs3_rbyd_cmp(
|
||||
// allocate an rbyd block
|
||||
#ifndef LFS3_RDONLY
|
||||
static int lfs3_rbyd_alloc(lfs3_t *lfs3, lfs3_rbyd_t *rbyd) {
|
||||
lfs3_sblock_t block = lfs3_alloc(lfs3, LFS3_ALLOC_ERASE);
|
||||
lfs3_sblock_t block = lfs3_alloc(lfs3, LFS3_alloc_ERASE);
|
||||
if (block < 0) {
|
||||
return block;
|
||||
}
|
||||
@@ -8221,7 +8229,7 @@ static int lfs3_mdir_alloc___(lfs3_t *lfs3, lfs3_mdir_t *mdir,
|
||||
|
||||
relocate:;
|
||||
// allocate another block with an erase
|
||||
lfs3_sblock_t block = lfs3_alloc(lfs3, LFS3_ALLOC_ERASE);
|
||||
lfs3_sblock_t block = lfs3_alloc(lfs3, LFS3_alloc_ERASE);
|
||||
if (block < 0) {
|
||||
return block;
|
||||
}
|
||||
@@ -10956,6 +10964,17 @@ static inline bool lfs3_alloc_canpreerase(const lfs3_t *lfs3) {
|
||||
#endif
|
||||
}
|
||||
|
||||
// is gbmap out-of-sync with disk?
|
||||
static inline bool lfs3_alloc_cansync(const lfs3_t *lfs3) {
|
||||
#if !defined(LFS3_RDONLY) && defined(LFS3_GBMAP)
|
||||
return lfs3_btree_cmp(&lfs3->gbmap.b, &lfs3->gbmap.b_p) != 0;
|
||||
#else
|
||||
// TODO adopt this localized void in flag functions?
|
||||
(void)lfs3;
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
// discard any lookahead/gbmap windows, this is necessary if block_count
|
||||
// changes
|
||||
#ifndef LFS3_RDONLY
|
||||
@@ -11019,7 +11038,8 @@ static void lfs3_alloc_markinusebptr(lfs3_t *lfs3,
|
||||
#endif
|
||||
|
||||
// needed in lfs3_alloc_adopt
|
||||
static lfs3_sblock_t lfs3_alloc_findfree(lfs3_t *lfs3);
|
||||
static lfs3_sblock_t lfs3_alloc_findfree(lfs3_t *lfs3,
|
||||
lfs3_ecksum_t *ecksum_);
|
||||
|
||||
// mark any not-in-use blocks as free
|
||||
#ifndef LFS3_RDONLY
|
||||
@@ -11031,7 +11051,7 @@ static void lfs3_alloc_adopt(lfs3_t *lfs3, lfs3_block_t known) {
|
||||
|
||||
// eagerly find the next free block so lookahead scans can make
|
||||
// the most progress
|
||||
lfs3_sblock_t block = lfs3_alloc_findfree(lfs3);
|
||||
lfs3_sblock_t block = lfs3_alloc_findfree(lfs3, NULL);
|
||||
if (block < 0 && block != LFS3_ERR_NOSPC) {
|
||||
// scanning the lookahead buffer shouldn't error
|
||||
LFS3_UNREACHABLE();
|
||||
@@ -11056,7 +11076,7 @@ static int lfs3_alloc_adoptgbmap(lfs3_t *lfs3,
|
||||
|
||||
// eagerly find the next free block so lookgbmap scans can make
|
||||
// the most progress
|
||||
lfs3_sblock_t block = lfs3_alloc_findfree(lfs3);
|
||||
lfs3_sblock_t block = lfs3_alloc_findfree(lfs3, NULL);
|
||||
if (block < 0 && block != LFS3_ERR_NOSPC) {
|
||||
return block;
|
||||
}
|
||||
@@ -11106,7 +11126,9 @@ static void lfs3_alloc_inc(lfs3_t *lfs3) {
|
||||
|
||||
// find next free block in lookahead/gbmap, if there is one
|
||||
#ifndef LFS3_RDONLY
|
||||
static lfs3_sblock_t lfs3_alloc_findfree(lfs3_t *lfs3) {
|
||||
static lfs3_sblock_t lfs3_alloc_findfree(lfs3_t *lfs3,
|
||||
lfs3_ecksum_t *ecksum_) {
|
||||
(void)ecksum_;
|
||||
while (true) {
|
||||
// known block in our gbmap?
|
||||
if (LFS3_IFDEF_GBMAP(
|
||||
@@ -11118,16 +11140,24 @@ static lfs3_sblock_t lfs3_alloc_findfree(lfs3_t *lfs3) {
|
||||
lfs3_block_t block;
|
||||
lfs3_stag_t tag = lfs3_gbmap_lookupnext(lfs3, &lfs3->gbmap.b,
|
||||
lfs3->gbmap.window,
|
||||
&block, NULL, NULL);
|
||||
&block, NULL,
|
||||
LFS3_IFDEF_PREERASE(&lfs3->gbmap.ecksum, NULL));
|
||||
if (tag < 0) {
|
||||
return tag;
|
||||
}
|
||||
|
||||
// free?
|
||||
if (tag == LFS3_TAG_BMFREE) {
|
||||
// free? erased?
|
||||
//
|
||||
// well, we can only use erased if pre-erase support is
|
||||
// enabled
|
||||
if (tag == LFS3_TAG_BMFREE
|
||||
|| LFS3_IFDEF_PREERASE(
|
||||
tag == LFS3_TAG_BMERASED,
|
||||
false)) {
|
||||
lfs3->gbmap.free = lfs3_min(
|
||||
(block+1) - lfs3->gbmap.window,
|
||||
lfs3->gbmap.known);
|
||||
|
||||
// in-use? bad? erased? treat as in-use
|
||||
} else {
|
||||
lfs3->gbmap.free = -lfs3_min(
|
||||
@@ -11139,6 +11169,11 @@ static lfs3_sblock_t lfs3_alloc_findfree(lfs3_t *lfs3) {
|
||||
// free block in our gbmap?
|
||||
if (lfs3->gbmap.free > 0) {
|
||||
// found a free block
|
||||
#ifndef LFS3_NO_PREERASE
|
||||
if (ecksum_) {
|
||||
*ecksum_ = lfs3->gbmap.ecksum;
|
||||
}
|
||||
#endif
|
||||
return lfs3->gbmap.window;
|
||||
}
|
||||
#endif
|
||||
@@ -11149,6 +11184,11 @@ static lfs3_sblock_t lfs3_alloc_findfree(lfs3_t *lfs3) {
|
||||
if (!(lfs3->lookahead.buffer[lfs3->lookahead.off / 8]
|
||||
& (1 << (lfs3->lookahead.off % 8)))) {
|
||||
// found a free block
|
||||
#if defined(LFS3_GBMAP) && !defined(LFS3_NO_PREERASE)
|
||||
if (ecksum_) {
|
||||
ecksum_->cksize = -1;
|
||||
}
|
||||
#endif
|
||||
return lfs3->lookahead.window;
|
||||
}
|
||||
|
||||
@@ -11167,46 +11207,29 @@ static inline lfs3_size_t lfs3_graft_count(lfs3_size_t graft_count);
|
||||
|
||||
// allocate a block
|
||||
#ifndef LFS3_RDONLY
|
||||
static lfs3_sblock_t lfs3_alloc(lfs3_t *lfs3, uint32_t flags) {
|
||||
static lfs3_sblock_t lfs3_alloc__(lfs3_t *lfs3, uint32_t flags,
|
||||
lfs3_ecksum_t *ecksum_) {
|
||||
(void)flags;
|
||||
while (true) {
|
||||
// scan our lookahead/gbmap for free blocks
|
||||
lfs3_sblock_t block = lfs3_alloc_findfree(lfs3);
|
||||
lfs3_sblock_t block = lfs3_alloc_findfree(lfs3,
|
||||
ecksum_);
|
||||
if (block < 0 && block != LFS3_ERR_NOSPC) {
|
||||
return block;
|
||||
}
|
||||
|
||||
if (block != LFS3_ERR_NOSPC) {
|
||||
// we should never alloc blocks {0,1}
|
||||
// we should never alloc blocks 0x{0,1}
|
||||
LFS3_ASSERT(block != 0 && block != 1);
|
||||
|
||||
// erase requested?
|
||||
if (lfs3_alloc_iserase(flags)) {
|
||||
int err = lfs3_bd_erase(lfs3, block);
|
||||
if (err) {
|
||||
// bad erase? try another block
|
||||
if (err == LFS3_ERR_CORRUPT) {
|
||||
lfs3_alloc_inc(lfs3);
|
||||
continue;
|
||||
}
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
// eagerly find the next free block to maximize how many blocks
|
||||
// lfs3_alloc_ckpoint makes available for scanning
|
||||
lfs3_alloc_inc(lfs3);
|
||||
lfs3_sblock_t block_ = lfs3_alloc_findfree(lfs3);
|
||||
lfs3_sblock_t block_ = lfs3_alloc_findfree(lfs3, NULL);
|
||||
if (block_ < 0 && block_ != LFS3_ERR_NOSPC) {
|
||||
return block_;
|
||||
}
|
||||
|
||||
#ifdef LFS3_DBGALLOCS
|
||||
LFS3_DEBUG("Allocated block 0x%"PRIx32", "
|
||||
"lookahead %"PRId32"/%"PRId32,
|
||||
block,
|
||||
lfs3->lookahead.known,
|
||||
lfs3->block_count);
|
||||
#endif
|
||||
return block;
|
||||
}
|
||||
|
||||
@@ -11260,6 +11283,125 @@ static lfs3_sblock_t lfs3_alloc(lfs3_t *lfs3, uint32_t flags) {
|
||||
}
|
||||
#endif
|
||||
|
||||
// alloc and optionally erase a block
|
||||
#ifndef LFS3_RDONLY
|
||||
static lfs3_sblock_t lfs3_alloc_(lfs3_t *lfs3, uint32_t flags,
|
||||
lfs3_ecksum_t *ecksum_) {
|
||||
// we need ecksum to be non-null here, hey this is an internal
|
||||
// API anyways
|
||||
#if defined(LFS3_GBMAP) && !defined(LFS3_NO_PREERASE)
|
||||
LFS3_ASSERT(ecksum_);
|
||||
#endif
|
||||
|
||||
while (true) {
|
||||
lfs3_sblock_t block = lfs3_alloc__(lfs3, flags,
|
||||
ecksum_);
|
||||
if (block < 0) {
|
||||
return block;
|
||||
}
|
||||
|
||||
// erase requested?
|
||||
if (lfs3_alloc_iserase(flags)) {
|
||||
// pre-erased?
|
||||
if (LFS3_IFDEF_GBMAP(LFS3_IFDEF_PREERASE(
|
||||
ecksum_->cksize != -1,
|
||||
false), false)) {
|
||||
#if defined(LFS3_GBMAP) && !defined(LFS3_NO_PREERASE)
|
||||
// check ecksum
|
||||
int err = lfs3_ecksum_ck(lfs3, ecksum_, block, 0);
|
||||
if (err && err != LFS3_ERR_CORRUPT) {
|
||||
return err;
|
||||
}
|
||||
|
||||
// good to go!
|
||||
if (err != LFS3_ERR_CORRUPT) {
|
||||
#ifdef LFS3_DBGALLOCS
|
||||
LFS3_DEBUG("Allocated block 0x%"PRIx32", "
|
||||
"lookahead %"PRId32"/%"PRId32,
|
||||
block,
|
||||
lfs3->lookahead.known,
|
||||
lfs3->block_count);
|
||||
#endif
|
||||
return block;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// needs an explicit erase
|
||||
int err = lfs3_bd_erase(lfs3, block);
|
||||
if (err) {
|
||||
// bad erase? try another block
|
||||
if (err == LFS3_ERR_CORRUPT) {
|
||||
lfs3_alloc_inc(lfs3);
|
||||
continue;
|
||||
}
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef LFS3_DBGALLOCS
|
||||
LFS3_DEBUG("Allocated block 0x%"PRIx32", "
|
||||
"lookahead %"PRId32"/%"PRId32,
|
||||
block,
|
||||
lfs3->lookahead.known,
|
||||
lfs3->block_count);
|
||||
#endif
|
||||
return block;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// allocate a block
|
||||
//
|
||||
// preerase: caller is responsible for perturbing erased-state
|
||||
#ifndef LFS3_RDONLY
|
||||
static lfs3_sblock_t lfs3_alloc(lfs3_t *lfs3, uint32_t flags) {
|
||||
#if defined(LFS3_GBMAP) && !defined(LFS3_NO_PREERASE)
|
||||
lfs3_ecksum_t ecksum_;
|
||||
#endif
|
||||
return lfs3_alloc_(lfs3, flags,
|
||||
LFS3_IFDEF_GBMAP(LFS3_IFDEF_PREERASE(
|
||||
&ecksum_,
|
||||
NULL), NULL));
|
||||
}
|
||||
#endif
|
||||
|
||||
// needed in lfs3_allocclaim
|
||||
#if !defined(LFS3_RDONLY) && defined(LFS3_GBMAP)
|
||||
static int lfs3_alloc_sync(lfs3_t *lfs3);
|
||||
#endif
|
||||
|
||||
// allocate a block and sync gbmap if necessary
|
||||
//
|
||||
// preerase: gbmap is synced if necessary, no perturb needed
|
||||
#ifndef LFS3_RDONLY
|
||||
static lfs3_sblock_t lfs3_allocclaim(lfs3_t *lfs3, lfs3_mdir_t *mdir,
|
||||
uint32_t flags) {
|
||||
(void)mdir;
|
||||
#if defined(LFS3_GBMAP) && !defined(LFS3_NO_PREERASE)
|
||||
lfs3_ecksum_t ecksum_;
|
||||
#endif
|
||||
lfs3_sblock_t block = lfs3_alloc_(lfs3, flags,
|
||||
LFS3_IFDEF_GBMAP(LFS3_IFDEF_PREERASE(
|
||||
&ecksum_,
|
||||
NULL), NULL));
|
||||
if (block < 0) {
|
||||
return block;
|
||||
}
|
||||
|
||||
#if defined(LFS3_GBMAP) && !defined(LFS3_NO_PREERASE)
|
||||
// need to claim?
|
||||
if (ecksum_.cksize != -1) {
|
||||
LFS3_ASSERT(lfs3_alloc_cansync(lfs3));
|
||||
// lfs3_mdir_commit implicitly commits any pending gbmap state
|
||||
return lfs3_mdir_commit(lfs3, mdir, LFS3_RATTRS(LFS3_RATTR_NULL));
|
||||
}
|
||||
#endif
|
||||
|
||||
return block;
|
||||
}
|
||||
#endif
|
||||
|
||||
// rebuild the gbmap
|
||||
#if !defined(LFS3_RDONLY) && defined(LFS3_GBMAP)
|
||||
static int lfs3_alloc_lookgbmap(lfs3_t *lfs3) {
|
||||
@@ -11374,6 +11516,19 @@ static int lfs3_alloc_preerase(lfs3_t *lfs3) {
|
||||
}
|
||||
#endif
|
||||
|
||||
// commit gbmap to disk
|
||||
#if !defined(LFS3_RDONLY) && defined(LFS3_GBMAP)
|
||||
static int lfs3_alloc_sync(lfs3_t *lfs3) {
|
||||
// noop if already in sync
|
||||
if (!lfs3_alloc_cansync(lfs3)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// lfs3_mdir_commit implicitly commits any pending gbmap state
|
||||
return lfs3_mdir_commit(lfs3, &lfs3->mroot, LFS3_RATTRS(LFS3_RATTR_NULL));
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -13538,7 +13693,8 @@ static int lfs3_file_crystallize_(lfs3_t *lfs3, lfs3_file_t *file,
|
||||
// if we relocate, we rewrite the entire block from block_pos
|
||||
// using what we can find in our tree/leaf/cache
|
||||
//
|
||||
block_ = lfs3_alloc(lfs3, LFS3_ALLOC_ERASE);
|
||||
block_ = lfs3_allocclaim(lfs3, &file->b.h.mdir,
|
||||
LFS3_alloc_ERASE);
|
||||
if (block_ < 0) {
|
||||
return block_;
|
||||
}
|
||||
@@ -16635,12 +16791,9 @@ static int lfs3_fs_gc_(lfs3_t *lfs3, lfs3_mgc_t *mgc,
|
||||
|
||||
// if we have nothing else to do, try to commit the gbmap to
|
||||
// disk so it's recoverable if we lose power
|
||||
} else if (LFS3_IFDEF_GBMAP(
|
||||
lfs3_btree_cmp(&lfs3->gbmap.b, &lfs3->gbmap.b_p) != 0,
|
||||
false)) {
|
||||
} else if (lfs3_alloc_cansync(lfs3)) {
|
||||
#if !defined(LFS3_RDONLY) && defined(LFS3_GBMAP)
|
||||
int err = lfs3_mdir_commit(lfs3, &lfs3->mroot,
|
||||
LFS3_RATTRS(LFS3_RATTR_NULL));
|
||||
int err = lfs3_alloc_sync(lfs3);
|
||||
if (err) {
|
||||
return err;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user