diff --git a/lfs.c b/lfs.c index df6a2302..44a8261b 100644 --- a/lfs.c +++ b/lfs.c @@ -593,19 +593,6 @@ static int lfs_rawunmount(lfs_t *lfs); /// Block allocator /// -#ifndef LFS_READONLY -static int lfs_alloc_lookahead(void *p, lfs_block_t block) { - lfs_t *lfs = (lfs_t*)p; - lfs_block_t off = ((block - lfs->lookahead.start) - + lfs->block_count) % lfs->block_count; - - if (off < lfs->lookahead.size) { - lfs->lookahead.buffer[off / 8] |= 1U << (off % 8); - } - - return 0; -} -#endif // allocations should call this when all allocated blocks are committed to // the filesystem @@ -624,7 +611,21 @@ static void lfs_alloc_drop(lfs_t *lfs) { } #ifndef LFS_READONLY -static int lfs_fs_rawgc(lfs_t *lfs) { +static int lfs_alloc_lookahead(void *p, lfs_block_t block) { + lfs_t *lfs = (lfs_t*)p; + lfs_block_t off = ((block - lfs->lookahead.start) + + lfs->block_count) % lfs->block_count; + + if (off < lfs->lookahead.size) { + lfs->lookahead.buffer[off / 8] |= 1U << (off % 8); + } + + return 0; +} +#endif + +#ifndef LFS_READONLY +static int lfs_alloc_scan(lfs_t *lfs) { // move lookahead buffer to the first unused block // // note we limit the lookahead buffer to at most the amount of blocks @@ -693,7 +694,7 @@ static int lfs_alloc(lfs_t *lfs, lfs_block_t *block) { // No blocks in our lookahead buffer, we need to scan the filesystem for // unused blocks in the next lookahead window. - int err = lfs_fs_rawgc(lfs); + int err = lfs_alloc_scan(lfs); if(err) { return err; } @@ -4189,6 +4190,14 @@ static int lfs_init(lfs_t *lfs, const struct lfs_config *cfg) { // wear-leveling. LFS_ASSERT(lfs->cfg->block_cycles != 0); + // check that compact_thresh makes sense + // + // metadata can't be compacted below block_size/2, and metadata can't + // exceed a block_size + LFS_ASSERT(lfs->cfg->compact_thresh == 0 + || lfs->cfg->compact_thresh >= lfs->cfg->block_size/2); + LFS_ASSERT(lfs->cfg->compact_thresh == (lfs_size_t)-1 + || lfs->cfg->compact_thresh <= lfs->cfg->block_size); // setup read cache if (lfs->cfg->read_buffer) { @@ -5080,6 +5089,57 @@ static lfs_ssize_t lfs_fs_rawsize(lfs_t *lfs) { return size; } +// explicit garbage collection +#ifndef LFS_READONLY +static int lfs_fs_rawgc(lfs_t *lfs) { + // force consistency, even if we're not necessarily going to write, + // because this function is supposed to take care of janitorial work + // isn't it? + int err = lfs_fs_forceconsistency(lfs); + if (err) { + return err; + } + + // try to compact metadata pairs, note we can't really accomplish + // anything if compact_thresh doesn't at least leave a prog_size + // available + if (lfs->cfg->compact_thresh + < lfs->cfg->block_size - lfs->cfg->prog_size) { + // iterate over all mdirs + lfs_mdir_t mdir = {.tail = {0, 1}}; + while (!lfs_pair_isnull(mdir.tail)) { + err = lfs_dir_fetch(lfs, &mdir, mdir.tail); + if (err) { + return err; + } + + // not erased? exceeds our compaction threshold? + if (!mdir.erased || ((lfs->cfg->compact_thresh == 0) + ? mdir.off > lfs->cfg->block_size - lfs->cfg->block_size/8 + : mdir.off > lfs->cfg->compact_thresh)) { + // the easiest way to trigger a compaction is to mark + // the mdir as unerased and add an empty commit + mdir.erased = false; + err = lfs_dir_commit(lfs, &mdir, NULL, 0); + if (err) { + return err; + } + } + } + } + + // try to populate the lookahead buffer, unless it's already full + if (lfs->lookahead.size < 8*lfs->cfg->lookahead_size) { + err = lfs_alloc_scan(lfs); + if (err) { + return err; + } + } + + return 0; +} +#endif + #ifndef LFS_READONLY static int lfs_fs_rawgrow(lfs_t *lfs, lfs_size_t block_count) { // shrinking is not supported @@ -6286,32 +6346,32 @@ int lfs_fs_traverse(lfs_t *lfs, int (*cb)(void *, lfs_block_t), void *data) { } #ifndef LFS_READONLY -int lfs_fs_gc(lfs_t *lfs) { +int lfs_fs_mkconsistent(lfs_t *lfs) { int err = LFS_LOCK(lfs->cfg); if (err) { return err; } - LFS_TRACE("lfs_fs_gc(%p)", (void*)lfs); + LFS_TRACE("lfs_fs_mkconsistent(%p)", (void*)lfs); - err = lfs_fs_rawgc(lfs); + err = lfs_fs_rawmkconsistent(lfs); - LFS_TRACE("lfs_fs_gc -> %d", err); + LFS_TRACE("lfs_fs_mkconsistent -> %d", err); LFS_UNLOCK(lfs->cfg); return err; } #endif #ifndef LFS_READONLY -int lfs_fs_mkconsistent(lfs_t *lfs) { +int lfs_fs_gc(lfs_t *lfs) { int err = LFS_LOCK(lfs->cfg); if (err) { return err; } - LFS_TRACE("lfs_fs_mkconsistent(%p)", (void*)lfs); + LFS_TRACE("lfs_fs_gc(%p)", (void*)lfs); - err = lfs_fs_rawmkconsistent(lfs); + err = lfs_fs_rawgc(lfs); - LFS_TRACE("lfs_fs_mkconsistent -> %d", err); + LFS_TRACE("lfs_fs_gc -> %d", err); LFS_UNLOCK(lfs->cfg); return err; } diff --git a/lfs.h b/lfs.h index 6742ffef..e144b847 100644 --- a/lfs.h +++ b/lfs.h @@ -227,6 +227,17 @@ struct lfs_config { // can track 8 blocks. lfs_size_t lookahead_size; + // Threshold for metadata compaction during lfs_fs_gc in bytes. Metadata + // pairs that exceed this threshold will be compacted during lfs_fs_gc. + // Defaults to ~88% block_size when zero, though the default may change + // in the future. + // + // Note this only affects lfs_fs_gc. Normal compactions still only occur + // when full. + // + // Set to -1 to disable metadata compaction during lfs_fs_gc. + lfs_size_t compact_thresh; + // Optional statically allocated read buffer. Must be cache_size. // By default lfs_malloc is used to allocate this buffer. void *read_buffer; @@ -709,18 +720,6 @@ lfs_ssize_t lfs_fs_size(lfs_t *lfs); // Returns a negative error code on failure. int lfs_fs_traverse(lfs_t *lfs, int (*cb)(void*, lfs_block_t), void *data); -// Attempt to proactively find free blocks -// -// Calling this function is not required, but may allowing the offloading of -// the expensive block allocation scan to a less time-critical code path. -// -// Note: littlefs currently does not persist any found free blocks to disk. -// This may change in the future. -// -// Returns a negative error code on failure. Finding no free blocks is -// not an error. -int lfs_fs_gc(lfs_t *lfs); - #ifndef LFS_READONLY // Attempt to make the filesystem consistent and ready for writing // @@ -733,6 +732,24 @@ int lfs_fs_gc(lfs_t *lfs); int lfs_fs_mkconsistent(lfs_t *lfs); #endif +#ifndef LFS_READONLY +// Attempt any janitorial work +// +// This currently: +// 1. Calls mkconsistent if not already consistent +// 2. Compacts metadata > compact_thresh +// 3. Populates the block allocator +// +// Though additional janitorial work may be added in the future. +// +// Calling this function is not required, but may allow the offloading of +// expensive janitorial work to a less time-critical code path. +// +// Returns a negative error code on failure. Accomplishing nothing is not +// an error. +int lfs_fs_gc(lfs_t *lfs); +#endif + #ifndef LFS_READONLY // Grows the filesystem to a new size, updating the superblock with the new // block count. diff --git a/runners/bench_runner.c b/runners/bench_runner.c index f4dce22b..58cae683 100644 --- a/runners/bench_runner.c +++ b/runners/bench_runner.c @@ -1321,6 +1321,7 @@ void perm_run( .block_cycles = BLOCK_CYCLES, .cache_size = CACHE_SIZE, .lookahead_size = LOOKAHEAD_SIZE, + .compact_thresh = COMPACT_THRESH, }; struct lfs_emubd_config bdcfg = { diff --git a/runners/bench_runner.h b/runners/bench_runner.h index b072970e..986ac904 100644 --- a/runners/bench_runner.h +++ b/runners/bench_runner.h @@ -95,11 +95,12 @@ intmax_t bench_define(size_t define); #define BLOCK_COUNT_i 5 #define CACHE_SIZE_i 6 #define LOOKAHEAD_SIZE_i 7 -#define BLOCK_CYCLES_i 8 -#define ERASE_VALUE_i 9 -#define ERASE_CYCLES_i 10 -#define BADBLOCK_BEHAVIOR_i 11 -#define POWERLOSS_BEHAVIOR_i 12 +#define COMPACT_THRESH_i 8 +#define BLOCK_CYCLES_i 9 +#define ERASE_VALUE_i 10 +#define ERASE_CYCLES_i 11 +#define BADBLOCK_BEHAVIOR_i 12 +#define POWERLOSS_BEHAVIOR_i 13 #define READ_SIZE bench_define(READ_SIZE_i) #define PROG_SIZE bench_define(PROG_SIZE_i) @@ -109,6 +110,7 @@ intmax_t bench_define(size_t define); #define BLOCK_COUNT bench_define(BLOCK_COUNT_i) #define CACHE_SIZE bench_define(CACHE_SIZE_i) #define LOOKAHEAD_SIZE bench_define(LOOKAHEAD_SIZE_i) +#define COMPACT_THRESH bench_define(COMPACT_THRESH_i) #define BLOCK_CYCLES bench_define(BLOCK_CYCLES_i) #define ERASE_VALUE bench_define(ERASE_VALUE_i) #define ERASE_CYCLES bench_define(ERASE_CYCLES_i) @@ -124,6 +126,7 @@ intmax_t bench_define(size_t define); BENCH_DEF(BLOCK_COUNT, ERASE_COUNT/lfs_max(BLOCK_SIZE/ERASE_SIZE,1))\ BENCH_DEF(CACHE_SIZE, lfs_max(64,lfs_max(READ_SIZE,PROG_SIZE))) \ BENCH_DEF(LOOKAHEAD_SIZE, 16) \ + BENCH_DEF(COMPACT_THRESH, 0) \ BENCH_DEF(BLOCK_CYCLES, -1) \ BENCH_DEF(ERASE_VALUE, 0xff) \ BENCH_DEF(ERASE_CYCLES, 0) \ @@ -131,7 +134,7 @@ intmax_t bench_define(size_t define); BENCH_DEF(POWERLOSS_BEHAVIOR, LFS_EMUBD_POWERLOSS_NOOP) #define BENCH_GEOMETRY_DEFINE_COUNT 4 -#define BENCH_IMPLICIT_DEFINE_COUNT 13 +#define BENCH_IMPLICIT_DEFINE_COUNT 14 #endif diff --git a/runners/test_runner.c b/runners/test_runner.c index 13befdc3..c6e933e0 100644 --- a/runners/test_runner.c +++ b/runners/test_runner.c @@ -1346,6 +1346,7 @@ static void run_powerloss_none( .block_cycles = BLOCK_CYCLES, .cache_size = CACHE_SIZE, .lookahead_size = LOOKAHEAD_SIZE, + .compact_thresh = COMPACT_THRESH, #ifdef LFS_MULTIVERSION .disk_version = DISK_VERSION, #endif @@ -1422,6 +1423,7 @@ static void run_powerloss_linear( .block_cycles = BLOCK_CYCLES, .cache_size = CACHE_SIZE, .lookahead_size = LOOKAHEAD_SIZE, + .compact_thresh = COMPACT_THRESH, #ifdef LFS_MULTIVERSION .disk_version = DISK_VERSION, #endif @@ -1515,6 +1517,7 @@ static void run_powerloss_log( .block_cycles = BLOCK_CYCLES, .cache_size = CACHE_SIZE, .lookahead_size = LOOKAHEAD_SIZE, + .compact_thresh = COMPACT_THRESH, #ifdef LFS_MULTIVERSION .disk_version = DISK_VERSION, #endif @@ -1606,6 +1609,7 @@ static void run_powerloss_cycles( .block_cycles = BLOCK_CYCLES, .cache_size = CACHE_SIZE, .lookahead_size = LOOKAHEAD_SIZE, + .compact_thresh = COMPACT_THRESH, #ifdef LFS_MULTIVERSION .disk_version = DISK_VERSION, #endif @@ -1795,6 +1799,7 @@ static void run_powerloss_exhaustive( .block_cycles = BLOCK_CYCLES, .cache_size = CACHE_SIZE, .lookahead_size = LOOKAHEAD_SIZE, + .compact_thresh = COMPACT_THRESH, #ifdef LFS_MULTIVERSION .disk_version = DISK_VERSION, #endif diff --git a/runners/test_runner.h b/runners/test_runner.h index 4be72e42..96099997 100644 --- a/runners/test_runner.h +++ b/runners/test_runner.h @@ -88,12 +88,13 @@ intmax_t test_define(size_t define); #define BLOCK_COUNT_i 5 #define CACHE_SIZE_i 6 #define LOOKAHEAD_SIZE_i 7 -#define BLOCK_CYCLES_i 8 -#define ERASE_VALUE_i 9 -#define ERASE_CYCLES_i 10 -#define BADBLOCK_BEHAVIOR_i 11 -#define POWERLOSS_BEHAVIOR_i 12 -#define DISK_VERSION_i 13 +#define COMPACT_THRESH_i 8 +#define BLOCK_CYCLES_i 9 +#define ERASE_VALUE_i 10 +#define ERASE_CYCLES_i 11 +#define BADBLOCK_BEHAVIOR_i 12 +#define POWERLOSS_BEHAVIOR_i 13 +#define DISK_VERSION_i 14 #define READ_SIZE TEST_DEFINE(READ_SIZE_i) #define PROG_SIZE TEST_DEFINE(PROG_SIZE_i) @@ -103,6 +104,7 @@ intmax_t test_define(size_t define); #define BLOCK_COUNT TEST_DEFINE(BLOCK_COUNT_i) #define CACHE_SIZE TEST_DEFINE(CACHE_SIZE_i) #define LOOKAHEAD_SIZE TEST_DEFINE(LOOKAHEAD_SIZE_i) +#define COMPACT_THRESH TEST_DEFINE(COMPACT_THRESH_i) #define BLOCK_CYCLES TEST_DEFINE(BLOCK_CYCLES_i) #define ERASE_VALUE TEST_DEFINE(ERASE_VALUE_i) #define ERASE_CYCLES TEST_DEFINE(ERASE_CYCLES_i) @@ -119,6 +121,7 @@ intmax_t test_define(size_t define); TEST_DEF(BLOCK_COUNT, ERASE_COUNT/lfs_max(BLOCK_SIZE/ERASE_SIZE,1)) \ TEST_DEF(CACHE_SIZE, lfs_max(64,lfs_max(READ_SIZE,PROG_SIZE))) \ TEST_DEF(LOOKAHEAD_SIZE, 16) \ + TEST_DEF(COMPACT_THRESH, 0) \ TEST_DEF(BLOCK_CYCLES, -1) \ TEST_DEF(ERASE_VALUE, 0xff) \ TEST_DEF(ERASE_CYCLES, 0) \ @@ -127,7 +130,7 @@ intmax_t test_define(size_t define); TEST_DEF(DISK_VERSION, 0) #define TEST_GEOMETRY_DEFINE_COUNT 4 -#define TEST_IMPLICIT_DEFINE_COUNT 14 +#define TEST_IMPLICIT_DEFINE_COUNT 15 #endif diff --git a/tests/test_alloc.toml b/tests/test_alloc.toml index e6fba975..9e4daee5 100644 --- a/tests/test_alloc.toml +++ b/tests/test_alloc.toml @@ -7,6 +7,7 @@ if = 'BLOCK_CYCLES == -1' defines.FILES = 3 defines.SIZE = '(((BLOCK_SIZE-8)*(BLOCK_COUNT-6)) / FILES)' defines.GC = [false, true] +defines.COMPACT_THRESH = ['-1', '0', 'BLOCK_SIZE/2'] code = ''' const char *names[] = {"bacon", "eggs", "pancakes"}; lfs_file_t files[FILES]; @@ -60,6 +61,7 @@ code = ''' defines.FILES = 3 defines.SIZE = '(((BLOCK_SIZE-8)*(BLOCK_COUNT-6)) / FILES)' defines.GC = [false, true] +defines.COMPACT_THRESH = ['-1', '0', 'BLOCK_SIZE/2'] code = ''' const char *names[] = {"bacon", "eggs", "pancakes"};