Skip to content

Commit ed7bd05

Browse files
authored
Merge pull request #912 from littlefs-project/relaxed-lookahead
Relaxed lookahead alignment, other internal block alloc readability improvements
2 parents 1195d60 + 6056767 commit ed7bd05

File tree

4 files changed

+91
-72
lines changed

4 files changed

+91
-72
lines changed

lfs.c

Lines changed: 77 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -596,42 +596,48 @@ static int lfs_rawunmount(lfs_t *lfs);
596596
#ifndef LFS_READONLY
597597
static int lfs_alloc_lookahead(void *p, lfs_block_t block) {
598598
lfs_t *lfs = (lfs_t*)p;
599-
lfs_block_t off = ((block - lfs->free.off)
599+
lfs_block_t off = ((block - lfs->lookahead.start)
600600
+ lfs->block_count) % lfs->block_count;
601601

602-
if (off < lfs->free.size) {
603-
lfs->free.buffer[off / 32] |= 1U << (off % 32);
602+
if (off < lfs->lookahead.size) {
603+
lfs->lookahead.buffer[off / 8] |= 1U << (off % 8);
604604
}
605605

606606
return 0;
607607
}
608608
#endif
609609

610-
// indicate allocated blocks have been committed into the filesystem, this
611-
// is to prevent blocks from being garbage collected in the middle of a
612-
// commit operation
613-
static void lfs_alloc_ack(lfs_t *lfs) {
614-
lfs->free.ack = lfs->block_count;
610+
// allocations should call this when all allocated blocks are committed to
611+
// the filesystem
612+
//
613+
// after a checkpoint, the block allocator may realloc any untracked blocks
614+
static void lfs_alloc_ckpoint(lfs_t *lfs) {
615+
lfs->lookahead.ckpoint = lfs->block_count;
615616
}
616617

617618
// drop the lookahead buffer, this is done during mounting and failed
618619
// traversals in order to avoid invalid lookahead state
619620
static void lfs_alloc_drop(lfs_t *lfs) {
620-
lfs->free.size = 0;
621-
lfs->free.i = 0;
622-
lfs_alloc_ack(lfs);
621+
lfs->lookahead.size = 0;
622+
lfs->lookahead.next = 0;
623+
lfs_alloc_ckpoint(lfs);
623624
}
624625

625626
#ifndef LFS_READONLY
626627
static int lfs_fs_rawgc(lfs_t *lfs) {
627-
// Move free offset at the first unused block (lfs->free.i)
628-
// lfs->free.i is equal lfs->free.size when all blocks are used
629-
lfs->free.off = (lfs->free.off + lfs->free.i) % lfs->block_count;
630-
lfs->free.size = lfs_min(8*lfs->cfg->lookahead_size, lfs->free.ack);
631-
lfs->free.i = 0;
628+
// move lookahead buffer to the first unused block
629+
//
630+
// note we limit the lookahead buffer to at most the amount of blocks
631+
// checkpointed, this prevents the math in lfs_alloc from underflowing
632+
lfs->lookahead.start = (lfs->lookahead.start + lfs->lookahead.next)
633+
% lfs->block_count;
634+
lfs->lookahead.next = 0;
635+
lfs->lookahead.size = lfs_min(
636+
8*lfs->cfg->lookahead_size,
637+
lfs->lookahead.ckpoint);
632638

633639
// find mask of free blocks from tree
634-
memset(lfs->free.buffer, 0, lfs->cfg->lookahead_size);
640+
memset(lfs->lookahead.buffer, 0, lfs->cfg->lookahead_size);
635641
int err = lfs_fs_rawtraverse(lfs, lfs_alloc_lookahead, lfs, true);
636642
if (err) {
637643
lfs_alloc_drop(lfs);
@@ -645,35 +651,48 @@ static int lfs_fs_rawgc(lfs_t *lfs) {
645651
#ifndef LFS_READONLY
646652
static int lfs_alloc(lfs_t *lfs, lfs_block_t *block) {
647653
while (true) {
648-
while (lfs->free.i != lfs->free.size) {
649-
lfs_block_t off = lfs->free.i;
650-
lfs->free.i += 1;
651-
lfs->free.ack -= 1;
652-
653-
if (!(lfs->free.buffer[off / 32] & (1U << (off % 32)))) {
654+
// scan our lookahead buffer for free blocks
655+
while (lfs->lookahead.next < lfs->lookahead.size) {
656+
if (!(lfs->lookahead.buffer[lfs->lookahead.next / 8]
657+
& (1U << (lfs->lookahead.next % 8)))) {
654658
// found a free block
655-
*block = (lfs->free.off + off) % lfs->block_count;
656-
657-
// eagerly find next off so an alloc ack can
658-
// discredit old lookahead blocks
659-
while (lfs->free.i != lfs->free.size &&
660-
(lfs->free.buffer[lfs->free.i / 32]
661-
& (1U << (lfs->free.i % 32)))) {
662-
lfs->free.i += 1;
663-
lfs->free.ack -= 1;
659+
*block = (lfs->lookahead.start + lfs->lookahead.next)
660+
% lfs->block_count;
661+
662+
// eagerly find next free block to maximize how many blocks
663+
// lfs_alloc_ckpoint makes available for scanning
664+
while (true) {
665+
lfs->lookahead.next += 1;
666+
lfs->lookahead.ckpoint -= 1;
667+
668+
if (lfs->lookahead.next >= lfs->lookahead.size
669+
|| !(lfs->lookahead.buffer[lfs->lookahead.next / 8]
670+
& (1U << (lfs->lookahead.next % 8)))) {
671+
return 0;
672+
}
664673
}
665-
666-
return 0;
667674
}
675+
676+
lfs->lookahead.next += 1;
677+
lfs->lookahead.ckpoint -= 1;
668678
}
669679

670-
// check if we have looked at all blocks since last ack
671-
if (lfs->free.ack == 0) {
672-
LFS_ERROR("No more free space %"PRIu32,
673-
lfs->free.i + lfs->free.off);
680+
// In order to keep our block allocator from spinning forever when our
681+
// filesystem is full, we mark points where there are no in-flight
682+
// allocations with a checkpoint before starting a set of allocations.
683+
//
684+
// If we've looked at all blocks since the last checkpoint, we report
685+
// the filesystem as out of storage.
686+
//
687+
if (lfs->lookahead.ckpoint <= 0) {
688+
LFS_ERROR("No more free space 0x%"PRIx32,
689+
(lfs->lookahead.start + lfs->lookahead.next)
690+
% lfs->cfg->block_count);
674691
return LFS_ERR_NOSPC;
675692
}
676693

694+
// No blocks in our lookahead buffer, we need to scan the filesystem for
695+
// unused blocks in the next lookahead window.
677696
int err = lfs_fs_rawgc(lfs);
678697
if(err) {
679698
return err;
@@ -2588,7 +2607,7 @@ static int lfs_rawmkdir(lfs_t *lfs, const char *path) {
25882607
}
25892608

25902609
// build up new directory
2591-
lfs_alloc_ack(lfs);
2610+
lfs_alloc_ckpoint(lfs);
25922611
lfs_mdir_t dir;
25932612
err = lfs_dir_alloc(lfs, &dir);
25942613
if (err) {
@@ -3274,7 +3293,7 @@ static int lfs_file_relocate(lfs_t *lfs, lfs_file_t *file) {
32743293
#ifndef LFS_READONLY
32753294
static int lfs_file_outline(lfs_t *lfs, lfs_file_t *file) {
32763295
file->off = file->pos;
3277-
lfs_alloc_ack(lfs);
3296+
lfs_alloc_ckpoint(lfs);
32783297
int err = lfs_file_relocate(lfs, file);
32793298
if (err) {
32803299
return err;
@@ -3537,7 +3556,7 @@ static lfs_ssize_t lfs_file_flushedwrite(lfs_t *lfs, lfs_file_t *file,
35373556
}
35383557

35393558
// extend file with new blocks
3540-
lfs_alloc_ack(lfs);
3559+
lfs_alloc_ckpoint(lfs);
35413560
int err = lfs_ctz_extend(lfs, &file->cache, &lfs->rcache,
35423561
file->block, file->pos,
35433562
&file->block, &file->off);
@@ -3580,7 +3599,7 @@ static lfs_ssize_t lfs_file_flushedwrite(lfs_t *lfs, lfs_file_t *file,
35803599
data += diff;
35813600
nsize -= diff;
35823601

3583-
lfs_alloc_ack(lfs);
3602+
lfs_alloc_ckpoint(lfs);
35843603
}
35853604

35863605
return size;
@@ -4197,15 +4216,14 @@ static int lfs_init(lfs_t *lfs, const struct lfs_config *cfg) {
41974216
lfs_cache_zero(lfs, &lfs->rcache);
41984217
lfs_cache_zero(lfs, &lfs->pcache);
41994218

4200-
// setup lookahead, must be multiple of 64-bits, 32-bit aligned
4219+
// setup lookahead buffer, note mount finishes initializing this after
4220+
// we establish a decent pseudo-random seed
42014221
LFS_ASSERT(lfs->cfg->lookahead_size > 0);
4202-
LFS_ASSERT(lfs->cfg->lookahead_size % 8 == 0 &&
4203-
(uintptr_t)lfs->cfg->lookahead_buffer % 4 == 0);
42044222
if (lfs->cfg->lookahead_buffer) {
4205-
lfs->free.buffer = lfs->cfg->lookahead_buffer;
4223+
lfs->lookahead.buffer = lfs->cfg->lookahead_buffer;
42064224
} else {
4207-
lfs->free.buffer = lfs_malloc(lfs->cfg->lookahead_size);
4208-
if (!lfs->free.buffer) {
4225+
lfs->lookahead.buffer = lfs_malloc(lfs->cfg->lookahead_size);
4226+
if (!lfs->lookahead.buffer) {
42094227
err = LFS_ERR_NOMEM;
42104228
goto cleanup;
42114229
}
@@ -4262,7 +4280,7 @@ static int lfs_deinit(lfs_t *lfs) {
42624280
}
42634281

42644282
if (!lfs->cfg->lookahead_buffer) {
4265-
lfs_free(lfs->free.buffer);
4283+
lfs_free(lfs->lookahead.buffer);
42664284
}
42674285

42684286
return 0;
@@ -4282,12 +4300,12 @@ static int lfs_rawformat(lfs_t *lfs, const struct lfs_config *cfg) {
42824300
LFS_ASSERT(cfg->block_count != 0);
42834301

42844302
// create free lookahead
4285-
memset(lfs->free.buffer, 0, lfs->cfg->lookahead_size);
4286-
lfs->free.off = 0;
4287-
lfs->free.size = lfs_min(8*lfs->cfg->lookahead_size,
4303+
memset(lfs->lookahead.buffer, 0, lfs->cfg->lookahead_size);
4304+
lfs->lookahead.start = 0;
4305+
lfs->lookahead.size = lfs_min(8*lfs->cfg->lookahead_size,
42884306
lfs->block_count);
4289-
lfs->free.i = 0;
4290-
lfs_alloc_ack(lfs);
4307+
lfs->lookahead.next = 0;
4308+
lfs_alloc_ckpoint(lfs);
42914309

42924310
// create root dir
42934311
lfs_mdir_t root;
@@ -4495,7 +4513,7 @@ static int lfs_rawmount(lfs_t *lfs, const struct lfs_config *cfg) {
44954513

44964514
// setup free lookahead, to distribute allocations uniformly across
44974515
// boots, we start the allocator at a random location
4498-
lfs->free.off = lfs->seed % lfs->block_count;
4516+
lfs->lookahead.start = lfs->seed % lfs->block_count;
44994517
lfs_alloc_drop(lfs);
45004518

45014519
return 0;
@@ -5468,10 +5486,10 @@ static int lfs1_mount(lfs_t *lfs, struct lfs1 *lfs1,
54685486
lfs->lfs1->root[1] = LFS_BLOCK_NULL;
54695487

54705488
// setup free lookahead
5471-
lfs->free.off = 0;
5472-
lfs->free.size = 0;
5473-
lfs->free.i = 0;
5474-
lfs_alloc_ack(lfs);
5489+
lfs->lookahead.start = 0;
5490+
lfs->lookahead.size = 0;
5491+
lfs->lookahead.next = 0;
5492+
lfs_alloc_ckpoint(lfs);
54755493

54765494
// load superblock
54775495
lfs1_dir_t dir;

lfs.h

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,7 @@ struct lfs_config {
224224
// Size of the lookahead buffer in bytes. A larger lookahead buffer
225225
// increases the number of blocks found during an allocation pass. The
226226
// lookahead buffer is stored as a compact bitmap, so each byte of RAM
227-
// can track 8 blocks. Must be a multiple of 8.
227+
// can track 8 blocks.
228228
lfs_size_t lookahead_size;
229229

230230
// Optional statically allocated read buffer. Must be cache_size.
@@ -235,9 +235,8 @@ struct lfs_config {
235235
// By default lfs_malloc is used to allocate this buffer.
236236
void *prog_buffer;
237237

238-
// Optional statically allocated lookahead buffer. Must be lookahead_size
239-
// and aligned to a 32-bit boundary. By default lfs_malloc is used to
240-
// allocate this buffer.
238+
// Optional statically allocated lookahead buffer. Must be lookahead_size.
239+
// By default lfs_malloc is used to allocate this buffer.
241240
void *lookahead_buffer;
242241

243242
// Optional upper limit on length of file names in bytes. No downside for
@@ -428,13 +427,13 @@ typedef struct lfs {
428427
lfs_gstate_t gdisk;
429428
lfs_gstate_t gdelta;
430429

431-
struct lfs_free {
432-
lfs_block_t off;
430+
struct lfs_lookahead {
431+
lfs_block_t start;
433432
lfs_block_t size;
434-
lfs_block_t i;
435-
lfs_block_t ack;
436-
uint32_t *buffer;
437-
} free;
433+
lfs_block_t next;
434+
lfs_block_t ckpoint;
435+
uint8_t *buffer;
436+
} lookahead;
438437

439438
const struct lfs_config *cfg;
440439
lfs_size_t block_count;

lfs_util.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,9 @@ uint32_t lfs_crc(uint32_t crc, const void *buffer, size_t size);
221221
#endif
222222

223223
// Allocate memory, only used if buffers are not provided to littlefs
224-
// Note, memory must be 64-bit aligned
224+
//
225+
// littlefs current has no alignment requirements, as it only allocates
226+
// byte-level buffers.
225227
static inline void *lfs_malloc(size_t size) {
226228
#if defined(LFS_MALLOC)
227229
return LFS_MALLOC(size);

tests/test_orphans.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ code = '''
9898
lfs_mount(&lfs, cfg) => 0;
9999
// create an orphan
100100
lfs_mdir_t orphan;
101-
lfs_alloc_ack(&lfs);
101+
lfs_alloc_ckpoint(&lfs);
102102
lfs_dir_alloc(&lfs, &orphan) => 0;
103103
lfs_dir_commit(&lfs, &orphan, NULL, 0) => 0;
104104
@@ -170,7 +170,7 @@ code = '''
170170
lfs_mount(&lfs, cfg) => 0;
171171
// create an orphan
172172
lfs_mdir_t orphan;
173-
lfs_alloc_ack(&lfs);
173+
lfs_alloc_ckpoint(&lfs);
174174
lfs_dir_alloc(&lfs, &orphan) => 0;
175175
lfs_dir_commit(&lfs, &orphan, NULL, 0) => 0;
176176

0 commit comments

Comments
 (0)